flutter-riverpod-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter Riverpod Expert - 2025 Best Practices

Flutter Riverpod 专家指南 - 2025最佳实践

You have expert knowledge in Flutter Riverpod state management following 2025 best practices. When the user is working with Riverpod or Flutter state management, apply these patterns and guidelines.
您拥有遵循2025最佳实践的Flutter Riverpod状态管理专业知识。当用户使用Riverpod或进行Flutter状态管理时,请应用以下模式和指南。

When to Use This Skill

适用场景

Activate this expertise when the user mentions:
  • Riverpod, providers, state management, or StateNotifier
  • AsyncNotifier, FutureProvider, StreamProvider, NotifierProvider
  • Code generation with riverpod_generator or build_runner
  • Data fetching, API integration, mutations, or reactive state
  • State synchronization, caching, autoDispose, or memory management
  • Provider testing, dependency injection, or repository patterns
  • Performance issues with rebuilds, provider selection, or optimization
  • Migration from old Riverpod patterns to modern approaches
当用户提及以下内容时,启用此专业能力:
  • Riverpod, providers, state management, 或 StateNotifier
  • AsyncNotifier, FutureProvider, StreamProvider, NotifierProvider
  • 使用riverpod_generator或build_runner进行代码生成
  • 数据获取、API集成、状态变更或响应式状态
  • 状态同步、缓存、autoDispose或内存管理
  • Provider测试、依赖注入或仓库模式
  • 重建、Provider选择或优化相关的性能问题
  • 从旧版Riverpod模式迁移到现代方案

Core Principles (2025)

核心原则(2025)

  1. Code Generation is STRONGLY RECOMMENDED - Use
    @riverpod
    annotations and
    riverpod_generator
  2. AsyncNotifierProvider is PREFERRED for async state (replaces FutureProvider/StreamProvider for consistency)
  3. AutoDispose by Default - Codegen makes providers auto-dispose automatically
  4. Repository Pattern - Separate data layer from state management
  5. Performance First - Use
    select()
    to optimize rebuilds
  1. 强烈推荐代码生成 - 使用
    @riverpod
    注解和
    riverpod_generator
  2. 优先使用AsyncNotifierProvider处理异步状态(为保持一致性,替代FutureProvider/StreamProvider)
  3. 默认启用AutoDispose - 代码生成会自动让Provider支持自动销毁
  4. 仓库模式 - 将数据层与状态管理分离
  5. 性能优先 - 使用
    select()
    优化重建

Provider Selection Guide

Provider选择指南

Quick Decision Tree

快速决策树

Immutable/Computed Values - Use
Provider
:
dart

String apiKey(Ref ref) => 'YOUR_API_KEY';


int totalPrice(Ref ref) {
  final cart = ref.watch(cartProvider);
  return cart.items.fold(0, (sum, item) => sum + item.price);
}
Simple Synchronous State - Use
NotifierProvider
:
dart

class Counter extends _$Counter {
  
  int build() => 0;

  void increment() => state++;
  void decrement() => state = max(0, state - 1);
}
Async Data with Mutations (PREFERRED 2025) - Use
AsyncNotifierProvider
:
dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    final repo = ref.watch(todoRepositoryProvider);
    return repo.fetchTodos();
  }

  Future<void> addTodo(String title) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final repo = ref.read(todoRepositoryProvider);
      await repo.createTodo(title);
      return repo.fetchTodos();
    });
  }

  Future<void> deleteTodo(String id) async {
    // Optimistic update
    state = AsyncData(state.value!.where((t) => t.id != id).toList());

    try {
      await ref.read(todoRepositoryProvider).deleteTodo(id);
    } catch (e) {
      ref.invalidateSelf(); // Rollback on error
    }
  }
}
Real-time Streams Only - Use
StreamProvider
:
dart

Stream<User?> authState(Ref ref) {
  return FirebaseAuth.instance.authStateChanges();
}
Key Rule: Prefer AsyncNotifierProvider over FutureProvider/StreamProvider for better consistency and mutation support.
不可变/计算值 - 使用
Provider
:
dart

