Flutter中使用Appwrite和Riverpod获取评论时遇Bad State: No Element错误
问题描述
Flutter项目结合Appwrite与Riverpod开发时,点击查看帖子评论频繁触发Bad State: No Element错误,热重载后可能暂时消失,但后续仍会复现。
相关代码片段:
ref.watch(getRepliesToPostProvider(widget.postModel!)).when( data: (post) { return ref.watch(getLatestPostProvider).when( data: (data) { if (data.events.contains( 'databases.*.collections.${Common.postCollection}.documents.*.create', )) { post.insert(0, PostModel.fromMap(data.payload)); } else if (data.events.contains( 'databases.*.collections.${Common.postCollection}.documents.*.update', )) { final startingIndex = data.events[0].lastIndexOf('documents.'); final endIndex = data.events[0].lastIndexOf('.update'); var postId = data.events[0] .substring(startingIndex + 10, endIndex); var newPost = post .where((element) => element.id == postId) .first; final postIndex = post.indexOf(newPost); post.removeWhere((element) => element.id == postId); newPost = PostModel.fromMap(data.payload); post.insert(postIndex, newPost); } return Expanded( child: ListView.builder( padding: const EdgeInsets.all(0), shrinkWrap: true, scrollDirection: Axis.vertical, physics: const ClampingScrollPhysics(), itemCount: post.length, itemBuilder: (BuildContext context, int index) { var disPost = post[index]; final latestPost = PostModel.fromMap(data.payload); bool isPostAlreadyPresent = false; for (final postLoop in post) { if (postLoop.id == latestPost.id) { isPostAlreadyPresent = true; break; } } return ref .watch(userDetailsProvider( widget.postModel!.uid)) .when( data: (user) { return Padding( padding: const EdgeInsets.only( left: 8.0, top: 2, right: 8, bottom: 2, ), child: Row( children: [ CircularProfileAvatar( user.profile, radius: 20, backgroundColor: Colors.transparent, borderWidth: 2, initialsText: Text( "AD", style: TextStyle( color: colorWhite), ), borderColor: colorWhite, elevation: 5.0, foregroundColor: Colors.brown .withOpacity(0.5), cacheImage: true, imageFit: BoxFit.cover, onTap: () { print('adil'); }, showInitialTextAbovePicture: false, ), const SizedBox( width: 15, ), Expanded( child: Text(disPost.text), ), ], ), ); }, error: (error, stk) => Container(), loading: () => Container()); }, ), ); }, error: (error, stk) => ErrorText( error: error.toString(), ), loading: () { return Expanded( child: ListView.builder( padding: const EdgeInsets.all(0), shrinkWrap: true, scrollDirection: Axis.vertical, physics: const ClampingScrollPhysics(), itemCount: post.length, itemBuilder: (BuildContext context, int index) { var disPost = post[index]; return ref .watch(userDetailsProvider( widget.postModel!.uid)) .when( data: (user) { return Padding( padding: const EdgeInsets.only( left: 8.0, top: 2, right: 8, bottom: 2, ), child: Row( children: [ CircularProfileAvatar( user.profile, radius: 20, backgroundColor: Colors.transparent, borderWidth: 2, initialsText: Text( "AD", style: TextStyle( color: colorWhite), ), borderColor: colorWhite, elevation: 5.0, foregroundColor: Colors .brown .withOpacity(0.5), cacheImage: true, imageFit: BoxFit.cover, onTap: () { print('adil'); }, showInitialTextAbovePicture: false, ), const SizedBox( width: 15, ), Expanded( child: Text(disPost.text), ), ], ), ); }, error: (error, stk) => Container(), loading: () => Container()); }), ); }); }, error: (error, stk) => ErrorText( error: error.toString(), ), loading: () => const Loader(), ),
错误截图:


