Stream 内的 Flutter ListView.Builder 不更新

Posted

技术标签:

【中文标题】Stream 内的 Flutter ListView.Builder 不更新【英文标题】:Flutter ListView.Builder inside Stream does not update 【发布时间】:2020-10-25 08:22:02 【问题描述】:

对不起,我的英语不好,我正在用颤振和 firebase 实时数据库做一个聊天应用程序。我正在尝试进行分页。首先,我加载最后 20 条聊天消息,当用户滚动到聊天顶部时,我想再加载 20 条消息。数据恢复正确,但列表视图没有更新。这是我的代码:

class _ChatState extends State<Chat> 
  TextEditingController messageEditingController = new TextEditingController();
  ScrollController _scrollController = ScrollController();
  bool _showKeyboard = true;
  Stream _chats;
  List item = [];

  var _firebaseRef = FirebaseDatabase().reference().child('chatRoom');

  sendMessage() 
    if (messageEditingController.text.isNotEmpty) 
      _firebaseRef.child(widget.chatRoomId).child("chats").push().set(
        "sendBy": widget.user.uid,
        "message": messageEditingController.text,
        "time": DateTime.now().millisecondsSinceEpoch
      );
    
    setState(() 
      messageEditingController.text = "";
    );
  

  @override
  void initState() 
    setState(() 
      _chats = _firebaseRef
          .child(widget.chatRoomId)
          .child("chats")
          .limitToLast(20)
          .onValue;
    );

    _scrollController.addListener(() 
      if (_scrollController.position.atEdge) 
        if (_scrollController.position.pixels == 0) 
         else 
          _requestMoreMessages();
        
      
    );

    super.initState();
  

  _requestMoreMessages() async 

    List _moreItems =[];

    _firebaseRef
        .child(widget.chatRoomId)
        .child("chats")
        .orderByChild("time")
        .endAt(item.elementAt(19)["time"])
        .limitToLast(20)
        .once()
        .then((snapshot) 
      Map<dynamic, dynamic> map = snapshot.value;
      map.forEach((key, data) 

        print(data["message"]);
        _moreItems.add(
          "key": key,
          "message": data['message'],
          "sendBy": data['sendBy'],
          "time": data['time']
        );
      );
      setState(() 
        item.addAll(_moreItems);
      );
    );
  

  Widget chatMessages() 

    return StreamBuilder(
      stream:_chats,
      builder: (context, snapshot) 
        if (snapshot.hasData &&
            !snapshot.hasError &&
            snapshot.data.snapshot.value != null) 
          Map data = snapshot.data.snapshot.value;
          item = [];
          data.forEach((index, data) => item.add(
                "key": index,
                "message": data['message'],
                "sendBy": data['sendBy'],
                "time": data['time']
              ));
          item.sort((b, a) => a["time"].compareTo(b["time"]));

          return ListView.builder(
              reverse: true,
              controller: _scrollController,
              itemCount: item.length,
              itemBuilder: (context, index) 
                return MessageTile(
                    message: item[index]['message'],
                    sendByMe: widget.user.uid == item[index]['sendBy'],
                    time: item[index]['time']);
              );
         else 
          return Container();
        
      ,
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
        appBar: appBarMain(context, widget.userName, widget.urlFoto, context),
        body: Column(
          children: <Widget>[
            Flexible(
              child: chatMessages(),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Container(
                      decoration: BoxDecoration(
                          border: Border.all(color: Colors.black26),
                          borderRadius: BorderRadius.circular(50),
                          color: Colors.black26),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          IconButton(
                            icon: !_showKeyboard == true
                                ? Icon(LineIcons.keyboard_o)
                                : Icon(LineIcons.smile_o),
                            onPressed: () 
                              if (MediaQuery.of(context).viewInsets.bottom ==
                                  0) 
                                SystemChannels.textInput
                                    .invokeMethod('TextInput.show');
                              
                              if (mounted) 
                                setState(() 
                                  _showKeyboard = !_showKeyboard;
                                );
                              
                            ,
                          ),
                          SizedBox(
                            width: 230,
                            child: TextField(
                              onTap: () 
                                if (mounted) 
                                  setState(() 
                                    _showKeyboard = true;
                                  );
                                
                              ,
                              controller: messageEditingController,
                              style: simpleTextStyle(),
                              textInputAction: TextInputAction.none,
                              decoration: InputDecoration(
                                hintText: "Escribe un mensaje",
                                hintStyle: TextStyle(
                                  color: Colors.black,
                                  fontSize: 16,
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    ClipOval(
                      child: Material(
                          color: Theme.of(context).primaryColorDark,
                          child: InkWell(
                            splashColor: Theme.of(context).accentColor,
                            child: IconButton(
                              color: Colors.white,
                              onPressed: () 
                                sendMessage();
                                //     addMessage();
                              ,
                              icon: Icon(LineIcons.paper_plane),
                            ),
                          )),
                    ),
                  ],
                ),
              ),
            )
          ],
        ));
  


【问题讨论】:

如果你想加载更多消息,我认为你应该使用 'startAfter' 而不是 'endAt'。 @konto211541 是云 Firestore 数据库的功能,但我使用的是实时数据库。我使用 'endAt' 获得了正确的数据,但我无法更新列表视图 @EduardoAndres 实时数据库和 Firestore 都有 startAtendAt 方法。我不确认您必须使用其他方法,但它确实存在于实时数据库 API 中。 【参考方案1】:

您正在获取数据并调用 setState(()),这将更新 UI。这也意味着您的StreamBuilder 将被重建,并且您正在使用 item = [];,这将在每次重建流构建器时清空列表。因此,您没有得到任何要显示的数据。

【讨论】:

感谢您的回答,我删除了“item = []”,现在我的 ListView 每次调用“_requestMoreMessages()”时都会复制初始数据,但不显示我正在检索的新数据从数据库中,我看不到我的错误:/ 您可以在 SteamBuilder 中移动 item.addAll(_moreItems);。这将做的是每次您的 StreammBuilder 重建时,它会将_moreItems 附加到项目列表中,这应该会有所帮助。【参考方案2】:

感谢 Shubham Gupta 的帮助,这是适合我的代码:

class _ChatState extends State<Chat> 
  TextEditingController messageEditingController = new TextEditingController();
  ScrollController _scrollController = ScrollController();
  int numeroDocumentos = 0;
  bool _showKeyboard = true;
  Stream _chats;
  List item = [];
  List _moreItems = [];

  bool primeraVez = true;

  var _firebaseRef = FirebaseDatabase().reference().child('chatRoom');

  sendMessage() 
    if (messageEditingController.text.isNotEmpty) 
      _firebaseRef.child(widget.chatRoomId).child("chats").push().set(
        "sendBy": widget.user.uid,
        "message": messageEditingController.text,
        "time": DateTime.now().millisecondsSinceEpoch
      );
    
    setState(() 
      messageEditingController.text = "";
    );
  

  @override
  void initState() 
    item = [];

    setState(() 
      _chats = _firebaseRef
          .child(widget.chatRoomId)
          .child("chats")
          .limitToLast(20)
          .onValue;
    );

    _scrollController.addListener(() 
      if (_scrollController.position.atEdge) 
        if (_scrollController.position.pixels == 0) 
         else 
          _requestMoreMessages();
        
      
    );

    super.initState();
  

  _requestMoreMessages() async 
    _firebaseRef
        .child(widget.chatRoomId)
        .child("chats")
        .orderByChild("time")
        .endAt(item.elementAt(numeroDocumentos - 1)["time"] - 1)
        .limitToLast(20)
        .once()
        .then((snapshot) 
      Map<dynamic, dynamic> map = snapshot.value;
      map.forEach((key, data) 
        print(data["message"]);
        _moreItems.add(
          "key": key,
          "message": data['message'],
          "sendBy": data['sendBy'],
          "time": data['time']
        );
      );
    ).then((value) 
      setState(() );
    );
  

  Widget chatMessages() 
    return StreamBuilder(
      stream: _chats,
      builder: (context, snapshot) 
        if (snapshot.hasData &&
            !snapshot.hasError &&
            snapshot.data.snapshot.value != null) 
          item = [];

          Map data = snapshot.data.snapshot.value;
          data.forEach((index, data) => item.add(
                "key": index,
                "message": data['message'],
                "sendBy": data['sendBy'],
                "time": data['time']
              ));

          item = List.from(item)..addAll(_moreItems);
          numeroDocumentos = item.length;
          item.sort((b, a) => a["time"].compareTo(b["time"]));
          return ListView.builder(
              reverse: true,
              controller: _scrollController,
              itemCount: item.length,
              itemBuilder: (context, index) 
                return MessageTile(
                    message: item[index]['message'],
                    sendByMe: widget.user.uid == item[index]['sendBy'],
                    time: item[index]['time']);
              );
         else 
          return Container();
        
      ,
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
        appBar: appBarMain(context, widget.userName, widget.urlFoto, context),
        body: Column(
          children: <Widget>[
            Flexible(
              child: chatMessages(),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Container(
                      decoration: BoxDecoration(
                          border: Border.all(color: Colors.black26),
                          borderRadius: BorderRadius.circular(50),
                          color: Colors.black26),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          IconButton(
                            icon: !_showKeyboard == true
                                ? Icon(LineIcons.keyboard_o)
                                : Icon(LineIcons.smile_o),
                            onPressed: () 
                              if (MediaQuery
                                  .of(context)
                                  .viewInsets
                                  .bottom ==
                                  0) 
                                SystemChannels.textInput
                                    .invokeMethod('TextInput.show');
                              
                              if (mounted) 
                                setState(() 
                                  _showKeyboard = !_showKeyboard;
                                );
                              
                            ,
                          ),
                          SizedBox(
                            width: 230,
                            child: TextField(
                              onTap: () 
                                if (mounted) 
                                  setState(() 
                                    _showKeyboard = true;
                                  );
                                
                              ,
                              controller: messageEditingController,
                              style: simpleTextStyle(),
                              textInputAction: TextInputAction.none,
                              decoration: InputDecoration(
                                border: InputBorder.none,
                                focusedBorder: InputBorder.none,
                                enabledBorder: InputBorder.none,
                                errorBorder: InputBorder.none,
                                disabledBorder: InputBorder.none,
                                hintText: "Escribe un mensaje",
                                hintStyle: TextStyle(
                                  color: Colors.black,
                                  fontSize: 16,
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    ClipOval(
                      child: Material(
                          color: Theme
                              .of(context)
                              .primaryColorDark,
                          child: InkWell(
                            splashColor: Theme
                                .of(context)
                                .accentColor,
                            child: IconButton(
                              color: Colors.white,
                              onPressed: () 
                                sendMessage();
                                //     addMessage();
                              ,
                              icon: Icon(LineIcons.paper_plane),
                            ),
                          )),
                    ),
                  ],
                ),
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: _showKeyboard == false ? buildSticker() : Container(),
            ),
          ],
        ));
  

  Widget buildSticker() 
    SystemChannels.textInput.invokeMethod('TextInput.hide');

    return EmojiPicker(
      rows: 4,
      columns: 8,
      buttonMode: ButtonMode.MATERIAL,
      numRecommended: 21,
      onEmojiSelected: (emoji, category) 
        messageEditingController.text =
            messageEditingController.text + emoji.emoji;

        messageEditingController.selection = TextSelection.fromPosition(
            TextPosition(offset: messageEditingController.text.length));
      ,
    );
  

【讨论】:

以上是关于Stream 内的 Flutter ListView.Builder 不更新的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 专题82 初识 Flutter Stream #yyds干货盘点#

Flutter 中的 Sink 和 Stream 有啥区别?

Flutter状态管理——单Stream和广播Stream

Flutter状态管理——单Stream和广播Stream

Flutter状态管理——单Stream和广播Stream

Flutter中如何返回刷新上一页?