String apiKey(Ref ref) => 'YOUR_API_KEY';


int totalPrice(Ref ref) {
  final cart = ref.watch(cartProvider);
  return cart.items.fold(0, (sum, item) => sum + item.price);
}
简单同步状态 - 使用
NotifierProvider
:
dart

class Counter extends _$Counter {
  
  int build() => 0;

  void increment() => state++;
  void decrement() => state = max(0, state - 1);
}
带变更的异步数据(2025优先方案) - 使用
AsyncNotifierProvider
:
dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    final repo = ref.watch(todoRepositoryProvider);
    return repo.fetchTodos();
  }

  Future<void> addTodo(String title) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final repo = ref.read(todoRepositoryProvider);
      await repo.createTodo(title);
      return repo.fetchTodos();
    });
  }

  Future<void> deleteTodo(String id) async {
    // 乐观更新
    state = AsyncData(state.value!.where((t) => t.id != id).toList());

    try {
      await ref.read(todoRepositoryProvider).deleteTodo(id);
    } catch (e) {
      ref.invalidateSelf(); // 出错时回滚
    }
  }
}
仅实时流场景 - 使用
StreamProvider
:
dart

Stream<User?> authState(Ref ref) {
  return FirebaseAuth.instance.authStateChanges();
}
关键规则:为了更好的一致性和变更支持,优先使用AsyncNotifierProvider而非FutureProvider/StreamProvider。

Code Generation Setup

代码生成设置

Dependencies (pubspec.yaml)

依赖(pubspec.yaml)

yaml
dependencies:
  flutter_riverpod: ^2.5.0
  riverpod_annotation: ^2.3.0

dev_dependencies:
  build_runner: ^2.4.0
  riverpod_generator: ^2.4.0
  custom_lint: ^0.6.0
  riverpod_lint: ^2.3.0
yaml
dependencies:
  flutter_riverpod: ^2.5.0
  riverpod_annotation: ^2.3.0

dev_dependencies:
  build_runner: ^2.4.0
  riverpod_generator: ^2.4.0
  custom_lint: ^0.6.0
  riverpod_lint: ^2.3.0

File Template

文件模板

Every provider file needs:
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'filename.g.dart';  // REQUIRED


class MyProvider extends _$MyProvider {
  
  Future<Data> build() async => fetchData();
}
每个Provider文件需要包含:
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'filename.g.dart';  // 必须包含


class MyProvider extends _$MyProvider {
  
  Future<Data> build() async => fetchData();
}

Run Generator

运行生成器

bash
undefined
bash
undefined

Watch mode (RECOMMENDED during development)

监听模式(开发期间推荐)

dart run build_runner watch -d
dart run build_runner watch -d

One-time generation

一次性生成

dart run build_runner build --delete-conflicting-outputs
undefined
dart run build_runner build --delete-conflicting-outputs
undefined

Performance Optimization Patterns

性能优化模式

Use ref.select() for Specific Fields

使用ref.select()监听特定字段

dart
// ❌ BAD: Rebuilds on ANY product change
final product = ref.watch(productProvider);
return Text('\$${product.price}');

// ✅ GOOD: Only rebuilds when price changes
final price = ref.watch(productProvider.select((p) => p.price));
return Text('\$$price');
dart
// ❌ BAD: Rebuilds on ANY product change
final product = ref.watch(productProvider);
return Text('\$${product.price}');

// ✅ GOOD: Only rebuilds when price changes
final price = ref.watch(productProvider.select((p) => p.price));
return Text('\$$price');

ref.watch() vs ref.select() vs ref.read() vs ref.listen()

ref.watch() vs ref.select() vs ref.read() vs ref.listen()

ref.watch() - Subscribe to changes (use in build):
dart

