如何用Riverpod注解在Flutter中实现MVVM并优化代码?
优化Riverpod注解实现MVVM的方案
核心问题分析
你当前的build()方法实现存在逻辑错误:用构造函数传入依赖后又在build里返回自身实例,这会导致初始化循环,而且违背了Riverpod的依赖注入逻辑——Riverpod的类provider应该通过ref在build方法中获取依赖,而非手动通过构造函数传参后重复实例化。
同时你需要平衡两个需求:
- 生产环境自动注入依赖,减少手动配置
- 测试时支持构造函数注入,明确依赖关系便于测试和团队协作
分步优化方案
1. 重构UseCase类
保留测试用的构造函数,生产环境依赖通过ref在build中自动获取:
import ... part 'invite_customer_usecase.g.dart'; // 配置keepAlive确保单例存活,或根据需求调整 @Riverpod(keepAlive: true) class InviteCustomerUseCase extends _$InviteCustomerUseCase { // 测试用命名构造函数,明确依赖 InviteCustomerUseCase.test({ required ShopRepository shopRepository, required UntactRepository untactRepository, required ManagerRepository managerRepository, required UserRepository userRepository, }) : _shopRepository = shopRepository, _untactRepository = untactRepository, _managerRepository = managerRepository, _userRepository = userRepository; late final ShopRepository _shopRepository; late final UntactRepository _untactRepository; late final ManagerRepository _managerRepository; late final UserRepository _userRepository; @override InviteCustomerUseCase build() { // 生产环境通过ref自动获取依赖 _shopRepository = ref.watch(shopRepositoryProvider); _untactRepository = ref.watch(untactRepositoryProvider); _managerRepository = ref.watch(managerRepositoryProvider); _userRepository = ref.watch(userRepositoryProvider); return this; } // 这里添加UseCase的业务方法,比如: Future<void> execute(String customerId) async { // 调用_repository的方法处理业务逻辑 await _shopRepository.inviteCustomer(customerId); } }
2. 重构ViewModel类
同样采用测试构造函数+build自动注入的模式,同时支持状态管理:
import ... part 'lobby_viewmodel.g.dart'; @Riverpod(keepAlive: true) class LobbyViewModel extends _$LobbyViewModel { // 测试用命名构造函数 LobbyViewModel.test({ required UntactRepository untactRepository, required ShopRepository shopRepository, required ManagerRepository managerRepository, required InviteCustomerUseCase inviteCustomerUseCase, }) : _untactRepository = untactRepository, _shopRepository = shopRepository, _managerRepository = managerRepository, _inviteCustomerUseCase = inviteCustomerUseCase; late final UntactRepository _untactRepository; late final ShopRepository _shopRepository; late final ManagerRepository _managerRepository; late final InviteCustomerUseCase _inviteCustomerUseCase; // 定义ViewModel的状态类型,比如用AsyncValue处理异步状态 @override AsyncValue<void> build() { // 初始化依赖 _untactRepository = ref.watch(untactRepositoryProvider); _shopRepository = ref.watch(shopRepositoryProvider); _managerRepository = ref.watch(managerRepositoryProvider); _inviteCustomerUseCase = ref.watch(inviteCustomerUseCaseProvider); // 初始状态可以是AsyncValue.data(null)或其他默认值 return const AsyncValue.data(null); } // 示例业务方法:邀请客户 Future<void> inviteCustomer(String customerId) async { state = const AsyncValue.loading(); try { await _inviteCustomerUseCase.execute(customerId); // 可以调用repository获取最新数据更新状态 final shopInfo = await _shopRepository.getShopInfo(); state = AsyncValue.data(shopInfo); } catch (e, stackTrace) { state = AsyncValue.error(e, stackTrace); } } }
3. 测试时的依赖注入
在测试中,你可以通过override provider来注入模拟依赖:
void main() { test('测试LobbyViewModel邀请客户', () async { final mockShopRepo = MockShopRepository(); final mockInviteUseCase = InviteCustomerUseCase.test( shopRepository: mockShopRepo, untactRepository: MockUntactRepository(), managerRepository: MockManagerRepository(), userRepository: MockUserRepository(), ); final container = ProviderContainer( overrides: [ inviteCustomerUseCaseProvider.overrideWithValue(mockInviteUseCase), ], ); final viewModel = container.read(lobbyViewModelProvider.notifier); await viewModel.inviteCustomer('test_id'); // 验证mock方法是否被调用 verify(mockShopRepo.inviteCustomer('test_id')).called(1); }); }
4. 单例与路由场景适配
- 保持存活:通过
@Riverpod(keepAlive: true)让Provider在页面跳转(pop/push)时不被销毁,实现跨页面共享的单例效果。 - 主动刷新/失效:在需要更新状态时,可通过以下方式操作:
// 在Widget中刷新ViewModel ref.refresh(lobbyViewModelProvider); // 或者失效Provider,下次watch时会重新初始化 ref.invalidate(lobbyViewModelProvider);
5. Widget层使用(HookConsumerWidget)
class LobbyPage extends HookConsumerWidget { const LobbyPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // 监听ViewModel的状态 final lobbyState = ref.watch(lobbyViewModelProvider); final lobbyNotifier = ref.read(lobbyViewModelProvider.notifier); return Scaffold( body: lobbyState.when( loading: () => const CircularProgressIndicator(), error: (e, _) => Text('错误:$e'), data: (_) => ElevatedButton( onPressed: () async { await lobbyNotifier.inviteCustomer('123'); }, child: const Text('邀请客户'), ), ), ); } }
补充说明
- 你的Repository Provider代码无需修改,保持现有通过
ref.watch注入依赖的逻辑即可。 - 命名构造函数(如
.test)是明确依赖的关键,既让团队成员一眼看到类需要的依赖,又能在测试时灵活替换模拟实现。
内容的提问来源于stack exchange,提问作者c-an
相关产品推荐
相关产品推荐

