Firebase StreamBuilder Firestore分页
我是一个新手,当scroll使用streambuilder到达顶部时,我正在尝试分页聊天。问题是:当我在scrollListener中进行查询时,streambuilder将其查询优先于scrollListener并返回旧响应。有没有办法做到这一点?我的选择是什么?谢谢 类聊天屏幕状态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
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[索引]);
},
),
);
}
},
),
<