Flutter Riverpod中FamilyAsyncNotifier正确调用及点击异常问题
解决FamilyAsyncNotifier首次调用无响应的问题
问题根源
- 首次点击登录按钮时,
ref.read获取的是Notifier初始化后的AsyncLoading状态,when方法会同步执行loading分支,但登录请求完成后状态更新为AsyncData时,由于没有监听状态变化,无法触发data分支的打印逻辑。 - 第二次点击时,Notifier已缓存
AsyncData状态,when方法直接执行data分支,因此能正常打印token;修改用户名后,新的Family实例会重新初始化,首次点击又回到AsyncLoading状态,重复上述问题。
解决方案
方案一:用ref.listen监听状态全生命周期变化
修改登录按钮的onPressed回调,通过ref.listen追踪状态的所有更新:
onPressed: () { final loginParam = LoginParam( userName: userNameController.text, userPassword: passwordController.text, ); ref.listen( appLoginProvider(loginParam), (previous, next) { next.when( data: (v) => debugPrint(v.token), error: (err, stack) => debugPrint(err.toString()), loading: () => debugPrint('loading'), ); }, fireImmediately: true, // 立即触发初始状态回调 ); },
ref.listen会捕获状态从loading到data/error的所有变化,确保请求完成后能触发对应逻辑。
方案二:利用AsyncValue.future等待异步结果
将onPressed改为异步函数,通过future属性直接等待登录请求完成:
onPressed: () async { try { final loginParam = LoginParam( userName: userNameController.text, userPassword: passwordController.text, ); final loginResponse = await ref.read(appLoginProvider(loginParam)).future; debugPrint(loginResponse.token); } catch (err) { debugPrint(err.toString()); } },
AsyncValue.future会在状态变为AsyncData时返回结果,变为AsyncError时抛出异常,无需手动处理loading状态(如需加载提示,可在调用时显示弹窗)。
优化建议:手动触发登录逻辑
原代码中build方法会在Notifier创建时自动发起请求,不符合"点击按钮才登录"的交互逻辑,可修改Notifier添加手动触发方法:
class AppLoginNotifier extends FamilyAsyncNotifier<LoginResponse, LoginParam> { Future<LoginResponse> doLogin(String userName, String userPassword) async { await Future.delayed(const Duration(seconds: 2)); return LoginResponse(token: userName, refresh_token: userPassword); } // 初始状态设为未加载 @override FutureOr<LoginResponse> build(LoginParam arg) async { throw UnimplementedError('请调用login方法发起登录'); } // 手动触发登录 Future<void> login() async { state = const AsyncLoading(); try { final response = await doLogin(arg.userName, arg.userPassword); state = AsyncData(response); } catch (e, stack) { state = AsyncError(e, stack); } } }
调用方式:
onPressed: () async { final loginParam = LoginParam( userName: userNameController.text, userPassword: passwordController.text, ); final notifier = ref.read(appLoginProvider(loginParam).notifier); await notifier.login(); // 获取最终状态 ref.read(appLoginProvider(loginParam)).when( data: (v) => debugPrint(v.token), error: (err, stack) => debugPrint(err.toString()), loading: () => debugPrint('loading'), ); },
内容的提问来源于stack exchange,提问作者ktary
相关产品推荐
相关产品推荐

