StreamBuilder Firestore分页

时间:2019-01-03 11:09:17

标签: firebase pagination dart flutter google-cloud-firestore

我是扑朔迷离的新手,并且尝试通过streambuilder滚动到顶部时对聊天进行分页。问题是:当我在scrollListener中进行查询时,streambuilder在scrollListener上方优先进行其查询,并返回旧的响应。有什么办法吗?我在这里有什么选择?谢谢!

ChatScreenState类

在initState中,我创建滚动侦听器。

  @override
void initState() {
 listScrollController = ScrollController();
 listScrollController.addListener(_scrollListener);
 super.initState();
}

在这里,我创建的StreamBuilder的查询限于20条最后一条消息。使用_messagesSnapshots作为全局列表。

@override
Widget build(BuildContext context) {
 return Scaffold(
    key: key,
    appBar: AppBar(title: Text("Chat")),
    body: Container(
      child: Column(
        children: <Widget>[
          Flexible(
              child: StreamBuilder<QuerySnapshot>(
            stream: Firestore.instance
                .collection('messages')
                .where('room_id', isEqualTo: _roomID)
                .orderBy('timestamp', descending: true)
                .limit(20)
                .snapshots(),
            builder: (context, snapshot) {
              if (!snapshot.hasData) return LinearProgressIndicator();
              _messagesSnapshots = snapshot.data.documents;

              return _buildList(context, _messagesSnapshots);
            },
          )),
          Divider(height: 1.0),
          Container(
            decoration: BoxDecoration(color: Theme.of(context).cardColor),
            child: _buildTextComposer(),
          ),
        ],
      ),
    ));
}

Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
 _messagesSnapshots = snapshot;

 return ListView.builder(
   controller: listScrollController,
   itemCount: _messagesSnapshots.length,
   reverse: true,
   itemBuilder: (context, index) {
     return _buildListItem(context, _messagesSnapshots[index]);
   },
 );
}

然后在_scollListener方法中查询接下来的20条消息,并将结果添加到“全局”列表中。

  _scrollListener() {

   // If reach top 
   if (listScrollController.offset >=
        listScrollController.position.maxScrollExtent &&
    !listScrollController.position.outOfRange) {

   // Then search last message
   final message = Message.fromSnapshot(
      _messagesSnapshots[_messagesSnapshots.length - 1]);

   // And get the next 20 messages from database
   Firestore.instance
      .collection('messages')
      .where('room_id', isEqualTo: _roomID)
      .where('timestamp', isLessThan: message.timestamp)
      .orderBy('timestamp', descending: true)
      .limit(20)
      .getDocuments()
      .then((snapshot) {

    // To save in the global list
    setState(() {
      _messagesSnapshots.addAll(snapshot.documents);
    });
  });

  // debug snackbar
  key.currentState.showSnackBar(new SnackBar(
    content: new Text("Top Reached"),
  ));
 }
}

3 个答案:

答案 0 :(得分:0)

首先,我怀疑这样的API是否适合带有实时数据的聊天应用程序的后端-分页的API更适合静态内容。 例如,如果在加载“第1页”之后添加了30条消息,“第2页”究竟指的是什么? 另外,请注意, Firebase会按文档对Firestore请求收取费用,因此,每次请求两次的邮件都会损害您的配额和钱包

如您所见,具有固定页面长度的分页API可能不合适。因此,我强烈建议您宁愿请求在特定时间间隔内发送的消息。 Firestore请求可能包含以下代码:

.where("time", ">", lastCheck).where("time", "<=", DateTime.now())

无论哪种方式,这里的my answer to a similar question about paginated APIs in Flutter都包含一个实际实现的代码,该实现在ListView滚动时加载新内容。

答案 1 :(得分:0)

我要发布我的代码,希望有人发布更好的解决方案,可能不是最好的解决方案,但是它可以工作。

在我的应用中,实际的解决方案是在到达顶部时更改列表的状态,停止播放流并显示旧消息。

所有代码(州)

class _MessageListState extends State<MessageList> {
  List<DocumentSnapshot> _messagesSnapshots;
  bool _isLoading = false;

  final TextEditingController _textController = TextEditingController();
  ScrollController listScrollController;
  Message lastMessage;
  Room room;

