#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()
. Требуется зависимость от gradleorg.koin:koin-androidx-viewmodel
. Документация Koin: doc.insert-koin.io/#/koin-android/viewmodel3. Чтобы решить другую вашу проблему с RecyclerView, обновляющим весь список каждый раз, вам нужно будет реализовать a
DiffUtil.ItemCallback
для вашего адаптера. Это позволит RecyclerView выяснить, какие части списка изменились, когда произошло изменение данных. codelabs.developers.google.com/codelabs /…4. В
onBindViewHolder()
вам нужно будет установить статус флажка на то, что вы сохранили вSymptomItem
. Это для того, чтобы при повороте экрана, поскольку представление будет уничтожено, вам нужно вручную получить его, чтобы визуально сопоставить данные.5. Это сработало. Большое вам спасибо за вашу помощь, вы спасли меня 🙂