State Management and DI

Our preferred package for state management and dependency injection is Riverpodopen in new window.

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(),
        ),
    );
}