Flutter:延迟加载来自 Firestore 的数据

Posted

技术标签:

【中文标题】Flutter:延迟加载来自 Firestore 的数据【英文标题】:Flutter: Lazy Load data from firestore 【发布时间】:2019-10-18 02:09:58 【问题描述】:

注意:我已经看到关于 ListView 的延迟加载的答案,但这些答案是针对自定义 API 而不是 firestore 数据库!

我有一个书籍摘要应用程序,应用程序从我的 Firebase/Firestore 数据库中获取数据,然后使用包裹在 StreamBuilder 中的 ListView.builder 显示它。

现在,我想延迟获取数据,我的意思是当用户滚动列表时,所需的数据会被加载,而不是一次加载数据然后延迟显示。

//The Widget used to display data:

Widget feed() 
  return Container(
    width: deviceWidth,
    height: deviceHeight / 3,
    child: StreamBuilder(
        stream: Firestore.instance
            .collection('feedItem')
            .orderBy('feedId', descending: true)
            .snapshots(),

        builder: (BuildContext context, AsyncSnapshot snapshot) 
          if (snapshot.hasData) 
            int totalLength = snapshot.data.documents.length;
            return ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: totalLength > 10 ? 10 : totalLength,
              itemBuilder: (BuildContext context, int index) 
                return Container(
                  width: deviceWidth / 2.5,
                  child: GestureDetector(
                    onTap: () 
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (BuildContext context) => FeedIntro(
                                  snapshot.data.documents[
                                      ((totalLength - 1) - index)]['feedId'])));
                    ,
                    child: Card(
                        child: Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: <Widget>[
                        Container(
                          // width: 150,
                          height: 150,
                          foregroundDecoration: BoxDecoration(
                              image: DecorationImage(
                                  image: NetworkImage(
                                    snapshot.data.documents[index]['feedImage'],
                                  ),
                                  fit: BoxFit.fill)),
                        ),
                        Center(
                            child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(snapshot.data.documents[index]['title']),
                        )),
                      ],
                    )),
                  ),
                );
              ,
            );
           else if (snapshot.hasError) 
            return Center(child: Text('Sorry Something went wrong!'));
           else 
            return Center(
              child: SizedBox(
                child: CircularProgressIndicator(),
                width: 50,
                height: 50,
              ),
            );
          
        ),
  );

【问题讨论】:

你所谓的“延迟加载”,很多人称之为“分页”。 Firestore 具有这种能力。 firebase.google.com/docs/firestore/query-data/query-cursors 要添加到@DougStevenson 评论,您可以将其与flutter firestore API(我认为firebase 团队尚未添加Dart 示例)从这里配对:pub.dev/documentation/cloud_firestore/latest/cloud_firestore/… 此外,这些方法是最近添加的到 Dart firestore 插件(dart 实现的不完整 API 可能是为什么团队尚未添加 dart 示例)。 :) 感谢您抽出宝贵时间!我知道了,我可以使用 limit 或 startAfter 来分段获取数据,但是对于我应该使用什么方法来集成,这与我当前的代码(StreamBuilder 和 ListView.Builder)有什么建议吗? @RajDhakad 有任何解决方案吗? @ShrutiRamnandanSharma 据我所知,firestore 中的所有数据都已缓存,因此在第一次(未缓存时)后,它会自动延迟获取。 【参考方案1】:

您对延迟加载的描述似乎与分页匹配。这是一个在 ListView.builder 中使用带有分页的 Firestore 的简单演示

此示例实现了来自 Firebase 官方文档的 sn-ps,用于 Firestore pagination。

在这个演示中,有两种方法可以在视图上加载数据。

使用刷新整个ListView RefreshIndicator 向下滚动以点击列表底部以加载ListView 中的下一个文档。 ScrollController 用于确定用户是否点击了列表的底部。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'DocObj.dart';

