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

时间:2020-04-25 06:59:23

标签: flutter dart google-cloud-firestore flutter-provider

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

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

    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<QuerySnapshot>,但可能会出错。 当然可以像这样给StreamProvider一些initialData-但这很麻烦,容易出错并且可能很昂贵:

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

...但是我希望有一种更聪明的方法来管理连接状态,并避免在发送文档之前返回空值?

3 个答案:

答案 0 :(得分: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,
                        );
                    }
                }
        )
    );

答案 1 :(得分:0)

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

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...'),
            ],
          )
        ],
      ),
    );
}

答案 2 :(得分:0)

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

由于streamProviser不提供连接状态,因此我首先使用streamBuilder,然后使用provider.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());
    });