  @override
  void initState() {
    listScrollController = ScrollController();
    listScrollController.addListener(_scrollListener);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    room = widget.room;
    return Flexible(
      child: StreamBuilder<QuerySnapshot>(
        stream: _isLoading
            ? null
            : Firestore.instance
                .collection('rooms')
                .document(room.id)
                .collection('messages')
                .orderBy('timestamp', descending: true)
                .limit(20)
                .snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) return LinearProgressIndicator();
          _messagesSnapshots = snapshot.data.documents;
          return _buildList(context, _messagesSnapshots);
        },
      ),
    );
  }

  Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
    _messagesSnapshots = snapshot;

    if (snapshot.isNotEmpty) lastMessage = Message.fromSnapshot(snapshot[0]);

    return ListView.builder(
      padding: EdgeInsets.all(10),
      controller: listScrollController,
      itemCount: _messagesSnapshots.length,
      reverse: true,
      itemBuilder: (context, index) {
        return _buildListItem(context, _messagesSnapshots[index]);
      },
    );
  }

  Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
    final message = Message.fromSnapshot(data);
    Widget chatMessage = message.sender != widget.me.id
        ? Bubble(
            message: message,
            isMe: false,
          )
        : Bubble(
            message: message,
            isMe: true,
          );
    return Column(
      children: <Widget>[chatMessage],
    );
  }

  loadToTrue() {
    _isLoading = true;
    Firestore.instance
        .collection('messages')
        .reference()
        .where('room_id', isEqualTo: widget.room.id)
        .orderBy('timestamp', descending: true)
        .limit(1)
        .snapshots()
        .listen((onData) {
      print("Something change");
      if (onData.documents[0] != null) {
        Message result = Message.fromSnapshot(onData.documents[0]);
        // Here i check if last array message is the last of the FireStore DB
        int equal = lastMessage?.compareTo(result) ?? 1;
        if (equal != 0) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    });
  }

  _scrollListener() {
    // if _scroll reach top 
    if (listScrollController.offset >=
            listScrollController.position.maxScrollExtent &&
        !listScrollController.position.outOfRange) {
      final message = Message.fromSnapshot(
          _messagesSnapshots[_messagesSnapshots.length - 1]);
      // Query old messages
      Firestore.instance
          .collection('rooms')
          .document(widget.room.id)
          .collection('messages')
          .where('timestamp', isLessThan: message.timestamp)
          .orderBy('timestamp', descending: true)
          .limit(20)
          .getDocuments()
          .then((snapshot) {
        setState(() {
          loadToTrue();
          // And add to the list
          _messagesSnapshots.addAll(snapshot.documents);
        });
      });
      // For debug purposes
//      key.currentState.showSnackBar(new SnackBar(
//        content: new Text("Top reached"),
//      ));
    }
  }
}

最重要的方法是:

_scrollListener

到达顶部时,我查询旧消息,并在setState中将 isLoading var设置为true,并使用旧消息设置要显示的数组。

  _scrollListener() {
    // if _scroll reach top
    if (listScrollController.offset >=
            listScrollController.position.maxScrollExtent &&
        !listScrollController.position.outOfRange) {
      final message = Message.fromSnapshot(
          _messagesSnapshots[_messagesSnapshots.length - 1]);
      // Query old messages
      Firestore.instance
          .collection('rooms')
          .document(widget.room.id)
          .collection('messages')
          .where('timestamp', isLessThan: message.timestamp)
          .orderBy('timestamp', descending: true)
          .limit(20)
          .getDocuments()
          .then((snapshot) {
        setState(() {
          loadToTrue();
          // And add to the list
          _messagesSnapshots.addAll(snapshot.documents);
        });
      });
      // For debug purposes
//      key.currentState.showSnackBar(new SnackBar(
//        content: new Text("Top reached"),
//      ));
    }
  }

和loadToTrue在我们寻找旧消息时监听。如果有新消息,我们将重新激活流。

loadToTrue

  loadToTrue() {
    _isLoading = true;
    Firestore.instance
        .collection('rooms')
        .document(widget.room.id)
        .collection('messages')
        .orderBy('timestamp', descending: true)
        .limit(1)
        .snapshots()
        .listen((onData) {
      print("Something change");
      if (onData.documents[0] != null) {
        Message result = Message.fromSnapshot(onData.documents[0]);
        // Here i check if last array message is the last of the FireStore DB
        int equal = lastMessage?.compareTo(result) ?? 1;
        if (equal != 0) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    });
  }

我希望这对遇到相同问题的所有人(@Purus)有所帮助,等到有人给我们提供更好的解决方案!

答案 2 :(得分:0)

我有办法将其存档。对不起,我的英语不好

bool loadMoreMessage = false; int lastMessageIndex = 25 ///假定每次滚动到ListView的顶部加载更多25个文档当我滚动到ListView的顶部=> setState loadMoreMessage = true;

这是我的代码:

StreamBuilder<List<Message>>(
        stream:
            _loadMoreMessage ? _streamMessage(lastMessageIndex): _streamMessage(25),
        builder: (context, AsyncSnapshot<List<Message>> snapshot) {
          if (!snapshot.hasData) {
            return Container();
          } else {
            listMessage = snapshot.data;
            return NotificationListener(
              onNotification: (notification) {
                if (notification is ScrollEndNotification) {
                  if (notification.metrics.pixels > 0) {
                    setState(() {
                      /// Logic here!
                      lastMessageIndex = lastMessageIndex + 25;
                      _loadMoreMessage = true;
                    });
                  }
                }
              },
              child: ListView.builder(
                controller: _scrollController,
                reverse: true,
                itemCount: snapshot.data.length,
                itemBuilder: (context, index) {
                  return ChatContent(listMessage[index]);
                },
              ),
            );
          }
        },
      ),