State Management and DI
Our preferred package for state management and dependency injection is Riverpod.
Prefer using watch over read
In general, use ref.watch rather than ref.read. This ensures that provider values stay up-to-date.
An important exception is when writing callbacks (e.g. onTap listeners); in these cases you should always use ref.read.
Avoid injecting WidgetRefs into classes
This ensures that classes are decoupled from Riverpod.
❌ Incorrect
// Riverpod-coupled Repository
class BlogPostRepository {
const BlogPostRepository({required this.ref});
final WidgetRef ref;
Future<BlogPost> getBlogPost(int id) => ref.watch(blogPostApi).getPost(id);
}
final blogPostRepositoryProvider => Provider((ref) => BlogPostRepository(ref: ref));
✅ Correct
// Riverpod-free Repository
class BlogPostRepository {
const BlogPostRepository({required this.api});
final BlogPostApi api;
Future<BlogPost> getBlogPost(int id) => api.getPost(id);
}
final blogPostRepositoryProvider => Provider((ref) => BlogPostRepository(ref.watch(blogPostApiProvider)));
Providers requiring asynchronous initialisation
Some packages, such as SharedPreferences, require asynchronous initialisation. You can provide the initialised values to Riverpod like this:
// shared_preferences_provider.dart
final sharedPreferencesProvider => Provider<SharedPreferences>((_) => throw UnimplementedError());
// main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize SharedPreferences
final sharedPreferences = await SharedPreferences.getInstance();
runApp(
ProviderScope(
overrides: [
// Provide the initialized value to Riverpod
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
],
child: const MyApp(),
),
);
}