Widget build(BuildContext context, WidgetRef ref) {
  final todos = ref.watch(todoListProvider);
  return ListView(...);
}
ref.select() - Subscribe to specific property (optimize rebuilds):
dart
final count = ref.watch(todoListProvider.select((todos) => todos.length));
final isAdult = ref.watch(personProvider.select((p) => p.age >= 18));
ref.read() - One-time read with NO subscription (event handlers only):
dart
onPressed: () {
  ref.read(todoListProvider.notifier).addTodo('New task');
}

// ❌ NEVER use read() in build to "optimize" - it won't rebuild!
ref.listen() - Side effects (navigation, snackbars, logging):
dart
ref.listen<AsyncValue<List<Todo>>>(
  todoListProvider,
  (previous, next) {
    next.whenOrNull(
      error: (error, stack) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error: $error')),
        );
      },
    );
  },
);
ref.watch() - 订阅变更(在build方法中使用):
dart

Widget build(BuildContext context, WidgetRef ref) {
  final todos = ref.watch(todoListProvider);
  return ListView(...);
}
ref.select() - 订阅特定属性(优化重建):
dart
final count = ref.watch(todoListProvider.select((todos) => todos.length));
final isAdult = ref.watch(personProvider.select((p) => p.age >= 18));
ref.read() - 一次性读取(无订阅,仅在事件处理器中使用):
dart
onPressed: () {
  ref.read(todoListProvider.notifier).addTodo('New task');
}

// ❌ NEVER use read() in build to "optimize" - it won't rebuild!
ref.listen() - 处理副作用(导航、提示栏、日志):
dart
ref.listen<AsyncValue<List<Todo>>>(
  todoListProvider,
  (previous, next) {
    next.whenOrNull(
      error: (error, stack) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error: $error')),
        );
      },
    );
  },
);

Avoid Watching in Loops

避免在循环中使用watch

dart
// ❌ BAD: Causes performance issues
ListView.builder(
  itemBuilder: (context, index) {
    final todo = ref.watch(todoProvider(ids[index])); // DON'T!
    return ListTile(...);
  },
);

// ✅ GOOD: Separate widget for each item
class TodoItem extends ConsumerWidget {
  const TodoItem({required this.todoId});
  final String todoId;

  
  Widget build(BuildContext context, WidgetRef ref) {
    final todo = ref.watch(todoProvider(todoId));
    return ListTile(title: Text(todo.title));
  }
}

class TodoList extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final ids = ref.watch(todoIdsProvider);
    return ListView.builder(
      itemCount: ids.length,
      itemBuilder: (context, index) => TodoItem(todoId: ids[index]),
    );
  }
}
dart
// ❌ BAD: Causes performance issues
ListView.builder(
  itemBuilder: (context, index) {
    final todo = ref.watch(todoProvider(ids[index])); // DON'T!
    return ListTile(...);
  },
);

// ✅ GOOD: Separate widget for each item
class TodoItem extends ConsumerWidget {
  const TodoItem({required this.todoId});
  final String todoId;

  
  Widget build(BuildContext context, WidgetRef ref) {
    final todo = ref.watch(todoProvider(todoId));
    return ListTile(title: Text(todo.title));
  }
}

class TodoList extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final ids = ref.watch(todoIdsProvider);
    return ListView.builder(
      itemCount: ids.length,
      itemBuilder: (context, index) => TodoItem(todoId: ids[index]),
    );
  }
}

Create Derived Providers

创建派生Provider

dart
// ✅ GOOD: Separate provider for computed state

List<Todo> filteredSortedTodos(Ref ref) {
  final todos = ref.watch(todoListProvider);
  final filter = ref.watch(filterProvider);
  final sortOrder = ref.watch(sortOrderProvider);

  final filtered = todos.where((t) => t.matches(filter)).toList();
  return filtered..sort(sortOrder.comparator);
}
dart
// ✅ GOOD: Separate provider for computed state

