?? Riverpod in Flutter: The Only Guide You Need
Muhammad Ismail
Passionate Software Engineer | Flutter & Full-Stack Developer | Mobile & Web Apps | Firebase, Supabase | UI/UX & Graphic Design | Tech Writer (SQL, MongoDB, Node.js, Firebase, Flutter, APIs & More)
70% of Flutter devs still struggle with state management—Riverpod fixes that!
Introduction
Why Riverpod
Riverpid Installation
Types of Riverpod Provider
Initial Setup
flutter pub add flutter_riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
ProviderScope
Now let's deep dive into each type of Riverpod Provider so sit back relax and enjoy the article
1. Provider
How Provider Works?
Provider Syntax & Usage
import 'package:flutter_riverpod/flutter_riverpod.dart';
// A simple provider that returns a static string
final greetingProvider = Provider<String>((ref) {
return "Hello, Riverpod!";
});
Consume the Provider in a Widget
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// Access the provider's value
final greeting = ref.watch(greetingProvider);
return Scaffold(
body: Center(
child: Text(greeting), // Displays "Hello, Riverpod!"
),
);
}
}
We have another way through which we can consume only the necessary widgets to rebuild
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider
final greetingProvider = Provider<String>((ref) => "Hello, Riverpod!");
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer(
builder: (context, ref, child) {
final greeting = ref.watch(greetingProvider);
return Text(greeting); // "Hello, Riverpod!"
},
),
),
);
}
}
Now if we have a Stateful widget and we want to read the provider value what do we have to do in that case? let's dive into it.
class HomeScreen extends ConsumerStatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen> {
@override
Widget build(BuildContext context) {
final greeting = ref.watch(greetingProvider); // Can use ref directly
return Scaffold(
body: Center(
child: Text(greeting), // "Hello, Riverpod!"
),
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart'; // Simple Provider
final greetingProvider = Provider<String>((ref) => "Hello, Riverpod!");
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => HomeScreenState()
}
class HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer(
builder: (context, ref, child) {
final greeting = ref.watch(greetingProvider);
return Text(greeting); // "Hello, Riverpod!"
),);
}
}
2. StateProvider
When to Use StateProvider?
Example
In this example of counter, we will see how to increment and update the UI and then reset the counter
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
// StateProvider for managing the counter
final counterProvider = StateProvider<int>((ref) => 0);
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_provider.dart';
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // Watch counter state
return Scaffold(
appBar: AppBar(title: Text("Riverpod Counter"),
action[
IconButton(onPressed: (){
ref.invalidate(counterProvider); // First way to reset counter
ref.referesh(counterProvider); //second way to reset counter
)
}
, icon: Icon(Icons.referesh)
]),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Counter: $counter',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state++; // Incrementation fist Way
ref.read(counterProvider.notifier).update((state)=> state+1); // Incrementaion second way
},
child: Text("Increment"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state--; // Decrement
},
child: Text("Decrement"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state = 0; // Reset
},
child: Text("Reset"),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
),
],
),
],
),
),
);
}
}
3.StateNotifierProvider
lib/
│── main.dart // Entry point of the app
│── providers/counter_notifier.dart // Counter logic using StateNotifier
│── views/counter_screen.dart // User interface screen for the counter
import 'package:flutter_riverpod/flutter_riverpod.dart';
// StateNotifier to manage counter state
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0); // Initial counter value is 0
void increment() => state++; // Increment counter
void decrement() => state--; // Decrement counter
void reset() => state = 0; // Reset counter
}
// Define a provider using StateNotifierProvider
final counterProvider =
StateNotifierProvider<CounterNotifier, int>((ref) => CounterNotifier());
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_notifier.dart';
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // Watch counter state
return Scaffold(
appBar: AppBar(title: Text("Riverpod Counter with StateNotifier")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Counter: $counter',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).increment();
},
child: Text("Increment"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).decrement();
},
child: Text("Decrement"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).reset();
},
child: Text("Reset"),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
),
],
),
],
),
),
);
}
}
领英推荐
4.FutureProvider
Example 1
lib/
│── main.dart // Entry point
│── providers/
│ │── data_provider.dart // FutureProvider for fetching data
│── views/
│ │── home_screen.dart // UI screen
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
// Simulated API call
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2)); // Simulating network delay
return "Hello from FutureProvider!";
}
// FutureProvider to fetch data asynchronously
final dataProvider = FutureProvider<String>((ref) async {
return fetchData();
});
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/data_provider.dart';
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncData = ref.watch(dataProvider); // Watching data state
return Scaffold(
appBar: AppBar(title: Text("FutureProvider Example")),
body: Center(
child: asyncData.when(
data: (data) => Text(
data,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
loading: () => CircularProgressIndicator(), // Show while fetching
error: (err, stack) => Text("Error loading data"), // Show on error
),
),
);
}
}
Example 2
Great! Let's implement FutureProvider with a real API call using JSONPlaceholder (a free REST API). We'll fetch a random post from their /posts/1 endpoint.
lib/
│── main.dart // Entry point
│── providers/
│ │── post_provider.dart // FutureProvider for API call
│── models/
│ │── post_model.dart // Post model
│── views/
│ │── home_screen.dart // UI screen
class PostModel {
final int id;
final String title;
final String body;
PostModel({required this.id, required this.title, required this.body});
// Factory method to convert JSON response into a Dart object
factory PostModel.fromJson(Map<String, dynamic> json) {
return PostModel(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/post_model.dart';
// Function to fetch post data from API
Future<PostModel> fetchPost() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
if (response.statusCode == 200) {
final jsonData = json.decode(response.body);
return PostModel.fromJson(jsonData);
} else {
throw Exception("Failed to load post");
}
}
// FutureProvider to manage API call state
final postProvider = FutureProvider<PostModel>((ref) async {
return fetchPost();
});
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/post_provider.dart';
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final postAsync = ref.watch(postProvider); // Watching API call state
return Scaffold(
appBar: AppBar(title: Text("Fetch API with FutureProvider")),
body: Center(
child: postAsync.when(
data: (post) => Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
post.title,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Text(
post.body,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
],
),
),
loading: () => CircularProgressIndicator(), // Show loader
error: (err, stack) => Text("Error: ${err.toString()}"), // Show error
),
),
);
}
}
5. StreamProvider
StreamProvider in Riverpod is used to listen to a stream of data and automatically rebuild the UI whenever the stream emits a new value. It's useful for real-time updates, such as:
Example
lib/
│── main.dart // Entry point
│── providers/
│ │── number_provider.dart // StreamProvider for real-time updates
│── views/
│ │── home_screen.dart // UI screen
import 'dart:async';
import 'dart:math';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Function that generates a new random number every 2 seconds
Stream<int> randomNumberStream() async* {
final random = Random();
while (true) {
await Future.delayed(Duration(seconds: 2));
yield random.nextInt(100); // Generate a random number (0-99)
}
}
// StreamProvider to manage the random number stream
final numberProvider = StreamProvider<int>((ref) {
return randomNumberStream();
});
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/number_provider.dart';
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final numberStream = ref.watch(numberProvider); // Watching stream updates
return Scaffold(
appBar: AppBar(title: Text("Live Stream with StreamProvider")),
body: Center(
child: numberStream.when(
data: (number) => Text(
"Live Number: $number",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
loading: () => CircularProgressIndicator(), // Show loader
error: (err, stack) => Text("Error: ${err.toString()}"), // Show error
),
),
);
}
}
6.ChangeNotifierProvider
Example
import 'package:flutter/material.dart';
class CounterNotifier extends ChangeNotifier {
int _count = 0;
int get count => _count; //getter
void increment() {
_count++;
notifyListeners(); // Notify UI about state change
}
void reset() {
_count = 0;
notifyListeners(); // Reset counter and update UI
}
}
// Create a provider for CounterNotifier
final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text("Counter with ChangeNotifierProvider")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Counter: ${counter.count}", style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => ref.read(counterProvider).increment(),
child: Text("Increment"),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () => ref.read(counterProvider).reset(),
child: Text("Reset"),
),
],
),
),
);
}
}
Real Project Example
create a Flutter project that includes: ? Toggle Button – Change UI state (e.g., Dark/Light Mode) ? User Name from Firestore – Fetch user's name from Firestore ? Data from API – Fetch external data (e.g., Posts from JSONPlaceholder) ? Theme Change Support – Allow user to switch between light and dark themes
Project Structure
/lib
│-- /models
│ ├── user_model.dart # User model
│-- /services
│ ├── api_service.dart # Fetch data from API
│ ├── firestore_service.dart # Fetch user data from Firestore
│-- /providers
│ ├── theme_provider.dart # Theme management
│ ├── user_provider.dart # Firestore user provider
│ ├── api_provider.dart # API fetch provider
│-- /views
│ ├── home_screen.dart # Main screen
│-- /widgets
│ ├── toggle_button.dart # Toggle button widget
│-- main.dart # Entry point
// UserModel
class User {
final String id;
final String name;
User({required this.id, required this.name});
factory User.fromMap(Map<String, dynamic> data) {
return User(
id: data['id'],
name: data['name'],
);
}
}
//Firestore Service
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/user_model.dart';
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
Future<User> getUserData(String userId) async {
final doc = await _db.collection('users').doc(userId).get();
return User.fromMap(doc.data()!);
}
}
// API service
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
Future<List<String>> fetchPosts() async {
final response =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
final List data = jsonDecode(response.body);
return data.map((post) => post['title'].toString()).toList();
} else {
throw Exception('Failed to load posts');
}
}
}
// User Provider
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/firestore_service.dart';
import '../models/user_model.dart';
final userProvider = FutureProvider.family<User, String>((ref, userId) {
return FirestoreService().getUserData(userId);
});
//API provider
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/api_service.dart';
final apiProvider = FutureProvider<List<String>>((ref) {
return ApiService().fetchPosts();
});
//Theme Provider
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';
class ThemeNotifier extends StateNotifier<ThemeMode> {
ThemeNotifier() : super(ThemeMode.light);
void toggleTheme() {
state = state == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
}
}
final themeProvider = StateNotifierProvider<ThemeNotifier, ThemeMode>((ref) {
return ThemeNotifier();
});
// Main File
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'views/home_screen.dart';
import 'providers/theme_provider.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeProvider);
return MaterialApp(
title: 'Riverpod Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: themeMode,
home: HomeScreen(),
);
}
}
//Home Screen UI
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/user_provider.dart';
import '../providers/api_provider.dart';
import '../providers/theme_provider.dart';
import '../widgets/toggle_button.dart';
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider('123'));
final postsAsync = ref.watch(apiProvider);
return Scaffold(
appBar: AppBar(
title: Text("Riverpod Demo"),
actions: [
IconButton(
icon: Icon(Icons.brightness_6),
onPressed: () => ref.read(themeProvider.notifier).toggleTheme(),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// ?? Firestore User Data
userAsync.when(
data: (user) => Text("User: ${user.name}", style: TextStyle(fontSize: 20)),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text("Error: $err"),
),
SizedBox(height: 20),
// ?? Toggle Button
ToggleButtonWidget(),
SizedBox(height: 20),
// ?? API Data
Expanded(
child: postsAsync.when(
data: (posts) => ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) => ListTile(title: Text(posts[index])),
),
loading: () => Center(child: CircularProgressIndicator()),
error: (err, _) => Text("Error loading posts"),
),
),
],
),
),
);
}
}
How Riverpod Handles Disposal?
@override
void dispose() {
print("CounterNotifier Disposed!");
super.dispose();
}
final timerProvider = Provider.autoDispose((ref) {
final timer = Timer.periodic(Duration(seconds: 1), (timer) {
print("Tick...");
});
ref.onDispose(() {
print("Timer Disposed!");
timer.cancel();
});
return timer;
});
Understanding .family in Riverpod
Example
final userProvider = FutureProvider.family<UserModel, String>((ref, userId) async {
final response = await http.get(Uri.parse('https://api.example.com/users/$userId'));
final data = jsonDecode(response.body);
return UserModel.fromJson(data);
});
Consumer(
builder: (context, ref, child) {
final userId = "123"; // This could come from a route or user selection
final userAsync = ref.watch(userProvider(userId));
return userAsync.when(
data: (user) => Text("User: ${user.name}"),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text("Error: $err"),
);
},
);
Wrapping Up: Mastering Riverpod in Flutter ??
Riverpod is a game-changer in Flutter state management, providing a scalable, testable, and efficient way to handle app states. In this article, we explored:
? The different types of Riverpod providers and their use cases. ? How to efficiently fetch and manage data using FutureProvider & StreamProvider. ? Advanced concepts like StateNotifierProvider, ChangeNotifierProvider, and Family. ? Best practices for maintaining and disposing of providers effectively.
What’s Next? Let’s Discuss! ??
Have you tried Riverpod in your projects? Which provider do you use the most? Let’s share insights in the comments! ??
If you found this article helpful, give it a like, share it with your fellow developers, and follow me for more Flutter content! ????
?? Further Reading:
?? State management is a journey, not a destination. Keep coding, and keep building! ???