跪拜 Guibai
← Back to the summary

Flutter State Management in Practice: Provider, BLoC, and Riverpod Side by Side

If you ask a Flutter developer, "What's the hardest tech stack to choose in Flutter?" nine times out of ten the answer will be — state management. From the official early Provide, to the hugely popular Provider, to the architecturally rigorous BLoC, and now the highly regarded Riverpod, the landscape of competing approaches leaves many beginners at a loss.

Today, we'll compare the three mainstream state management solutions from a practical perspective, helping you find the architecture that best fits your current project.

1. Why do we need state management?

In Flutter, "UI = f(State)". When an application becomes complex and we need to share data across multiple widget levels, relying solely on StatefulWidget to pass callbacks (Callback hell) makes the code extremely coupled and hard to maintain. The core goal of state management tools is to separate UI rendering from business logic.

2. Comparing the three major solutions

2.1 Provider: Classic and intuitive

Provider is a lightweight dependency injection and state management tool once officially recommended. It is built on top of Flutter's native InheritedWidget.

Practical code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 1. Define state (Model)
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Notify listeners to rebuild
  }
}

// 2. Inject and use
class ProviderApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MaterialApp(
        home: Scaffold(
          body: Center(
            // Use Consumer for local rebuilds
            child: Consumer<CounterModel>(
              builder: (context, model, child) => Text('${model.count}'),
            ),
          ),
          floatingActionButton: Builder(
            builder: (ctx) => FloatingActionButton(
              // listen: false is used to only call methods without listening for state changes
              onPressed: () => ctx.read<CounterModel>().increment(),
              child: Icon(Icons.add),
            ),
          ),
        ),
      ),
    );
  }
}

2.2 BLoC: Rigorous event-driven architecture

BLoC (Business Logic Component) forces developers to think in terms of streams, clearly distinguishing between events and states.

Concept flow: UI emits Event -> BLoC receives and processes -> BLoC yields State -> UI listens to State and rebuilds

Practical code:

import 'package:flutter_bloc/flutter_bloc.dart';

// 1. Events & States
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}

// 2. BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) => emit(state + 1));
  }
}

// 3. UI
class BlocApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: Scaffold(
        body: Center(
          child: BlocBuilder<CounterBloc, int>(
            builder: (context, count) => Text('$count'),
          ),
        ),
        floatingActionButton: Builder(
          builder: (ctx) => FloatingActionButton(
            onPressed: () => ctx.read<CounterBloc>().add(CounterIncrementPressed()),
            child: Icon(Icons.add),
          ),
        ),
      ),
    );
  }
}

2.3 Riverpod: The rebirth of Provider

Riverpod is the next-generation state management library developed by Remi, the author of Provider. It overcomes Provider's dependency on the Flutter tree.

Practical code:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. Declare Provider (defined like a global variable, but safe)
final counterProvider = StateProvider<int>((ref) => 0);

// 2. Wrap the app with ProviderScope
void main() {
  runApp(ProviderScope(child: RiverpodApp()));
}

// 3. UI inherits ConsumerWidget
class RiverpodApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Listen to state changes
    final count = ref.watch(counterProvider);

    return MaterialApp(
      home: Scaffold(
        body: Center(child: Text('$count')),
        floatingActionButton: FloatingActionButton(
          // Update state
          onPressed: () => ref.read(counterProvider.notifier).state++,
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

3. How to choose? (Decision tree)

4. Conclusion

There is no absolutely perfect state management solution, only the choice that best fits the current team and business scenario. Mastering their core ideas — separation of concerns, unidirectional data flow — is the key to improving architectural ability. Which one are you using? Feel free to leave a comment and discuss!

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

电气工程师 1 likes

signals is so convenient