Files
minimax-skills/skills/flutter-dev/references/riverpod-state.md
mljxxx 2995582a5e feat: add flutter-dev skill
Add comprehensive Flutter cross-platform development guide covering:
- Widget patterns and const optimization
- Riverpod/Bloc state management
- GoRouter navigation
- Performance optimization
- Testing strategies
- Platform-specific implementations

Made-with: Cursor
2026-03-26 17:35:55 +08:00

5.3 KiB

Riverpod State Management

Riverpod 2.0 state management guide covering provider types, notifier patterns, and widget integration.

Provider Types

import 'package:flutter_riverpod/flutter_riverpod.dart';

// Simple computed value
final greetingProvider = Provider<String>((ref) {
  final name = ref.watch(userNameProvider);
  return 'Hello, $name';
});

// Simple mutable state
final counterProvider = StateProvider<int>((ref) => 0);

// Async state (API calls)
final usersProvider = FutureProvider<List<User>>((ref) async {
  final api = ref.read(apiProvider);
  return api.getUsers();
});

// Stream state (real-time)
final messagesProvider = StreamProvider<List<Message>>((ref) {
  return ref.read(chatServiceProvider).messagesStream;
});

Provider Type Reference

Provider Use Case
Provider Computed/derived values, dependency injection
StateProvider Simple mutable state (counter, toggle)
FutureProvider Async operations (one-time fetch)
StreamProvider Real-time data streams
NotifierProvider Complex state with methods
AsyncNotifierProvider Async state with methods

Notifier Pattern (Riverpod 2.0)

Synchronous Notifier

@riverpod
class TodoList extends _$TodoList {
  @override
  List<Todo> build() => [];

  void add(Todo todo) {
    state = [...state, todo];
  }

  void toggle(String id) {
    state = [
      for (final todo in state)
        if (todo.id == id) 
          todo.copyWith(completed: !todo.completed) 
        else 
          todo,
    ];
  }

  void remove(String id) {
    state = state.where((t) => t.id != id).toList();
  }
}

Async Notifier

@riverpod
class UserProfile extends _$UserProfile {
  @override
  Future<User> build() async {
    return ref.read(apiProvider).getCurrentUser();
  }

  Future<void> updateName(String name) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      final updated = await ref.read(apiProvider).updateUser(name: name);
      return updated;
    });
  }

  Future<void> refresh() async {
    ref.invalidateSelf();
    await future;
  }
}

Usage in Widgets

class TodoScreen extends ConsumerWidget {
  const TodoScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);

    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        final todo = todos[index];
        return ListTile(
          key: ValueKey(todo.id),
          title: Text(todo.title),
          leading: Checkbox(
            value: todo.completed,
            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
          ),
        );
      },
    );
  }
}

Selective Rebuilds with select

class UserAvatar extends ConsumerWidget {
  const UserAvatar({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Only rebuilds when avatarUrl changes
    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));

    return CircleAvatar(
      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,
    );
  }
}

Async State Handling

class UserProfileScreen extends ConsumerWidget {
  const UserProfileScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProfileProvider);

    return userAsync.when(
      data: (user) => UserProfileContent(user: user),
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, stack) => ErrorView(
        message: err.toString(),
        onRetry: () => ref.invalidate(userProfileProvider),
      ),
    );
  }
}

Consumer for Scoped Rebuilds

class MyScreen extends StatelessWidget {
  const MyScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Static content'),
        Consumer(
          builder: (context, ref, child) {
            final count = ref.watch(counterProvider);
            return Text('Count: $count');
          },
        ),
      ],
    );
  }
}

Provider Modifiers

// Auto-dispose when no longer used
@riverpod
class AutoDisposeExample extends _$AutoDisposeExample {
  @override
  String build() => 'value';
}

// Family - parameterized providers
@riverpod
Future<User> userById(UserByIdRef ref, String id) async {
  return ref.read(apiProvider).getUser(id);
}

// Usage
final user = ref.watch(userByIdProvider('123'));

Best Practices

Do Don't
Use ref.watch() in build Use ref.watch() in callbacks
Use ref.read() in callbacks Use ref.read() in build
Use select() for granular rebuilds Watch entire state unnecessarily
Create new state instances Mutate state directly
Use AsyncValue.guard() for errors Catch errors manually

Quick Reference

Method When to Use
ref.watch() In build method, rebuilds on change
ref.read() In callbacks, one-time read
ref.listen() Side effects on change
ref.invalidate() Force provider refresh
ref.refresh() Invalidate and get new value

Riverpod is an open-source state management library by Remi Rousselet.