Android MVVM Room создание объектов LiveData RecyclerViewItem другими объектами LiveData

#android #kotlin #mvvm #android-room

#Android #kotlin #mvvm #android-room

Вопрос:

У меня есть класс объекта Room «Симптом» с именем симптома и его идентификатором.

 @Entity(tableName = "symptoms")
data class Symptom(
    @PrimaryKey @NonNull val id: Int,
    val name: String) {

    override fun toString(): String {
        return "Symptom $id: $name"
    }
}
  

Я получаю это в следующих классах:

SymptomDao

 @Dao
interface SymptomDao {

    @Query("SELECT * FROM symptoms WHERE id=:id LIMIT 1")
    fun getSymptom(id: Int): Symptom

    @Query("SELECT * FROM symptoms")
    fun getAllSymptoms(): LiveData<List<Symptom>>
}
  

SymptomRepository

 class SymptomRepository(private val symptomDao: SymptomDao) {

    fun getSymptom(id: Int) = symptomDao.getSymptom(id)

    fun getAllSymptoms() = symptomDao.getAllSymptoms()
}
  

SymptomsViewModel

 class SymptomsViewModel(symptomRepository: SymptomRepository): ViewModel() {

    private val symptomsList = symptomRepository.getAllSymptoms()
    private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()

    fun getAllSymptoms(): LiveData<List<Symptom>> {
        return symptomsList
    }

    fun getAllSymptomsItems(): LiveData<List<SymptomItem>> {
        return symptomsItemsList
    }
}
  

У меня есть RecyclerView со списком симптомов с флажками, чтобы запомнить, какие симптомы списка выбирает пользователь:

 data class SymptomItem(
    val symptom: Symptom,
    var checked: Boolean = false)
  

Вопрос

Мой вопрос в том, как я могу обойтись LiveData<List<SymptomItem>> LiveData<List<Symptom>> ? Я только начал изучать MVVM и не могу найти простого ответа, как это сделать. Я уже пытался заполнить этот список различными способами, но он теряет checked переменную при каждом повороте телефона. Я буду благодарен за любые подсказки.

Ответ №1:

Вам нужно будет сохранить, какие элементы проверяются, сохранив их идентификаторы в списке в ViewModel. Затем вам нужно будет объединить список ваших Symptom объектов и список проверяемых элементов и сгенерировать список SymptomItem объектов.

Я собираюсь использовать Kotlin Flow для достижения этой цели.

 @Dao
interface SymptomDao {

    @Query("SELECT * FROM symptoms")
    fun flowAllSymptoms(): Flow<List<Symptom>>
}
  
 class SymptomRepository(private val symptomDao: SymptomDao) {

    fun flowAllSymptoms() = symptomDao.flowAllSymptoms()
}
  
 class SymptomsViewModel(
    private val symptomRepository: SymptomRepository
) : ViewModel() {

    private val symptomsListFlow = symptomRepository.flowAllSymptoms()

    private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()

    private var checkedIdsFlow = MutableStateFlow(emptyList<Int>())

    init {
        viewModelScope.launch {
            collectSymptomsItems()
        }
    }

    private suspend fun collectSymptomsItems() =
        flowSymptomsItems().collect { symptomsItems ->
            symptomsItemsList.postValue(symptomsItems)
        }

    private fun flowSymptomsItems() =
        symptomsListFlow
        .combine(checkedIdsFlow) { list, checkedIds ->
            list.map { SymptomItem(it, checkedIds.contains(it.id)) }
        }

    fun checkItem(id: Int) {
        (checkedIdsFlow.value as MutableList<Int>).add(id)
        checkedIdsFlow.value = checkedIdsFlow.value
    }

    fun uncheckItem(id: Int) {
        (checkedIdsFlow.value as MutableList<Int>).remove(id)
        checkedIdsFlow.value = checkedIdsFlow.value
    }

    fun getSymptomsItems(): LiveData<List<SymptomItem>> {
        return symptomsItemsList
    }
}
  

В вашем Фрагменте просмотрите getSymptomsItems() и обновите данные вашего адаптера.

Код не тестируется, возможно, вам придется внести небольшие изменения, чтобы он скомпилировался.

Комментарии:

1. Спасибо за совет. К сожалению, он по-прежнему забывает, какие элементы SymptomsItems выбираются (после поворота телефона), и он обновляет RecyclerView каждый раз, когда я нажимаю флажок, поэтому он загружает весь список каждый раз, когда я нажимаю элемент (я уже показываю анимацию флажка, поэтому я не хочу обновлять весь RecyclerView).

2. Вы неправильно вводите свою ViewModel во фрагмент, что, скорее всего, является проблемой. Заменить SymptomsViewModel by inject() на SymptomsViewModel by viewModel() . Требуется зависимость от gradle org.koin:koin-androidx-viewmodel . Документация Koin: doc.insert-koin.io/#/koin-android/viewmodel

3. Чтобы решить другую вашу проблему с RecyclerView, обновляющим весь список каждый раз, вам нужно будет реализовать a DiffUtil.ItemCallback для вашего адаптера. Это позволит RecyclerView выяснить, какие части списка изменились, когда произошло изменение данных. codelabs.developers.google.com/codelabs /…

4. В onBindViewHolder() вам нужно будет установить статус флажка на то, что вы сохранили в SymptomItem . Это для того, чтобы при повороте экрана, поскольку представление будет уничтожено, вам нужно вручную получить его, чтобы визуально сопоставить данные.

5. Это сработало. Большое вам спасибо за вашу помощь, вы спасли меня 🙂