# Flutter Patterns

> When to activate: Flutter widget patterns, StatelessWidget, StatefulWidget, BuildContext, InheritedWidget, key usage, widget composition

- Skill: `mattakushi432/flutter-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/flutter-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/flutter-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/flutter-patterns

---

# Flutter Widget Patterns

## StatelessWidget vs StatefulWidget

Use `StatelessWidget` when UI depends only on props. Use `StatefulWidget` when local mutable state is needed.

```dart
// Prefer StatelessWidget + external state (Riverpod/BLoC)
class UserCard extends StatelessWidget {
  const UserCard({super.key, required this.user});
  final User user;

  @override
  Widget build(BuildContext context) {
    return Card(child: Text(user.name));
  }
}

// StatefulWidget for genuinely local UI state (animations, focus, text input)
class ExpandableCard extends StatefulWidget {
  const ExpandableCard({super.key, required this.title, required this.child});
  final String title;
  final Widget child;

  @override
  State<ExpandableCard> createState() => _ExpandableCardState();
}

class _ExpandableCardState extends State<ExpandableCard> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      GestureDetector(
        onTap: () => setState(() => _expanded = !_expanded),
        child: Text(widget.title),
      ),
      if (_expanded) widget.child,
    ]);
  }
}
```

## BuildContext Anti-Patterns

```dart
// BAD: storing context across async gaps
Future<void> submit(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  Navigator.of(context).pop(); // context may be stale
}

// GOOD: check mounted before using context after await
Future<void> submit(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  if (!context.mounted) return;
  Navigator.of(context).pop();
}
```

## InheritedWidget for DI

```dart
class ThemeModel extends InheritedWidget {
  const ThemeModel({super.key, required this.primaryColor, required super.child});
  final Color primaryColor;

  static ThemeModel of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeModel>()!;
  }

  @override
  bool updateShouldNotify(ThemeModel old) => primaryColor != old.primaryColor;
}

// Usage
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final color = ThemeModel.of(context).primaryColor;
    return Container(color: color);
  }
}
```

## Key Usage

```dart
// GlobalKey: access state or RenderObject from outside widget tree
final formKey = GlobalKey<FormState>();
Form(key: formKey, child: ...);
formKey.currentState?.validate();

// ValueKey: differentiate list items by value (triggers rebuild correctly)
ListView(children: items.map((i) => ListTile(key: ValueKey(i.id), title: Text(i.name))).toList());

// UniqueKey: force widget recreation (e.g., reset animation)
Image(key: UniqueKey(), image: NetworkImage(url));
```

## Composition over Inheritance

```dart
// BAD: subclassing widgets
class BigRedButton extends ElevatedButton { ... } // Avoid

// GOOD: compose with wrapper widgets
class PrimaryButton extends StatelessWidget {
  const PrimaryButton({super.key, required this.label, required this.onPressed});
  final String label;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) => ElevatedButton(
    style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
    onPressed: onPressed,
    child: Text(label),
  );
}
```

## Layout Patterns

```dart
// Flexible vs Expanded
Row(children: [
  Flexible(flex: 1, child: Container(color: Colors.red)),   // can be smaller
  Expanded(flex: 2, child: Container(color: Colors.blue)),  // must fill
]);

// Sliver-based scrolling for performance
CustomScrollView(slivers: [
  SliverAppBar(pinned: true, title: const Text('Title')),
  SliverList(delegate: SliverChildBuilderDelegate(
    (context, i) => ListTile(title: Text('Item $i')),
    childCount: 100,
  )),
]);
```

## const Constructors

Always use `const` where possible — Flutter skips rebuilding const widgets.

```dart
// GOOD
const Text('Hello');
const SizedBox.shrink();
const EdgeInsets.all(16);

// Widget with const constructor
class MyWidget extends StatelessWidget {
  const MyWidget({super.key}); // required for const usage at call site
}
```

