J'ai une application de chat à Flutter en utilisant Firestore, et j'ai deux collections principales:
chats
, Qui est claveté sur l' auto-ids, et amessage
,timestamp
et lesuid
champs.users
, qui est activéuid
et possède unname
champ
Dans mon application, je montre une liste de messages (de la messages
collection), avec ce widget:
class ChatList extends StatelessWidget {
@override
Widget build(BuildContext context) {
var messagesSnapshot = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
var streamBuilder = StreamBuilder<QuerySnapshot>(
stream: messagesSnapshot,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapshot) {
if (querySnapshot.hasError)
return new Text('Error: ${querySnapshot.error}');
switch (querySnapshot.connectionState) {
case ConnectionState.waiting: return new Text("Loading...");
default:
return new ListView(
children: querySnapshot.data.documents.map((DocumentSnapshot doc) {
return new ListTile(
title: new Text(doc['message']),
subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(doc['timestamp']).toString()),
);
}).toList()
);
}
}
);
return streamBuilder;
}
}
Mais maintenant, je veux montrer le nom de l'utilisateur (de la users
collection) pour chaque message.
J'appelle normalement cela une jointure côté client, bien que je ne sois pas sûr que Flutter ait un nom spécifique pour cela.
J'ai trouvé un moyen de le faire (que j'ai posté ci-dessous), mais je me demande s'il existe un autre moyen / meilleur / plus idiomatique pour faire ce type d'opération dans Flutter.
Alors: quelle est la façon idiomatique dans Flutter de rechercher le nom d'utilisateur pour chaque message dans la structure ci-dessus?
firebase
flutter
google-cloud-firestore
Frank van Puffelen
la source
la source
Réponses:
J'ai obtenu une autre version qui semble légèrement meilleure que ma réponse avec les deux constructeurs imbriqués .
Ici, j'ai isolé le chargement des données dans une méthode personnalisée, en utilisant une
Message
classe dédiée pour contenir les informations d'un messageDocument
et l'utilisateur associé facultatifDocument
.Comparé à la solution avec les constructeurs imbriqués, ce code est plus lisible, principalement parce que la gestion des données et le générateur d'interface utilisateur sont mieux séparés. Il charge également uniquement les documents utilisateur pour les utilisateurs qui ont publié des messages. Malheureusement, si l'utilisateur a publié plusieurs messages, il chargera le document pour chaque message. Je pourrais ajouter un cache, mais je pense que ce code est déjà un peu long pour ce qu'il accomplit.
la source
Map<String, UserModel>
et ne charger le document utilisateur qu'une seule fois.Si je lis cela correctement, le problème résume: comment transformer un flux de données qui nécessite de faire un appel asynchrone pour modifier les données dans le flux?
Dans le contexte du problème, le flux de données est une liste de messages et l'appel asynchrone consiste à récupérer les données utilisateur et à mettre à jour les messages avec ces données dans le flux.
Il est possible de le faire directement dans un objet de flux Dart en utilisant la
asyncMap()
fonction. Voici du code Dart pur qui montre comment le faire:La plupart du code imite les données provenant de Firebase en tant que flux d'une carte de messages et d'une fonction asynchrone pour récupérer les données utilisateur. La fonction importante ici est
getMessagesStream()
.Le code est légèrement compliqué par le fait qu'il s'agit d'une liste de messages provenant du flux. Pour empêcher les appels à récupérer les données utilisateur de se produire de manière synchrone, le code utilise un
Future.wait()
pour rassembler unList<Future<Message>>
et créer unList<Message>
lorsque tous les Futures sont terminés.Dans le contexte de Flutter, vous pouvez utiliser le flux provenant de
getMessagesStream()
dans aFutureBuilder
pour afficher les objets Message.la source
Vous pouvez le faire avec RxDart comme ça .. https://pub.dev/packages/rxdart
pour rxdart 0.23.x
la source
f.reference.snapshots()
, car cela consiste essentiellement à recharger l'instantané et je préférerais ne pas compter sur le client Firestore pour qu'il soit assez intelligent pour les dédupliquer (même si je suis presque certain qu'il le fait).Stream<Messages> messages = f.reference.snapshots()...
, vous pouvez le faireStream<Messages> messages = Observable.just(f)...
. Ce que j'aime dans cette réponse, c'est qu'elle observe les documents utilisateur, donc si un nom d'utilisateur est mis à jour dans la base de données, la sortie le reflète tout de suite.Idéalement, vous souhaitez exclure toute logique métier telle que le chargement de données dans un service distinct ou en suivant le modèle BloC, par exemple:
Ensuite, vous pouvez simplement utiliser le Bloc dans votre composant et écouter le
chatBloc.messages
flux.la source
Permettez-moi de proposer ma version d'une solution RxDart. J'utilise
combineLatest2
avec unListView.builder
pour créer chaque widget de message. Lors de la construction de chaque Widget de message je recherche le nom de l'utilisateur avec le correspondantuid
.Dans cet extrait, j'utilise une recherche linéaire pour le nom de l'utilisateur, mais cela peut être amélioré en créant une
uid -> user name
cartela source
La première solution que j'ai trouvée consiste à imbriquer deux
StreamBuilder
instances, une pour chaque collection / requête.Comme indiqué dans ma question, je sais que cette solution n'est pas excellente, mais au moins cela fonctionne.
Certains problèmes que je vois avec cela:
Si vous connaissez une meilleure solution, veuillez poster comme réponse.
la source