#kotlin #android-livedata #mutablelivedata
#kotlin #android-livedata #изменяемые живые данные
Вопрос:
Я должен выполнять асинхронные операции. В viewmodel они должны работать вместе для пользовательского интерфейса. Как я могу отфильтровать список LiveData на основе ключей в карте LiveData? (Идентификаторы объектов в списке соответствуют ключам карты)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
private val _allJourneys = MutableLiveData<List<Journey>>()
val allJourneys: LiveData<List<Journey>> get() = _allJourneys
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
private val _enrolledMap = MutableLiveData<Map<String, String>>()
val enrolledMap: LiveData<Map<String, String>> get() = _enrolledMap
fun getEnrolled() {
viewModelScope.launch {
progressRepository.getEnrolledJourneysMapOfUser().observeForever {
Timber.d("Map values: $it")
_enrolledMap.value = it
}
}
}
fun getJourneys() {
viewModelScope.launch {
journeysRepository.getAll().observeForever { it ->
_allJourneys.value = it.filter {
// enrolledMap.containsKey(it.id) ??? Nullpointer
}
}
}
}
Комментарии:
1. вы хотите фильтровать поездки на основе ключей enrolledMap, правильно? Как насчет использования преобразований ? Таким образом, вы можете переместить наблюдателя в пользовательский интерфейс и просто передать ссылку на LiveData. MediatorLiveData также может быть полезен
2. @Stachu Спасибо за комментарий. На каком этапе архитектуры мне фильтровать поездки на основе карты? Я использую MVVM и облачный Firestore.
3. выполнять
journeysRepository.getAll()
иprogressRepository.getEnrolledJourneysMapOfUser()
возвращать LiveData?4. @Stachu LiveData<Список выхода<Путешествие>>, это так.
Ответ №1:
как насчет чего-то подобного (на основе примера MediatorLiveData отсюда)
val allJourneys: LiveData<List<Journey>> = journeysRepository.getAll()
val enrolledMap: LiveData<Map<String, String>> = progressRepository.getEnrolledJourneysMapOfUser()
private val _filteredJourneys = MediatorLiveData<List<Journey>>()
private val filteredJourneys: LiveData<List<Journey>> = _filteredJourneys
init {
_filteredJourneys.addSource(allJourneys) {
combineLatestData(allJourneys,enrolledMap)
}
_filteredJourneys.addSource(enrolledMap) {
combineLatestData(allJourneys,enrolledMap)
}
}
private fun combineLatestData(
journeysLD: LiveData<List<Journey>>,
enrolledLD: LiveData<Map<String, String>>
): List<Journey>? {
val j = journeysLD.value
val e = enrolledLD.value
val result = listOf<Journey>()
if (j == null || e == null) {
return result // TODO filter j by e
}
return null
}
изменения в allJourney
и enrolledMap
LiveData должны вызвать combineLatestData
Комментарии:
1. В верхнем значении: функция приостановки ‘GetAll’ должна вызываться только из сопрограммы или другой функции приостановки
2.
getAll
приостановлено из-за облачного хранилища Firestore? Для Room я никогда не использую suspend с LiveData3. Да, в зависимости от того, пуста ли локальная база данных, извлекаются удаленные данные.
Ответ №2:
observeForever
это неправильный подход внутри ViewModel. При каждом get__
вызове будет создаваться новая подписка, и вы получите несколько подписок и, возможно, утечки памяти внутри вашей ViewModel, пока сборщик мусора не очистит застрявшие подписки.
Вместо этого, поскольку вы используете LiveData, вы должны просто использовать возвращаемые значения и сохранять их в виде полей.
val allJourneys: LiveData<List<Journey>> get() = journeysRepository.getAll()
val enrolledMap: LiveData<Map<String, String>> get() = progressRepository.getEnrolledJourneysMapOfUser()
//fun getEnrolled() {
// viewModelScope.launch {
// .observeForever {
// Timber.d("Map values: $it")
// _enrolledMap.value = it
// }
// }
//
//}
//fun getJourneys() {
// viewModelScope.launch {
// .observeForever { it ->
// _allJourneys.value = it.filter {
// // enrolledMap.containsKey(it.id) ??? Nullpointer
// }
// }
// }
//}
Поскольку LiveData из репозитория, который поступает из DAO, уже будет обрабатывать выборку фоновых данных.
Если это делается вручную с приостановкой функций в репозитории, то вы можете использовать switchMap
liveData { emitSource
— но в данном случае это не кажется необходимым.
Комментарии:
1. Если я сохраняю их как поля, я получаю сообщение об ошибке: «Функция приостановки ‘GetAll’ должна вызываться только из сопрограммы или другой функции приостановки»