Eu tenho um aplicativo de bate-papo no Flutter usando o Firestore e tenho duas coleções principais:
chats
, Que é introduzido na auto-ids, e temmessage
,timestamp
euid
campos.users
, que está ativadouid
e tem umname
campo
No meu aplicativo, mostro uma lista de mensagens (da messages
coleção), com 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;
}
}
Mas agora eu quero mostrar o nome do usuário (da users
coleção) para cada mensagem.
Normalmente eu chamo isso de associação do lado do cliente, embora não tenha certeza se Flutter tem um nome específico para ela.
Eu encontrei uma maneira de fazer isso (que eu publiquei abaixo), mas me pergunto se existe outra maneira / melhor / mais idiomática de fazer esse tipo de operação no Flutter.
Então: qual é a maneira idiomática no Flutter de procurar o nome de usuário para cada mensagem na estrutura acima?
firebase
flutter
google-cloud-firestore
Frank van Puffelen
fonte
fonte
Respostas:
Eu tenho outra versão funcionando, que parece um pouco melhor do que minha resposta com os dois construtores aninhados .
Aqui, eu isolado no carregamento de dados em um método personalizado, usando uma
Message
classe dedicada para armazenar as informações de uma mensagemDocument
e o usuário associado opcionalDocument
.Comparado à solução com construtores aninhados, esse código é mais legível, principalmente porque o tratamento de dados e o construtor de UI são melhor separados. Ele também carrega apenas os documentos do usuário para os usuários que postaram mensagens. Infelizmente, se o usuário postou várias mensagens, ele carregará o documento para cada mensagem. Eu poderia adicionar um cache, mas acho que esse código já é um pouco longo para o que ele realiza.
fonte
Map<String, UserModel>
e carregar o documento do usuário apenas uma vez.Se estou lendo isso corretamente, o problema é resumido em: como você transforma um fluxo de dados que requer uma chamada assíncrona para modificar dados no fluxo?
No contexto do problema, o fluxo de dados é uma lista de mensagens e a chamada assíncrona é buscar os dados do usuário e atualizar as mensagens com esses dados no fluxo.
É possível fazer isso diretamente em um objeto de fluxo Dart usando a
asyncMap()
função Aqui está um código Dart puro que demonstra como fazê-lo:A maior parte do código está imitando os dados provenientes do Firebase como um fluxo de um mapa de mensagens e uma função assíncrona para buscar dados do usuário. A função importante aqui é
getMessagesStream()
.O código é um pouco complicado pelo fato de ser uma lista de mensagens que chegam no fluxo. Para impedir que chamadas para buscar dados do usuário ocorram de forma síncrona, o código usa a
Future.wait()
para reunir aList<Future<Message>>
e criar aList<Message>
quando todos os futuros tiverem sido concluídos.No contexto do Flutter, você pode usar o fluxo proveniente de
getMessagesStream()
aFutureBuilder
para exibir os objetos Message.fonte
Você pode fazer isso com o RxDart assim. Https://pub.dev/packages/rxdart
para rxdart 0.23.x
fonte
f.reference.snapshots()
, pois isso é essencialmente recarregar o instantâneo e eu preferiria não confiar que o cliente Firestore seja inteligente o suficiente para deduplicá-los (mesmo que eu esteja quase certo de que ele é desduplicado).Stream<Messages> messages = f.reference.snapshots()...
, você pode fazerStream<Messages> messages = Observable.just(f)...
. O que eu gosto nessa resposta é que ela observa os documentos do usuário; portanto, se um nome de usuário é atualizado no banco de dados, a saída reflete isso imediatamente.Idealmente, você deseja excluir qualquer lógica comercial, como carregamento de dados em um serviço separado ou seguir o padrão BloC, por exemplo:
Então você pode apenas usar o bloco no seu componente e ouvir o
chatBloc.messages
fluxo.fonte
Permita-me apresentar minha versão de uma solução RxDart. Eu uso
combineLatest2
com umListView.builder
para criar cada widget de mensagem. Durante a construção de cada mensagem Widget, procuro o nome do usuário com o correspondenteuid
.Neste snippet, uso uma pesquisa linear para o nome do usuário, mas isso pode ser melhorado com a criação de um
uid -> user name
mapafonte
A primeira solução que trabalhei é aninhar duas
StreamBuilder
instâncias, uma para cada coleção / consulta.Como afirmado na minha pergunta, sei que esta solução não é ótima, mas pelo menos funciona.
Alguns problemas que vejo com isso:
Se você conhece uma solução melhor, poste como resposta.
fonte