防止 Flutter StreamBuilder 在 ListView 中复制数据

Posted

技术标签:

【中文标题】防止 Flutter StreamBuilder 在 ListView 中复制数据【英文标题】:Preventing Flutter StreamBuilder from Duplicating Data in ListView 【发布时间】:2020-04-29 13:06:39 【问题描述】:

我遇到了 StreamBuilder 和 ListView 的问题。

我的初始构建按预期工作,从我的数据库加载所有节点并将它们添加到 ListView:

--节点1 --节点2 --节点3

但是,当向 DB(节点 4)添加新节点时,StreamBuilder 会识别更改并将整个节点列表附加到 ListView,从而导致重复数据:

--节点1 --节点2 --节点 3 --节点1 --节点2 --节点 3 --节点4


class _HomeScreenState extends State<HomeScreen> 
  DatabaseReference usersChatsRef =
      FirebaseDatabase().reference().child('users-chats');

  @override
  void initState() 
    super.initState();

    Stream<List<UsersChats>> getData(User currentUser) async* 
      var usersChatsStream = usersChatsRef.child(currentUser.uid).onValue;
      var foundChats = List<UsersChats>();

      await for (var userChatSnapshot in usersChatsStream) 
        Map dictionary = userChatSnapshot.snapshot.value;
        if (dictionary != null) 
          for (var dictItem in dictionary.entries) 
            UsersChats thisChat;
            if (dictItem.key != null) 
              thisChat = UsersChats.fromMap(dictItem);
             else 
              thisChat = UsersChats();
            
            foundChats.add(thisChat);
          
        

        yield foundChats;
      
    
  

  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);

    return Scaffold(
      appBar: AppBar(),
      body: StreamBuilder<List<UsersChats>>(
          stream: getData(user),
          builder:
              (BuildContext context, AsyncSnapshot<List<UsersChats>> snap) 
            if (snap.hasError || !snap.hasData)
              return Text('Error: $snap.error');
            switch (snap.connectionState) 
              case ConnectionState.waiting:
                return Text("Loading...");
              default:
                return ListView.builder(
                  itemCount: snap.data.length,
                  itemBuilder: (context, index) 
                    return ListTile(
                      title: Text(snap.data[index].id),
                      subtitle: Text(snap.data[index].lastUpdate),
                    );
                  ,
                );
            
          ),
    );
  

class UsersChats 
  String id;
  String lastUpdate;

  UsersChats(
      this.id,
      this.lastUpdate);

  factory UsersChats.fromMap(MapEntry<dynamic, dynamic> data) 
    return UsersChats(
        id: data.key ?? '',
        lastUpdate: data.value['lastUpdate'] ?? '');
  

我在 build 方法之外引用流,因为我需要在流上执行多个异步函数(如本线程 How do I join data from two Firestore collections in Flutter? 中所述)。

任何帮助将不胜感激!

【问题讨论】:

听起来usersChatsRef.child(currentUser.uid).onValue 在每次更改时都会发出整个列表,而不是只推送差异。这意味着您不必“添加”到foundChat,只需删除之前的列表即可。 感谢您的快速回复!你知道我会如何修改getData 函数吗?一直在尝试不同的方法,但到目前为止没有任何运气。 尝试在“等待”的第一行清除foundChats 太棒了!做到了,非常感谢!!! 剩下的唯一问题是,当我使用Navigator.push 并导航离开然后返回主屏幕时,ListView 的数据消失了。也许需要 StreamSubscription(正如您在此处概述的那样:***.com/questions/54101589/…)?我正在努力添加这个,如果成功我会发布我的结果。 【参考方案1】:

发布工作代码。再次感谢雷米的提示!希望此代码对其他人有所帮助!

class _HomeScreenState extends State<HomeScreen> 
  DatabaseReference usersChatsRef =
      FirebaseDatabase().reference().child('users-chats');

  List<UsersChats> myChats = [];

  StreamSubscription _streamSubscription;

  @override
  void initState() 
    super.initState();

    _streamSubscription = getData().listen((data) 

      setState(() 
        myChats = data;
      );
    );

  

  @override
  void dispose() 
    _streamSubscription?.cancel(); // don't forget to close subscription
    super.dispose();
  


  Stream<List<UsersChats>> getData() async* 
    var usersChatsStream = usersChatsRef.child('userId').onValue;
    var foundChats = List<UsersChats>();

    await for (var userChatSnapshot in usersChatsStream) 
      foundChats.clear();
      Map dictionary = userChatSnapshot.snapshot.value;
      if (dictionary != null) 
        for (var dictItem in dictionary.entries) 
          UsersChats thisChat;
          if (dictItem.key != null) 
            thisChat = UsersChats.fromMap(dictItem);
           else 
            thisChat = UsersChats();
          
          foundChats.add(thisChat);
        
      

      yield foundChats;
    
  

  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);

    return Scaffold(
      appBar: AppBar(),
      body: ListView.builder(
        itemCount: myChats.length,
        itemBuilder: (context, index) 
            title: Text(myChats[index].id),
            subtitle: Text(myChats[index].lastUpdate),
            onTap: () 
              Navigator.pushNamed(context, MessagesScreen.id);
            ,
          );
        ,
      ),
    );
  



【讨论】:

以上是关于防止 Flutter StreamBuilder 在 ListView 中复制数据的主要内容,如果未能解决你的问题,请参考以下文章

来自一个 StreamBuilder 的 Flutter 快照显示在另一个 StreamBuilder 中

Flutter:Streambuilder - 关闭流

Flutter:StreamBuilder 快照——无数据

Flutter Streambuilder 正在复制项目

无法从 StreamBuilder (Flutter) 查询子集合

在页面 flutter,streambuilder,listview 之间导航时列表视图位置丢失