问题根源
错误直接出在更新事件处理逻辑中:
var newPost = post.where((element) => element.id == postId).first;
当Appwrite推送更新事件时,本地评论列表post中可能还不存在对应postId的元素,调用.first会直接抛出No Element异常。此外还有两处潜在问题:
- 直接修改
ref.watch返回的列表对象,违反Riverpod状态不可变的设计规范 - UI层重复构建相同加载状态代码,且存在冗余的帖子存在判断逻辑
修复方案
1. 处理.first的空安全问题
将.first替换为带默认值的.firstWhere,避免找不到元素时抛出异常:
// 替换原有的newPost获取逻辑 final targetPost = post.firstWhere( (element) => element.id == postId, orElse: () => PostModel(id: "", text: "", uid: ""), // 定义空PostModel作为默认值 ); // 确认找到有效元素后再执行更新 if (targetPost.id.isNotEmpty) { final postIndex = post.indexOf(targetPost); post.removeWhere((element) => element.id == postId); final updatedPost = PostModel.fromMap(data.payload); post.insert(postIndex, updatedPost); }
2. 移到Provider内部处理状态更新
Riverpod的状态更新应该在Provider内部完成,避免UI层直接修改状态:
// 示例StateNotifierProvider结构 final getRepliesToPostProvider = StateNotifierProvider.family<RepliesNotifier, List<PostModel>, PostModel>((ref, postModel) { return RepliesNotifier(ref, postModel); }); class RepliesNotifier extends StateNotifier<List<PostModel>> { final Ref ref; final PostModel postModel; RepliesNotifier(this.ref, this.postModel) : super([]) { fetchReplies(); listenToUpdates(); } // 从Appwrite获取评论列表 void fetchReplies() async { final replies = await // 你的Appwrite请求逻辑 state = replies; } // 监听Appwrite实时事件 void listenToUpdates() { ref.listen(getLatestPostProvider, (previous, next) { if (next.events.contains('databases.*.collections.${Common.postCollection}.documents.*.update')) { final startingIndex = next.events[0].lastIndexOf('documents.'); final endIndex = next.events[0].lastIndexOf('.update'); final postId = next.events[0].substring(startingIndex + 10, endIndex); // 通过生成新列表更新状态 state = state.map((post) { return post.id == postId ? PostModel.fromMap(next.payload) : post; }).toList(); } else if (next.events.contains('databases.*.collections.${Common.postCollection}.documents.*.create')) { state = [PostModel.fromMap(next.payload), ...state]; } }); } }
3. 简化UI层逻辑
抽离评论项Widget,移除冗余代码:
// 抽离评论项组件 class CommentItem extends StatelessWidget { final PostModel post; final String uid; const CommentItem({super.key, required this.post, required this.uid}); @override Widget build(BuildContext context) { return ref.watch(userDetailsProvider(uid)).when( data: (user) { return Padding( padding: const EdgeInsets.only(left: 8.0, top: 2, right: 8, bottom: 2), child: Row( children: [ CircularProfileAvatar( user.profile, radius: 20, backgroundColor: Colors.transparent, borderWidth: 2, initialsText: const Text("AD", style: TextStyle(color: Colors.white)), borderColor: Colors.white, elevation: 5.0, foregroundColor: Colors.brown.withOpacity(0.5), cacheImage: true, imageFit: BoxFit.cover, onTap: () => print('adil'), showInitialTextAbovePicture: false, ), const SizedBox(width: 15), Expanded(child: Text(post.text)), ], ), ); }, error: (_, __) => const SizedBox.shrink(), loading: () => const SizedBox.shrink(), ); } } // 主UI部分 ref.watch(getRepliesToPostProvider(widget.postModel!)).when( data: (posts) { return Expanded( child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, scrollDirection: Axis.vertical, physics: const ClampingScrollPhysics(), itemCount: posts.length, itemBuilder: (_, index) => CommentItem( post: posts[index], uid: widget.postModel!.uid, ), ), ); }, error: (error, stk) => ErrorText(error: error.toString()), loading: () => const Loader(), );
内容的提问来源于stack exchange,提问作者Zionnite
相关产品推荐
相关产品推荐