List<Todo> filteredSortedTodos(Ref ref) {
  final todos = ref.watch(todoListProvider);
  final filter = ref.watch(filterProvider);
  final sortOrder = ref.watch(sortOrderProvider);

  final filtered = todos.where((t) => t.matches(filter)).toList();
  return filtered..sort(sortOrder.comparator);
}

Repository Pattern Architecture

仓库模式架构

3-Layer Architecture

三层架构

1. Data Layer - Repository:
dart

TodoRepository todoRepository(Ref ref) {
  return TodoRepository(dio: ref.watch(dioProvider));
}

class TodoRepository {
  TodoRepository({required this.dio});
  final Dio dio;

  Future<List<Todo>> fetchTodos() async {
    final response = await dio.get('/todos');
    return (response.data as List)
      .map((json) => Todo.fromJson(json))
      .toList();
  }

  Future<Todo> createTodo(String title) async {
    final response = await dio.post('/todos', data: {'title': title});
    return Todo.fromJson(response.data);
  }

  Future<void> deleteTodo(String id) async {
    await dio.delete('/todos/$id');
  }
}
2. Application Layer - State Management:
dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    final repository = ref.watch(todoRepositoryProvider);
    return repository.fetchTodos();
  }

  Future<void> addTodo(String title) async {
    final repository = ref.read(todoRepositoryProvider);

    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await repository.createTodo(title);
      return repository.fetchTodos();
    });
  }
}
3. Presentation Layer - UI:
dart
class TodoListScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final todosAsync = ref.watch(todoListProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: todosAsync.when(
        data: (todos) => ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) => TodoTile(todos[index]),
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => ErrorView(error: error),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddDialog(context, ref),
        child: const Icon(Icons.add),
      ),
    );
  }
}
1. 数据层 - 仓库:
dart

TodoRepository todoRepository(Ref ref) {
  return TodoRepository(dio: ref.watch(dioProvider));
}

class TodoRepository {
  TodoRepository({required this.dio});
  final Dio dio;

  Future<List<Todo>> fetchTodos() async {
    final response = await dio.get('/todos');
    return (response.data as List)
      .map((json) => Todo.fromJson(json))
      .toList();
  }

  Future<Todo> createTodo(String title) async {
    final response = await dio.post('/todos', data: {'title': title});
    return Todo.fromJson(response.data);
  }

