首页 > 解决方案 > Stream 内的 Flutter ListView.Builder 不更新

问题描述

抱歉我的英语不好,我正在用颤振和 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),
                            ),
                          )),
                    ),
                  ],
                ),
              ),
            )
          ],
        ));
  }

}

标签: firebaseflutterlistviewfirebase-realtime-database

解决方案


感谢 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));
      },
    );
  }
}

推荐阅读