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

Firebase实现关注/取消关注功能时按钮状态不更新问题求助

问题原因与解决方案

问题分析

  1. UI状态依赖缓存数据:按钮的isFollowing状态判断依赖allUserProvider.currentUser,但该对象未同步Firestore的最新更新,导致状态判断始终是旧值。
  2. notifyListeners()调用时机错误:在Firebase事务内部调用notifyListeners(),此时事务尚未提交到Firestore,数据未真正更新,触发的UI重建无法获取最新状态。
  3. StreamBuilder未关联当前用户最新数据:StreamBuilder监听整个用户集合,但判断关注状态时未使用流中当前用户的实时数据,而是依赖provider缓存。

解决方案

方案1:在StreamBuilder中直接使用当前用户实时数据判断状态

修改UI代码,从Stream的快照中获取当前用户的最新following列表,替代provider缓存的判断逻辑:

StreamBuilder(
  stream: FirebaseFirestore.instance.collection('users_info').snapshots(),
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return Center(child: Text(snapshot.error.toString()));
    }
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(child: CircularProgressIndicator());
    }
    
    // 从快照中获取当前用户的最新数据
    final currentUserDoc = snapshot.data!.docs.firstWhere(
      (doc) => doc.id == allUserProvider.currentUser?.uid,
      orElse: () => throw Exception("Current user not found"),
    );
    final currentUserData = UserData.fromSnapshot(currentUserDoc.data(), currentUserDoc.id);
    
    final List<UserData> allUsers = snapshot.data!.docs
        .map((doc) => UserData.fromSnapshot(doc.data(), doc.id))
        .toList();
    
    return ListView.builder(
      shrinkWrap: true,
      itemCount: allUsers.length,
      itemBuilder: (context, index){
        UserData user = allUsers[index];
        bool isCurrentUser = user.uid == currentUserData.uid;
        
        // 使用当前用户实时的following列表判断状态
        final bool isFollowing = currentUserData.following?.contains(user.uid ?? '') ?? false;
        
        if(user.uid == currentUserId){
          return Container();
        }
        
        return ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(user.image ?? ''),
          ),
          title: Text(
            user.name ?? '',
            style: const TextStyle(
              fontFamily: GlobalVariables.fontsPally,
              fontWeight: FontWeight.bold,
            ),
          ),
          subtitle: Text(
            user.uid ?? 'no id',
            style: const TextStyle(
              color: Color(0xFFC1C0C9),
              fontFamily: GlobalVariables.fontsSatoshi,
            ),
          ),
          trailing: !isCurrentUser ?
              ElevatedButton(
                onPressed: (){
                  print('${userId} and ${user.uid}');
                  allUserProvider.followUser(user.uid!);
                },
                child: Text(isFollowing ? 'UnFollow' : 'Follow' ),
              ) : null
        );
      }
    );
  }
)

方案2:修正Provider的followUser方法,同步更新currentUser

调整followUser方法,在事务提交后重新获取当前用户的最新数据,更新provider的currentUser后再通知UI更新:

Future<void> followUser(String targetUserId) async {
  final DocumentReference userDoc =
      FirebaseFirestore.instance.collection('users_info').doc(_userId);
  final DocumentReference targetUserDoc =
      FirebaseFirestore.instance.collection('users_info').doc(targetUserId);

  // 执行Firebase事务更新数据
  await FirebaseFirestore.instance.runTransaction((transaction) async {
    final userSnapshot = await transaction.get(userDoc);
    final targetUserSnapshot = await transaction.get(targetUserDoc);

    if (!userSnapshot.exists || !targetUserSnapshot.exists) {
      throw Exception('Document does not exist!');
    }

    final Map<String, dynamic> userData = userSnapshot.data() as Map<String, dynamic>;
    final Map<String, dynamic> targetUserData =
        targetUserSnapshot.data() as Map<String, dynamic>;

    List<String> following = List<String>.from(userData['following']);
    List<String> followers = List<String>.from(targetUserData['followers']);

    if (!following.contains(targetUserId)) {
      following.add(targetUserId);
      userData['followingCount'] = following.length.clamp(0, 1000000);
      targetUserData['followerCount'] = (followers.length + 1).clamp(0, 1000000);
      followers.add(_userId!);
    } else {
      following.remove(targetUserId);
      userData['followingCount'] = following.length.clamp(0, 1000000);
      targetUserData['followerCount'] = (followers.length - 1).clamp(0, 1000000);
      followers.remove(_userId);
    }

    if (userData['followingCount'] < 0 || targetUserData['followerCount'] < 0) {
      throw Exception('Invalid follow/unfollow operation!');
    }

    transaction.update(userDoc, {'following': following, 'followingCount': userData['followingCount']});
    transaction.update(targetUserDoc, {'followers': followers, 'followerCount': targetUserData['followerCount']});
  });

  // 事务完成后,获取当前用户最新数据并更新provider
  final updatedUserSnapshot = await userDoc.get();
  if (updatedUserSnapshot.exists) {
    currentUser = UserData.fromSnapshot(updatedUserSnapshot.data() as Map<String, dynamic>, updatedUserSnapshot.id);
  }

  // 通知UI更新状态
  notifyListeners();
}

额外优化建议

  • 避免监听整个users_info集合,改为合并当前用户和目标用户的文档流,减少不必要的UI重建:
    Stream<List<DocumentSnapshot>> combineUserStreams() {
      final currentUserStream = FirebaseFirestore.instance.collection('users_info').doc(_userId).snapshots();
      final allUsersStream = FirebaseFirestore.instance.collection('users_info').snapshots();
      return StreamZip([currentUserStream, allUsersStream]).map((data) => [data[0], ...data[1].docs]);
    }
    
  • 检查UserData模型的fromSnapshot方法,确保following字段解析正确,避免空指针或数据解析错误。

内容的提问来源于stack exchange,提问作者Rashid Ali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 10:45:06