234 lines
5.1 KiB
Markdown
234 lines
5.1 KiB
Markdown
|
|
# Widget Patterns
|
||
|
|
|
||
|
|
Flutter widget best practices covering const optimization, responsive layouts, hooks, and sliver patterns.
|
||
|
|
|
||
|
|
## Optimized Widget Pattern
|
||
|
|
|
||
|
|
Always use `const` constructors for static widgets to prevent unnecessary rebuilds:
|
||
|
|
|
||
|
|
```dart
|
||
|
|
class OptimizedCard extends StatelessWidget {
|
||
|
|
final String title;
|
||
|
|
final VoidCallback onTap;
|
||
|
|
|
||
|
|
const OptimizedCard({
|
||
|
|
super.key,
|
||
|
|
required this.title,
|
||
|
|
required this.onTap,
|
||
|
|
});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Card(
|
||
|
|
child: InkWell(
|
||
|
|
onTap: onTap,
|
||
|
|
child: Padding(
|
||
|
|
padding: const EdgeInsets.all(16),
|
||
|
|
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Extracting Const Widgets
|
||
|
|
|
||
|
|
```dart
|
||
|
|
class MyScreen extends StatelessWidget {
|
||
|
|
const MyScreen({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Column(
|
||
|
|
children: const [
|
||
|
|
_Header(),
|
||
|
|
_Body(),
|
||
|
|
_Footer(),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class _Header extends StatelessWidget {
|
||
|
|
const _Header();
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return const Text('Header');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Responsive Layout
|
||
|
|
|
||
|
|
```dart
|
||
|
|
class ResponsiveLayout extends StatelessWidget {
|
||
|
|
final Widget mobile;
|
||
|
|
final Widget? tablet;
|
||
|
|
final Widget desktop;
|
||
|
|
|
||
|
|
const ResponsiveLayout({
|
||
|
|
super.key,
|
||
|
|
required this.mobile,
|
||
|
|
this.tablet,
|
||
|
|
required this.desktop,
|
||
|
|
});
|
||
|
|
|
||
|
|
static const double mobileBreakpoint = 650;
|
||
|
|
static const double desktopBreakpoint = 1100;
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return LayoutBuilder(
|
||
|
|
builder: (context, constraints) {
|
||
|
|
if (constraints.maxWidth >= desktopBreakpoint) return desktop;
|
||
|
|
if (constraints.maxWidth >= mobileBreakpoint) return tablet ?? mobile;
|
||
|
|
return mobile;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Breakpoint Reference
|
||
|
|
|
||
|
|
| Type | Width | Usage |
|
||
|
|
|------|-------|-------|
|
||
|
|
| Mobile | < 650pt | Single column, bottom nav |
|
||
|
|
| Tablet | 650-1100pt | Two columns, side nav optional |
|
||
|
|
| Desktop | > 1100pt | Multi-column, persistent nav |
|
||
|
|
|
||
|
|
## Custom Hooks (flutter_hooks)
|
||
|
|
|
||
|
|
```dart
|
||
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||
|
|
|
||
|
|
class CounterWidget extends HookWidget {
|
||
|
|
const CounterWidget({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final counter = useState(0);
|
||
|
|
final controller = useTextEditingController();
|
||
|
|
final isMounted = useIsMounted();
|
||
|
|
|
||
|
|
useEffect(() {
|
||
|
|
debugPrint('Widget mounted');
|
||
|
|
return () {
|
||
|
|
debugPrint('Widget disposed');
|
||
|
|
};
|
||
|
|
}, const []);
|
||
|
|
|
||
|
|
return Column(
|
||
|
|
children: [
|
||
|
|
Text('Count: ${counter.value}'),
|
||
|
|
ElevatedButton(
|
||
|
|
onPressed: () => counter.value++,
|
||
|
|
child: const Text('Increment'),
|
||
|
|
),
|
||
|
|
TextField(controller: controller),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Common Hooks
|
||
|
|
|
||
|
|
| Hook | Purpose |
|
||
|
|
|------|---------|
|
||
|
|
| `useState` | Local state management |
|
||
|
|
| `useEffect` | Side effects with cleanup |
|
||
|
|
| `useMemoized` | Expensive computation caching |
|
||
|
|
| `useTextEditingController` | Text field controller |
|
||
|
|
| `useAnimationController` | Animation controller |
|
||
|
|
| `useFocusNode` | Focus management |
|
||
|
|
| `useIsMounted` | Check if widget is mounted |
|
||
|
|
|
||
|
|
## Sliver Patterns
|
||
|
|
|
||
|
|
```dart
|
||
|
|
CustomScrollView(
|
||
|
|
slivers: [
|
||
|
|
SliverAppBar(
|
||
|
|
expandedHeight: 200,
|
||
|
|
pinned: true,
|
||
|
|
flexibleSpace: FlexibleSpaceBar(
|
||
|
|
title: const Text('Title'),
|
||
|
|
background: Image.network(imageUrl, fit: BoxFit.cover),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
SliverPadding(
|
||
|
|
padding: const EdgeInsets.all(16),
|
||
|
|
sliver: SliverList(
|
||
|
|
delegate: SliverChildBuilderDelegate(
|
||
|
|
(context, index) => ListTile(
|
||
|
|
key: ValueKey(items[index].id),
|
||
|
|
title: Text(items[index].title),
|
||
|
|
),
|
||
|
|
childCount: items.length,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SliverToBoxAdapter(
|
||
|
|
child: Padding(
|
||
|
|
padding: EdgeInsets.all(16),
|
||
|
|
child: Text('Footer'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Sliver Types
|
||
|
|
|
||
|
|
| Sliver | Usage |
|
||
|
|
|--------|-------|
|
||
|
|
| `SliverAppBar` | Collapsing app bar |
|
||
|
|
| `SliverList` | Lazy list |
|
||
|
|
| `SliverGrid` | Lazy grid |
|
||
|
|
| `SliverToBoxAdapter` | Single non-sliver widget |
|
||
|
|
| `SliverPadding` | Add padding to sliver |
|
||
|
|
| `SliverFillRemaining` | Fill remaining space |
|
||
|
|
|
||
|
|
## Key Usage Patterns
|
||
|
|
|
||
|
|
```dart
|
||
|
|
ListView.builder(
|
||
|
|
itemCount: items.length,
|
||
|
|
itemBuilder: (context, index) {
|
||
|
|
final item = items[index];
|
||
|
|
return Dismissible(
|
||
|
|
key: ValueKey(item.id),
|
||
|
|
child: ListTile(
|
||
|
|
key: ValueKey('tile_${item.id}'),
|
||
|
|
title: Text(item.title),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
| Key Type | When to Use |
|
||
|
|
|----------|-------------|
|
||
|
|
| `ValueKey` | Unique ID available |
|
||
|
|
| `ObjectKey` | Object identity matters |
|
||
|
|
| `UniqueKey` | Force rebuild |
|
||
|
|
| `GlobalKey` | Access state across tree |
|
||
|
|
|
||
|
|
## Optimization Checklist
|
||
|
|
|
||
|
|
| Pattern | Implementation |
|
||
|
|
|---------|----------------|
|
||
|
|
| const widgets | Add `const` to static widgets |
|
||
|
|
| Keys | Use `ValueKey` for list items |
|
||
|
|
| Select | `ref.watch(provider.select(...))` |
|
||
|
|
| RepaintBoundary | Isolate expensive repaints |
|
||
|
|
| ListView.builder | Lazy loading for lists |
|
||
|
|
| const constructors | Always use when possible |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Flutter and Material Design are trademarks of Google LLC.*
|