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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 09:19:57