【问题标题】:flutter firestore pagination using streambuilder使用streambuilder颤动firestore分页
【发布时间】: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;
      });
    }
  }

【问题讨论】:

    标签: firebase flutter pagination google-cloud-firestore stream-builder


    【解决方案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:你们有没有发现这个问题?上面的代码对我有用,但我想在添加新文档后立即更新它(如果是聊天应用程序),而这段代码没有这样做。
      猜你喜欢
      • 2019-05-29
      • 2020-05-28
      • 2020-03-01
      • 1970-01-01
      • 2021-09-17
      • 2021-01-06
      • 2020-10-18
      • 2020-10-20
      • 2019-12-11
      相关资源
      最近更新 更多