Flutter Riverpod Tutorial Part 7: Advanced Riverpod Patterns

Search for a command to run...

No comments yet. Be the first to comment.
In this series, I'll cover every concept of Riverpod in a comprehensive manner, so that any user can understand total ins and outs of any Riverpod concept.
In this tutorial, we'll explore how to set up dependency injection with Riverpod, manage dependencies across your app, and test with dependency injection. Dependency injection is a technique where an object receives its dependencies from an external ...
While I generally write about Flutter. This problem has been bugging me for a while and there seem to be limited resources to find a solution. So for anyone who is looking for a solution. Here you go: Type edge:flags into the address bar and open it...
In this tutorial, we’ll go through covering interceptor, which is a very crucial feature of Dio. You can find the entire source code for the project here: dio_tasker Understanding Interceptors: What is an interceptor? An interceptor in Dio is a powe...

In this part, we’ll enhance our Task Manager app by parsing JSON responses and handling different types of errors. We’ll use Dio’s built-in error handling mechanisms. You can find the source code of the entire app here: dio_tasker Parsing JSON Respon...

While building apps in Flutter is extremely fluid and wonderfully intuitive. We obviously know that we can’t build the next TikTok or Twitter by only building offline apps. Which is why we absolutely need internet capabilities in our Flutter apps. Th...

Understanding when to use setState in Flutter is crucial for managing our app’s state effectively. Here’s a detailed guide: When to use setState ? Updating the UI: Use setState when we need to update the UI in response to changes in the internal sta...

In this tutorial we'll dive into advanced Riverpod patterns.
Using the family Modifier
Using the AutoDispose Modifier
Keeping Providers Alive with keepAlive
Declaring Dependencies with dependencies
family ModifierThe family modifier allows you to create parameterized providers. This is useful when the provider's logic depends on external parameters.
User Model Class:
Let's create a user model class:
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> jsonUser) => User(
id: jsonUser['id'], name: jsonUser['name'], email: jsonUser['email']);
}
userProvider with family:
Now that we have the User let's also create a FutureProvider that gives us the user.
So couple of point in the following code
We are using the family modifier.
Thanks to the family modifier, we are able to pass an argument to the FutureProvider.
In here we are passing int userId
Now we are using the userId in the http.get() to get JSON data associated with the particular userId
final userProvider = FutureProvider.family<User, int>((ref, userId) async {
final apiResponse = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/users/$userId'));
if (apiResponse.statusCode == 200) {
return User.fromJson(jsonDecode(apiResponse.body));
} else {
throw Exception('Unable to fetch the user');
}
});
userWidget:
Let's create a ConsumerWidget that takes userId as a parameters and populates the users details in a ListTile
class UserWidget extends HookConsumerWidget {
final int userId;
const UserWidget({super.key, required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsyncValue = ref.watch(userProvider(userId));
return Padding(
padding: const EdgeInsets.all(8),
child: userAsyncValue.when(
data: (user) => ListTile(
title: Text(user.name),
subtitle: Text(user.email),
),
error: (error, stackTrace) => Text('Error: ${error.toString()}'),
loading: () => const CircularProgressIndicator()),
);
}
}
Finally I am creating a Screen where I can call multiple users
class UsersFamilyList extends StatelessWidget {
const UsersFamilyList({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Users List with Family Modifier'),
),
body: const Column(
children: [
UserWidget(userId: 1),
UserWidget(userId: 2),
UserWidget(userId: 3),
],
),
);
}
}
Always remember that in this tutorial. We have covered two things:
family: Creates a parameterized provider that takes a user ID.
FutureProvider.family: Fetches user data based on the provided ID.
Perfect Now we can see the output here:

You can find the source code here: Family Modifier
AutoDispose ModifierThe AutoDispose modifier helps manage resources by automatically disposing of providers when they are no longer needed.
State Provider with autoDispose modifier:
First of all let's create a StateProvider with a autoDispose modifier. In here we are creating a basic counterProvider that also has auto dispose. So due to that the app should dispose of the provider whenever we are not using it.
final counterAutoDisposeProvider = StateProvider.autoDispose((ref) => 0);
Counter Page:
Let's create a page where we will use the above provider. We'll create a basic page with a FAB that'll increment the value of the counter
class CounterPage extends HookConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterAutoDisposeProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Counter Auto Dispose'),
),
body: Center(
child: Text(
'Count: $count',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterAutoDisposeProvider.notifier).state++,
child: const Icon(Icons.add),
),
);
}
}
You can find the source code for the above section here: autoDipose Modifier
And as you can see the counter resets to 0. Every time we leave the screen

keepAliveThe keepAlive method ensures that a provider remains active even when there are no listeners.
Create the keepAlive Provider:
final counterKeepAliveProvider = StateProvider.autoDispose<int>((ref) {
ref.keepAlive();
return 0;
});
keepAlive Page:
Let's create a basic page that'll read the counter similar to our previous page
class KeepAliveCounterPage extends HookConsumerWidget {
const KeepAliveCounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterKeepAliveProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Counter Auto Dispose'),
),
body: Center(
child: Text(
'Count: $count',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterKeepAliveProvider.notifier).state++,
child: const Icon(Icons.add),
),
);
}
}
You can find the source code here: keepAlive Provider
And as you can see that the counter retains the value despite exiting the screen:

dependenciesDeclaring dependencies ensures that a provider depends on another provider and rebuilds when the dependent provider changes.
Define a configProvider:
import 'package:hooks_riverpod/hooks_riverpod.dart';
final configProvider = StateProvider<String>((ref) => 'Config');
final dependentProvider = Provider<String>((ref) {
final config = ref.watch(configProvider);
return 'Depends on $config';
});
Let's change the value on a screen and see if it changes it.
Dependent Provider Page:
In the following screen. Let's change the value of configProvider when a button is clicked. So that we can see the new value from dependentProvider
class DependentProviderPage extends HookConsumerWidget {
const DependentProviderPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final dependentString = ref.watch(dependentProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Dependent Provider'),
),
body: SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
dependentString,
style: const TextStyle(fontSize: 24),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () =>
ref.read(configProvider.notifier).state = "New Config",
child: const Text('Change Config'))
],
),
));
}
}
You can find the source code here: dependent_provider
There you go:

So finally we have looked at various modifiers and advanced patterns from Riverpod. I hope these tutorials help you in better maintenance of your application state.