My personal revision notes for Flutter Riverpod. The goal of this repository is to quickly revise concepts without watching tutorials again.
- StateProvider
- NotifierProvider
- FutureProvider (Async Provider)
- AsyncNotifierProvider
- StreamProvider
- Family Provider
- AutoDispose
- Dependency Injection
- Riverpod Generator
- Freezed
- JSON Serializable
- Go Router
StateProvider
↓
NotifierProvider
↓
FutureProvider
↓
AsyncNotifierProvider
↓
StreamProvider
↓
Family
↓
AutoDispose
↓
Dependency Injection
↓
Riverpod Generator
| Provider | Mutable State | Async | Best Use Case |
|---|---|---|---|
| StateProvider | ✅ | ❌ | Simple state (counter, switch, checkbox) |
| NotifierProvider | ✅ | ❌ | Business logic, Todo, Forms |
| FutureProvider | ❌ | ✅ | Read-only API calls |
| AsyncNotifierProvider | ✅ | ✅ | Login, CRUD, API with state updates |
| StreamProvider | Live Updates | Stream | Firebase, Chat, WebSocket |
| Scenario | Recommended Provider |
|---|---|
| Counter | StateProvider |
| Dark Mode | StateProvider |
| Todo App | NotifierProvider |
| Shopping Cart | NotifierProvider |
| Fetch User Profile | FutureProvider |
| Login API | AsyncNotifierProvider |
| Update User | AsyncNotifierProvider |
| Chat App | StreamProvider |
| Firebase Firestore | StreamProvider |
Stores simple mutable state.
- Counter
- Switch
- Checkbox
- Dropdown
- Selected Index
final counterProvider = StateProvider<int>((ref) => 0);Read
ref.watch(counterProvider);Update
ref.read(counterProvider.notifier).state++;- Small amount of state only.
- Avoid putting business logic here.
- Easy to learn.
- Can become messy for large applications.
Stores state with business logic.
- Todo App
- Shopping Cart
- Forms
- Complex UI State
class CounterNotifier extends Notifier<int> {
@override
int build() => 0;
void increment() {
state++;
}
}
final counterProvider =
NotifierProvider<CounterNotifier, int>(CounterNotifier.new);Read
ref.watch(counterProvider);Call Method
ref.read(counterProvider.notifier).increment();- Recommended over StateProvider for medium/large projects.
- Keeps UI clean.
- Business logic stays inside Notifier.
Load asynchronous data.
- Fetch API
- Load Database
- Read User Profile
final userProvider = FutureProvider<User>((ref) async {
return api.getUser();
});Usage
final user = ref.watch(userProvider);
user.when(
data: (data) {},
loading: () {},
error: (e, s) {},
);- Read-only provider.
- Automatically caches data.
- Cannot manually change state.
Async business logic with mutable state.
- Login
- Register
- CRUD
- Refresh Data
- API Calls
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
return api.getUser();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
return api.getUser();
});
}
}
final userProvider =
AsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);- Most powerful Riverpod provider.
- Can modify async state.
- Preferred over FutureProvider when state changes.
Listen to continuous updates.
- Firebase
- Chat
- Notifications
- WebSocket
- Live Location
final messageProvider =
StreamProvider<List<Message>>((ref) {
return repository.messages();
});Usage
ref.watch(messageProvider);- Automatically listens.
- Automatically cancels stream.
- Perfect for realtime apps.
Pass parameters to providers.
final userProvider =
FutureProvider.family<User, String>((ref, id) async {
return api.getUser(id);
});Usage
ref.watch(userProvider("123"));- User by ID
- Product Details
- Search Query
- Category Filter
Dispose provider automatically when unused.
final provider =
FutureProvider.autoDispose((ref) async {
...
});- Prevents memory leaks.
- API is reloaded after reopening unless kept alive.
- Good for temporary screens.
Keep Alive
ref.keepAlive();Provide services without manually creating objects.
final apiProvider = Provider<ApiService>((ref) {
return ApiService();
});Usage
final api = ref.read(apiProvider);- Loose coupling
- Easy testing
- Better architecture
- Reusable services
Generate providers automatically.
flutter_riverpod
riverpod_annotation
riverpod_generator
build_runnerAnnotation
@riverpod
int counter(Ref ref) {
return 0;
}Generate Code
dart run build_runner buildWatch Mode
dart run build_runner watch- Less boilerplate
- Better type safety
- Cleaner code
- Recommended for modern Riverpod
Generate immutable models.
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
}) = _User;
factory User.fromJson(Map<String, dynamic> json)
=> _$UserFromJson(json);
}Generate
dart run build_runner build- Immutable classes
- copyWith()
- Equality
- Union/Sealed Classes
- JSON support
Convert JSON ↔ Dart Objects.
Example
factory User.fromJson(Map<String, dynamic> json)
=> _$UserFromJson(json);
Map<String, dynamic> toJson()
=> _$UserToJson(this);- Cleaner model classes
- No manual parsing
- Type safe
Navigation and Deep Linking.
Create Router
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
],
);Navigate
context.go('/home');Push
context.push('/details');Back
context.pop();Pass Parameter
context.push('/user/1');- Declarative routing
- Supports deep linking
- Nested navigation
- Route guards
- Named routes
- Rebuilds UI when state changes.
- Use inside build().
ref.watch(counterProvider);- Reads once.
- Does not rebuild.
ref.read(counterProvider.notifier);- Reacts to state changes.
- No widget rebuild.
ref.listen(provider, (previous, next) {});Use for:
- Snackbar
- Dialog
- Navigation
- Toast
Generate Code
dart run build_runner buildWatch Changes
dart run build_runner watchClean Generated Files
dart run build_runner cleanDelete Conflicting Files
dart run build_runner build --delete-conflicting-outputs- Keep UI free from business logic.
- Prefer
NotifierProvideroverStateProviderfor anything beyond simple state. - Use
AsyncNotifierProviderfor CRUD and mutable async operations. - Use
FutureProviderfor read-only async data. - Use
StreamProviderfor real-time updates. - Use
Familywhen providers need parameters. - Use
AutoDisposefor screen-specific providers. - Prefer code generation (
@riverpod) in new projects. - Use
Freezedfor immutable models. - Inject dependencies using
Provider. - Keep providers small and focused on a single responsibility.
| Need | Use |
|---|---|
| Simple State | StateProvider |
| Business Logic | NotifierProvider |
| Read API | FutureProvider |
| CRUD/API State | AsyncNotifierProvider |
| Live Updates | StreamProvider |
| Parameters | Family |
| Dispose Automatically | AutoDispose |
| Dependency Injection | Provider |
| Code Generation | Riverpod Generator |
| Immutable Models | Freezed |
| JSON Parsing | json_serializable |
| Navigation | Go Router |
- Dio
- Retrofit
- Isar Database
- Clean Architecture
- Repository Pattern
- Unit Testing
- Integration Testing
- Secure Storage
- Firebase
- Offline First Architecture
Rule of Thumb
- Simple state → StateProvider
- Business logic → NotifierProvider
- Read async data → FutureProvider
- Mutable async state → AsyncNotifierProvider
- Realtime updates → StreamProvider
If you're unsure, start with NotifierProvider for synchronous state and AsyncNotifierProvider for asynchronous state.