Firebase StreamBuilder Firestore分页

Firebase StreamBuilder Firestore分页,firebase,pagination,dart,flutter,google-cloud-firestore,Firebase,Pagination,Dart,Flutter,Google Cloud Firestore,我是一个新手,当scroll使用streambuilder到达顶部时,我正在尝试分页聊天。问题是:当我在scrollListener中进行查询时,streambuilder将其查询优先于scrollListener并返回旧响应。有没有办法做到这一点?我的选择是什么?谢谢 类聊天屏幕状态 class _MessageListState extends State<MessageList> { List<DocumentSnapshot> _messagesSnapsho

我是一个新手,当scroll使用streambuilder到达顶部时,我正在尝试分页聊天。问题是:当我在scrollListener中进行查询时,streambuilder将其查询优先于scrollListener并返回旧响应。有没有办法做到这一点?我的选择是什么?谢谢

类聊天屏幕状态

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"),
//      ));
    }
  }
}
  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;
          });
        }
      }
    });
  }
在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]);
   },
 );
}

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

如您所见,具有固定页面长度的分页API可能并不适合。这就是为什么我强烈建议您不要请求在特定时间间隔内发送的消息。 Firestore请求可能包含以下代码:

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

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

正如您所看到的,具有固定页面长度的分页API可能并不合适。这就是为什么我强烈建议您请求在特定时间间隔内发送的消息。 Firestore请求可能包含以下代码:

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

.where(“time”,“lastCheck”).where(“time”,“p>我将发布我的代码我希望有人发布一个更好的解决方案,可能不是最好的,但它很有效

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

所有代码(状态)

和LoadToRue,它们在我们查找旧消息时侦听。如果有新消息,我们将重新激活流

loadToTrue

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"),
//      ));
    }
  }
}
  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)的人,等到有人给我们一个更好的解决方案!

我要发布我的代码,我希望有人发布一个更好的解决方案,可能不是最好的,但它是有效的

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

所有代码(状态)

和LoadToRue,它们在我们查找旧消息时侦听。如果有新消息,我们将重新激活流

loadToTrue

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"),
//      ));
    }
  }
}
  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)的人,并等待有人给我们更好的解决方案!

我有办法存档它。很抱歉我的英语不好

bool loadMoreMessage=false; int lastMessageIndex=25///假设每次滚动到ListView顶部时,在我滚动到ListView顶部时加载25个以上的文档=>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]);
                },
              ),
            );
          }
        },
      ),
StreamBuilder(
流:
_loadMoreMessage?\u streamMessage(lastMessageIndex):\u streamMessage(25),
生成器:(上下文,异步快照){
如果(!snapshot.hasData){
返回容器();
}否则{
listMessage=snapshot.data;
返回通知侦听器(
通知:(通知){
如果(通知为ScrollEndNotification){
如果(notification.metrics.pixels>0){
设置状态(){
///逻辑在这里!
lastMessageIndex=lastMessageIndex+25;
_loadMoreMessage=true;
});
}
}
},
子项:ListView.builder(
控制器:\ u滚动控制器,
相反:是的,
itemCount:snapshot.data.length,
itemBuilder:(上下文,索引){
返回聊天内容(listMessage[索引]);
},
),
);
}
},
),

我有办法把它存档。很抱歉我的英语不好

bool loadMoreMessage=false; int lastMessageIndex=25///假设每次滚动到ListView顶部时,在我滚动到ListView顶部时加载25个以上的文档=>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]);
                },
              ),
            );
          }
        },
      ),
StreamBuilder(
流:
_loadMoreMessage?\u streamMessage(lastMessageIndex):\u streamMessage(25),
生成器:(上下文,异步快照){
如果(!snapshot.hasData){
返回容器();
}否则{
listMessage=snapshot.data;
返回通知侦听器(
通知:(通知){
如果(通知为ScrollEndNotification){
如果(notification.metrics.pixels>0){
设置状态(){
///逻辑在这里!
lastMessageIndex=lastMessageIndex+25;
_loadMoreMessage=true;
});
}
}
},
子项:ListView.builder(
控制器:\ u滚动控制器,
相反:是的,
itemCount:snapshot.data.length,
itemBuilder:(上下文,索引){
返回聊天内容(listMessage[索引]);
},
),
);
}
},
),
<