Riverpod in Flutter: A Complete Guide
Introduction
Riverpod is a powerful and modern state management solution for Flutter. It builds upon the concepts of Provider but removes limitations like context dependency, making it more scalable and testable.
In this guide, we’ll cover:
-
Why use Riverpod?
-
Installation
-
Core concepts
-
Different types of providers
-
State management with Riverpod
-
Dependency injection
-
Async programming
-
Notifiers and state modifiers
-
Example project
Why Use Riverpod?
✅ No BuildContext Required – Unlike Provider, Riverpod doesn’t require context.watch
or context.read
.
✅ Improved Performance – It ensures widgets only rebuild when necessary.
✅ Testability – Riverpod makes writing tests simpler.
✅ Scalability – Ideal for small to large-scale applications.
Installation
To use Riverpod in your Flutter app, add the following dependency in pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.0.0
Then, run:
flutter pub get
1. Setting Up Riverpod
Riverpod requires a ProviderScope
at the root of your application.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
2. Understanding Providers
Riverpod provides different types of providers for different use cases. Let’s explore them.
a) Provider (Read-Only State)
Used for providing immutable values.
final helloProvider = Provider((ref) => "Hello, Riverpod!");
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final message = ref.watch(helloProvider);
return Scaffold(
body: Center(child: Text(message)),
);
}
}
b) StateProvider (Mutable State)
Used for simple state changes.
final counterProvider = StateProvider((ref) => 0);
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text("Count: $count")),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
c) FutureProvider (Asynchronous Data)
Used for handling async operations.
final dataProvider = FutureProvider((ref) async {
await Future.delayed(Duration(seconds: 2));
return "Data loaded!";
});
class AsyncScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncData = ref.watch(dataProvider);
return Scaffold(
body: Center(
child: asyncData.when(
data: (data) => Text(data),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text("Error: $err"),
),
),
);
}
}
d) StreamProvider (Stream Handling)
Used for handling real-time data.
final streamProvider = StreamProvider((ref) async* {
for (int i = 0; i < 10; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
});
class StreamScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final streamData = ref.watch(streamProvider);
return Scaffold(
body: Center(
child: streamData.when(
data: (data) => Text("Count: $data"),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text("Error: $err"),
),
),
);
}
}
3. Riverpod Notifiers
a) StateNotifierProvider (For Complex States)
Used for handling complex states.
class CounterNotifier extends StateNotifier {
CounterNotifier() : super(0);
void increment() => state++;
}
final counterNotifierProvider =
StateNotifierProvider((ref) => CounterNotifier());
class NotifierScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterNotifierProvider);
return Scaffold(
body: Center(child: Text("Count: $count")),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterNotifierProvider.notifier).increment(),
child: Icon(Icons.add),
),
);
}
}
4. Dependency Injection with Riverpod
Riverpod makes it easy to inject dependencies.
final apiServiceProvider = Provider((ref) => ApiService());
class ApiService {
String fetchData() => "Fetched Data";
}
class DependencyScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final apiService = ref.watch(apiServiceProvider);
return Scaffold(
body: Center(child: Text(apiService.fetchData())),
);
}
}
5. Persisting State in Riverpod
Use shared_preferences or hive for local storage.
final sharedPrefsProvider = Provider((ref) {
throw UnimplementedError();
});
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
final sharedPreferences = await SharedPreferences.getInstance();
runApp(
ProviderScope(
overrides: [sharedPrefsProvider.overrideWithValue(sharedPreferences)],
child: MyApp(),
),
);
}
Conclusion
Riverpod is a game-changer for Flutter state management. Whether you need simple state handling, async operations, or dependency injection, Riverpod provides a clean and scalable solution.
If you're building a Flutter app, give Riverpod a try! 🚀
Comments
Post a Comment