  Future<void> deleteTodo(String id) async {
    await dio.delete('/todos/$id');
  }
}
2. 应用层 - 状态管理:
dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    final repository = ref.watch(todoRepositoryProvider);
    return repository.fetchTodos();
  }

  Future<void> addTodo(String title) async {
    final repository = ref.read(todoRepositoryProvider);

    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await repository.createTodo(title);
      return repository.fetchTodos();
    });
  }
}
3. 展示层 - UI:
dart
class TodoListScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final todosAsync = ref.watch(todoListProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: todosAsync.when(
        data: (todos) => ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) => TodoTile(todos[index]),
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => ErrorView(error: error),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddDialog(context, ref),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Dependency Injection

依赖注入

dart
// Services

Dio dio(Ref ref) {
  final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
  dio.interceptors.add(LogInterceptor());
  return dio;
}


Future<SharedPreferences> sharedPreferences(Ref ref) async {
  return await SharedPreferences.getInstance();
}


AuthService authService(Ref ref) {
  return AuthService(
    dio: ref.watch(dioProvider),
    storage: ref.watch(sharedPreferencesProvider).value!,
  );
}

// Repositories depend on services

UserRepository userRepository(Ref ref) {
  return UserRepository(
    dio: ref.watch(dioProvider),
    authService: ref.watch(authServiceProvider),
  );
}

// State providers depend on repositories

class CurrentUser extends _$CurrentUser {
  
  Future<User?> build() async {
    final authService = ref.watch(authServiceProvider);
    final userId = await authService.getCurrentUserId();

    if (userId == null) return null;

    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchUser(userId);
  }

  Future<void> logout() async {
    final authService = ref.read(authServiceProvider);
    await authService.logout();
    ref.invalidateSelf();
  }
}
dart
// 服务

Dio dio(Ref ref) {
  final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
  dio.interceptors.add(LogInterceptor());
  return dio;
}


Future<SharedPreferences> sharedPreferences(Ref ref) async {
  return await SharedPreferences.getInstance();
}


AuthService authService(Ref ref) {
  return AuthService(
    dio: ref.watch(dioProvider),
    storage: ref.watch(sharedPreferencesProvider).value!,
  );
}

// 仓库依赖服务

UserRepository userRepository(Ref ref) {
  return UserRepository(
    dio: ref.watch(dioProvider),
    authService: ref.watch(authServiceProvider),
  );
}

// 状态Provider依赖仓库

class CurrentUser extends _$CurrentUser {
  
  Future<User?> build() async {
    final authService = ref.watch(authServiceProvider);
    final userId = await authService.getCurrentUserId();

    if (userId == null) return null;

    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchUser(userId);
  }

  Future<void> logout() async {
    final authService = ref.read(authServiceProvider);
    await authService.logout();
    ref.invalidateSelf();
  }
}

Family Providers (Parameterized)

Family Provider(带参数)

Family providers are automatic with code generation when you add parameters:
dart
// Simple family provider

Future<User> user(Ref ref, String id) async {
  final dio = ref.watch(dioProvider);
  final response = await dio.get('/users/$id');
  return User.fromJson(response.data);
}

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

// Family with AsyncNotifier

class UserNotifier extends _$UserNotifier {
  
  Future<User> build(String id) async {
    final repo = ref.watch(userRepositoryProvider);
    return repo.fetchUser(id);
  }

  Future<void> updateName(String newName) async {
    final userId = arg; // Access the parameter via 'arg'
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await ref.read(userRepositoryProvider).updateUser(userId, name: newName);
      return ref.read(userRepositoryProvider).fetchUser(userId);
    });
  }
}

// Complex parameters need proper equality
class UserFilter {
  const UserFilter({required this.role, required this.active});
  final String role;
  final bool active;

  
  bool operator ==(Object other) =>
    identical(this, other) ||
    other is UserFilter &&
    role == other.role &&
    active == other.active;

  
  int get hashCode => Object.hash(role, active);
}


Future<List<User>> filteredUsers(Ref ref, UserFilter filter) async {
  return fetchUsers(filter);
}
代码生成会自动支持带参数的Family Provider:
dart
// 简单的Family Provider

Future<User> user(Ref ref, String id) async {
  final dio = ref.watch(dioProvider);
  final response = await dio.get('/users/$id');
  return User.fromJson(response.data);
}

// 使用方式
final user = ref.watch(userProvider('123'));

// 带AsyncNotifier的Family Provider

class UserNotifier extends _$UserNotifier {
  
  Future<User> build(String id) async {
    final repo = ref.watch(userRepositoryProvider);
    return repo.fetchUser(id);
  }

  Future<void> updateName(String newName) async {
    final userId = arg; // 通过'arg'访问参数
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await ref.read(userRepositoryProvider).updateUser(userId, name: newName);
      return ref.read(userRepositoryProvider).fetchUser(userId);
    });
  }
}

// 复杂参数需要正确的相等性判断
class UserFilter {
  const UserFilter({required this.role, required this.active});
  final String role;
  final bool active;

  
  bool operator ==(Object other) =>
    identical(this, other) ||
    other is UserFilter &&
    role == other.role &&
    active == other.active;

  
  int get hashCode => Object.hash(role, active);
}


Future<List<User>> filteredUsers(Ref ref, UserFilter filter) async {
  return fetchUsers(filter);
}

AutoDispose and Caching

AutoDispose与缓存

Code generation makes providers auto-dispose by default.
dart
// Default: auto-dispose when no listeners

Future<String> data(Ref ref) async => fetchData();

// Keep alive permanently
(keepAlive: true)
Future<Config> config(Ref ref) async => loadConfig();

