Mam aplikację do czatowania we Flutter przy użyciu Firestore i mam dwie główne kolekcje:
chats
, Które jest osadzone na auto-ID, i mamessage
,timestamp
iuid
pól.users
, który jest włączonyuid
i maname
pole
W mojej aplikacji pokazuję listę wiadomości (z messages
kolekcji), z tym widgetem:
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;
}
}
Ale teraz chcę pokazać nazwę użytkownika (z users
kolekcji) dla każdej wiadomości.
Zwykle nazywam to dołączeniem po stronie klienta, chociaż nie jestem pewien, czy Flutter ma dla niego określoną nazwę.
Znalazłem jeden sposób na zrobienie tego (co zamieściłem poniżej), ale zastanawiam się, czy istnieje inny / lepszy / bardziej idiomatyczny sposób wykonywania tego typu operacji we Flutter.
Więc: w jaki sposób idiomatyczny sposób Fluttera sprawdza nazwę użytkownika dla każdej wiadomości w powyższej strukturze?
firebase
flutter
google-cloud-firestore
Frank van Puffelen
źródło
źródło
Odpowiedzi:
Mam inną wersję, która działa nieco lepiej niż moja odpowiedź z dwoma zagnieżdżonymi programami budującymi .
Tutaj wyizolowałem ładowanie danych w metodzie niestandardowej, używając dedykowanej
Message
klasy do przechowywania informacji z wiadomościDocument
i opcjonalnego powiązanego użytkownikaDocument
.W porównaniu z rozwiązaniem z zagnieżdżonymi konstruktorami ten kod jest bardziej czytelny, głównie dlatego, że obsługa danych i konstruktor interfejsu użytkownika są lepiej oddzielone. Ładuje również dokumenty użytkownika tylko dla użytkowników, którzy opublikowali wiadomości. Niestety, jeśli użytkownik opublikował wiele wiadomości, załaduje dokument dla każdej wiadomości. Mógłbym dodać pamięć podręczną, ale myślę, że ten kod jest już trochę za długi na to, co osiąga.
źródło
Map<String, UserModel>
i załadować dokument użytkownika tylko raz.Jeśli czytam to poprawnie, problem streszcza się: jak przekształcić strumień danych, który wymaga wykonania asynchronicznego wywołania w celu zmodyfikowania danych w strumieniu?
W kontekście problemu strumień danych jest listą wiadomości, a wywołanie asynchroniczne ma pobrać dane użytkownika i zaktualizować wiadomości o te dane w strumieniu.
Można to zrobić bezpośrednio w obiekcie strumienia Dart za pomocą
asyncMap()
funkcji. Oto czysty kod Dart, który pokazuje, jak to zrobić:Większość kodu naśladuje dane pochodzące z Firebase jako strumień mapy wiadomości oraz funkcję asynchroniczną do pobierania danych użytkownika. Ważną funkcją jest tutaj
getMessagesStream()
.Kod jest nieco skomplikowany przez fakt, że jest to lista wiadomości przychodzących do strumienia. Aby zapobiec wywoływaniu synchronicznych wywołań danych użytkownika, kod używa a
Future.wait()
do zebraniaList<Future<Message>>
i utworzenia plikuList<Message>
po zakończeniu wszystkich kontraktów futures.W kontekście trzepotanie, można użyć strumienia pochodzących z
getMessagesStream()
w sposóbFutureBuilder
wyświetlać obiekty wiadomość.źródło
Możesz to zrobić za pomocą RxDart w ten sposób .. https://pub.dev/packages/rxdart
dla rxdart 0.23.x
źródło
f.reference.snapshots()
, ponieważ zasadniczo polega to na ponownym załadowaniu migawki i wolałbym nie polegać na tym, że klient Firestore jest wystarczająco inteligentny, aby je zduplikować (chociaż jestem prawie pewien, że deduplikuje).Stream<Messages> messages = f.reference.snapshots()...
możesz zrobićStream<Messages> messages = Observable.just(f)...
. W tej odpowiedzi podoba mi się to, że obserwuje dokumenty użytkownika, więc jeśli nazwa użytkownika zostanie zaktualizowana w bazie danych, dane wyjściowe natychmiast ją odzwierciedlą.Idealnie chcesz wykluczyć dowolną logikę biznesową, taką jak ładowanie danych do oddzielnej usługi lub przestrzeganie wzorca BloC, np .:
Następnie możesz po prostu użyć bloku w swoim komponencie i słuchać
chatBloc.messages
strumienia.źródło
Pozwól mi przedstawić moją wersję rozwiązania RxDart. Używam
combineLatest2
z,ListView.builder
aby zbudować każdą wiadomość Widget. Podczas konstruowania każdej wiadomości Widżet wyszukuję nazwę użytkownika z odpowiednimuid
.W tym fragmencie używam liniowego wyszukiwania nazwy użytkownika, ale można to poprawić, tworząc
uid -> user name
mapęźródło
Pierwszym rozwiązaniem, które dostałem, jest zagnieżdżenie dwóch
StreamBuilder
instancji, po jednej dla każdej kolekcji / zapytania.Jak stwierdzono w moim pytaniu, wiem, że to rozwiązanie nie jest świetne, ale przynajmniej działa.
Niektóre problemy z tym widzę:
Jeśli znasz lepsze rozwiązanie, napisz jako odpowiedź.
źródło