Flutter — State Management with Riverpod

Tony Owen
5 min readJan 24, 2022

When I started developing in Flutter, I just used StatefulWidget to handle any screen state. It works, it’s straight forward; and it’s what people should use when starting out to understand the build / state system. But, what about application state?

Application State

This is where I struggled a bit. Of course you can pass data around when you’re navigating, but this quickly becomes unmanageable. So Flutter promotes the use of InheritedWidget as a way to manage global state. InheritedWidget provides a way to look up the widget tree to find a widget, and find state. But it’s a lot of boilerplate code to setup and use. So Google (very wisely) switched, and said “hey, go use this library .. Provider”. So I did.

Provider

Provider in its own words is “A wrapper around InheritedWidget to make them easier to use and more reusable”, and it does just that. It lets you declare Providers further up the widget tree, and easily access them or watch them using something like final value = context.watch<int>(); . But, this was also a library I didn’t 100% get along with. Having to make sure that the Provider was in a widget further up the tree, or wrapping your widget in some kind of consumer widget. It just wasn’t clicking with me. So I went back to looking around, and found (amongst many others) Riverpod.

Riverpod

Riverpod considers itself, a rewrite of Provider to make improvements that would be otherwise impossible. For me this library made application state management easy, and it’s now past 1.0. So lets have a look at how I use it (I’m sure there are many ways people do)…

The death of StatefulWidget

You can use Riverpod with a StatefulWidget, but there should be little need. Instead you can now use a ConsumerWidget or if you’re using Flutter hooks a HookConsumerWidget . With both of these your build function changes, and gives you a WidgetRef this is what is used to access the providers from Riverpod.

Alright Samuel .. here’s a small example:

In this simple example a provider is created to return the oh so original Hello World! … then inside the ConsumerWidget build method, we read that provider to the myThing variable. Which we then use in the Text widget. This is all made possible because somewhere above this in the widget tree we have use a ProviderScope . I usually just wrap my app with this, and then everything is available.

Too simple

Yeah, yeah, I know. That example up there is pointless. So lets look at a few more:

This time, we change the Provider to a StateProvider . The difference here is a Provider exposes a read-only value, whereas StateProvider exposes a value that can be modified from outside. Now instead of reading the value (one time), we’re watching the value for changes. When the value changes a redraw will be initiated inside the widget. So now from the button, we access the provider’s notifier and set its state to a new String. Now pressing the button will change the string value, and update the Text widget on screen.

This is just a one to one example, but as myProvider is global .. it could be accessed and changed from anywhere.

Types or Providers

  • Provider — A provider that provides a read only value
  • StateProvider — A provider thats value can be modified from outside
  • StreamProvider — A provider that creates a stream and exposes it latest event
  • FutureProvider — A provider that asynchronously provides a single value
  • More .. go learn about them here

Dependency Injection

You don’t just have to use providers for values like Int , String , Bool etc. You can use it to provide a Singleton of a class for example. This I found an interesting way to kind of use the library for dependency injection:

What’s going on here?

  • So we have a Movie data class, a MoviesRepo , and a MoviesApi
  • I created three providers, one for the api, one for the repo, and the last to hold the list of movies
  • The widget knows nothing about where the data comes from, just that it will receive a list of movies
  • The repo gets the api it needs from reading the moviesApiProvider.

This ties in nicely to testability, how can this be tested if they are global variables?

Testing

Under Widget Testing and UI Testing, all we need to do is supply override values. Here you could just supply a mock, to test different scenarios later on. Because we’re injecting the dependency into MovieRepo constructor, then unit testing becomes as simple as injecting the mock during a test.

Riverpod is a great library created by Remi Rousselet , and it is definitely my preferred choice when it comes to state management in Flutter

--

--