Flutter StreamBuilder 与 Firestore 分页
Posted
技术标签:
【中文标题】Flutter StreamBuilder 与 Firestore 分页【英文标题】:Flutter StreamBuilder with Firestore Pagination 【发布时间】:2020-05-28 20:36:54 【问题描述】:我正在尝试构建一个基本的聊天功能,其中所有用户聊天消息都作为文档存储在“聊天”集合中。我已经成功实现了分页,以确保在用户滚动之前不会过度提取数据。
但是,即使我有一个 StreamBuilder,新的聊天文档也不会像往常一样自动出现。为什么流构建器没有注册和显示这些新消息?
这是我的代码:
class MotivatorChat extends StatefulWidget
@override
_MotivatorChatState createState() => _MotivatorChatState();
class _MotivatorChatState extends State<MotivatorChat>
Firestore firestore = Firestore.instance;
List<DocumentSnapshot> chats = [];
bool isLoading = false;
bool hasMore = true;
int documentLimit = 10;
DocumentSnapshot lastDocument;
ScrollController _scrollController = ScrollController();
StreamController<List<DocumentSnapshot>> _controller = StreamController<List<DocumentSnapshot>>();
Stream<List<DocumentSnapshot>> get _streamController => _controller.stream;
@override
void initState()
super.initState();
getChats();
_scrollController.addListener(()
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.20;
if (maxScroll - currentScroll <= delta)
getChats();
);
getChats() async
if (!hasMore)
print('No More Chats');
return;
if (isLoading)
return;
setState(()
isLoading = true;
);
QuerySnapshot querySnapshot;
if (lastDocument == null)
querySnapshot = await firestore
.collection('chats')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.getDocuments();
else
querySnapshot = await firestore
.collection('chats')
.orderBy('timestamp', descending: true)
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
print(1);
if (querySnapshot.documents.length < documentLimit)
hasMore = false;
lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
chats.addAll(querySnapshot.documents);
_controller.sink.add(chats);
setState(()
isLoading = false;
);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('Flutter Pagination with Firestore'),
),
body: Column(children: [
Expanded(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController,
builder: (sContext, snapshot)
print(snapshot.connectionState);
if (snapshot.hasData && snapshot.data.length > 0)
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index)
return Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
height: 20,
child: Text(snapshot.data[index].data['text']),
),
);
,
);
else
return Center(
child: Text('No Data...'),
);
,
),
),
isLoading
? Container(
width: MediaQuery
.of(context)
.size
.width,
padding: EdgeInsets.all(5),
color: Colors.yellowAccent,
child: Text(
'Loading',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
)
: Container(),
]),
);
更新了 StreamBuilder
StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController,
builder: (sContext, snapshot)
if (snapshot.connectionState == ConnectionState.none)
return Text("None");
else if (snapshot.connectionState == ConnectionState.waiting)
return Text("Loading");
else if (snapshot.connectionState == ConnectionState.active)
if (snapshot.hasData && snapshot.data.length > 0)
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index)
return Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
height: 20,
child: Text(snapshot.data[index].data['text']),
),
);
,
);
else
return Center(
child: Text('No Data...'),
);
else
return Text("return list");
,
),
【问题讨论】:
使用snapshot.connectionState
查看。
@JohnJoe 连接状态是活动的,但仍未触发流添加新消息。
请粘贴您的最新代码。
@JohnJoe 更新了!
我没有看到你在某处使用connectionState.active
。
【参考方案1】:
看看这段代码是否有用:
class _MessagesState extends State<Messages>
ScrollController _scrollController = ScrollController();
@override
void initState()
super.initState();
_scrollController.addListener(()
if (_scrollController.offset >=
(_scrollController.position.maxScrollExtent) &&
!_scrollController.position.outOfRange)
_getChats();
);
final StreamController<List<DocumentSnapshot>> _chatController =
StreamController<List<DocumentSnapshot>>.broadcast();
List<List<DocumentSnapshot>> _allPagedResults = [<DocumentSnapshot>[]];
static const int chatLimit = 10;
DocumentSnapshot? _lastDocument;
bool _hasMoreData = true;
Stream<List<DocumentSnapshot>> listenToChatsRealTime()
_getChats();
return _chatController.stream;
void _getChats()
final CollectionReference _chatCollectionReference = FirebaseFirestore
.instance
.collection("ChatRoom")
.doc(widget.chatRoomId)
.collection("channel");
var pagechatQuery = _chatCollectionReference
.orderBy('createdAt', descending: true)
.limit(chatLimit);
if (_lastDocument != null)
pagechatQuery = pagechatQuery.startAfterDocument(_lastDocument!);
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot)
if (snapshot.docs.isNotEmpty)
var generalChats = snapshot.docs.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists)
_allPagedResults[currentRequestIndex] = generalChats;
else
_allPagedResults.add(generalChats);
var allChats = _allPagedResults.fold<List<DocumentSnapshot>>(
<DocumentSnapshot>[],
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1)
_lastDocument = snapshot.docs.last;
_hasMoreData = generalChats.length == chatLimit;
,
);
@override
Widget build(BuildContext context)
return Container(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: listenToChatsRealTime(),
builder: (ctx, chatSnapshot)
if (chatSnapshot.connectionState == ConnectionState.waiting ||
chatSnapshot.connectionState == ConnectionState.none)
return chatSnapshot.hasData
? Center(
child: CircularProgressIndicator(),
)
: Center(
child: Text("Start a conversation."),
);
else
if (chatSnapshot.hasData)
final chatDocs = chatSnapshot.data!;
final user = Provider.of<User?>(context);
return ListView.builder(
controller: _scrollController,
reverse: true,
itemBuilder: (ctx, i)
Map chatData = chatDocs[i].data() as Map;
return MessageBubble(
username: chatData['username'],
message: chatData['text'],
isMe: chatData['senderId'] == user!.uid,
key: ValueKey(chatDocs[i].id));
,
itemCount: chatDocs.length,
);
else
return CircularProgressIndicator();
),
);
我参考了这个答案:Pagination in Flutter with Firebase Realtime Database
【讨论】:
【参考方案2】:如果
hasMore
为假,则流不会接收新数据。
不要检查 hasMore
// if (!hasMore)
// print('No More Chats');
// return;
//
并检查文件
// if (querySnapshot.documents.length < documentLimit)
// hasMore = false;
//
if (querySnapshot.documents.isEmpty)
print('No More Chats');
setLoading(false);
return;
我在DartPad上测试,它每3秒发送一次数据。
【讨论】:
问题不在于滚动。即使不滚动,流构建器如何继续收听流并添加其他聊天?有没有办法收听流并添加到列表中? 问题是你的滚动逻辑有错误。如果hasMore
为假,则流不会接收新数据。
你可以在 DartPad 上试试这个例子。它每 3 秒接收一次数据。 dartpad.dartlang.org/965e9634d2a9fa2e64db0556d9c9bc8c【参考方案3】:
我尝试了您的代码并将文档限制设置为 20,它工作正常。
DartPad 上的示例
【讨论】:
检索数据不是问题。挑战在于它何时更新并将新文档添加到 Firestore。我希望它自动更新,但此解决方案不适用于此 因为当一个新文档被添加到 Firestore 时,hasMore 总是 false ... @PJQuakJag 你设法解决了吗?我有同样的问题以上是关于Flutter StreamBuilder 与 Firestore 分页的主要内容,如果未能解决你的问题,请参考以下文章
Flutter StreamBuilder 与 Firestore 分页
Flutter:如何将 StreamBuilder 与 ListView.separated 一起使用
Flutter StreamBuilder 与 initialData 和 null-awareness
FirebaseStorage + Flutter,streamBuilder?