У меня две коллекции: Пользователи и Книги. Мне нужно получить результаты обоих из них, обновляются ли пользователи или книги, а затем объединить результаты вместе в LinkedHashMap для использования в качестве меню listView.
Я думал, что MediatorLiveData будет подходящим вариантом, но если я помещу запрос Users и Query of Books, тогда я получу null от одного из двух объектов LiveData, потому что срабатывает только один или другой. Я подумал, может быть, если один из них сработает, то, возможно, у меня будет выполняться запрос внутри каждого addSource () в MediatorLiveData, но я не уверен, что это правильный путь.
Мой пост о MediatorLiveData находится здесь: Использование MediatorLiveData для слияния с потоками LiveData (Firestore) QuerySnapshot дает странные результаты
Мои два запроса и объекты LiveData следующие:
//getUsers query using FirebaseQueryLiveData class
private Query getUsersQuery() {
FirebaseAuth mAuth = FirebaseAuth.getInstance();
adminID = mAuth.getUid();
query = FirebaseFirestore.getInstance().collection("admins")
.document(adminID)
.collection("users")
return query;
}
private FirebaseQueryLiveData usersLiveData = new FirebaseQueryLiveData(getUsersQuery());
//getBooks query using FirebaseQueryLiveData class
private Query getBooksQuery () {
FirebaseGroupID firebaseGroupID = new FirebaseGroupID(getApplication());
groupID = firebaseGroupID.getGroupID();
query = FirebaseFirestore.getInstance().collection("books")
.whereEqualTo("groupID", groupID)
return query;
}
private FirebaseQueryLiveData booksLiveData = new FirebaseQueryLiveData(getBooksQuery());
Каким-то образом, когда пользователи обновляются, мне также нужно получить данные о книгах, а затем объединить их, но мне также нужно, чтобы это произошло, если книги обновляются, а затем получают данные пользователей и объединяют их.
Любые идеи очень приветствуются.
Дополнительное примечание / наблюдение Итак, я не исключаю полностью объект MediatorLiveData. Конечно, это позволяет мне прослушивать два разных объекта LiveData в одном методе, однако я не хочу объединять два из них напрямую, потому что мне нужно действовать с каждым объектом liveData индивидуально. Итак, в качестве примера: usersLiveData срабатывает, потому что мы создаем или изменяем пользователя, затем мне нужно запрашивать книги, получать результаты и объединять пользователей и книги и т. д.
Ниже представлены мои данные MediatorLiveData в их нынешнем виде:
//MediatorLiveData merge two LiveData QuerySnapshot streams
private MediatorLiveData<QuerySnapshot> usersBooksLiveDataMerger() {
final MediatorLiveData<QuerySnapshot> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
return mediatorLiveData;
}
Прямо сейчас он возвращает нулевые результаты другого источника LiveData. Вместо этого мне нужно запросить, а затем слить. Есть идеи, как это сделать? По этому поводу не так много информации.
Я попытался поместить запрос внутри функции, которая вызывается с помощью Transformations.map (), но из-за того, что это асинхронный вызов, оператор return вызывается до завершения запроса.
Вот моя попытка использования функции:
private class ListenUsersGetBooks implements Function<QuerySnapshot, LinkedHashMap<User, List<Book>>> {
@Override
public LinkedHashMap<User, List<Book>> apply(final QuerySnapshot input) {
userBookList = new LinkedHashMap<>();
getBooksQuery().get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
List<User> users = input.toObjects(User.class);
List<Book> books = task.getResult().toObjects(Book.class);
Log.d(TAG, "USERLIST! " + users);
Log.d(TAG, "BOOKLIST! " + books);
for (User user : users) {
bookList = new ArrayList<>();
for (Book book : books) {
if (user.getUserID().equals(book.getUserID())
&& book.getBookAssigned()) {
bookList.add(book);
}
else if (user.getAllBookID().equals(book.getBookID())) {
bookList.add(book);
}
}
userBookList.put(user, bookList);
}
Log.d(TAG,"OBSERVE userBookList: " + userBookList);
}
});
return userBookList;
}
}
Вот где я нахожусь с вашими предложениями, Сэм.
Я добавил методы получения и установки в класс MyResult, поскольку в противном случае он не давал мне доступа к переменным-членам в наблюдателе:
public class MyResult {
QuerySnapshot usersSnapshot;
QuerySnapshot booksSnapshot;
//default constructor
public MyResult() {
}
public QuerySnapshot getUsersSnapshot() {
return usersSnapshot;
}
public void setUsersSnapshot(QuerySnapshot usersSnapshot) {
this.usersSnapshot = usersSnapshot;
}
public QuerySnapshot getBooksSnapshot() {
return booksSnapshot;
}
public void setBooksSnapshot(QuerySnapshot booksSnapshot) {
this.booksSnapshot = booksSnapshot;
}
public boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Вот метод MediatorLiveData и get. Я изменил инициализацию класса MyResult на = new MyResult (); думая, что возникла проблема с использованием mediatorLiveData.getValue (); как метод инициализации и получения.
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
public MediatorLiveData<MyResult> getUsersBooksLiveDataMerger() {
return usersBooksLiveDataMerger();
}
И, наконец, наблюдатель:
mainViewModel.getUsersBooksLiveDataMerger().observe(this, new Observer<MainViewModel.MyResult>() {
@Override
public void onChanged(@Nullable MainViewModel.MyResult myResult) {
if (myResult == null || !myResult.isComplete()) {
// Ignore, this means only one of the queries has fininshed
Log.d(TAG, "OBSERVE BLAH!!!!");
return;
}
// If you get to here, you know all the queries are ready!
// ...
List<Book> books;
List<User> users;
books = myResult.getBooksSnapshot().toObjects(Book.class);
users = myResult.getUsersSnapshot().toObjects(User.class);
Log.d(TAG, "OBSERVE MERGE users: " + users);
Log.d(TAG, "OBSERVE MERGE books: " + books);
}
});
Обратите внимание: я сделал нулевую проверку в mediatorLiveData, просто взял его для тестирования.
Каким-то образом мне нужно инициировать запрос моих книг, если запускаются только мои пользователи, И мне нужно инициировать запрос моих пользователей, если запускаются только мои книги ... Я чувствую, что есть шаг до MediatorLiveData, который должен произойти, чтобы мы могли убедиться один liveData запускает другой запрос. Имеет ли это смысл?
Вот простая версия того, что вы могли бы сделать, я надеюсь, это имеет смысл.
Вы близки с MediatorLiveData. Вместо MediatorLiveData<QuerySnapshot> вы, вероятно, захотите использовать такой настраиваемый объект:
class MyResult {
public QuerySnapshot usersSnapshot;
public QuerySnapshot booksSnapshot;
public MyResult() {}
boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Затем в ваших наблюдателях сделайте что-то вроде этого:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.usersSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.booksSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Затем, когда вы наблюдаете за объединенными данными в реальном времени:
usersBooksLiveDataMerger().observe(new Observer<MyResult>() {
@Override
public void onChanged(@Nullable MyResult result) {
if (result == null || !result.isComplete()) {
// Ignore, this means only one of the queries has fininshed
return;
}
// If you get to here, you know all the queries are ready!
// ...
}
});
Вы правы: это должен быть MediatorLiveData <MyResult>
Также я не имел в виду, чтобы этот код был готов для копирования и вставки: обязательно проверьте наличие null, когда это необходимо!
Конечно. Спасибо за исправление. Я настраиваюсь с вашей отправной точки. На данный момент я не получаю никаких результатов в обозревателе. Я посмотрю, смогу ли я определить, где что-то идет не так.
Хорошо, вот проблема, с которой я столкнулся. QuerySnapshots запускаются так, как должны. Однако проблема в том, что мне нужны результаты обоих запросов, даже если срабатывает только один. Из-за того, как настроен ваш образец, я никогда не получу полных результатов, потому что скорее всего сработает только один из них. Я могу редактировать / добавлять пользователя, но когда это происходит, мне все равно нужно запрашивать книги. Я что-то пропустил?
Я чувствую, что мне нужно как-то вызвать другой запрос, когда один срабатывает, ТОГДА объедините их вместе, используя MediatorLiveData.
Хорошо, я вернулся и снова прочитал документы LiveData. Думаю, я понимаю, что для этого должен работать MediatorLiveData. Поскольку LiveData будет обновляться, когда жизненный цикл станет активным или после изменения состояния, как описано в документации. Итак, мне просто нужно выяснить, почему я не передаю данные наблюдателю.
Да, точно. Данные начинают поступать, когда есть активный наблюдатель. Добавление наблюдателя к посреднику также активирует «дочерние» источники.
Несмотря на то, что мне пришлось внести некоторые коррективы в ваш ответ, Сэм, вы помогли мне сузить круг моей проблемы, поэтому я сделаю ваш ответ как САМЫЙ. Еще раз спасибо, @Sam Stern!
Думаю, я решил это. Мы объявляли новый объект MyResult в каждом методе mediatorLiveData.addSource (). Это означало, что мы получали новый объект для каждого QuerySnapshot, чтобы мы никогда не смогли заставить их объединиться друг с другом.
Вот обновление для MediatorLiveData:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
final MyResult current = new MyResult();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
@Override
public void onChanged(@Nullable QuerySnapshot querySnapshot) {
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Теперь получаю пользователей и книги в наблюдателе в Activity! Теперь единственное, что мне нужно сделать, это преобразовать (объединить данные) в LinkedHashMap, но я думаю, что понял это. Спасибо, Сэм!
Вы можете значительно упростить использование, используя мой LiveDataZipExtensionshttps://gist.github.com/Benjiko99/d2e5406aab0a4a775ea747956ae16624
С ними вам не нужно создавать объект для хранения вашего комбинированного результата.
Пример использования
val firstNameLD = MutableLiveData<String>().apply { value = "John" }
val lastNameLD = MutableLiveData<String>().apply { value = "Smith" }
// The map function will get called once all zipped LiveData are present
val fullNameLD = zip(firstNameLD, lastNameLD).map { (firstName, lastName) ->
"$firstName $lastName"
}
Спасибо, Сэм! Я очень ценю руководство. Я пробовал ваш образец кода, но получаю сообщение об ошибке несовместимых типов: MyResult current = mediatorLiveData.getValue (); Мы устанавливаем QuerySnapshot на Class MyResult, и Android это не нравится.