The Top 5 Flutter State Management Solutions: A Deep Dive

State management is a critical part of any user-facing application’s architecture. It ensures the app’s data and interface remain synchronized as users interact. In the following tutorial, you will explore the most popular state management solutions offered in Flutter. By Agustinus Theodorus.

5 (1) · 1 Review

Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Provider

Provider is a popular package in Flutter that makes state management simple and easy to implement. Provider is simpler compared to other packages on this list. It has a low learning curve and is perfect for small projects with limited complexity.

To begin using Provider, you only need to define a model class that extends ChangeNotifier.

class ColorToggler extends ChangeNotifier {
  bool _isRed = true;
  bool get isRed => _isRed;

  void toggleColor() {
    _isRed = !_isRed;
    notifyListeners();
  }
}

Then insert the ColorToggler provider into the widget to embed its state.

class ColorTogglerPage extends StatelessWidget {
  void _toggleColor(BuildContext context) {
    Provider.of<ColorToggler>(context, listen: false).toggleColor();
  }

  @override
  Widget build(BuildContext context) {
    var isRed = Provider.of<ColorToggler>(context).getColorToggler;
    return TextButton(
      style: TextButton.styleFrom(
        foregroundColor: isRed ? Colors.red : Colors.indigo,
      ),
      onPressed: () => _toggleColor(context),
      child: const Text('Change my Color!'),
    );
  }
}

And finally, create the instance of ColorToggler somewhere higher at widget tree.

class ColorTogglerApp extends StatelessWidget {
  const ColorTogglerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider.value(
        value: ColorToggler(),
        child: ColorTogglerPage(),
      ),
    );
  }
}

The Provider.of in combination with ChangeNotifierProvider listen to a state changes, and when notifyListeners is called, the data will change, thus updating the UI.

Compared with other packages, Provider is easy to implement and is interoperable, requiring little architectural change. However, to use Provider in a large-scale app, you must incorporate better architectural patterns to use it efficiently.

Setting up Provider requires installing the provider package using flutter pub. Next, you must create a Notifier class, like in the previous example. Then, you can create a Listener class and wrap your widget within it or follow the example above and use the Provider.of syntax. Note the former option is more efficient but requires more boilerplate code to run.

As for testability, Provider lacks documentation, but it’s possible if you use the WidgetTester and inject the Provider within its context via the pumpWidget function. The following GitHub Issue is the closest documentation available if you want official documentation.

The Provider.of option gives you immense flexibility when using it with other packages. It can be easily refactored to work with any other state management system, including BLoC architecture, commonly used in Flutter for complex applications requiring more than a basic state management solution.

Provider is a fine choice for developers new to Flutter who want to start with state quickly. Also, it gives you a choice between creating a simple state management solution and a more complex one as your project grows. If you want to read a more in-depth walkthrough of implementing Provider in your application, consider reading this Kodeco article regarding the Provider package.

One alternative to Provider that has gained popularity is Riverpod, which the creator of Flutter Hooks developed. Riverpod is a state management library inspired by Provider but doesn’t have a Flutter vendor lock-in. Removing the need to use the BuildContext allows you to use Riverpod as a Dart package, not just a Flutter package.

Riverpod is an alternative, but sadly, you won’t learn it in this article. It will only serve as a mention of its existence as an alternative to Provider. However, if you want to read more about Riverpod, go to their official getting started page.

BLoC

Another popular state management solution in Flutter is BLoC, which stands for Business Logic Component. Between handling the main user interface and state management, programming UI applications can be complex. BLoC helps you separate UI and business logic, making it easier to maintain the state and update the UI without touching other parts of the code.

In BLoC architecture, a widget will interact with a bloc that manages the business logic and provides it with data. This article will adapt and simplify an example from one of Kodeco’s most recent tutorials for using BLoC 8.0. In the following section, you’ll look at color toggler.

abstract class ColorTogglerEvent {}
class ToggleColorEvent extends ColorTogglerEvent {}

class ColorTogglerBloc extends Bloc<ColorTogglerEvent, ColorTogglerState> {
  ColorTogglerBloc() : super(const ColorTogglerState()) {
    on<ToggleColorEvent>(_onToggleColor);
  }

  void _onToggleColor(
    ToggleColorEvent event,
    Emitter<ColorTogglerState> emit,
  ) {
    // ColorToggler logic...
    emit(state.copyWith(isRed: !state.isRed));
  }
}

You define a BLoC by creating a class that extends from the BLoC class, which defines what type of events the bloc can emit and which functions emit them. Then, inject your BLoC state inside your widget tree via the BLoC providers.

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (ctx) => ColorTogglerBloc(),
        child: const ColorTogglerPage(),
      ),
    );
  }
}

Injecting the BLoC provider from the top of the widget structure helps proliferate your BLoC provider within the app. But to proliferate multiple BLoC providers and access them in the same widget tree, you can research using the MultiBlocProvider.

Here’s an example of how to use it from within the ColorTogglerPage.

class ColorTogglerPage extends StatelessWidget {
  const ColorTogglerPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final bloc = context.watch<ColorTogglerBloc>();

    return TextButton(
      style: TextButton.styleFrom(
        foregroundColor: bloc.state.isRed ? Colors.red : Colors.indigo,
      ),
      onPressed: () => bloc.add(ToggleColorEvent()),
      child: const Text('Change my Color!'),
    );
  }
}

The important part inside the code snippet above is final state = context.watch<ColorTogglerBloc>(). This line of code listens to the BLoC state and its corresponding events.

class ColorTogglerState extends Equatable {
  const ColorTogglerState({this.isRed = true});

  final bool isRed;

  ColorTogglerState copyWith({bool? isRed}) {
    return ColorTogglerState(isRed: isRed ?? this.isRed);
  }

  @override
  List<Object?> get props => [isRed];
}

Assume that each ColorTogglerState contains a status property that represents the current color. To display the current color, you can access it via bloc.state.isRed inside ColorTogglerPage‘s build method.

Installing BLoC is straightforward. However, ease of setup is more complicated than the rest, and you need to create a lot of boilerplates for the application to work.

For example, to create a simple application like the previous example, you need to manually define your app’s events, states and blocs. All of those are in separate classes. The BLoC package is a powerful Flutter state management tool but requires more boilerplate code than others.

BLoC provides a native testing package called bloc_test. It implements a comprehensive testing solution for any Flutter app that uses the BLoC package. Testing a BLoC using this package is direct and well-documented. You’ll need not worry about testing your app’s BLoC because this package handles most of the heavy lifting.

BLoC cannot be a “secondary” package you use for state management because it relies on boilerplate code. The only interoperability use would be to use BLoC as your Flutter app’s primary state management solution while using either GetX or Provider for the more straightforward stateless widget solutions.

Finally, documentation is complete, with many examples for developers to follow. BLoC is popular among Flutter developers. You’ll likely find a lot of people sharing other online resources.

The BLoC pattern can make your code more organized and maintainable but requires careful planning and execution. If you want a more in-depth walkthrough of implementing the most up-to-date BLoC version in your application, consider reading this Kodeco article on getting started with BLoC 8.0. It covers the basics of setting up a BLoC pattern in your Flutter application and provides examples.

Agustinus Theodorus

Contributors

Agustinus Theodorus

Author

Vid Palčar

Tech Editor

Adriana Kutenko

Illustrator

Yan Zaitsev

Final Pass Editor

Brian Moakley

Team Lead

Over 300 content creators. Join our team.