Как фильтровать список живых данных на основе карты живых данных?

#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 с LiveData

3. Да, в зависимости от того, пуста ли локальная база данных, извлекаются удаленные данные.

Ответ №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’ должна вызываться только из сопрограммы или другой функции приостановки»