// Conditional keep alive - cache on success

Future<String> cachedData(Ref ref) async {
  final data = await fetchData();
  ref.keepAlive(); // Cache this result forever
  return data;
}

// Timed cache (5 minutes)

Future<String> timedCache(Ref ref) async {
  final data = await fetchData();
  final link = ref.keepAlive();
  Timer(const Duration(minutes: 5), link.close);
  return data;
}

// Manual disposal - cleanup resources

Stream<int> websocket(Ref ref) {
  final client = WebSocketClient();

  ref.onDispose(() {
    client.close(); // Cleanup when provider is disposed
  });

  return client.stream;
}
代码生成会让Provider默认支持自动销毁
dart
// 默认:无监听器时自动销毁

Future<String> data(Ref ref) async => fetchData();

// 永久保持存活
(keepAlive: true)
Future<Config> config(Ref ref) async => loadConfig();

// 条件性保持存活 - 成功时缓存

Future<String> cachedData(Ref ref) async {
  final data = await fetchData();
  ref.keepAlive(); // 永久缓存此结果
  return data;
}

// 定时缓存(5分钟)

Future<String> timedCache(Ref ref) async {
  final data = await fetchData();
  final link = ref.keepAlive();
  Timer(const Duration(minutes: 5), link.close);
  return data;
}

// 手动销毁 - 清理资源

Stream<int> websocket(Ref ref) {
  final client = WebSocketClient();

  ref.onDispose(() {
    client.close(); // Provider销毁时清理
  });

  return client.stream;
}

Error Handling

错误处理

Comprehensive Error Handling

全面错误处理

dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    try {
      final repository = ref.watch(todoRepositoryProvider);
      return await repository.fetchTodos();
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) {
        ref.read(authServiceProvider).logout();
        throw UnauthorizedException();
      }
      throw NetworkException(e.message);
    } catch (e) {
      throw UnexpectedException(e.toString());
    }
  }

  Future<void> addTodo(String title) async {
    state = const AsyncLoading();

    state = await AsyncValue.guard(() async {
      final repository = ref.read(todoRepositoryProvider);
      await repository.createTodo(title);
      return repository.fetchTodos();
    });
  }
}
dart

class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    try {
      final repository = ref.watch(todoRepositoryProvider);
      return await repository.fetchTodos();
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) {
        ref.read(authServiceProvider).logout();
        throw UnauthorizedException();
      }
      throw NetworkException(e.message);
    } catch (e) {
      throw UnexpectedException(e.toString());
    }
  }

  Future<void> addTodo(String title) async {
    state = const AsyncLoading();

    state = await AsyncValue.guard(() async {
      final repository = ref.read(todoRepositoryProvider);
      await repository.createTodo(title);
      return repository.fetchTodos();
    });
  }
}

UI Error Handling

UI错误处理

dart
// Using .when()
todosAsync.when(
  data: (todos) => ListView.builder(...),
  loading: () => const Center(child: CircularProgressIndicator()),
  error: (error, stack) {
    if (error is NetworkException) {
      return ErrorView(
        message: 'Network error. Check your connection.',
        onRetry: () => ref.invalidate(todoListProvider),
      );
    }
    if (error is UnauthorizedException) {
      return const ErrorView(message: 'Please log in again.');
    }
    return ErrorView(message: 'Error: $error');
  },
);

// Listen for errors (side effects)
ref.listen<AsyncValue<List<Todo>>>(
  todoListProvider,
  (previous, next) {
    next.whenOrNull(
      error: (error, stack) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(error.toString()),
            backgroundColor: Colors.red,
          ),
        );
      },
    );
  },
);
dart
// 使用.when()
todosAsync.when(
  data: (todos) => ListView.builder(...),
  loading: () => const Center(child: CircularProgressIndicator()),
  error: (error, stack) {
    if (error is NetworkException) {
      return ErrorView(
        message: '网络错误,请检查连接。',
        onRetry: () => ref.invalidate(todoListProvider),
      );
    }
    if (error is UnauthorizedException) {
      return const ErrorView(message: '请重新登录。');
    }
    return ErrorView(message: '错误: $error');
  },
);

