使用streambuilder颤动firestore分页
Posted
技术标签:
【中文标题】使用streambuilder颤动firestore分页【英文标题】:flutter firestore pagination using streambuilder 【发布时间】:2020-03-30 04:03:37 【问题描述】:您好,我正在尝试在列表中使用分页。并且列表数据来自firebase。我不确定如何使用流生成器对数据进行分页。现在我通过在 didChangeDependencies() 函数中调用 getdocuments 来进行分页。但添加新数据后,列表不会更新。如果有人可以提供帮助,那对我来说会很棒。这就是我现在正在做的事情..
didChangeDependencies()
super.didChangeDependencies();
getProducts();
_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)
getProducts();
);
getProducts() async
if (!hasMore)
return;
if (isLoading)
return;
setState(()
isLoading = true;
);
QuerySnapshot querySnapshot;
if (lastDocument == null)
querySnapshot = await firestore
.collection('products')
.limit(documentLimit)
.orderBy('timestamp', descending: true)
.getDocuments();
else
querySnapshot = await firestore
.collection('products')
.startAfterDocument(lastDocument)
.limit(documentLimit)
.orderBy('timestamp', descending: true)
.getDocuments();
if (querySnapshot.documents.length < documentLimit)
hasMore = false;
if (querySnapshot.documents.isNotEmpty)
lastDocument =
querySnapshot.documents[querySnapshot.documents.length - 1];
products.addAll(querySnapshot.documents);
setState(()
isLoading = false;
);
【问题讨论】:
【参考方案1】:试试下面的代码:
class ProductList extends StatefulWidget
@override
_ProductListState createState() => _ProductListState();
class _ProductListState extends State<ProductList>
StreamController<List<DocumentSnapshot>> _streamController =
StreamController<List<DocumentSnapshot>>();
List<DocumentSnapshot> _products = [];
bool _isRequesting = false;
bool _isFinish = false;
void onChangeData(List<DocumentChange> documentChanges)
var isChange = false;
documentChanges.forEach((productChange)
if (productChange.type == DocumentChangeType.removed)
_products.removeWhere((product)
return productChange.document.documentID == product.documentID;
);
isChange = true;
else
if (productChange.type == DocumentChangeType.modified)
int indexWhere = _products.indexWhere((product)
return productChange.document.documentID == product.documentID;
);
if (indexWhere >= 0)
_products[indexWhere] = productChange.document;
isChange = true;
);
if(isChange)
_streamController.add(_products);
@override
void initState()
Firestore.instance
.collection('products')
.snapshots()
.listen((data) => onChangeData(data.documentChanges));
requestNextPage();
super.initState();
@override
void dispose()
_streamController.close();
super.dispose();
@override
Widget build(BuildContext context)
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo)
if (scrollInfo.metrics.maxScrollExtent == scrollInfo.metrics.pixels)
requestNextPage();
return true;
,
child: StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController.stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot)
if (snapshot.hasError) return new Text('Error: $snapshot.error');
switch (snapshot.connectionState)
case ConnectionState.waiting:
return new Text('Loading...');
default:
log("Items: " + snapshot.data.length.toString());
return ListView.separated(
separatorBuilder: (context, index) => Divider(
color: Colors.black,
),
itemCount: snapshot.data.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: new ListTile(
title: new Text(snapshot.data[index]['name']),
subtitle: new Text(snapshot.data[index]['description']),
),
),
);
,
));
void requestNextPage() async
if (!_isRequesting && !_isFinish)
QuerySnapshot querySnapshot;
_isRequesting = true;
if (_products.isEmpty)
querySnapshot = await Firestore.instance
.collection('products')
.orderBy('index')
.limit(5)
.getDocuments();
else
querySnapshot = await Firestore.instance
.collection('products')
.orderBy('index')
.startAfterDocument(_products[_products.length - 1])
.limit(5)
.getDocuments();
if (querySnapshot != null)
int oldSize = _products.length;
_products.addAll(querySnapshot.documents);
int newSize = _products.length;
if (oldSize != newSize)
_streamController.add(_products);
else
_isFinish = true;
_isRequesting = false;
产品项的 JSON:
"index": 1,
"name": "Pork",
"description": "Thịt heo/lợn"
链接 Github 示例: https://github.com/simplesoft-duongdt3/flutter_firestore_paging
【讨论】:
您能否提供使用查询快照流进行分页的代码....不是文档快照列表 @SouravDas 我已经为你添加了示例小部件。请检查一下。 我想要实时更新的分页...但是您的代码仅适用于分页..但实时更新不起作用 我正在关注这个并得到了我的结果。但是,这最初不会给我在 initstate 中的产品集合带来数千次阅读吗?如果是这样,在我们阅读它们时,是否值得听听他们对 requestNextPage 方法的更改? @PabloCrucitta - 谢谢。添加 .limit(whatever_pagination_you_have)... 所以它会减少你的阅读量。这将只监听已添加的快照,而不是所有不必要的快照。【参考方案2】:我用聊天而不是产品做了类似的事情。
看看这段代码是否有用:
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
【讨论】:
【参考方案3】:@sourav-das,请看这段代码
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
class MyHomePage extends StatefulWidget
@override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
Firestore firestore = Firestore.instance;
List<DocumentSnapshot> products = [];
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();
getProducts();
_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)
getProducts();
);
getProducts() async
if (!hasMore)
print('No More Products');
return;
if (isLoading)
return;
setState(()
isLoading = true;
);
QuerySnapshot querySnapshot;
if (lastDocument == null)
querySnapshot = await firestore
.collection('products')
.orderBy('name')
.limit(documentLimit)
.getDocuments();
else
querySnapshot = await firestore
.collection('products')
.orderBy('name')
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
print(1);
if (querySnapshot.documents.length < documentLimit)
hasMore = false;
lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
products.addAll(querySnapshot.documents);
_controller.sink.add(products);
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)
if (snapshot.hasData && snapshot.data.length > 0)
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index)
return ListTile(
contentPadding: EdgeInsets.all(5),
title: Text(snapshot.data[index].data['name']),
subtitle: Text(snapshot.data[index].data['short_desc']),
);
,
);
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进行分页。所以我可以听数据的变化.. @SouravDas 请检查我是否使用 StreamBuilder 编辑了代码。 当我滚动它时,它会删除列表中的前一项,用新项替换它们。就像如果总共有 20 个项目,我添加了 10 个限制,然后在滚动前 10 个后,它会删除它们并添加新的 10 个项目......这不是正确的方法 您为变量 products 赋值,但您在哪里使用它...... streambuilder 和以前一样......如果可以,请提供正确的解决方案。谢谢 @MaulikGajjar:你们有没有发现这个问题?上面的代码对我有用,但我想在添加新文档后立即更新它(如果是聊天应用程序),而这段代码没有这样做。以上是关于使用streambuilder颤动firestore分页的主要内容,如果未能解决你的问题,请参考以下文章
过滤 Streambuilder/listviewBuilder 颤动
在颤动中使用 StreamBuilder 时将数据传递到下一个屏幕