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

Riverpod类型不匹配报错修复、MVVM架构及SOLID合规咨询

一、解决Provider类型不匹配错误

问题本质

managerLocalStorageImplProvider是状态Notifier Provider,默认ref.watch()获取的是它的状态值ManagerLocalStorageState,但你需要的是实现了ManagerLocalStorage接口的ManagerLocalStorageImpl实例(即Notifier本身)。

修复代码

通过.notifier属性获取Notifier实例即可匹配参数类型:

@riverpod
ShopRepository shopRepository(ShopRepositoryRef ref) => ShopRepositoryImpl(
      dio: authDio(),
      // 新增.notifier获取Notifier实例
      managerLocalStorage: ref.watch(managerLocalStorageImplProvider.notifier),
    );

二、基于Riverpod的MVVM结构化示例

分层规范

  • 数据源层:封装本地/远程数据读写逻辑,对外暴露抽象接口
  • 仓库层:聚合多数据源,提供统一业务数据接口
  • ViewModel层:持有仓库实例,处理业务逻辑,暴露可监听状态给UI
  • UI层:监听ViewModel状态,纯渲染逻辑

完整代码示例

1. 数据源层(本地存储)

// 抽象接口
abstract class ManagerLocalStorage {
  Future<String?> getToken();
  Future<void> saveToken(String token);
}

// 具体实现+Riverpod注解
@riverpod
class ManagerLocalStorageImpl extends _$ManagerLocalStorageImpl implements ManagerLocalStorage {
  late final FlutterSecureStorage _storage;

  @override
  ManagerLocalStorageState build() {
    _storage = ref.read(secureStorageProvider);
    return ManagerLocalStorageState();
  }

  @override
  Future<String?> getToken() => _storage.read(key: 'token');

  @override
  Future<void> saveToken(String token) => _storage.write(key: 'token', value: token);
}

2. 仓库层

// 抽象接口
abstract class ShopRepository {
  Future<ShopInfo> getShopInfo();
}

// 具体实现
class ShopRepositoryImpl extends ShopRepository {
  ShopRepositoryImpl({
    required Dio dio,
    required ManagerLocalStorage localStorage,
  })  : _apiClient = ShopApiClient(dio, baseUrl: Constants.shopDomain),
        _localStorage = localStorage;

  final ShopApiClient _apiClient;
  final ManagerLocalStorage _localStorage;

  @override
  Future<ShopInfo> getShopInfo() async {
    final token = await _localStorage.getToken();
    return _apiClient.getShopInfo(token: token);
  }
}

// 仓库Provider(建议把authDio也封装为Provider)
@riverpod
ShopRepository shopRepository(ShopRepositoryRef ref) => ShopRepositoryImpl(
      dio: ref.read(authDioProvider),
      managerLocalStorage: ref.watch(managerLocalStorageImplProvider.notifier),
    );

3. ViewModel层

@riverpod
class ShopViewModel extends _$ShopViewModel {
  late final ShopRepository _shopRepo;

  @override
  AsyncValue<ShopInfo> build() {
    _shopRepo = ref.read(shopRepositoryProvider);
    // 初始加载数据
    return _fetchShopInfo();
  }

  Future<AsyncValue<ShopInfo>> _fetchShopInfo() async {
    try {
      final info = await _shopRepo.getShopInfo();
      return AsyncData(info);
    } catch (e) {
      return AsyncError(e, StackTrace.current);
    }
  }

  // 外部调用的刷新方法
  Future<void> refreshShopInfo() async {
    state = await _fetchShopInfo();
  }
}

4. UI层

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final shopState = ref.watch(shopViewModelProvider);
    
    return Scaffold(
      appBar: AppBar(title: const Text('店铺信息')),
      body: shopState.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (e, _) => Center(child: Text('加载失败:$e')),
        data: (info) => ListTile(
          title: Text(info.name),
          subtitle: Text(info.address),
          trailing: IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => ref.read(shopViewModelProvider.notifier).refreshShopInfo(),
          ),
        ),
      ),
    );
  }
}

三、规避Riverpod全局特性的SOLID合规方案

1. 始终依赖抽象而非具体实现

  • 业务代码只面向接口(如ManagerLocalStorage、ShopRepository)编写,不直接依赖具体Provider或实现类
  • 好处:符合依赖倒置原则,可轻松替换实现(比如测试用Mock)

2. 限定Provider作用域

  • 使用ProviderScope创建局部作用域,将特定业务模块的Provider限定在对应页面/组件树内,避免全局污染
  • 示例:
class ShopModule extends StatelessWidget {
  const ShopModule({super.key, required this.child});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      overrides: [
        // 局部替换Provider实现,用于测试或模块定制
        shopRepositoryProvider.overrideWith((ref) => MockShopRepository()),
      ],
      child: child,
    );
  }
}

3. 禁止非注入式的全局调用

  • 不在非Widget/非Provider的类中直接调用ref.read或全局Provider,所有依赖通过构造函数注入
  • 好处:符合单一职责原则,类的依赖关系清晰,便于测试和维护

4. 测试隔离

  • 单元测试中使用ProviderContainer和overrideWith完全隔离全局状态,确保测试不依赖外部环境
  • 示例:
test('ShopRepository获取店铺信息', () async {
  final container = ProviderContainer(
    overrides: [
      managerLocalStorageImplProvider.overrideWith((ref) => MockManagerLocalStorage()),
      authDioProvider.overrideWith((ref) => MockDio()),
    ],
  );
  final repo = container.read(shopRepositoryProvider);
  final info = await repo.getShopInfo();
  expect(info.name, '测试店铺');
});

内容的提问来源于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 09:51:15