Как синхронизировать состояние ViewModel с (комнатой) БД?

Я читал большую часть документации по Android и многое другое, но не могу найти информацию по этой теме. Я знаю, как создавать и редактировать состояние внутри моей модели представления, как описано во многих лабораторных работах по коду Android. Но я не уверен, как связать свое состояние внутри viewModel с данными моей БД.

О моем приложении

  • Приложение должно отображать список объектов на главном экране.
  • Приложение должно хранить этот список объектов в базе данных моей комнаты.
  • Пользователь должен иметь возможность добавлять объекты в этот список.

Это моя модель представления:

@HiltViewModel
class MyViewModel @Inject constructor(
    private val myRepository: MyRepository
) : ViewModel() {

    private val _list = getList().toMutableStateList()
    val List: List<MyEntity>
        get() = _list

    private fun getList(): List<MyEntity> {
        return myRepository.getList()
    }

    fun addEntity(entity: MyEntity) {
        myRepository.upsertMyEntity(entity)
    }
}

Внутри моего Composable я инициализирую viewModle следующим образом:

myViewModel: MyViewModel = hiltViewModel<MyViewModel>()

и внутри составного элемента я получаю значение списка следующим образом:

myViewModel.List

Проблема

С данным кодом моя ViewModel получает состояние базы данных списка только один раз при запуске.

private val _list = getList().toMutableStateList()

Как MutableStateList может узнать, что я добавил объект в БД? Или наоборот, как БД может узнать, что я добавил объект в Список?

Возможное решение

Я мог бы отредактировать функцию «addEntity» внутри моей модели представления следующим образом:

fun addEntity(entity: MyEntity) {
    myRepository.upsertMyEntity(entity)
    _list.add(entity)
}

Но является ли это лучшей практикой? Разве MutableStateList не должен быть каким-то образом напрямую подключен к базе данных? Таким образом, создается впечатление, что я добавляю что-то вручную в единственный источник истины (мою базу данных) и вручную обновляю свое состояние, которое, возможно, может оказаться в другом состоянии, чем моя база данных. Например, если база данных выдает исключение, но состояние пользовательского интерфейса все равно меняется.

1
0
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если вы измените свой Dao так, чтобы он возвращал Flow<List<MyEntity>>, Room создаст поток, который автоматически обновит свое значение, когда что-то изменится в базе данных. Ваша модель представления просто преобразует это в StateFlow:

val list: StateFlow<List<MyEntity>> = myRepository.getList()
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = emptyList(),
    )

Теперь ваши составные элементы могут просто собрать этот поток в Compose State вот так:

val list: List<MyEntity> by myViewModel.list.collectAsStateWithLifecycle()

(нужна зависимость androidx.lifecycle:lifecycle-runtime-compose в Gradle)

Этот список в Compose теперь всегда будет содержать текущее состояние вашей базы данных: если вы добавите новое значение, оно также будет автоматически обновляться. addEntity может оставаться как есть, его единственная обязанность — обновлять базу данных, а не состояние пользовательского интерфейса.

Приятно знать, что комната может создавать потоки! Большое спасибо за ответ, Левиафан :)

CodeEnjoyer 06.04.2024 01:01

Вы можете получить список из своего Дао как Поток и наблюдать за ним. Например:

@Dao
interface RoomDao{
  @Query("SELECT * FROM table)
  fun getList(): Flow<List<MyEntity>>
}

В репозитории используйте dao

class Repository(val roomDao: RoomDao){
fun getList(): Flow<List<MyEntity>> = roomDao.getList()

В представленииModel

@HiltViewModel
class MyViewModel @Inject constructor(
 private val myRepository: MyRepository
) : ViewModel() {

private val list = myRepository.getList().stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = emptyList(),
)}

Для Room можно использовать либо Flow, либо LiveData.

Эй, спасибо за ваш ответ, но я не смог вызвать .asStateFlow() в своем списке типа Flow<List<MyEntity>>

CodeEnjoyer 06.04.2024 00:58

Ничего страшного, поменял. Рад, что вы получили ответ, который вам нужен

Cj_Rodriguez 06.04.2024 01:03

Другие вопросы по теме