iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Riverpod

Riverpod is the modern Flutter state-management library — same author as Provider, compile-time safety, easier testing, async support. Use ConsumerWidget/ConsumerStatefulWidget with providers (StateProvider, FutureProvider, StreamProvider, NotifierProvider) to scope state cleanly.

Provider, Notifier, ref, async

EXAMPLE
// 1) Install
// pubspec.yaml
// dependencies:
//   flutter_riverpod: ^2.5.0
//   riverpod_annotation: ^2.3.0
// dev_dependencies:
//   build_runner: ^2.4.0
//   riverpod_generator: ^2.4.0

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 2) Root — wrap MaterialApp in ProviderScope
void main() {
    runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
    const MyApp({super.key});
    @override
    Widget build(BuildContext context) {
        return const MaterialApp(home: CounterPage());
    }
}

// 3) Simplest — Provider for a constant value
final greetingProvider = Provider<String>((ref) => 'Hello');

class GreetingView extends ConsumerWidget {
    const GreetingView({super.key});
    @override
    Widget build(BuildContext context, WidgetRef ref) {
        final greeting = ref.watch(greetingProvider);
        return Text(greeting);
    }
}

// 4) StateProvider — simple mutable state
final counterProvider = StateProvider<int>((ref) => 0);

class CounterPage extends ConsumerWidget {
    const CounterPage({super.key});
    @override
    Widget build(BuildContext context, WidgetRef ref) {
        final count = ref.watch(counterProvider);
        return Scaffold(
            body: Center(child: Text('Count: $count', style: const TextStyle(fontSize: 36))),
            floatingActionButton: FloatingActionButton(
                onPressed: () => ref.read(counterProvider.notifier).state++,
                child: const Icon(Icons.add),
            ),
        );
    }
}

// 5) NotifierProvider — richer state with methods
class Counter extends Notifier<int> {
    @override
    int build() => 0;

    void increment() => state++;
    void decrement() => state--;
    void reset() => state = 0;
    Future<void> incrementAsync() async {
        await Future.delayed(const Duration(seconds: 1));
        state++;
    }
}

final counterProvider2 = NotifierProvider<Counter, int>(Counter.new);

class CounterPage2 extends ConsumerWidget {
    const CounterPage2({super.key});
    @override
    Widget build(BuildContext context, WidgetRef ref) {
        final count = ref.watch(counterProvider2);
        final notifier = ref.read(counterProvider2.notifier);
        return Column(
            children: [
                Text('$count'),
                ElevatedButton(onPressed: notifier.increment, child: const Text('+1')),
                ElevatedButton(onPressed: notifier.reset, child: const Text('Reset')),
            ],
        );
    }
}

// 6) FutureProvider — async data
final userProvider = FutureProvider<User>((ref) async {
    final id = ref.watch(currentUserIdProvider);
    return await api.fetchUser(id);
});

class UserView extends ConsumerWidget {
    const UserView({super.key});
    @override
    Widget build(BuildContext context, WidgetRef ref) {
        final userAsync = ref.watch(userProvider);
        return userAsync.when(
            data:    (user)  => Text('Hello ${user.name}'),
            loading: ()       => const CircularProgressIndicator(),
            error:   (e, _)   => Text('Error: $e'),
        );
    }
}

// 7) StreamProvider — real-time streams
final messagesProvider = StreamProvider<List<Message>>((ref) {
    return firestore.collection('messages').snapshots().map((snap) =>
        snap.docs.map((d) => Message.fromJson(d.data())).toList(),
    );
});

// 8) Family — parameterised providers
final userByIdProvider = FutureProvider.family<User, int>((ref, id) async {
    return await api.fetchUser(id);
});

// Usage:
ref.watch(userByIdProvider(42));

// 9) autoDispose — clean up when no longer watched
final searchProvider = FutureProvider.autoDispose.family<List<Result>, String>((ref, query) async {
    return await api.search(query);
});

// When all widgets stop watching, the cache is dropped. Saves memory.

