1
votes

Comment vérifier le flux de capture instantanée ConnectionState de Firestore avec StreamProvider?

Cet exemple de la documentation cloud_firestore utilise un StreamBuilder et le ConnectionState d'un AsyncSnapshot pour gérer le flux dans ses différents états. Existe-t-il un moyen similaire de gérer le ConnectionState lors de l'accès au flux via un StreamProvider au lieu d'un StreamBuilder ? Quel est le meilleur moyen d'éviter qu'il renvoie null dans un court laps de temps jusqu'à ce qu'il ait réellement des documents de Firestore?

Voici l'exemple de la documentation cloud_firestore avec StreamBuilder:

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

J'ai un flux assez basique:

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

Ceci est accessible via un StreamProvider (ont omis d'autres fournisseurs ici):

    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);
    }

J'ai essayé en quelque sorte de convertir le QuerySnapshot en AsyncSnapshot<QuerySnapshot> mais je me suis probablement trompé. Pourrait bien sûr donner au StreamProvider des données initialData comme ceci - mais c'est encombrant, sujet aux erreurs et probablement cher:

    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(),
                );
            }
          },
        );
      }
    }

... mais j'espère qu'il existe un moyen plus intelligent de gérer l'état de la connexion et de l'éviter de renvoyer null avant de pouvoir émettre des documents?


0 commentaires

3 Réponses :


1
votes

J'ai été confronté à cela et je ne voulais pas déclarer un initialData pour contourner ce problème.

Ce que j'ai fait, c'est créer un StreamBuilder en tant qu'enfant de StreamProvider . Pour que je puisse utiliser la propriété snapshot.connectionState de StreamBuilder dans StreamProvider.

Voici le code:

    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 commentaires

Belle solution de contournement. Dans mon cas, j'aurais dû utiliser plusieurs StreamBuilders imbriqués car je dépends de plusieurs StreamProviders, donc j'ai préféré l'approche ci-dessous.



0
votes

Probablement pas la solution la plus élégante, mais j'ai fini par utiliser une simple variable booléenne, ce qui est vrai alors que tous les StreamProviders n'ont pas émis de valeurs.

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


0 commentaires

0
votes

Je ne sais pas si c'est correct mais c'est ainsi que je le mets en œuvre.

Puisque streamProviser ne fournit pas d'état de connexion, j'utilise d'abord streamBuilder, puis provider.value pour distribuer les données.

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());
    });


0 commentaires