Future<void> main() async 
  WidgetsFlutterBinding.ensureInitialized();
  // Initialize Firebase
  await Firebase.initializeApp();
  runApp(MyApp());


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key key, this.title) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  var scrollController = ScrollController();

  @override
  void initState() 
    super.initState();
    getDocuments();
    scrollController.addListener(() 
      if (scrollController.position.atEdge) 
        if (scrollController.position.pixels == 0)
          print('ListView scroll at top');
        else 
          print('ListView scroll at bottom');
          getDocumentsNext(); // Load next documents
        
      
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: listDocument.length != 0
            ? RefreshIndicator(
                child: ListView.builder(
                  physics: AlwaysScrollableScrollPhysics(),
                  controller: scrollController,
                  itemCount: listDocument.length,
                  itemBuilder: (context, index) 
                    return ListTile(
                      title: Text('$listDocument[index].documentName'),
                    );
                  ,
                ),
                onRefresh: getDocuments, // Refresh entire list
              )
            : CircularProgressIndicator(),
      ),
    );
  

  List<DocObj> listDocument;
  QuerySnapshot collectionState;
  // Fetch first 15 documents
  Future<void> getDocuments() async 
    listDocument = List();
    var collection = FirebaseFirestore.instance
        .collection('sample_pagination')
        .orderBy("name")
        .limit(15);
    print('getDocuments');
    fetchDocuments(collection);
  

  // Fetch next 5 documents starting from the last document fetched earlier
  Future<void> getDocumentsNext() async 
    // Get the last visible document
    var lastVisible = collectionState.docs[collectionState.docs.length-1];
    print('listDocument legnth: $collectionState.size last: $lastVisible');

    var collection = FirebaseFirestore.instance
        .collection('sample_pagination')
        .orderBy("name").startAfterDocument(lastVisible).limit(5);

    fetchDocuments(collection);
  

  fetchDocuments(Query collection)
    collection.get().then((value) 
      collectionState = value; // store collection state to set where to start next
      value.docs.forEach((element) 
        print('getDocuments $element.data()');
        setState(() 
          listDocument.add(DocObj(DocObj.setDocDetails(element.data())));
        );
      );
    );
  

要解析文档中的数据,您可以为您的对象创建一个模型。

class DocObj 
  var documentName;

  DocObj(DocObj doc) 
    this.documentName = doc.getDocName();
  

  dynamic getDocName() => documentName;

  DocObj.setDocDetails(Map<dynamic, dynamic> doc)
      : documentName = doc['name'];

该示例处理来自 Firestore 的数据。

这是应用运行时的外观。

【讨论】:

ListView.builder 完成这项工作。一个初学者的错误是在SingleChildScrollView 中添加ListView.builder,它试图一次加载所有内容,使ListView.builder 无用,或者有时将数据直接加载到SingleChildScrollView。我希望这个评论可以帮助一些初学者。【参考方案2】:

我遇到了这个问题,我所做的是我在 listview builder 中添加了滚动控制器,我跟踪它是否到达列表底部,如果到达底部,则更新快照的限制。

我已经对此进行了实验并且效果很好。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class Sample extends StatefulWidget 
  @override
  _SampleState createState() => _SampleState();


class _SampleState extends State<Sample> 
  ScrollController _chatScrollController;
  int loadMoreMsgs = 25; // at first it will load only 25
  int a = 50; // 'loadMoreMsgs' will added by 'a' if we load more msgs in listview.
  
  @override
  void initState() 
    _chatScrollController = ScrollController()
      ..addListener(() 
        if (_chatScrollController.position.atEdge) 
          if (_chatScrollController.position.pixels == 0)
            print('ListView scrolled to top');
          else 
            setState(() 
              loadMoreMsgs =  loadMoreMsgs + a;
            );
            print('ListView scrolled to bottom');
          
        
      );
    super.initState();
  
  
  @override
  Widget build(BuildContext context) 
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('CollectionName').limit(loadMoreMsgs).snapshots(),
      builder: (context, snapshot) 
        return ListView.builder(
          controller: _chatScrollController,
          itemBuilder: (context, index) 
            return Text('This is a sample');
          ,
        );
      ,
    );
  

我只写了理解所需的代码。

【讨论】:

以上是关于Flutter:延迟加载来自 Firestore 的数据的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:使用来自 Firestore 的多集合流创建动态更新的 DataTable

来自Json的Flutter Firestore返回Null?

在 Flutter 中的垂直 ScrollView 内的水平 ListView 中显示来自 Firestore 的数据

使用来自firestore数据库的geoflutterfire查询结果,使用flutter应用程序返回null或啥都没有,但语法没有错误

有没有办法通过flutter查看来自云Firestore的用户的关注者或关注者?

实施 Firestore 时,Flutter Web 应用程序不会加载