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?
3 Réponses :
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, ); } } ) );
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.
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...'), ], ) ], ), ); }
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()); });