State management in Flutter has come a long way. If you have been using Flutter for a while now you should have come across SetState, Inherited widget, Scoped Model, Bloc Pattern, Rxdart, Redux, MobX, GetIt, Provider, Flutter bloc, Cubit, StateNotifier, ValueNotifier, etc. With the existence of all these state management tools, deciding which to use becomes difficult.
The Provider package was very welcome in the Flutter community because it is a simple but very powerful state management tool and it is very easy to use. So if Provider is so great, why Riverpod?
According to the creator, who is also the creator of the Provider package, Riverpod is considered a rewrite of Provider that includes improvements that would be otherwise impossible.
In summary, the Riverpod package is here to solve the flaws in the Provider package;
Riverpod catches programming errors at compile time rather than at runtime, unlike Provider,
Riverpod removes nesting for listening/combining objects,
Riverpod ensures that the application is testable.
This article will teach you everything you need to know about the Riverpod package and how to start using it in your Flutter application.
To complete this tutorial you will need to:
Download and install Android Studio or Visual Studio Code
Download and install Flutter.
Set up your editor as described here.
Once you have your environment set up for Flutter you can run the following command to create a new application:
$ flutter create riverpod_example
Open the pubspec.yaml for the newly created project and add the Riverpod package like this:
1
2
3
4
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^ 0.14 .0 + 3
Run the command “flutter pub get” in your terminal to include the package in your project.
A provider is an object that encapsulates a state and allows it to be listened to. The Riverpod package has seven different providers for different use cases. Here we will look at each of them.
The Provider is used to provide a single value, for instance, I can have a provider provide my name as follows:
final myNameProvider = Provider<String>((ref) => 'Morgan');
The StateProvider is used to manipulate simple states that can be modified like an enum. For instance, I can use a StateProvider to provide the ThemeMode for the application to change the application to dark mode or light mode.
final appThemeModeProvider = StateProvider<ThemeMode>((ref)=>ThemeMode.light);
The StateNotifierProvider is used to manipulate advanced states that would otherwise be difficult to manipulate with simpler providers such as StateProvider or Provider. It is used together with a StateNotifier to expose its current state. We can have a mood state management class as follows;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
abstract class MoodState {}
class MoodLoading extends MoodState {}
class MoodError extends MoodState {}
class MoodLoaded extends MoodState {
final String mood;
MoodLoaded(this.mood);
}
class MoodNotifier extends StateNotifier < MoodState > {
MoodNotifier(): super(MoodLoading());
Future < void > fetchMood([String mood = "Happy"]) async {
try {
state = MoodLoading();
//Pretending to do something meaningful here like making api call
await Future.delayed(const Duration(seconds: 2));
state = MoodLoaded(mood);
} catch (error) {
state = MoodError()
}
}
}
final moodProvider = StateNotifierProvider < MoodNotifier, MoodState > ((ref) =>
MoodNotifier());
The ChangeNotifierProvider creates and subscribes to a ChangeNotifier. The ChangeNotifier is very similar to a StateNotifier but used differently.
1
2
3
4
5
6
7
8
9
10
11
class OwnerNameNotifier extends ChangeNotifier {
String name;
OwnerNameNotifier(this.name);
void changeOwnerName(String ownerName) {
name = ownerName;
notifyListeners();
}
}
final ownerNameProvider = ChangeNotifierProvider < OwnerNameNotifier > ((ref) => OwnerNameNotifier('Morgan'));
FutureProvider is commonly used to represent an asynchronous operation, such as reading a file or sending an HTTP request, which is then listened to by the user interface.
1 2 3
final movieProvider = FutureProvider < Movie > ((ref) async { return dio.get('http://themoviedb/movie/popular'); });
The StreamProvider can be used to indicate a value that is loaded asynchronously and changes over time.
1
2
3
final numberProvider = StreamProvider < int > ((_) {
return Stream.fromIterable([1, 2, 3, 4, 5]);
});
The ScopedProvider is used to provide a separate value for a specific part of the application.
final productIndex = ScopedProvider<int>((ref) => throw UnimplementedError());
Before you use it, you have to override its value.
1
2
3
4
5
ProviderScope(overrides: [
productIndex.overrideWithValue(1)
],
child: ChildWidget()
)
The way to read a provider depends on where you are trying to use it.
Consuming the provider inside a widget class,
Using a provider from another provider,
Using a provider from a repository or service class.
Consuming the provider inside a widget class
There are four ways to use a provider inside a Flutter widget
Use the Consumer widget from the Riverpod package
You can use the Consumer widget at any level in your Flutter application widget tree.
1 2 3 4 5 6 7
Consumer( builder: (BuildContext context, T Function < T > (ProviderBase < Object, T > ) watch, Widget child) { final name = watch(myNameProvider); return Text(name); }, )
Extend the ConsumerWidget
Another way to listen to a provider is to create a new widget class that extends from the ConsumerWidget.
1
2
3
4
5
6
7
8
9
10
class MyHomeWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final themeMode = watch(appThemeModeProvider)
.state;
return MaterialApp(
themeMode: themeMode,
);
}
}
Listen to a state change using ProviderListener from the Riverpod package
You will not be interested in rebuilding a widget for a state change every time. You may wish to use your widget tree to push a route or show a dialog in some specific cases. The ProviderListener Widget would be used to implement such functionality.
1 2 3 4 5 6 7 8 9
ProviderListener( provider: moodProvider, onChange: (BuildContext context, MoodState moodState) { if (moodState is MoodError) { ScaffoldMessenger.of(context) .showSnackBar( SnackBar(content: Text("Unable to get your mood"))); } },
Using context.read(provider)
inside a callback or places where we are not interested in listening to the state rebuild, for example in an ElevatedButton.
1 2 3 4
ElevatedButton(onPressed: () { context.read(moodProvider.notifier) .fetchMood(); }, child: Text("Fetch mood"))
Using a provider from another provider
To listen to a state of a provider from another provider, we would use the watch method from the ref object passed to the callback of the provider.
1 2 3 4
final ownerNameProvider = ChangeNotifierProvider < OwnerNameNotifier > ((ref) { final myName = ref.watch(myNameProvider); return OwnerNameNotifier(myName); });
Using a provider from a repository or a service class
In this case, you will have to pass in the read method of the ref object to the repository or service class. For instance:
1
2
3
4
5
6
7
8
9
10
11
12
final tokenProvider = StateProvider < String > ((ref) => ' ');
class UserService {
final Reader read;
UserService(this.read);
Future < void > authenticateUser() async {
final String token = read(tokenProvider)
.state;
//Use the token to authenticate user
}
}
The Riverpod package comes with a new concept called modifiers. The modifier is a way of adding additional functionality to a provider. At the time of writing this article there are two modifiers, namely:
.family
.autoDispose
The .family modifier is used to provide an external parameter to a provider,
1
2
3
final movieProvider = FutureProvider.family < Movie, String > ((ref, id) async {
return dio.get('http://themoviedb/movie/$id');
});
The .autoDispose is used to destroy the state of a provider once it is no longer in use.
1 2 3
final movieProvider = FutureProvider.autoDispose < Movie > ((ref) async { return dio.get('http://themoviedb/movie/popular'); });
I hope you have learned how to use Riverpod to manage the state of your Flutter application from this tutorial. In part two of this tutorial, we will have a comprehensive project that will make use of the concepts taught here to build a Movie application. See you in the next article!
The official Riverpod documentation.
Check Out Topcoder Flutter Freelance Gigs