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

Flutter中如何在按钮点击后直接获取Riverpod请求响应并显示?

问题描述

在Flutter项目中,点击按钮时需要显示"请求发送中"的SnackBar,请求响应返回后还要显示响应内容。尝试使用ref.read(insertFilesProvider).requireValue获取响应时,报错:Tried to call requireValue on an AsyncValue that has no value。不清楚如何预加载provider的AsyncValue缓存(build方法返回空字符串,难道不会初始化吗?还是requireValue仅能配合ref.watch/listen使用?),目前用widget的build方法里ref.listen()加microtask作为临时方案,想知道能不能在按钮的onPressed回调中直接实现响应显示?

简化后的按钮代码

class InsertFilesButton extends HookConsumerWidget {
  const InsertFilesButton({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Listen for changes to show server response
    // TODO: make it work onPressed
    // Hack: wait for next frame with microtask() for code to actually execute
    ref.listen(
        insertFilesProvider,
        (prev, now) => Future.microtask(() {
              switch (now) {
                case AsyncData(value: final data):
                  SnackBarUtils.showSnackBar(context, data);
              }
            }));

    return FilledButton(
      onPressed: () async {
        // Get selectedFiles from its provider
        final selectedFiles = ref.read(selectedInsertFilesProvider);
        SnackBarUtils.showSnackBar(context, "Enqueuing jobs on server...");
        ref.read(insertFilesProvider.notifier).process(selectedFiles);
        // final result = ref.read(insertFilesProvider);
        // log.d(result);
        // Error: Unhandled Exception: Bad state: Tried to call `requireValue` on an `AsyncValue` that has no value
        // SnackBarUtils.showSnackBar(context, result.requireValue);
      },
      child: const Text("Insert Files"),
    );
  }
}

对应的Provider代码

@riverpod
class InsertFiles extends _$InsertFiles {
  @override
  Future<String> build() async => '';

  Future<void> process(
    List files,
  ) async {
    final bodyData = jsonEncode({'filenames': files});

    final response = await dio.post(
      '$api/insert/',
      options: Options(headers: {
        HttpHeaders.contentTypeHeader: "application/json",
      }),
      data: jsonEncode(bodyData),
    );
    final parsedResponse = InsertFilesResponse.fromJson(response.data);
    final result = parsedResponse.message;

    state = AsyncData(result);
  }
}

@freezed
class InsertFilesResponse with _$InsertFilesResponse {
  factory InsertFilesResponse({required String message}) = _InsertFilesResponse;

  factory InsertFilesResponse.fromJson(Map<String, Object?> json) =
      _$InsertFilesResponseFromJson(json);
}
解决方案

为什么直接用ref.read(insertFilesProvider).requireValue会报错?

  • requireValue仅在AsyncValue处于AsyncData状态时才能安全调用,调用process后立刻read provider,此时请求还在异步执行,provider的状态还是初始的未完成Future<String>,并非AsyncData,所以会抛出异常。
  • 初始build方法返回的空字符串是未完成的Future,provider初始化时是AsyncLoading状态,直到这个Future完成才会变成AsyncData(""),但这和后续调用process更新的状态是独立的。

可以在onPressed中直接实现响应显示的两种方法:

方法1:让process方法返回结果

修改Provider的process方法,直接返回请求结果,在onPressed中await该方法即可拿到响应:

// 修改InsertFiles provider的process方法
Future<String> process(List files) async {
  final bodyData = jsonEncode({'filenames': files});

  final response = await dio.post(
    '$api/insert/',
    options: Options(headers: {
      HttpHeaders.contentTypeHeader: "application/json",
    }),
    data: jsonEncode(bodyData),
  );
  final parsedResponse = InsertFilesResponse.fromJson(response.data);
  final result = parsedResponse.message;

  state = AsyncData(result);
  return result; // 返回结果
}

然后更新按钮的onPressed逻辑:

onPressed: () async {
  final selectedFiles = ref.read(selectedInsertFilesProvider);
  SnackBarUtils.showSnackBar(context, "Enqueuing jobs on server...");
  // await process方法拿到结果
  final result = await ref.read(insertFilesProvider.notifier).process(selectedFiles);
  SnackBarUtils.showSnackBar(context, result);
},

方法2:在onPressed中临时监听provider状态

如果不想修改process方法的返回值,可以在onPressed中临时监听provider状态,拿到结果后取消监听:

onPressed: () async {
  final selectedFiles = ref.read(selectedInsertFilesProvider);
  SnackBarUtils.showSnackBar(context, "Enqueuing jobs on server...");
  
  // 临时监听provider状态
  final listener = ref.listenManual(
    insertFilesProvider,
    (prev, now) {
      if (now is AsyncData<String>) {
        SnackBarUtils.showSnackBar(context, now.value);
        // 拿到结果后取消监听,避免重复触发
        listener.close();
      }
    },
  );

  ref.read(insertFilesProvider.notifier).process(selectedFiles);
},

当前ref.listen方案优化

如果保留当前的监听方案,可以去掉microtask,同时过滤初始空字符串和重复状态:

ref.listen(insertFilesProvider, (prev, now) {
  // 过滤初始空字符串、加载状态和重复状态
  if (now is AsyncData<String> && now.value.isNotEmpty && prev != now) {
    SnackBarUtils.showSnackBar(context, now.value);
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:25:09