Flutter Bloc中API返回401时如何从API服务类触发登出?
解决方案分析
不推荐直接持有Bloc实例的原因
直接在APIService里存Authentication Bloc的实例属于强耦合写法,违背了单一职责原则——APIService的本职是处理网络请求,身份认证状态管理是Bloc的活儿。这种耦合会让代码难测难维护,比如后续改Bloc实现或登出逻辑时,还得同步改APIService。
推荐的实现方式
方式1:用全局事件总线解耦
通过事件总线让APIService和Bloc互不依赖:
- 定义一个全局事件总线(可以用
event_bus包,也可以自己写个简单的Stream); - 拦截到401时,APIService只需要发送一个
UnauthorizedEvent事件; - Authentication Bloc监听这个事件,收到后自动触发登出逻辑。
示例代码:
// 定义触发登出的事件 class UnauthorizedEvent {} // 全局事件总线实例 final eventBus = EventBus(); // APIService里的拦截器逻辑 onError: (DioError error, handler) async { if (error.response?.statusCode == 401) { // 发送未授权事件 eventBus.fire(UnauthorizedEvent()); // 先失效token,比如清空本地存储 await TokenStorage.clearToken(); } handler.next(error); } // Authentication Bloc中监听事件 class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> { late final StreamSubscription _eventSubscription; AuthenticationBloc() : super(AuthenticationInitial()) { // 监听未授权事件,触发登出 _eventSubscription = eventBus.on<UnauthorizedEvent>().listen((_) { add(LogoutEvent()); }); // 处理登出事件的逻辑 on<LogoutEvent>((event, emit) async { await UserStorage.clearUserInfo(); emit(AuthenticationUnauthenticated()); // 跳转登录页 // Navigator.of(context).pushReplacementNamed('/login'); }); } @override Future<void> close() { _eventSubscription.cancel(); return super.close(); } }
方式2:把拦截器逻辑移到Bloc层
把Dio的初始化和拦截器配置放到Authentication Bloc里,让Bloc直接掌控401的处理:
- 在Bloc内部创建Dio实例并添加拦截器;
- 拦截到401时直接触发Bloc自己的登出事件;
- 其他业务服务通过依赖注入获取Bloc里的Dio实例即可。
示例代码:
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> { late final Dio _dio; AuthenticationBloc() : super(AuthenticationInitial()) { _dio = Dio(); _dio.interceptors.add(InterceptorsWrapper( onError: (DioError error, handler) async { if (error.response?.statusCode == 401) { // 直接触发登出事件 add(LogoutEvent()); } handler.next(error); }, )); on<LogoutEvent>((event, emit) async { await TokenStorage.clearToken(); await UserStorage.clearUserInfo(); emit(AuthenticationUnauthenticated()); // 跳转登录页 }); } // 对外提供Dio实例给其他服务使用 Dio get dioInstance => _dio; }
方式3:用Repository层做中间协调
如果项目有Repository层,可以让它作为网络请求和状态管理的中间层:
- APIService遇到401时抛出自定义的
UnauthorizedException; - Repository捕获这个异常,通知Authentication Bloc触发登出;
- Bloc执行登出逻辑。
示例代码:
// 自定义未授权异常 class UnauthorizedException implements Exception {} // APIService只负责抛异常 class APIService { final Dio _dio; APIService(this._dio); Future<Response> fetchUserData() async { try { return await _dio.get('/user/data'); } on DioError catch (e) { if (e.response?.statusCode == 401) { throw UnauthorizedException(); } rethrow; } } } // Repository层协调异常和Bloc class UserRepository { final APIService _apiService; final AuthenticationBloc _authBloc; UserRepository(this._apiService, this._authBloc); Future<Response> fetchUserData() async { try { return await _apiService.fetchUserData(); } on UnauthorizedException { _authBloc.add(LogoutEvent()); rethrow; } } }
总结
直接在APIService里持有Bloc实例虽然能跑通,但耦合问题会埋下隐患。更推荐用事件总线或把拦截器移到Bloc层的方案,既能解耦,又能清晰划分各模块职责;如果项目已有Repository层,第三种方案也很适配。
内容的提问来源于stack exchange,提问作者George
相关产品推荐
相关产品推荐

