Ich habe eine Chat-App in Flutter mit Firestore und zwei Hauptsammlungen:
chats
Wird die auf Auto-IDs eingegeben hat , und hatmessage
,timestamp
unduid
Felder.users
, die eingegebenuid
ist und einname
Feld hat
In meiner App zeige ich eine Liste von Nachrichten (aus der messages
Sammlung) mit diesem 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;
}
}
Aber jetzt möchte ich den Namen des Benutzers (aus der users
Sammlung) für jede Nachricht anzeigen.
Normalerweise nenne ich das einen clientseitigen Join, obwohl ich nicht sicher bin, ob Flutter einen bestimmten Namen dafür hat.
Ich habe einen Weg gefunden, dies zu tun (den ich unten gepostet habe), aber ich frage mich, ob es einen anderen / besseren / idiomatischeren Weg gibt, um diese Art von Operation in Flutter durchzuführen.
Also: Wie wird in Flutter idiomatisch nach dem Benutzernamen für jede Nachricht in der obigen Struktur gesucht?
firebase
flutter
google-cloud-firestore
Frank van Puffelen
quelle
quelle
Antworten:
Ich habe eine andere Version zum Laufen gebracht, die etwas besser zu sein scheint als meine Antwort mit den beiden verschachtelten Buildern .
Hier habe ich das Laden von Daten in einer benutzerdefinierten Methode isoliert und eine dedizierte
Message
Klasse verwendet, um die Informationen aus einer NachrichtDocument
und dem optionalen zugeordneten Benutzer zu speichernDocument
.Im Vergleich zur Lösung mit verschachtelten Buildern ist dieser Code besser lesbar, hauptsächlich weil die Datenverarbeitung und der UI-Builder besser voneinander getrennt sind. Außerdem werden nur die Benutzerdokumente für Benutzer geladen, die Nachrichten gepostet haben. Wenn der Benutzer mehrere Nachrichten gepostet hat, wird das Dokument leider für jede Nachricht geladen. Ich könnte einen Cache hinzufügen, aber ich denke, dieser Code ist schon ein bisschen lang für das, was er erreicht.
quelle
Map<String, UserModel>
nur einmal laden.Wenn ich dies richtig lese, wird das Problem wie folgt abstrahiert: Wie transformieren Sie einen Datenstrom, für den ein asynchroner Aufruf erforderlich ist, um Daten im Datenstrom zu ändern?
Im Kontext des Problems ist der Datenstrom eine Liste von Nachrichten, und der asynchrone Aufruf besteht darin, die Benutzerdaten abzurufen und die Nachrichten mit diesen Daten im Strom zu aktualisieren.
Mit der
asyncMap()
Funktion ist dies direkt in einem Dart-Stream-Objekt möglich . Hier ist ein reiner Dart-Code, der zeigt, wie es geht:Der größte Teil des Codes ahmt die von Firebase kommenden Daten als Stream einer Nachrichtenkarte und als asynchrone Funktion zum Abrufen von Benutzerdaten nach. Die wichtige Funktion hier ist
getMessagesStream()
.Der Code wird leicht durch die Tatsache kompliziert, dass es sich um eine Liste von Nachrichten handelt, die in den Stream kommen. Um zu verhindern, dass Aufrufe zum Abrufen von Benutzerdaten synchron erfolgen, verwendet der Code a
Future.wait()
, um a zu erfassenList<Future<Message>>
und a zu erstellen,List<Message>
wenn alle Futures abgeschlossen sind.Im Kontext von Flutter können Sie den Stream aus
getMessagesStream()
a verwendenFutureBuilder
, um die Nachrichtenobjekte anzuzeigen.quelle
Sie können es mit RxDart so machen .. https://pub.dev/packages/rxdart
für rxdart 0.23.x.
quelle
f.reference.snapshots()
, da dadurch der Snapshot im Wesentlichen neu geladen wird und ich mich lieber nicht darauf verlassen möchte, dass der Firestore-Client intelligent genug ist, um diese zu deduplizieren (obwohl ich fast sicher bin, dass er dedupliziert).Stream<Messages> messages = f.reference.snapshots()...
können Sie tunStream<Messages> messages = Observable.just(f)...
. Was mir an dieser Antwort gefällt, ist, dass sie die Benutzerdokumente beobachtet. Wenn also ein Benutzername in der Datenbank aktualisiert wird, spiegelt die Ausgabe dies sofort wider.Idealerweise möchten Sie jede Geschäftslogik ausschließen, z. B. das Laden von Daten in einen separaten Dienst oder das Befolgen des BloC-Musters, z.
Dann können Sie einfach den Block in Ihrer Komponente verwenden und den
chatBloc.messages
Stream anhören .quelle
Gestatten Sie mir, meine Version einer RxDart-Lösung herauszubringen. Ich benutze
combineLatest2
mit einemListView.builder
, um jede Nachricht Widget zu erstellen. Während der Erstellung jedes Nachrichten-Widgets suche ich den Namen des Benutzers mit dem entsprechendenuid
.In diesem Snippet verwende ich eine lineare Suche nach dem Namen des Benutzers, aber das kann durch Erstellen einer
uid -> user name
Karte verbessert werdenquelle
Die erste Lösung, die ich zum Laufen gebracht habe, besteht darin, zwei
StreamBuilder
Instanzen zu verschachteln , eine für jede Sammlung / Abfrage.Wie in meiner Frage angegeben, weiß ich, dass diese Lösung nicht großartig ist, aber zumindest funktioniert sie.
Einige Probleme sehe ich damit:
Wenn Sie eine bessere Lösung kennen, posten Sie diese bitte als Antwort.
quelle