У меня есть приложение чата во Flutter, использующее Firestore, и у меня есть две основные коллекции:
chats
, Который вводится на авто идентификаторов, и имеетmessage
,timestamp
иuid
поле.users
, который включенuid
, и имеетname
поле
В моем приложении я показываю список сообщений (из messages
коллекции), с этим виджетом:
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;
}
}
Но теперь я хочу показать имя пользователя (из users
коллекции) для каждого сообщения.
Я обычно называю это соединением на стороне клиента, хотя я не уверен, есть ли у Flutter определенное имя для него.
Я нашел один способ сделать это (который я разместил ниже), но задаюсь вопросом, есть ли другой / лучший / более идиоматический способ сделать этот тип операции во Флаттере.
Итак: каков идиоматический способ во Флаттере искать имя пользователя для каждого сообщения в приведенной выше структуре?
firebase
flutter
google-cloud-firestore
Фрэнк ван Пуффелен
источник
источник
Ответы:
Я получил другую версию, которая кажется немного лучше, чем мой ответ с двумя вложенными компоновщиками .
Здесь я выделил загрузку данных в пользовательском методе, используя выделенный
Message
класс для хранения информации из сообщенияDocument
и необязательного связанного пользователяDocument
.По сравнению с решением с вложенными компоновщиками этот код более читабелен, в основном потому, что обработка данных и построитель пользовательского интерфейса лучше разделены. Он также загружает пользовательские документы только для пользователей, которые разместили сообщения. К сожалению, если пользователь разместил несколько сообщений, он загрузит документ для каждого сообщения. Я мог бы добавить кеш, но думаю, что этот код уже немного длинен для достижения цели.
источник
Map<String, UserModel>
и загрузить пользовательский документ только один раз.Если я правильно читаю, проблема сводится к следующему: как преобразовать поток данных, который требует асинхронного вызова для изменения данных в потоке?
В контексте проблемы поток данных представляет собой список сообщений, и асинхронный вызов предназначен для извлечения пользовательских данных и обновления сообщений с этими данными в потоке.
Это можно сделать непосредственно в объекте потока Дарт, используя
asyncMap()
функцию. Вот некоторый чистый код Dart, который демонстрирует, как это сделать:Большая часть кода имитирует данные, поступающие из Firebase, в виде потока карты сообщений и асинхронной функции для извлечения пользовательских данных. Важная функция здесь
getMessagesStream()
.Код немного усложняется тем, что это список сообщений, поступающих в потоке. Чтобы предотвратить синхронные вызовы для извлечения пользовательских данных, код использует a
Future.wait()
для сбораList<Future<Message>>
и создания a,List<Message>
когда все фьючерсы завершены.В контексте Flutter вы можете использовать поток из
getMessagesStream()
aFutureBuilder
для отображения объектов Message.источник
Вы можете сделать это с помощью RxDart следующим образом. Https://pub.dev/packages/rxdart
для rxdart 0.23.x
источник
f.reference.snapshots()
, так как это по сути перезагрузка снимка, и я предпочел бы не полагаться на то, что клиент Firestore достаточно умен, чтобы дедуплицировать их (даже если я почти уверен, что он действительно дедуплицирует).Stream<Messages> messages = f.reference.snapshots()...
, вы можете сделатьStream<Messages> messages = Observable.just(f)...
. Что мне нравится в этом ответе, так это то, что он просматривает пользовательские документы, поэтому, если имя пользователя обновляется в базе данных, результат сразу же отражается.В идеале вы хотите исключить любую бизнес-логику, такую как загрузка данных в отдельный сервис или следование шаблону BloC, например:
Тогда вы можете просто использовать Bloc в своем компоненте и слушать
chatBloc.messages
поток.источник
Позвольте мне представить свою версию решения RxDart. Я использую
combineLatest2
сListView.builder
построить каждое сообщение Widget. Во время построения каждого виджета сообщения я ищу имя пользователя с соответствующимuid
.В этом фрагменте я использую линейный поиск имени пользователя, но это можно улучшить, создав
uid -> user name
картуисточник
Первое решение, которое я получил, - это вложение двух
StreamBuilder
экземпляров, по одному для каждой коллекции / запроса.Как указано в моем вопросе, я знаю, что это решение не является хорошим, но, по крайней мере, оно работает.
Некоторые проблемы, которые я вижу с этим:
Если вы знаете лучшее решение, пожалуйста, напишите в качестве ответа.
источник