#firebase #flutter #dart #google-cloud-firestore
# #firebase #flutter #dart #google-облако-firestore
Вопрос:
У меня есть коллекция firebase с именем «обзоры» с вложенной коллекцией «клиенты».
Я хочу получить все обзоры в режиме реального времени от их владельцев из Firebase Firestore, но я немного растерялся, когда дело дошло до правильного сопоставления данных и возврата результата слушателя.
Это модель «отзывов»:
class Review {
final String reviewTitle;
final String reviewContent;
final String reviewCategory;
final String reviewTimestamp;
final int reviewVotesCount;
final Client client;
Review(
{this.reviewTitle,
this.reviewContent,
this.reviewCategory,
this.reviewTimestamp,
this.reviewVotesCount,
this.client});
}
Это класс обслуживания:
class ReviewService {
var currentUser = FirebaseAuth.instance.currentUser;
var firestoreInstance = FirebaseFirestore.instance;
List<Review> fetchAllThreads() {
Review review;
Client client;
List<Thread> mReviewsList = new List<Review>();
firestoreInstance.collection('reviews').snapshots().listen((result) {
result.docs.forEach((result) {
firestoreInstance
.collection('reviews')
.doc(result.id)
.collection('clients')
.get()
.then((result) {/*here I get the result.data()*/});
});
});
}
Вопрос после того, как я получу result.data()
, как я могу сопоставить его с моей моделью, чтобы я мог добавить объект результата mReviewsList
, а затем вернуть mReviewsList
?
Комментарии:
1. Вы не сможете вернуть a
List<Review>
непосредственно изfetchAllThreads
, потомуlisten()
что он асинхронный и возвращается немедленно. Обратный вызов вызывается некоторое время спустя после получения результатов запроса. Вместо этого вы должны сделать эту функцию асинхронной, чтобы возвращать будущее, которое разрешается после завершения запроса. Вы также должны использоватьget()
вместоsnapshots()
, чтобы получить единый набор результатов для возврата.2. Но если вам нужны результаты в реальном времени с течением времени, вы вообще не сможете вернуть их из функции, поскольку функция может вернуть только один раз. Функция должна выполнять обратный вызов для получения снимков по мере их доступности или возвращать поток, который можно прослушивать. Вызывающему абоненту необходимо будет правильно отказаться от подписки на поток, иначе произойдет утечка.
Ответ №1:
Вы можете добавить конструктор factory в свой Review
класс, чтобы создать его из Map, и то же самое относится к Client
.
factory Review.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Review(
reviewTitle: map['reviewTitle'],
reviewContent: map['reviewContent'],
reviewCategory: map['reviewCategory'],
reviewTimestamp: map['reviewTimestamp'],
reviewVotesCount: map['reviewVotesCount'],
client: Client.fromMap(map['client']),
);
}
Если вы используете VS Code, там может пригодиться расширение ‘Dart Data Class Generator’, а также в pub.dev есть несколько пакетов генерации кода для сериализации и десериализации
Теперь вместо вашего комментария вы можете сделать это:
mReviewsList.add(Review.fromMap(result.data()));
Обновить:
Основываясь на комментарии Дуга, если вы хотите сопоставить свои данные с вашей моделью и вернуть поток, вы можете создать вспомогательную функцию следующим образом:
Stream<List<T>> collectionStream<T>({
@required String path,
@required T builder(Map<String, dynamic> data),
}) {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = reference.snapshots();
return snapshots
.map((snapshot) => snapshot.docs.map((snapshot) => builder(snapshot.data())).toList());
}
Чтобы использовать его, просто вызовите его следующим образом:
final stream = collectionStream<Review>(path: "reviews", builder: (data) => Review.fromMap(data));
если вы хотите получить данные только один раз, вы также можете создать вспомогательную функцию для этого:
Future<List<T>> getDocuments<T>({
String path,
@required T builder(Map<String, dynamic> data),
}) async {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = await reference.get();
final docs = snapshots.docs.map((doc) => builder(doc.data())).toList();
return docs;
}
и назовите это так же:
final reviews = getDocuments<Review>(path: "reviews", builder: (data) => Review.fromMap(data));
Комментарии:
1. К сожалению, это просто преобразует моментальный снимок в другой объект. В нем не рассматривается, как вернуть эти данные вызывающей стороне, учитывая, что данные доступны внутри асинхронного обратного вызова.
2. @DougStevenson Я сосредоточился
how can I map it to my model
только на части, но теперь я обновил ответ, чтобы охватить весь вопрос.3. после настройки здесь и там, я заставил его работать, ваше решение кажется подходящим, но единственная проблема сейчас заключается в том, что возвращаемый результат пуст
4. @ThorvaldOlavsen используете ли вы эмулятор firestore? Если это так, попробуйте без использования эмулятора (я тестировал его локально, и он не работает с эмулятором, но отлично работает при подключении к удаленному firestore).
5. возможно, вам следует добавить данные клиента в данные отзывов. Другой вариант — запустить два потока, а затем объединить их, возможно, с помощью такой библиотеки, как RxDart.