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后立刻readprovider,此时请求还在异步执行,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
相关产品推荐
相关产品推荐