// 监听错误(副作用)
ref.listen<AsyncValue<List<Todo>>>(
  todoListProvider,
  (previous, next) {
    next.whenOrNull(
      error: (error, stack) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(error.toString()),
            backgroundColor: Colors.red,
          ),
        );
      },
    );
  },
);

Testing

测试

Provider Testing

Provider测试

dart
test('TodoList fetches todos correctly', () async {
  final container = ProviderContainer.test(
    overrides: [
      todoRepositoryProvider.overrideWithValue(MockTodoRepository()),
    ],
  );

  final todos = await container.read(todoListProvider.future);

  expect(todos.length, 2);
  expect(todos[0].title, 'Test Todo 1');
});

test('TodoList adds todo correctly', () async {
  final mockRepo = MockTodoRepository();
  final container = ProviderContainer.test(
    overrides: [
      todoRepositoryProvider.overrideWithValue(mockRepo),
    ],
  );

  await container.read(todoListProvider.notifier).addTodo('New Todo');

  verify(() => mockRepo.createTodo('New Todo')).called(1);
});
dart
test('TodoList fetches todos correctly', () async {
  final container = ProviderContainer.test(
    overrides: [
      todoRepositoryProvider.overrideWithValue(MockTodoRepository()),
    ],
  );

  final todos = await container.read(todoListProvider.future);

  expect(todos.length, 2);
  expect(todos[0].title, 'Test Todo 1');
});

test('TodoList adds todo correctly', () async {
  final mockRepo = MockTodoRepository();
  final container = ProviderContainer.test(
    overrides: [
      todoRepositoryProvider.overrideWithValue(mockRepo),
    ],
  );

  await container.read(todoListProvider.notifier).addTodo('New Todo');

  verify(() => mockRepo.createTodo('New Todo')).called(1);
});

Widget Testing

Widget测试

dart
testWidgets('TodoListScreen displays todos', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        todoRepositoryProvider.overrideWithValue(MockTodoRepository()),
      ],
      child: const MaterialApp(home: TodoListScreen()),
    ),
  );

  await tester.pumpAndSettle();

  expect(find.text('Test Todo 1'), findsOneWidget);
  expect(find.text('Test Todo 2'), findsOneWidget);
});
dart
testWidgets('TodoListScreen displays todos', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        todoRepositoryProvider.overrideWithValue(MockTodoRepository()),
      ],
      child: const MaterialApp(home: TodoListScreen()),
    ),
  );

  await tester.pumpAndSettle();

  expect(find.text('Test Todo 1'), findsOneWidget);
  expect(find.text('Test Todo 2'), findsOneWidget);
});

Common Anti-Patterns to AVOID

需要避免的常见反模式

Performance Pitfalls

性能陷阱

dart
// ❌ Using ref.read() to avoid rebuilds
final todos = ref.read(todoListProvider); // Won't rebuild!

// ✅ Use ref.watch() or ref.select()
final count = ref.watch(todoListProvider.select((todos) => todos.length));
dart
// ❌ Using ref.read() to avoid rebuilds
final todos = ref.read(todoListProvider); // Won't rebuild!

// ✅ Use ref.watch() or ref.select()
final count = ref.watch(todoListProvider.select((todos) => todos.length));

Memory Leaks

内存泄漏

dart
// ❌ Not disposing resources

Stream<int> badWebsocket(Ref ref) {
  final client = WebSocketClient();
  return client.stream; // Never closed!
}

// ✅ Dispose resources

Stream<int> goodWebsocket(Ref ref) {
  final client = WebSocketClient();
  ref.onDispose(() => client.close());
  return client.stream;
}
dart
// ❌ Not disposing resources

Stream<int> badWebsocket(Ref ref) {
  final client = WebSocketClient();
  return client.stream; // Never closed!
}

