如何使用 StreamProvider 检查 Firestore 快照流的 ConnectionState?

Posted

技术标签:

【中文标题】如何使用 StreamProvider 检查 Firestore 快照流的 ConnectionState?【英文标题】:How check the ConnectionState of Firestore snapshot stream with StreamProvider? 【发布时间】:2020-08-08 20:03:46 【问题描述】:

cloud_firestore 文档中的此示例使用 StreamBuilderAsyncSnapshotConnectionState 来处理处于不同状态的流。当通过StreamProvider 而不是StreamBuilder 访问流时,是否有类似的方法来管理ConnectionState?什么是避免它在短时间内返回 null 直到它实际上有来自 Firestore 的文档的最佳方法?

这里是 cloud_firestore 文档中带有 StreamBuilder 的示例:

    class BookList extends StatelessWidget 
      @override
      Widget build(BuildContext context) 
        return StreamBuilder<QuerySnapshot>(
          stream: Firestore.instance.collection('books').snapshots(),
          builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) 
            if (snapshot.hasError)
              return new Text('Error: $snapshot.error');
            switch (snapshot.connectionState) 
              case ConnectionState.waiting: return new Text('Loading...');
              default:
                return new ListView(
                  children: snapshot.data.documents.map((DocumentSnapshot document) 
                    return new ListTile(
                      title: new Text(document['title']),
                      subtitle: new Text(document['author']),
                    );
                  ).toList(),
                );
            
          ,
        );
      
    

我有一个相当基本的流:

    List<AuditMark> _auditMarksFromSnapshot(QuerySnapshot qs) 
      return qs.documents.map((DocumentSnapshot ds) 
        return AuditMark.fromSnapshot(ds);
      ).toList();
    

    Stream<List<AuditMark>> get auditMarks 
      return Firestore.instance
          .collection('_auditMarks')
          .snapshots()
          .map(_auditMarksFromSnapshot);
    

这是通过StreamProvider 访问的(此处省略了其他提供者):

    void main() async 
      runApp(MultiProvider(
        providers: [
          StreamProvider<List<AuditMark>>(
              create: (_) => DatabaseService().auditMarks, ),
        ],
        child: MyApp(),
      ));
    

我尝试以某种方式将QuerySnapshot 转换为AsyncSnapshot&lt;QuerySnapshot&gt;,但可能弄错了。 当然可以像这样给StreamProvider 一些initialData - 但这很麻烦,容易出错并且可能很昂贵:

    initialData: <AuditMark>[
      AuditMark.fromSnapshot(await Firestore.instance
          .collection('_auditMarks')
          .orderBy('value')
          .getDocuments()
          .then((value) => value.documents.first))

...但我希望有一种更智能的方法来管理连接状态并避免它在发出文档之前返回 null?

【问题讨论】:

【参考方案1】:

我一直在处理这个问题,不想声明initialData 来绕过这个问题。

我所做的是创建一个 StreamBuilder 作为 StreamProvider 的 。 这样我就可以在 StreamProvider 中使用 StreamBuilder 的 snapshot.connectionState 属性。

代码如下:

    return StreamProvider<List<AuditMark>>.value(
        value: DatabaseService().auditMarks,
        child: StreamBuilder<List<AuditMark>>(
                stream: DatabaseService().auditMarks,
                builder: (context, snapshot) 
                    if (!snapshot.hasError) 
                        switch (snapshot.connectionState) 
                            case ConnectionState.none: // if no connection
                                return new Text(
                                    "Offline!",
                                    style: TextStyle(fontSize: 24, color: Colors.red),
                                    textAlign: TextAlign.center,
                                );
                            case ConnectionState.waiting
                                // while waiting the data, this is where you'll avoid NULL
                                return Center(child: CircularProgressIndicator());
                            default:
                                return ListView.builder(
                                    // in my case I was getting NULL for itemCount
                                    itemCount: logs.length,
                                    itemBuilder: (context, index) 
                                        return LogsTile(log: logs[index]);
                                    ,
                                );
                        
                    
                    else 
                        return new Text(
                            "Error: $snapshot.error",
                            style: TextStyle(fontSize: 17, color: Colors.red),
                            textAlign: TextAlign.center,
                        );
                    
                
        )
    );

【讨论】:

不错的解决方法。在我的情况下,我不得不使用多个嵌套的 StreamBuilder,因为我依赖于多个 StreamProvider,因此更喜欢下面的方法。【参考方案2】:

可能不是最优雅的解决方案,但我最终使用了一个简单的 bool 变量,虽然并非所有 StreamProviders 都发出了值,但该变量是正确的。

bool _waitForStreams = false;
if (Provider.of<List<AuditMark>>(context) == null) _waitForStreams = true;
if (Provider.of<...>>(context) == null) _waitForStreams = true; 
(etc. repeat for every StreamProvider)

// show "loading..." message while not all StreamProviders have supplied values
if (_waitForStreams) 
  return Scaffold(
    appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(),
              SizedBox(height: 25.0),
              Text('loading...'),
            ],
          )
        ],
      ),
    );

【讨论】:

【参考方案3】:

我不知道它是否正确,但这是我实现它的方式。

由于streamProviser不提供连接状态,我先用streamBuilderprovider.value分发数据:

return StreamBuilder<BusinessM>(
    stream: db.businessDetails(), //firebase stream mapped to business model class
    builder: (context, snapshot) 
      if (snapshot.connectionState == ConnectionState.active)
        return Provider<BusinessM>.value(
          value: snapshot.data,
          child: Businesspage(),
        );
      else
        return Center(child: CircularProgressIndicator());
    );

【讨论】:

以上是关于如何使用 StreamProvider 检查 Firestore 快照流的 ConnectionState?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据riverpods streamProvider 更新嵌套屏幕?

Flutter:如何将值从 streamprovider / listview builder 传递给另一个类

Riverpod:是不是有在另一个 StreamProvider 中读取 StreamProvider 的正确方法?

StreamProvider 不更新状态

使用 StreamProvider 和 StreamBuilder 时出错

Flutter Riverpod:使用 StreamProvider 返回 2 个流