Tengo una aplicación de chat en Flutter usando Firestore, y tengo dos colecciones principales:
chats
, Cuya forma impide el auto-ids, y tienemessage
,timestamp
yuid
campos.users
, que está activadouid
y tiene unname
campo
En mi aplicación muestro una lista de mensajes (de la messages
colección), con este 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;
}
}
Pero ahora quiero mostrar el nombre del usuario (de la users
colección) para cada mensaje.
Normalmente lo llamo una unión del lado del cliente, aunque no estoy seguro de si Flutter tiene un nombre específico para ello.
He encontrado una forma de hacer esto (que he publicado a continuación), pero me pregunto si hay otra forma mejor / más idiomática de hacer este tipo de operación en Flutter.
Entonces: ¿cuál es la forma idiomática en Flutter de buscar el nombre de usuario para cada mensaje en la estructura anterior?
firebase
flutter
google-cloud-firestore
Frank van Puffelen
fuente
fuente
Respuestas:
Tengo otra versión funcionando que parece un poco mejor que mi respuesta con los dos constructores anidados .
Aquí aislé la carga de datos en un método personalizado, usando una
Message
clase dedicada para contener la información de un mensajeDocument
y el usuario asociado opcionalDocument
.En comparación con la solución con los constructores anidados, este código es más legible, principalmente porque el manejo de datos y el generador de IU están mejor separados. También solo carga los documentos de usuario para los usuarios que han publicado mensajes. Desafortunadamente, si el usuario ha publicado varios mensajes, cargará el documento para cada mensaje. Podría agregar un caché, pero creo que este código ya es un poco largo para lo que logra.
fuente
Map<String, UserModel>
solo documento y cargarlo una vez.Si estoy leyendo esto correctamente, el problema se resume en: ¿cómo se transforma una secuencia de datos que requiere hacer una llamada asincrónica para modificar los datos en la secuencia?
En el contexto del problema, el flujo de datos es una lista de mensajes, y la llamada asincrónica es buscar los datos del usuario y actualizar los mensajes con estos datos en el flujo.
Es posible hacer esto directamente en un objeto de flujo Dart usando la
asyncMap()
función. Aquí hay un código Dart puro que demuestra cómo hacerlo:La mayor parte del código está imitando los datos que provienen de Firebase como una secuencia de un mapa de mensajes y una función asíncrona para obtener datos del usuario. La función importante aquí es
getMessagesStream()
.El código se complica ligeramente por el hecho de que es una lista de mensajes que llegan en la transmisión. Para evitar que las llamadas para obtener datos de usuario ocurran de forma síncrona, el código usa a
Future.wait()
para recopilar ayList<Future<Message>>
crear unList<Message>
cuando todos los Futuros se hayan completado.En el contexto de Flutter, puede usar la secuencia proveniente de
getMessagesStream()
aFutureBuilder
para mostrar los objetos del Mensaje.fuente
Puede hacerlo con RxDart así ... https://pub.dev/packages/rxdart
para rxdart 0.23.x
fuente
f.reference.snapshots()
, ya que eso es esencialmente volver a cargar la instantánea y preferiría no confiar en que el cliente de Firestore sea lo suficientemente inteligente como para deduplicarlos (aunque estoy casi seguro de que deduplica).Stream<Messages> messages = f.reference.snapshots()...
, puedes hacerloStream<Messages> messages = Observable.just(f)...
. Lo que me gusta de esta respuesta es que observa los documentos del usuario, por lo que si se actualiza un nombre de usuario en la base de datos, el resultado lo refleja de inmediato.Idealmente, desea excluir cualquier lógica empresarial, como la carga de datos en un servicio separado o seguir el patrón BloC, por ejemplo:
Luego puede usar el Bloque en su componente y escuchar la
chatBloc.messages
transmisión.fuente
Permítame presentar mi versión de una solución RxDart. Utilizo
combineLatest2
con unListView.builder
para construir cada mensaje Widget. Durante la construcción de cada mensaje Widget busco el nombre del usuario con el correspondienteuid
.En este fragmento, uso una búsqueda lineal para el nombre del usuario, pero eso se puede mejorar creando un
uid -> user name
mapafuente
La primera solución que conseguí es anidar dos
StreamBuilder
instancias, una para cada colección / consulta.Como dije en mi pregunta, sé que esta solución no es excelente, pero al menos funciona.
Algunos problemas que veo con esto:
Si conoce una mejor solución, publique como respuesta.
fuente