Flutter UI Building
Widget Decision
| Need |
Widget |
| Vertical list |
Column (few) or ListView (many) |
| Horizontal list |
Row (few) or ListView.horizontal (many) |
| Overlapping widgets |
Stack + Positioned |
| Grid |
GridView.builder |
| Spacing/sizing |
SizedBox (preferred) or Container |
| Decorations (shadow, border) |
Container with BoxDecoration |
Detailed Guides
Essential Patterns
Standard Widget Structure
class ProductCard extends StatelessWidget {
const ProductCard({
super.key,
required this.product,
this.onTap,
});
final Product product;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
Text(product.description, style: theme.textTheme.bodyMedium),
],
),
),
),
);
}
}
Use const Constructors
// ✅ Widget cached, never rebuilds
const SizedBox(height: 16)
const Icon(Icons.home)
const Text('Static text')
// ❌ Recreated every build
SizedBox(height: 16)
Icon(Icons.home)
SizedBox vs Container
// ✅ SizedBox for spacing (lighter)
const SizedBox(height: 16)
const SizedBox(width: 8)
SizedBox(width: 100, height: 100, child: widget)
// ✅ Container for decoration
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [BoxShadow(...)],
),
child: widget,
)
Keys for Lists
// ✅ Use keys when list items can reorder
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index].id), // Unique, stable key
title: Text(items[index].name),
);
},
)
Layout Quick Reference
Expand vs Flexible
Row(
children: [
Expanded(child: Text('Takes remaining space')), // flex: 1, fit: tight
Flexible(child: Text('Takes needed space, can shrink')), // fit: loose
const SizedBox(width: 100), // Fixed width
],
)
Stack Positioning
Stack(
children: [
// Background (fills entire stack)
Positioned.fill(child: Image.asset('bg.png', fit: BoxFit.cover)),
// Top-right corner
Positioned(top: 16, right: 16, child: CloseButton()),
// Centered
Center(child: Text('Centered content')),
// Bottom, full width
Positioned(left: 0, right: 0, bottom: 0, child: BottomBar()),
],
)
Theme Usage
// Access theme
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final textTheme = theme.textTheme;
// Use theme values
Text('Title', style: textTheme.headlineMedium)
Container(color: colorScheme.primaryContainer)
Icon(Icons.star, color: colorScheme.primary)
Common Patterns
Loading State
Widget build(BuildContext context) {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (error != null) {
return Center(child: Text('Error: $error'));
}
return ContentWidget(data: data);
}
Pull to Refresh
RefreshIndicator(
onRefresh: () async {
await viewModel.refresh();
},
child: ListView.builder(...),
)
Safe Area
Scaffold(
body: SafeArea(
child: Column(
children: [...],
),
),
)