// ✅ Dispose resources

Stream<int> goodWebsocket(Ref ref) {
  final client = WebSocketClient();
  ref.onDispose(() => client.close());
  return client.stream;
}

Multiple Sources of Truth

多数据源

dart
// ❌ BAD: Which is the source of truth?
class BadWidget extends StatefulWidget {
  int localCount = 0; // Local state

  
  Widget build(BuildContext context, WidgetRef ref) {
    final providerCount = ref.watch(counterProvider); // Provider state
    return Text('$localCount vs $providerCount'); // Confusing!
  }
}

// ✅ GOOD: Single source of truth
class GoodWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}
dart
// ❌ BAD: Which is the source of truth?
class BadWidget extends StatefulWidget {
  int localCount = 0; // Local state

  
  Widget build(BuildContext context, WidgetRef ref) {
    final providerCount = ref.watch(counterProvider); // Provider state
    return Text('$localCount vs $providerCount'); // Confusing!
  }
}

// ✅ GOOD: Single source of truth
class GoodWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

Not Invalidating Dependent Providers

未失效依赖Provider

dart
// ❌ BAD
Future<void> logout() async {
  state = null;
  // Other providers still have old user data!
}

// ✅ GOOD: Invalidate dependent providers
Future<void> logout() async {
  state = null;
  ref.invalidate(userProfileProvider);
  ref.invalidate(userSettingsProvider);
  ref.invalidate(userNotificationsProvider);
}

// ✅ EVEN BETTER: Make providers watch auth

Future<UserProfile> userProfile(Ref ref) async {
  final user = ref.watch(authProvider);
  if (user == null) throw UnauthenticatedException();
  return fetchUserProfile(user.id); // Auto-refetches when user changes
}
dart
// ❌ BAD
Future<void> logout() async {
  state = null;
  // Other providers still have old user data!
}

// ✅ GOOD: Invalidate dependent providers
Future<void> logout() async {
  state = null;
  ref.invalidate(userProfileProvider);
  ref.invalidate(userSettingsProvider);
  ref.invalidate(userNotificationsProvider);
}

// ✅ EVEN BETTER: Make providers watch auth

Future<UserProfile> userProfile(Ref ref) async {
  final user = ref.watch(authProvider);
  if (user == null) throw UnauthenticatedException();
  return fetchUserProfile(user.id); // Auto-refetches when user changes
}

Instructions for Use

使用说明

When the user is working with Riverpod:
  1. Always recommend code generation with
    @riverpod
    annotations
  2. Prefer AsyncNotifierProvider over FutureProvider/StreamProvider for async state
  3. Optimize performance by suggesting
    select()
    when watching specific fields
  4. Follow repository pattern for clean architecture
  5. Use proper error handling with AsyncValue.guard() and .when()
  6. Remind about autoDispose and caching strategies
  7. Point out anti-patterns if you see them in user code
  8. Provide complete, working examples with proper imports
当用户使用Riverpod时:
  1. 始终推荐代码生成,使用
    @riverpod
    注解
  2. 优先使用AsyncNotifierProvider处理异步状态,而非FutureProvider/StreamProvider
  3. 优化性能,建议在监听特定字段时使用
    select()
  4. 遵循仓库模式实现清晰的架构
  5. 使用正确的错误处理,结合AsyncValue.guard()和.when()
  6. 提醒autoDispose和缓存策略
  7. 指出反模式如果在用户代码中发现
  8. 提供完整可运行的示例,包含正确的导入

Reference

参考

For complete details and advanced patterns, refer to:
/Users/pablito/EVOworkspace/flutter/CesarferPromotoresFlutter/promotores/RIVERPOD_2025_BEST_PRACTICES.md
如需完整细节和高级模式,请参考:
/Users/pablito/EVOworkspace/flutter/CesarferPromotoresFlutter/promotores/RIVERPOD_2025_BEST_PRACTICES.md