Flutter FutureBuilder数据混淆:搜索结果错误加载帖子数据
Search组件FutureBuilder数据混淆问题修复
问题说明
开发的Search状态组件默认通过FutureBuilder从FirebaseFirestore的postsCollection查询并展示用户帖子;当用户在搜索栏提交内容后,调用setState将displayResults设为true,切换到另一个FutureBuilder从userCollection查询并展示用户搜索结果。但出现异常:展示搜索结果的FutureBuilder获取到了展示帖子的FutureBuilder的快照数据,本该解析为User类型的数据实际是Post类型。
问题原因
- FutureBuilder快照缓存机制:Flutter会缓存Future的执行结果,切换显示的FutureBuilder时,若新Future尚未完成,会暂时复用之前的快照数据,导致类型不匹配。
- build方法内创建Future:直接在build方法中初始化Future,每次组件build时都会重新创建Future,既浪费资源,又加剧快照复用问题。
解决方案
- 将Future实例存储在State类变量中,而非build方法内,确保状态切换时Future与对应逻辑绑定。
- 给两个FutureBuilder设置唯一
Key,让Flutter区分不同组件,避免快照复用。 - 仅在用户提交搜索时初始化搜索用户的Future,避免不必要的查询。
修改后的代码
Search组件代码
class Search extends StatefulWidget { const Search({super.key}); @override State<Search> createState() => _SearchState(); } class _SearchState extends State<Search> { final TextEditingController searchController = TextEditingController(); bool displayResults = false; // 存储两个Future实例 Future<QuerySnapshot<Map<String, dynamic>>>? _postsFuture; Future<QuerySnapshot<Map<String, dynamic>>>? _searchUsersFuture; @override void initState() { super.initState(); // 初始化帖子查询Future _postsFuture = FirebaseFirestore.instance .collection(postsCollection) .orderBy('datePublished') .limit(20) .get(); } @override void dispose() { searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: mobileBackgroundColor, title: TextFormField( decoration: const InputDecoration( labelText: '搜索用户', ), controller: searchController, onFieldSubmitted: (String _) { setState(() { displayResults = true; // 提交搜索时初始化用户查询Future _searchUsersFuture = FirebaseFirestore.instance .collection(userCollection) .where( 'username', isGreaterThanOrEqualTo: searchController.text, ) .get(); }); }, ), centerTitle: false, ), body: displayResults ? FutureBuilder( key: const Key('search_users_future'), future: _searchUsersFuture, builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snap) { if (!snap.hasData) { return const Center( child: CircularProgressIndicator(), ); } return ListView.builder( itemCount: snap.data!.docs.length, itemBuilder: (context, index) { model.User user = model.User.fromSnap(snap.data!.docs[index]); return ListTile( leading: CircleAvatar( backgroundImage: NetworkImage( user.getProfilePicture(), ), ), title: Text(user.username), ); }, ); }, ) : FutureBuilder( key: const Key('posts_future'), future: _postsFuture, builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) { if (!snapshot.hasData) { return const Center( child: CircularProgressIndicator(), ); } return GridView.custom( gridDelegate: SliverQuiltedGridDelegate( crossAxisCount: 4, mainAxisSpacing: 2, crossAxisSpacing: 2, repeatPattern: QuiltedGridRepeatPattern.inverted, pattern: [ const QuiltedGridTile(3, 2), const QuiltedGridTile(1, 1), const QuiltedGridTile(1, 1), const QuiltedGridTile(2, 2), ], ), childrenDelegate: SliverChildBuilderDelegate( (context, index) => Image.network( snapshot.data!.docs[index]['imageUrl'], ), childCount: snapshot.data!.docs.length, ), ); }, ), ); } }
User类型定义
class User { final String username; final String email; final String profilePicture; final String uid; final String bio; final List followers; final List following; const User( {required this.username, required this.uid, required this.profilePicture, required this.bio, required this.email, required this.followers, required this.following}); Map<String, dynamic> toJson() => { "username": username, "uid": uid, "email": email, "profilePicture": profilePicture, "bio": bio, "followers": followers, "following": following, }; static User fromSnap(DocumentSnapshot snapshot) { var snap = snapshot.data() as Map<String, dynamic>; return User( username: snap['username'], uid: snap['uid'], profilePicture: snap['profilePicture'], bio: snap['bio'], email: snap['email'], followers: snap['followers'], following: snap['following']); } String getProfilePicture() { if (profilePicture.isEmpty) { return defaulProfilePicture; } else { return profilePicture; } } }
Post类型定义
class Post { final String uid; final String postId; final String imageUrl; final String description; final DateTime datePublished; final List likes; final int likeCount; const Post({ required this.uid, required this.description, required this.imageUrl, required this.datePublished, required this.likes, required this.postId, required this.likeCount, }); Map<String, dynamic> toJson() => { "uid": uid, "description": description, "imageUrl": imageUrl, "datePublished": datePublished, "likes": likes, "likeCount": likeCount, "postId": postId, }; static Post fromSnap(DocumentSnapshot snapshot) { var snap = snapshot.data() as Map<String, dynamic>; return Post( uid: snap['uid'], description: snap['description'], imageUrl: snap['imageUrl'], datePublished: snap['datePublished'], likes: snap['likes'], likeCount: snap['likeCount'], postId: snap['postId'], ); } }
内容的提问来源于stack exchange,提问作者jhonny
相关产品推荐
相关产品推荐

