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

Flutter如何在ApiService中获取BuildContext处理令牌过期登出

解决方案:ApiService中无需持有BuildContext实现令牌过期登出

ApiService作为独立的业务服务,不应该直接持有BuildContext——这会导致代码耦合,还可能引发内存泄漏或上下文无效的问题。你用BuildContextProvider没生效,是因为它没有正确挂载到Widget树中,无法获取有效上下文。以下是几种可行的解耦方案:

方案1:事件总线解耦(推荐)

通过全局事件总线发送"令牌过期"事件,在Widget层监听事件并执行UI操作,完全隔离业务层与UI层的依赖。

步骤实现:

  1. 定义事件类
class TokenExpiredEvent {}
  1. 创建全局事件总线(可使用第三方包event_bus,或自己用StreamController实现)
// 使用event_bus包的方式
final authEventBus = EventBus();

// 自定义StreamController实现(无需第三方包)
// final authEventBus = StreamController<TokenExpiredEvent>.broadcast();
  1. 修改拦截器代码,发送事件替代直接操作UI
// 替换AppInterceptors中原来的BuildContextProvider代码
if (dioException.type == DioExceptionType.badResponse &&
    dioException.response?.statusCode == 400 &&
    dioException.requestOptions.path.contains("refreshToken")) {
  Fimber.w("api_auth_service 400 unauth");

  // 发送令牌过期事件
  authEventBus.fire(TokenExpiredEvent());
  UserRepository().logout();
}
  1. 在顶层Widget(如MainApp)中监听事件
class MainApp extends StatefulWidget {
  @override
  _MainAppState createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  late StreamSubscription _tokenExpiredSubscription;

  @override
  void initState() {
    super.initState();
    // 监听令牌过期事件
    _tokenExpiredSubscription = authEventBus.on<TokenExpiredEvent>().listen((event) {
      context.read<LoginCubit>().logout(context);
      Device.showToast(context, "登录已过期,请重新登录。");
    });
  }

  @override
  void dispose() {
    _tokenExpiredSubscription.cancel();
    // 自定义StreamController需关闭:authEventBus.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 你的应用配置
    );
  }
}

方案2:全局导航键+回调

通过全局导航键获取上下文,配合顶层Widget注册的回调执行UI操作。

步骤实现:

  1. 定义全局导航键和回调
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void Function(BuildContext)? onTokenExpired;
  1. 在MaterialApp中配置导航键并初始化回调
MaterialApp(
  navigatorKey: navigatorKey,
  builder: (context, child) {
    onTokenExpired = (context) {
      context.read<LoginCubit>().logout(context);
      Device.showToast(context, "登录已过期,请重新登录。");
    };
    return child!;
  },
  // 其他配置
)
  1. 修改拦截器代码调用回调
if (dioException.type == DioExceptionType.badResponse &&
    dioException.response?.statusCode == 400 &&
    dioException.requestOptions.path.contains("refreshToken")) {
  Fimber.w("api_auth_service 400 unauth");

  // 通过全局导航键获取上下文并执行回调
  onTokenExpired?.call(navigatorKey.currentContext!);
  UserRepository().logout();
}

方案3:UI层捕获自定义异常

ApiService只负责抛出业务异常,将UI操作放到Bloc/ViewModel等UI逻辑层处理,上下文自然可用。

步骤实现:

  1. 定义自定义异常并在拦截器中抛出
class TokenExpiredException implements Exception {}

// 修改AppInterceptors的onError逻辑
if (dioException.type == DioExceptionType.badResponse &&
    dioException.response?.statusCode == 400 &&
    dioException.requestOptions.path.contains("refreshToken")) {
  Fimber.w("api_auth_service 400 unauth");
  UserRepository().logout();
  // 抛出自定义异常
  handler.reject(DioException(
    requestOptions: dioException.requestOptions,
    error: TokenExpiredException(),
  ));
  return;
}
  1. 在UI层逻辑中捕获异常并处理
// 示例:在业务Cubit中调用API时捕获异常
try {
  await ApiService().request("/some/api");
} catch (e) {
  if (e is DioException && e.error is TokenExpiredException) {
    context.read<LoginCubit>().logout(context);
    Device.showToast(context, "登录已过期,请重新登录。");
  }
  // 处理其他异常
}

为什么BuildContextProvider没生效?

你直接实例化BuildContextProvider()((context) => ...)的方式错误——这个Provider需要挂载到Widget树中,通过context.read或Provider.of获取,直接创建实例无法接入Widget树的上下文链,自然拿不到有效Context。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 15:35:15