Skip to content
DeveloperMemos

Merging Providers with Riverpod

Flutter, State Management, Dart, Riverpod2 min read

I wrote a quick tutorial a couple of days ago that showed how to handle the state of a counter with Riverpod. In that post I only really briefly wrote about ProviderReference so today I'm writing another short tutorial about merging two Providers, using the the last tutorial as a base. If you haven't viewed the last tutorial you should probably read it first, you can check it out here.

A quick recap

First of all there were a couple of packages that needed to be added to pubspec.yaml in the last tutorial. They were:

In the last tutorial we had a class called CounterState to hold the counter's state:

1// To create a StateNotifier you first need to create class/model to hold the state
2class CounterState {
3
4 CounterState({this.value = 0});
5 final int value;
6
7 // This is just a simple utility method, you might want to try out freezed
8 // for more complex implementations
9 CounterState copyWith({int count}) {
10 return CounterState(
11 value: count ?? this.value
12 );
13 }
14
15}

We also created a StateNotifier, CounterNotifier, to handle updates for the above class:

1class CounterNotifier extends StateNotifier<CounterState> {
2 CounterNotifier() : super(CounterState());
3
4 increase() => state = state.copyWith(count: state.value + 1);
5 decrease() => state = state.copyWith(count: state.value - 1);
6}
7
8// Adding .autoDispose will dispose of the provider when it is no longer needed
9final counterProvider = StateNotifierProvider.autoDispose((_) => CounterNotifier());

We then finally created a widget called CounterPage that showed the counter's value and increase and decrease buttons.

Flutter Original Counter App

Merging Providers

In the last tutorial we only actually had one Provider, and that was counterProvider. This time I'm writing about merging Providers so we're going to create a StateProvider that will hold an int value(a multiplier to be more specific).

1// A StateProvider for an int value
2final multiplierProvider = StateProvider.autoDispose((_) => 4);

No changes need to be made to counterProvider, it is fine as it is already. Next we're going to create a Provider the will merge two values together: the value of the counter and the multiplier. Like I said in the last tutorial, when you create Provider you will always have something called a ProviderReference, you can use this reference to link to other existing Providers.

So here's the last Provider we're going to create:

1// A Provider that merges both of the above providers
2final valueProvider = Provider.autoDispose((ref) {
3 // Watch the value of the original counter
4 final value = ref.watch(counterProvider.state).value;
5 // Watch the value of the multiplier
6 final multiplier = ref.watch(multiplierProvider).state;
7 // Multiply the original value by the multiplier
8 return value * multiplier;
9});

The above provider generates an int value again. The ref variable above is a ProviderReference which has a few useful methods, the most useful are probably read and watch. In this case, we're watching the value of the counter and then the value of the multiplier. We then return the original value multiplied by the multiplier. We're using ref.watch instead of read.read in this case because you don't get updates if you use read.

In most cases you will want to use watch unless you're hooking into something that will never change like maybe a repository class that you wrapped in a Provider purely for dependency injection purposes.

The UI

All that's left to do is consume the new Provider in a HookWidget. I didn't really have to change much at all from the last tutorial, here's the new HookWidget:

1lass CounterPage extends HookWidget {
2
3 @override
4 Widget build(BuildContext context) {
5 final int value = useProvider(valueProvider);
6 final CounterNotifier counterNotifier = useProvider(counterProvider);
7 return Scaffold(
8 appBar: AppBar(
9 title: Text('CounterPage'),
10 ),
11 body: Container(
12 width: double.infinity,
13 child: Column(
14 mainAxisAlignment: MainAxisAlignment.center,
15 crossAxisAlignment: CrossAxisAlignment.center,
16 children: [
17 Padding(
18 padding: EdgeInsets.all(10),
19 child: Text('Count: $value')
20 ),
21 ElevatedButton(
22 child: Text('Increase'),
23 onPressed: () => counterNotifier.increase(),
24 ),
25 ElevatedButton(
26 child: Text('Decrease'),
27 onPressed: () => counterNotifier.decrease(),
28 )
29 ],
30 ),
31 ),
32 );
33 }
34
35}

As you can see we're no longer listening to the state of CounterNotifier anymore. Now we can just listen to valueProvider directly to get the (multiplied) value of the counter. Now when you tap "Increase" or "Decrease" the counter value should move in increments of 4.

In closing

I hope this tutorial was helpful if you were looking for a simple example of merging two Providers together with Riverpod. I didn't really get to explain the differences between Provider, StateProvider or StateNotifierProvider. Just like with the Provider package, there's a lot of different types of Providers. I guess I will have to leave that for another post in the future. Anyway, if you want to see the full code of what I wrote above you can see a gist of it here.