You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:18:14