This website uses cookies to enhance the user experience

State Management (Provider, Riverpod, Bloc)

Share:

In building apps with Flutter, more of your time will likely be spent on state management than on building UIs. State Management is the backbone of any Flutter Application. The state is information that can be read when a Widget is built and might change during the lifetime of the Widget. It's the core principle that allows our apps to work.

The purpose of this article is to guide you through three different state management approaches in Flutter: Provider, Riverpod, and Bloc. We'll use a basic movie app demonstration for our examples. This simple application will simply display a list of movie characters, and allow us to add new characters to the list.

Firstly, we'll kick off with the Provider package.

Provider

Provider is a staple in Flutter state management. It works by wrapping around your application, providing objects to widgets that consume them. The provider can provide anything from simple values to complex objects.

In our movie app, we'll have a class named CharacterNotifier, this class is responsible for managing a list of movie characters.

class CharacterNotifier with ChangeNotifier {
  static List<String> _characters = ['Harry Potter', 'Hermione Granger', 'Ron Weasley'];

  List<String> get characters => _characters;

  void addCharacter(String character) {
    _characters.add(character);
    notifyListeners();
  }
}

We create a provider for our CharacterNotifier:

ChangeNotifierProvider<CharacterNotifier>(
    create: (context) => CharacterNotifier(),
    child: MyApp(),
)

Now, the CharacterNotifier is accessible to all child widgets of MyApp. To read the value, we use Provider.of<T>:

final characterNotifier = Provider.of<CharacterNotifier>(context);
final characters = characterNotifier.characters;

To update the list of characters, we can use the addCharacter of the CharacterNotifier.

characterNotifier.addCharacter('Dumbledore');

Riverpod

Riverpod is a newer state management solution aiming to solve Provider's shortcomings. Riverpod eliminates the need for context, takes care of disposing of objects for you, and more.

In our example, we have a StateProvider for our list of movie characters. We can access and mutate it using a watch or read functions.

final characterProvider = StateProvider<List<String>>(
  (ref) => ['Harry Potter', 'Hermione Granger', 'Ron Weasley'],
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final characters = context.read(characterProvider).state;
  }
}

class AddCharacterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () => context.read(characterProvider).state.add('Dumbledore'),
      tooltip: 'Add Character',
      child: Icon(Icons.add),
    );
  }
}

Riverpod provides much more than just state management; it also supports multiple providers out-of-the-box, asynchronous loading, and more.

Bloc

Bloc (Business Logic Component) is an advanced state management library that uses Streams for data inputs into the Bloc and Outputs from a Bloc to the UI.

In our movie app, we have a CharacterBloc that will govern our list of movie characters.

class CharacterEvent { }

class AddCharacter extends CharacterEvent {
  final String character;

  AddCharacter(this.character);
}

class CharacterBloc {
  List<String> _characters = ['Harry Potter', 'Hermione Granger', 'Ron Weasley'];

  final _characterStateController = StreamController<List<String>>();
  StreamSink<List<String>> get _inCharacters => _characterStateController.sink;
  Stream<List<String>> get characters => _characterStateController.stream;

  final _characterEventController = StreamController<CharacterEvent>();
  Sink<CharacterEvent> get characterEventSink => _characterEventController.sink;

  CharacterBloc() {
    _characterEventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CharacterEvent event) {
    if (event is AddCharacter) {
      _characters.add(event.character);
    }
    _inCharacters.add(_characters);
  }

  void dispose() {
    _characterEventController.close();
    _characterStateController.close();
  }
}

To listen to state changes, we use a StreamBuilder widget:

StreamBuilder(
  stream: bloc.characters,
  builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
    return ListView.builder(
      itemCount: snapshot.data.length,
      itemBuilder: (context, index) => ListTile(title: Text(snapshot.data[index])),
    );
  },
)

To add a new character, we add an event to the Bloc's sink:

bloc.characterEventSink.add(AddCharacter('Dumbledore'))

There you have it! Three different ways to manage state in a Flutter app. Each has its own strengths and is suited to different kinds of applications. The best one for your use case depends on your personal preference and the specific needs of your application.

0 Comment


Sign up or Log in to leave a comment


Recent job openings