// 10) ref methods
// ref.watch(provider)   — subscribe; rebuild on change
// ref.read(provider)    — one-time read; no subscription
// ref.listen(provider, (prev, next) { ... })  — side effects on change
// ref.invalidate(provider) — clear cached value; forces re-fetch
// ref.refresh(provider)    — invalidate + return new value

// 11) AsyncNotifier — modern async state
class UserAsyncNotifier extends AsyncNotifier<User> {
    @override
    Future<User> build() async {
        return await api.fetchUser(ref.watch(currentUserIdProvider));
    }

    Future<void> refresh() async {
        state = const AsyncValue.loading();
        state = await AsyncValue.guard(() => api.fetchUser(ref.read(currentUserIdProvider)));
    }

    Future<void> updateName(String name) async {
        await api.updateName(state.value!.id, name);
        state = AsyncValue.data(state.value!.copyWith(name: name));
    }
}

final userAsyncProvider = AsyncNotifierProvider<UserAsyncNotifier, User>(UserAsyncNotifier.new);

// 12) Code generation (cleaner with @riverpod)
// import 'package:riverpod_annotation/riverpod_annotation.dart';
// part 'counter.g.dart';
//
// @riverpod
// int counter(CounterRef ref) => 0;
//
// @riverpod
// Future<User> user(UserRef ref) async => api.fetchUser(42);
//
// @riverpod
// class CounterNotifier extends _$CounterNotifier {
//   @override int build() => 0;
//   void increment() => state++;
// }
//
// Run: flutter pub run build_runner build

// 13) Testing — override providers
testWidgets('counter increments', (tester) async {
    await tester.pumpWidget(
        ProviderScope(
            overrides: [counterProvider.overrideWith((ref) => 100)],
            child: const MaterialApp(home: CounterPage()),
        ),
    );
    expect(find.text('Count: 100'), findsOneWidget);
});

// 14) ProviderScope override at runtime
ProviderScope(
    overrides: [
        apiProvider.overrideWithValue(MockApi()),
    ],
    child: const App(),
);

// 15) Provider dependencies
final authTokenProvider = StateProvider<String?>((ref) => null);
final apiProvider = Provider<Api>((ref) => Api(token: ref.watch(authTokenProvider)));
final userProvider = FutureProvider<User>((ref) async {
    final api = ref.watch(apiProvider);
    return await api.fetchMe();
});

// When authTokenProvider changes → apiProvider rebuilds → userProvider re-fetches.

// 16) Common bugs
// • Reading providers in initState → use addPostFrameCallback or ConsumerStatefulWidget
// • Using ref.watch inside callbacks → infinite rebuild; use ref.read
// • Forgetting ProviderScope at the root → 'No ProviderScope above this widget'
// • autoDispose without retainer → frequently re-fetched data; consider keepAlive
// • Riverpod 1.x API confused with 2.x — many examples online are outdated
// • Notifier.build() doing heavy work synchronously — use AsyncNotifier instead
// • Side effects in build() — Riverpod warns; use ref.listen instead
// • Mixing Provider and Riverpod in same app — confusing; pick one
// • Forgetting code generator for @riverpod annotation
// • Provider scope shadowed by nested ProviderScope — overrides may not apply

Why it matters

Riverpod 2.x is the modern Flutter state-management default: providers (Provider, StateProvider, NotifierProvider, FutureProvider, StreamProvider, AsyncNotifier) wrap state, ConsumerWidget subscribes via ref.watch. Use family for parameterised providers, autoDispose to free memory, and ref.listen for side effects. Provider overrides make testing trivial.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
final counterProvider = StateProvider<int>((_) => 0);

class Counter extends ConsumerWidget {
    @override
    Widget build(BuildContext c, WidgetRef r) {
        final n = r.watch(counterProvider);
        return TextButton(
            onPressed: () => r.read(counterProvider.notifier).state++,
            child: Text('$n'),
        );
    }
}
Try it Yourself »

Test yourself

Q1. Riverpod compared to Provider is…
Q2. A widget reads providers via…
Q3. Riverpod providers are scoped to…

Discussion

Loading…