
Riverpod 2 结合代码生成,简洁地解决了 Flutter 状态管理问题。提供者是不可变声明;框架负责生命周期、缓存和销毁。
设置
# 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:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
// 同步提供者
@riverpod
String appVersion(AppVersionRef ref) => '2.0.0';
// 异步提供者(自动处理加载/错误状态)
@riverpod
Future<User> user(UserRef ref, String userId) async {
return ref.watch(userRepositoryProvider).getById(userId);
}
// 保持存活:在组件销毁后仍然存在
@Riverpod(keepAlive: true)
AppConfig appConfig(AppConfigRef ref) => AppConfig.fromEnv();

用于复杂状态的 AsyncNotifier
@riverpod
class ProductList extends _$ProductList {
@override
Future<List<Product>> build() async {
return ref.read(productRepositoryProvider).getProducts();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(productRepositoryProvider).getProducts()
);
}
Future<void> loadMore() async {
final current = state.valueOrNull ?? [];
final more = await ref.read(productRepositoryProvider)
.getProducts(page: (current.length ~/ 20) + 1);
state = AsyncData([...current, ...more]);
}
Future<void> delete(String id) async {
await ref.read(productRepositoryProvider).delete(id);
state = state.whenData(
(products) => products.where((p) => p.id != id).toList(),
);
}
}

ConsumerWidget 的使用
class ProductScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsState = ref.watch(productListProvider);
return productsState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => ref.invalidate(productListProvider),
child: const Text('Retry'),
),
]),
),
data: (products) => ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductTile(product: products[i]),
),
);
}
}
仓库模式 + 依赖注入
abstract class ProductRepository {
Future<List<Product>> getProducts({int page = 1});
Future<void> delete(String id);
}
class HttpProductRepository implements ProductRepository {
HttpProductRepository(this._client);
final http.Client _client;
@override
Future<List<Product>> getProducts({int page = 1}) async {
final resp = await _client.get(Uri.parse('/api/products?page=$page'));
if (resp.statusCode != 200) throw ApiException(resp);
return (jsonDecode(resp.body)['data'] as List).map(Product.fromJson).toList();
}
}
@riverpod
ProductRepository productRepository(ProductRepositoryRef ref) =>
HttpProductRepository(ref.watch(httpClientProvider));
// 测试时覆盖
ProviderScope(
overrides: [productRepositoryProvider.overrideWithValue(MockProductRepository())],
child: const MyApp(),
)
测试
test('ProductList deletes item', () async {
final mock = MockProductRepository();
when(mock.getProducts()).thenAnswer((_) async => [
Product(id: '1', name: 'Item A'),
Product(id: '2', name: 'Item B'),
]);
final container = ProviderContainer(overrides: [
productRepositoryProvider.overrideWithValue(mock),
]);
addTearDown(container.dispose);
await container.read(productListProvider.future);
await container.read(productListProvider.notifier).delete('1');
final products = container.read(productListProvider).valueOrNull!;
expect(products.length, equals(1));
expect(products.first.id, equals('2'));
});
Riverpod 代码生成消除了手动类型转换。提供者是函数;状态是不可变的;测试变得简单直接。
→ 使用 JSON Viewer 工具分析你的 API 响应。