Flutter State Management with Riverpod
Use Riverpod for reactive state management with compile-time safety and testable provider architecture.
Overview
Riverpod represents the evolution of state management in Flutter, addressing many shortcomings of previous solutions like Provider, Bloc, and GetX. Its core innovation is the concept of providers as first-class values, enabling compile-time safety and excellent IDE support through code generation or the provider package's built-in syntax. The architecture promotes unidirectional data flow where state flows down from providers to widgets, and events flow up through callbacks. This separation creates a clear mental model that scales from simple local state to complex global application state. Providers are inherently composable, allowing complex state logic to be built from simpler building blocks through provider composition. Unlike Provider which relies on InheritedWidget, Riverpod manages its own widget-style registry that works across the entire widget tree. This approach solves the "provider not found" issues that plague Provider implementations and enables features like testing providers in complete isolation. The ref.invalidate() method allows manual state refresh without creating new provider instances, while auto-dispose handles memory management for providers that should not persist when no longer needed. Code generation with riverpod_generator brings additional benefits including automatic annotation processing, optimized provider code generation, and integration with freezed for immutable state classes. The generated code maintains full type safety while reducing boilerplate significantly. For production applications, this combination of Riverpod with code generation represents the current best practice for Flutter state management.
Code Example
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../models/todo.dart';
import '../repositories/todo_repository.dart';
part 'todo_provider.freezed.dart';
@freezed
class TodoState with _$TodoState {
const factory TodoState({
@Default([]) List<Todo> todos,
@Default(AsyncValue.loading()) AsyncValue<void> loadingState,
String? errorMessage,
}) = _TodoState;
}
class TodoNotifier extends StateNotifier<TodoState> {
final TodoRepository _repository;
TodoNotifier(this._repository) : super(const TodoState()) {
loadTodos();
}
Future<void> loadTodos() async {
state = state.copyWith(loadingState: const AsyncValue.loading());
try {
final todos = await _repository.getTodos();
state = state.copyWith(todos: todos, loadingState: const AsyncValue.data(null));
} catch (e, st) {
state = state.copyWith(
loadingState: AsyncValue.error(e, st),
errorMessage: e.toString(),
);
}
}
Future<void> addTodo(Todo todo) async {
await _repository.addTodo(todo);
await loadTodos();
}
}
final todoRepositoryProvider = Provider<TodoRepository>((ref) {
return TodoRepository();
});
final todoProvider = StateNotifierProvider<TodoNotifier, TodoState>((ref) {
return TodoNotifier(ref.watch(todoRepositoryProvider));
});More Flutter Rules
Flutter Clean Architecture Layers
Structure Flutter applications with domain, data, and presentation layers for maintainability.
// Domain Layer - Entities
class Article {
final String id;
final String title;
final String content;
final String authorId;
final DateTime ...Flutter BLoC Pattern Implementation
Implement BLoC for predictable state management with clear event-state separation.
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart...Flutter Navigation with GoRouter
Implement declarative routing with GoRouter for deep linking and nested navigation.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '....