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

将Riverpod的Ref传入RestClient服务是否为最佳实践?

问题描述

我有Xamarin.Forms开发经验,目前正在学习Flutter,尝试复现过往项目中的常见场景。我编写了一个基于Dio实现的RestClient服务类,用来处理所有底层Rest请求。当请求返回401 - Unauthorized状态时,我希望触发应用登出逻辑(此处已做简化处理)。

我使用Riverpod管理业务逻辑,但RestClient内部没有Ref来与providers交互,因此我将Ref传入RestClient的构造函数,以此更新authenticationProvider的状态。目前该方案运行正常,但我担心这种方式会造成组件间过度耦合,不确定这是否是合理的做法。

现有代码

rest_client.dart

class RestClient {
  final Dio dio;
  final Ref ref;

  RestClient._({required this.dio, required this.ref});

  factory RestClient({required Ref ref}) {
    var dio = Dio(BaseOptions(
      baseUrl: "https://myurl.com/",
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
    ));

    dio.interceptors.add(InterceptorsWrapper(
      onError: (e, handler) {
        if (e.response != null && e.response!.statusCode == 401) {
          ref.read(authenticationProvider.notifier).invalidateToken();
        }
      },
    ));

    return RestClient._(dio: dio, ref: ref);
  }

  /**** ... *****/
  /**** Methods to call api endpoints *****/
  /**** ... *****/
}

authentication_provider.dart

class AutenthicationNotifier extends StateNotifier<String?> {
  // *** Constructor and other methods ***

  void invalidateToken() {
    // body
  }
}

final authenticationProvider = StateNotifierProvider<AutenthicationNotifier, String?>((ref) {
  return AutenthicationNotifier();
});

RestClient Provider

final restClientProvider = Provider((ref) => RestClient(ref: ref));
分析与改进建议

当前方案的问题

你担心的耦合问题确实存在:

  • RestClient直接依赖Riverpod的Ref和具体的authenticationProvider,导致它无法脱离Riverpod复用,一旦更换状态管理库或重构authenticationProvider,RestClient必须跟着修改。
  • 单元测试难度提升,需要模拟Ref和authenticationProvider的行为才能测试RestClient的网络逻辑。

推荐改进方案:回调函数解耦

让RestClient只专注网络请求逻辑,把401的处理逻辑通过回调函数的方式交给外部,彻底解除和Riverpod的耦合:

修改后的rest_client.dart

class RestClient {
  final Dio dio;
  final void Function() onUnauthorized;

  RestClient._({required this.dio, required this.onUnauthorized});

  factory RestClient({required void Function() onUnauthorized}) {
    var dio = Dio(BaseOptions(
      baseUrl: "https://myurl.com/",
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
    ));

    dio.interceptors.add(InterceptorsWrapper(
      onError: (e, handler) {
        if (e.response != null && e.response!.statusCode == 401) {
          onUnauthorized();
        }
        // 记得继续传递错误,避免拦截器阻塞错误处理
        handler.next(e);
      },
    ));

    return RestClient._(dio: dio, onUnauthorized: onUnauthorized);
  }

  // ... API请求方法
}

修改后的RestClient Provider

final restClientProvider = Provider((ref) => RestClient(
      onUnauthorized: () => ref.read(authenticationProvider.notifier).invalidateToken(),
    ));

其他可选方案

  1. 事件总线:通过事件总线发布Unauthorized事件,由上层业务逻辑监听并处理登出。但这种方式会让代码流向变得不直观,适合更复杂的跨组件通信场景。
  2. 外部添加拦截器:把Dio拦截器的逻辑移到Provider中,RestClient只负责创建Dio实例和提供API方法。这种方式会拆分RestClient的职责,适合需要灵活配置拦截器的场景。
总结

当前方案功能正常但耦合性较高,推荐使用回调函数的方式解耦,让RestClient保持单一职责,专注于网络请求逻辑,业务相关的登出操作交给上层状态管理处理,既提升了代码的可维护性,也方便后续的单元测试和复用。

内容的提问来源于stack exchange,提问作者camionkraken

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 01:39:40