Riverpod 2.0:如何用FutureOr和AutoDisposeAsyncNotifier处理API状态码?
Riverpod中API请求状态与错误的优雅处理
问题场景
你当前的登录Provider代码未处理API非200状态码的情况,当响应体无法解析为Map<String, dynamic>时,jsonDecode会直接抛出未捕获的异常,导致Riverpod无法切换到AsyncError状态,UI层也就无法正常处理错误场景。
原Provider代码:
final LoginAsyncProvider = AsyncNotifierProvider.autoDispose<LoginProvider, User>(() { return LoginProvider(); }); class LoginProvider extends AutoDisposeAsyncNotifier<User> { FutureOr<User> login({required String userName, required String password}) async { final response = await http.post(Uri.https(Api.mainDomain, Api.login)); final json = jsonDecode(response.body) as Map<String, dynamic>; User _user = User.fromJson(json); state = AsyncData(_user); return _user; } @override FutureOr<User> build() async { return login(userName: "someUser", password: "somePass"); } }
UI状态处理代码:
... child: switch (userAsyncValue) { AsyncData(:final value) => Text(text: value.userName), AsyncError() => const Text('Some Error happened'), _ => const CircularProgressIndicator(), }, ...
解决方案
要优雅处理API请求的各类失败场景,需在Provider中主动捕获状态码异常、解析异常,并将错误信息封装后抛出,让Riverpod自动切换为AsyncError状态,最终在UI层统一处理。
1. 自定义API异常类型
先定义异常类区分错误类型,方便UI层展示精准提示:
enum ApiErrorType { invalidStatusCode, parseError, unknown, } class ApiException implements Exception { final ApiErrorType type; final String message; ApiException({required this.type, required this.message}); }
2. 改造LoginProvider登录逻辑
在login方法中分层处理状态码校验、数据解析,任何环节出错都抛出自定义异常,并确保Riverpod能捕获到错误状态:
class LoginProvider extends AutoDisposeAsyncNotifier<User> { Future<User> login({required String userName, required String password}) async { try { // 传递登录参数(原代码遗漏此步骤) final response = await http.post( Uri.https(Api.mainDomain, Api.login), body: {'userName': userName, 'password': password}, ); // 校验状态码 if (response.statusCode != 200) { String errorMsg; switch (response.statusCode) { case 401: errorMsg = "用户名或密码错误"; break; case 404: errorMsg = "登录接口不存在"; break; default: errorMsg = "请求失败,状态码:${response.statusCode}"; } throw ApiException( type: ApiErrorType.invalidStatusCode, message: errorMsg, ); } // 解析响应体 late final Map<String, dynamic> json; try { json = jsonDecode(response.body) as Map<String, dynamic>; } catch (e) { throw ApiException( type: ApiErrorType.parseError, message: "数据解析失败:${e.toString()}", ); } // 转换为User对象(若fromJson可能抛错,可额外加try-catch) final User _user = User.fromJson(json); state = AsyncData(_user); return _user; } catch (e) { // 更新状态为AsyncError并重新抛出,确保Riverpod同步状态 state = AsyncError(e, StackTrace.current); rethrow; } } @override FutureOr<User> build() async { // 建议返回初始空状态,而非硬编码测试请求 // 登录操作由UI层主动触发:ref.read(LoginAsyncProvider.notifier).login(...) return User.empty(); } }
3. UI层优化错误处理
现在可在UI层根据自定义异常类型展示不同提示:
... child: switch (userAsyncValue) { AsyncData(:final value) => Text(value.userName), AsyncError(:final error) when error is ApiException => Text((error as ApiException).message), AsyncError() => const Text('未知错误'), _ => const CircularProgressIndicator(), }, ...
关键注意事项
- 必须重新抛出异常:在Provider的catch块中更新
AsyncError状态后,一定要rethrow,否则AsyncNotifier无法将错误状态同步给UI。 - 避免build方法硬编码请求:build方法会在Provider初始化时自动执行,建议返回初始空状态,登录操作由UI层主动触发。
- 精准区分错误类型:自定义异常能让UI层展示更贴合场景的错误提示,提升用户体验。
内容的提问来源于stack exchange,提问作者J. O'Ryan
相关产品推荐
相关产品推荐

