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?
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 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 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.
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
You don’t just have to use providers for values like
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
Moviedata class, a
MoviesRepo, and a
- 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?
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.