skills/flutter/references/best-practices-state.md

4.5 KiB

name description
best-practices-state Flutter state management best practices - when to use setState, state lifting, and state management patterns

State Management Best Practices

Guidelines for managing state effectively in Flutter applications.

When to Use setState

Use setState() for local widget state.

// ✅ Good - local UI state
class CounterWidget extends StatefulWidget {
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_counter'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

State Lifting

Lift state to nearest common ancestor.

// ✅ Good - state lifted to parent
class ParentWidget extends StatefulWidget {
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        DisplayWidget(counter: _counter),
        ControlWidget(onIncrement: _increment),
      ],
    );
  }
}

ValueNotifier for Simple State

Use ValueNotifier for simple observable values.

// ✅ Good - simple observable state
class CounterModel {
  final ValueNotifier<int> count = ValueNotifier<int>(0);

  void increment() {
    count.value++;
  }

  void dispose() {
    count.dispose();
  }
}

ChangeNotifier for Complex State

Use ChangeNotifier for complex state with multiple properties.

// ✅ Good - complex state
class UserModel extends ChangeNotifier {
  String _name = '';
  String _email = '';

  String get name => _name;
  String get email => _email;

  void updateName(String name) {
    _name = name;
    notifyListeners();
  }

  void updateEmail(String email) {
    _email = email;
    notifyListeners();
  }
}

Avoid Unnecessary Rebuilds

Split widgets to minimize rebuilds.

// ❌ Bad - entire widget rebuilds
class BadWidget extends StatefulWidget {
  @override
  State<BadWidget> createState() => _BadWidgetState();
}

class _BadWidgetState extends State<BadWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ExpensiveWidget(), // Rebuilds unnecessarily
        Text('Count: $_counter'),
        ElevatedButton(
          onPressed: () => setState(() => _counter++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

// ✅ Good - split widgets
class GoodWidget extends StatefulWidget {
  @override
  State<GoodWidget> createState() => _GoodWidgetState();
}

class _GoodWidgetState extends State<GoodWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const ExpensiveWidget(), // const prevents rebuild
        CounterDisplay(counter: _counter),
        ElevatedButton(
          onPressed: () => setState(() => _counter++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

State Management Patterns

Provider Pattern

// Use Provider package for app-wide state
ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
)

BLoC Pattern

// Use BLoC pattern for complex state logic
class CounterBloc {
  final _counterController = StreamController<int>.broadcast();
  Stream<int> get counter => _counterController.stream;
  
  int _count = 0;
  
  void increment() {
    _count++;
    _counterController.add(_count);
  }
  
  void dispose() {
    _counterController.close();
  }
}

Key Points

  • Use setState() for local widget state only
  • Lift state to nearest common ancestor
  • Use ValueNotifier for simple observable values
  • Use ChangeNotifier for complex state
  • Split widgets to minimize rebuilds
  • Use const constructors when possible
  • Consider state management packages for app-wide state
  • Always dispose state management objects
  • Avoid storing state in global variables
  • Use appropriate pattern based on complexity