Flutter中基于Bloc/Cubit实现全局API异常统一处理方案
Flutter flutter_bloc 全局统一错误处理实现方案
核心思路是把错误识别、特殊错误分发、通用业务响应三层解耦,完全不需要在每个业务Bloc中重复编写相同错误处理逻辑。
1. 网络层全局拦截,统一标记特殊错误
无论使用dio还是http库,都在最外层的全局拦截/统一请求封装中识别登录失效类错误,不要把错误判断散落在单个接口逻辑中。
首先定义专属异常类型,只要接口返回匹配登录失效规则(HTTP 401状态码、后端约定的失效错误码等),就直接抛出该自定义异常,其他业务错误正常透传即可。
以dio为例的实现代码:
// 自定义登录凭证失效专属异常 class AppUnauthenticatedException implements Exception { final String message; AppUnauthenticatedException([this.message = "登录已过期,请重新登录"]); } // 全局网络拦截器 class GlobalAuthInterceptor extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) { // 按自身项目后端返回结构调整判断规则,示例为code=401代表登录失效 final bizCode = response.data?["code"] as int?; if (bizCode == 401) { handler.reject( DioError( requestOptions: response.requestOptions, error: AppUnauthenticatedException(), ), false, ); return; } super.onResponse(response, handler); } @override void onError(DioError err, ErrorInterceptorHandler handler) { // 兼容HTTP状态码直接返回401的场景 if (err.response?.statusCode == 401) { err.error = AppUnauthenticatedException(); } super.onError(err, handler); } }
如果使用http库,只需要封装统一的get/post请求方法,所有接口调用都走该封装,在封装内统一做上述错误判断抛出异常即可。
2. 全局Bloc观察者捕获特殊错误,自动触发登出逻辑
利用flutter_bloc自带的BlocObserver能力,所有Bloc/Cubit中未被捕获的异常都会触发onError回调,我们在这里统一识别登录失效异常,直接触发全局AuthBloc的登出事件,完全不需要单个业务Bloc感知。
实现代码:
class AppBlocObserver extends BlocObserver { final AuthBloc authBloc; AppBlocObserver(this.authBloc); @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) { // 识别到登录失效异常,直接触发登出,加判断避免重复触发 if (error is AppUnauthenticatedException && authBloc.state is! AuthUnauthenticated) { authBloc.add(AuthLogoutEvent()); } super.onError(bloc, error, stackTrace); } }
在App入口初始化时完成全局配置:
void main() { // 初始化网络实例,加入全局拦截器 final dio = Dio(); dio.interceptors.add(GlobalAuthInterceptor()); // 初始化全局生命周期的AuthBloc final authBloc = AuthBloc(dio: dio, localStorage: SharedPrefsStorage()); // 注册全局Bloc观察者 Bloc.observer = AppBlocObserver(authBloc); runApp( MultiBlocProvider( providers: [ // 根节点注入全局AuthBloc,不随页面销毁 BlocProvider.value(value: authBloc), // 其他业务Bloc正常注册 ], child: const MyApp(), ), ); }
3. AuthBloc统一处理登出,UI层全局响应跳转
AuthBloc在收到登出事件时,统一完成本地缓存清理(删除token、用户信息等),然后发出未登录状态即可。
UI层只需要在根组件(一般是MaterialApp外层)做一次全局状态监听,只要收到未登录状态,就清空路由栈跳转至登录页,不需要单个页面单独处理:
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: BlocListener<AuthBloc, AuthState>( listener: (context, state) { if (state is AuthUnauthenticated) { // 清空所有历史路由,跳转登录页 Navigator.of(context).pushNamedAndRemoveUntil( "/login", (route) => false, ); } }, child: const AppRootPage(), ), ); } }
可选优化:封装BaseBloc/BaseCubit统一处理通用错误
如果还有网络超时、服务端通用报错等其他需要统一处理的错误,可以封装业务Bloc基类,内置统一的安全请求方法,减少重复的try-catch代码:
abstract class BaseCubit<State> extends Cubit<State> { BaseCubit(super.initialState); Future<void> safeCall( Future<void> Function() request, { required State Function(String errorMsg) buildErrorState, }) async { try { await request(); } on AppUnauthenticatedException { // 登录失效异常已经由全局观察者处理,直接重抛即可,无需额外逻辑 rethrow; } on DioError catch (e) { // 统一处理网络类错误,返回对应错误状态 emit(buildErrorState(e.message ?? "网络异常,请稍后重试")); } catch (e) { emit(buildErrorState(e.toString())); } } } // 业务Bloc使用示例 class UserInfoCubit extends BaseCubit<UserInfoState> { final Dio dio; UserInfoCubit(this.dio) : super(UserInfoInitial()); Future<void> loadUserInfo() async { emit(UserInfoLoading()); await safeCall( () async { final res = await dio.get("/api/user/info"); emit(UserInfoLoaded(UserModel.fromJson(res.data))); }, buildErrorState: (msg) => UserInfoLoadError(msg), ); } }
注意事项
- 不要在Repository层或其他中间层吞掉
AppUnauthenticatedException,保证异常能正常透传到Bloc层被观察者捕获 - 全局AuthBloc需要放在App根节点注入,保持应用级单例,不要随页面销毁
- 触发登出逻辑前要加状态判断,避免同一个错误重复触发多次登出操作
内容的提问来源于stack exchange,提问作者Emelianov
相关产品推荐
相关产品推荐

