Riverpod AsyncValue.when未触发错误构建器问题排查
问题根源
你犯了一个Riverpod AsyncNotifier的基础使用错误:AsyncNotifier的泛型类型被错误地设置成了AsyncValue<VirtualBook>,而正确的类型应该是VirtualBook。
AsyncNotifier的设计逻辑是:你只需要在build或异步方法里返回业务数据的Future(比如Future<VirtualBook>),它会自动帮你把这个Future包装成AsyncValue状态(加载中/成功/失败)。但你现在手动返回了AsyncValue<VirtualBook>,导致Riverpod把这个AsyncValue本身当作了业务数据,最终外层又套了一层AsyncValue——也就是你的bookDetails实际类型是AsyncValue<AsyncValue<VirtualBook>>。
这就解释了为什么服务器返回500错误时,when方法会走到data回调:外层的AsyncValue是成功状态,它的data就是你手动返回的那个AsyncError对象,所以你才会在日志里看到data.hasError为true,但此时外层已经是data状态了。
修正代码
修正后的Riverpod代码
final bookDetailsProvider = AutoDisposeAsyncNotifierProviderFamily< BookDetailsNotifier, VirtualBook, int>( () => BookDetailsNotifier(), ); class BookDetailsNotifier extends AutoDisposeFamilyAsyncNotifier<VirtualBook, int> { @override Future<VirtualBook> build(int arg) async { return _loadBookDetails(arg); } Future<VirtualBook> _loadBookDetails(int id) async { try { final res = await ref.read(virualBooksRepositoryProvider).getBookById(id); return res; // 直接返回数据,不用包AsyncValue.data } on ApiResponseError catch (e, s) { printRed('Error Response'); printRed(e); rethrow; // 直接抛出错误,AsyncNotifier会自动包装成AsyncError } catch (e, s) { printRed(e); printRed(s); throw 'Something went wrong'; // 抛出错误即可 } } }
修正后的UI代码(简化部分逻辑)
class BookDetailsPage extends ConsumerWidget { const BookDetailsPage(this.bookId, {super.key}); final int bookId; @override Widget build(BuildContext context, WidgetRef ref) { final bookDetails = ref.watch(bookDetailsProvider(bookId)); return AppScaffold( titleIcon: Icons.book, title: 'Book Details', body: Padding( padding: const EdgeInsets.all(d_2), child: bookDetails.when( error: (error, stackTrace) { printYellow('Error'); return Center(child: Text(error.toString())); }, loading: () { printYellow('Loading'); return const Center(child: CircularProgressIndicator()); }, data: (book) { // 这里直接拿到VirtualBook类型的数据 printYellow('Got Data'); return ListView.separated( itemCount: book.chapters.length, separatorBuilder: (context, index) => const SizedBox(height: d_2), itemBuilder: (context, index) { final chapter = book.chapters[index]; return ChapterTile( index: index, chapter: chapter, ); }, ); }, ), ), ); } }
关键修正点
- 把
AutoDisposeFamilyAsyncNotifier<AsyncValue<VirtualBook>, int>改成AutoDisposeFamilyAsyncNotifier<VirtualBook, int> build和_loadBookDetails的返回类型从Future<AsyncValue<VirtualBook>>改成Future<VirtualBook>- 成功时直接返回业务数据,失败时直接抛出错误,不需要手动创建
AsyncValue对象
这样修改后,AsyncNotifier会正确地处理异步状态:加载时返回AsyncLoading,成功时返回AsyncData<VirtualBook>,失败时返回AsyncError,UI里的when方法就能正确触发对应的回调了。
内容的提问来源于stack exchange,提问作者Kedar Karki
相关产品推荐
相关产品推荐

