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

Flutter FutureBuilder数据混淆:搜索结果错误加载帖子数据

Search组件FutureBuilder数据混淆问题修复

问题说明

开发的Search状态组件默认通过FutureBuilder从FirebaseFirestore的postsCollection查询并展示用户帖子;当用户在搜索栏提交内容后,调用setState将displayResults设为true,切换到另一个FutureBuilder从userCollection查询并展示用户搜索结果。但出现异常:展示搜索结果的FutureBuilder获取到了展示帖子的FutureBuilder的快照数据,本该解析为User类型的数据实际是Post类型。

问题原因

  1. FutureBuilder快照缓存机制:Flutter会缓存Future的执行结果,切换显示的FutureBuilder时,若新Future尚未完成,会暂时复用之前的快照数据,导致类型不匹配。
  2. build方法内创建Future:直接在build方法中初始化Future,每次组件build时都会重新创建Future,既浪费资源,又加剧快照复用问题。

解决方案

  1. 将Future实例存储在State类变量中,而非build方法内,确保状态切换时Future与对应逻辑绑定。
  2. 给两个FutureBuilder设置唯一Key,让Flutter区分不同组件,避免快照复用。
  3. 仅在用户提交搜索时初始化搜索用户的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:26:59