StreamBuilder Firestore分页 [英] StreamBuilder Firestore Pagination
问题描述
我是扑朔迷离的新手,并且我想在通过streambuilder滚动到顶部时对聊天进行分页.问题是:当我在scrollListener中进行查询时,streambuilder在scrollListener上方优先进行其查询,并返回旧响应.有什么办法吗?我在这里有什么选择?谢谢!
I m new to flutter, and I'm trying to paginate a chat when scroll reach top with streambuilder. The problem is: when i make the query in scrollListener streambuilder priorize his query above the scrollListener and returns de old response. Is there any way to do this? What are my options here? Thanks!
ChatScreenState类
在initState中,我创建滚动侦听器.
In initState I create the scroll listener.
@override
void initState() {
listScrollController = ScrollController();
listScrollController.addListener(_scrollListener);
super.initState();
}
在这里,我创建的StreamBuilder的查询限制为20条最后一条消息.使用_messagesSnapshots作为全局列表.
Here i create the StreamBuilder with the query limited to 20 last messages. Using the _messagesSnapshots as global List.
@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条消息,并将结果添加到全局"列表中.
And in the _scollListener method i query the next 20 messages and add the result to the Global list.
_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"),
));
}
}
推荐答案
我要发布我的代码,希望有人发布更好的解决方案,可能不是最好的解决方案,但是它可以工作.
I'm gonna post my code i hope someone post a better solution, probably is not the best but it works.
在我的应用中,实际的解决方案是在到达顶部时更改列表的状态,停止播放流并显示旧消息.
In my app the actual solution is change the state of the list when reach the top, stop stream and show old messages.
所有代码(州)
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,并使用旧消息设置要显示的数组.
When reach the top i query old messages and in setState i set isLoading var to true and set with the old messages the array i m gonna show.
_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在我们寻找旧消息时进行监听.如果有新消息,我们将重新激活流.
And loadToTrue that listen while we are looking for old messages. If there is a new message we re activate the stream.
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)有所帮助,等到有人为我们提供更好的解决方案!
I hope this helps anyone who have the same problem (@Purus) and wait until someone give us a better solution!
这篇关于StreamBuilder Firestore分页的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!