Flutter State Management

State management is a core concept in Flutter development.

This section will introduce Flutter's built-in state management approaches, including setState, Provider, and ChangeNotifier.


What is state management

State management refers to the mechanism for managing data changes and UI updates in an application. When data changes, the UI needs to update accordingly; this is the problem that state management solves.

State TypesDescriptionExamples
Local StateState within a single WidgetInput box text, animation progress
Shared StateState that needs to be shared across multiple WidgetsUser login information, themes
Applied stateState that the entire application needs to accessShopping cart contents, user configuration

Local state - setState

For simple local state, using StatefulWidget and setState is sufficient.

Example: Counter example

class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  // Local state
  int _count = 0;

  // Increment count
  void _increment() {
    setState(() {
      _count++;
    });
  }

  // Decrement count
  void _decrement() {
    setState(() {
      _count--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(Count: $_count, style: const TextStyle(fontSize: 32)),
        const SizedBox(height: 20),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(onPressed: _decrement, child: const Text('-')),
            const SizedBox(width: 20),
            ElevatedButton(onPressed: _increment, child: const Text('+')),
          ],
        ),
      ],
    );
  }
}

Provider state management

Provider is one of the state management solutions officially recommended by Flutter, suitable for managing state shared across components.

Install Provider

Add the dependency in pubspec.yaml:

dependencies:
  provider: ^6.0.0

ChangeNotifier Pattern

Example: Provider + ChangeNotifier

// 1. Define data model (inherit ChangeNotifier)
class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    // Notify listeners that state has changed
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

// 2. Provide data at the top of the app
void main() {
  runApp(
    // Provide CounterModel to child widgets
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

// 3. Use data in the widget
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    // Read CounterModel
    final counter = context.watch<CounterModel>();

    return Column(
      children: [
        Text('Count: ${counter.count}', style: const TextStyle(fontSize: 32)),
        ElevatedButton(
          onPressed: () => counter.increment(),
          child: const Text('Increase'),
        ),
      ],
    );
  }
}

Multi-Provider composition

Example: Using multiple Providers

void main() {
  runApp(
    MultiProvider(
      providers: [
        // User Information Provider
        ChangeNotifierProvider(create: (_) => UserModel()),
        // Theme Provider
        ChangeNotifierProvider(create: (_) => ThemeModel()),
        // Shopping Cart Provider
        ChangeNotifierProvider(create: (_) => CartModel()),
      ],
      child: const MyApp(),
    ),
  );
}

// Use context.read when you don't need to listen for changes.
class SomeWidget extends StatelessWidget {
  const SomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // Read but do not listen
    final user = context.read<UserModel>();

    return ElevatedButton(
      onPressed: () => user.logout(),
      child: const Text('Log Out'),
    );
  }
}

Lifting State Up

When multiple child components need to share state, the state can be lifted to the nearest common parent component.

Example: State lifting

// Parent component manages state
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  // Shared state
  bool _isEnabled = false;

  // Callback method
  void _toggleEnabled() {
    setState(() {
      _isEnabled = !_isEnabled;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Child component A
        ChildWidgetA(
          isEnabled: _isEnabled,
        ),
        // Child component B
        ChildWidgetB(
          isEnabled: _isEnabled,
          onToggle: _toggleEnabled,
        ),
      ],
    );
  }
}

// Child Component A (only shows state)
class ChildWidgetA extends StatelessWidget {
  final bool isEnabled;

  const ChildWidgetA({super.key, required this.isEnabled});

  @override
  Widget build(BuildContext context) {
    return Text('Status: ${isEnabled ? "Enabled" : "Disabled"}');
  }
}

// Child component B (controls state)
class ChildWidgetB extends StatelessWidget {
  final bool isEnabled;
  final VoidCallback onToggle;

  const ChildWidgetB({
    super.key,
    required this.isEnabled,
    required this.onToggle,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onToggle,
      child: Text(isEnabled ? 'Close' : 'Enable'),
    );
  }
}

The choice of state management depends on the application scale: simple apps use setState, medium-sized apps use Provider, and complex apps can consider solutions such as Riverpod, BLoC, or GetX.

other extensions