State Management and DI
Our preferred package for state management and dependency injection is Riverpod.
watch
over read
Prefer using 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
.
WidgetRef
s into classes
Avoid injecting 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(),
),
);
}