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

Riverpod FutureProvider的AsyncValue.when在VoidCallback中无响应问题

问题

点击按钮时触发FutureProvider,期望根据返回值修改应用状态,但点击后仅loading回调触发,数据返回时data回调未执行,推测是组件重建导致onTap执行中断。尝试过AsyncValue.whenData,回调仍未触发。

现有代码

按钮onTap回调

ElevatedButton(
  onPressed: () {
    final promptResp = ref.watch(
      getProvider(
        Tuple2(
          '/gen-image',
          {'prompt': controller.text},
        ),
      ),
    );

    promptResp.when<void>(data: (value) {
      ref.read(isLoading.notifier).state = false;
      final data = jsonDecode(value.body);
      ref.read(imagesProvider.notifier).state = data;
    }, error: (e, r) {
      return;
    }, loading: () {
      ref.read(isLoading.notifier).state = true;
    });
  },
)

对应的Provider代码

final getProvider = FutureProvider.family(
    (ref, Tuple2<String?, Map<String, dynamic>?> info) async {
  final path = info.item1;
  final params = info.item2;

  final url = backendUrl().replace(queryParameters: params, path: path);

  final resp = await http.get(url, headers: {
    'X-FIREBASE_TOKEN': await ref.read(authProvider).currentUser!.getIdToken()
  });

  return resp;
});
解决方案

核心问题是在onPressed回调内直接watch FutureProvider:watch依赖组件的BuildContext,当isLoading状态变化触发组件重建时,原来的onPressed上下文已失效,后续的data回调自然无法执行。

推荐两种可行的修正方式:


方式一:手动处理异步流程(更直观)

直接通过ref.read()获取FutureProvider的future,手动控制加载、成功、错误状态:

ElevatedButton(
  onPressed: () async {
    final ref = ref; // 保留ref引用,避免组件重建后丢失
    ref.read(isLoading.notifier).state = true;
    
    try {
      // 读取Provider的future并等待结果
      final resp = await ref.read(getProvider(
        Tuple2(
          '/gen-image',
          {'prompt': controller.text},
        ),
      ).future);
      
      // 处理返回数据
      final data = jsonDecode(resp.body);
      ref.read(imagesProvider.notifier).state = data;
    } catch (e, stackTrace) {
      // 这里可以添加错误处理逻辑,比如弹窗提示
    } finally {
      // 无论成功或失败,都结束加载状态
      ref.read(isLoading.notifier).state = false;
    }
  },
)

方式二:使用ref.listen监听状态变化

通过ref.listen订阅Provider的状态变化,即使组件重建,监听依然有效(完成后需手动关闭监听避免内存泄漏):

ElevatedButton(
  onPressed: () {
    // 创建Provider实例
    final imageGenProvider = getProvider(
      Tuple2(
        '/gen-image',
        {'prompt': controller.text},
      ),
    );
    
    // 订阅状态变化
    final listener = ref.listen(imageGenProvider, (previous, next) {
      next.when(
        data: (value) {
          ref.read(isLoading.notifier).state = false;
          final data = jsonDecode(value.body);
          ref.read(imagesProvider.notifier).state = data;
          listener.close(); // 完成后关闭监听
        },
        error: (e, r) {
          ref.read(isLoading.notifier).state = false;
          listener.close(); // 错误时也关闭监听
          // 添加错误处理逻辑
        },
        loading: () {
          ref.read(isLoading.notifier).state = true;
        },
      );
    });
  },
)

总结

  • 方式一代码更简洁,逻辑清晰,适合大多数场景;
  • 方式二更适合需要持续监听Provider状态的复杂场景。

内容的提问来源于stack exchange,提问作者Mudassir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:27:45