Как наблюдать возвращаемое значение из класса репозитория в ViewModel?

У меня есть приложение для Android, использующее архитектуру MVVM. По нажатию кнопки я запускаю сопрограмму, которая вызывает метод ViewModel для выполнения сетевого запроса. В моей ViewModel у меня есть LiveData, наблюдаемый для возврата этого запроса, но я не вижу его обновления. Кажется, что мой метод репозитория не вызывается, и я не знаю, почему.

Пользовательский интерфейс Click Listener

searchButton.setOnClickListener{
    CoroutineScope(IO).launch{
        viewModel.getUser(username.toString())
    }
}

ViewModel — наблюдаемые и вызываемый метод

private var _user: MutableLiveData<User?> = MutableLiveData<User?>()
val user: LiveData <User?>
    get() = _user
...

suspend fun getUser(userId:String) {
   _user = liveData{
       emit(repository.getUser(userId))
   } as MutableLiveData<User?>
}
...

Когда я выполняю отладку, выполнение переходит в метод getUser ViewModel, но не переходит в область liveData для обновления моего наблюдаемого _user MutableLiveData, и я не уверен, что делаю неправильно.

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

Ответы 2

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

Нет необходимости использовать liveData построитель сопрограмм, потому что getUser является приостановленной функцией, и вы уже вызываете ее в сопрограмме. Просто опубликуйте результат просто на _user.

suspend fun getUser(userId: String) {
    _user.postValue(repository.getUser(userId))
}

То, что вы сделали в своем коде, вызвало назначение нового экземпляра LiveData_user, в то время как наблюдатель во фрагменте наблюдает за предыдущим LiveData, экземпляр которого создается private var _user: MutableLiveData<User?> = MutableLiveData<User?>(). Таким образом, обновление теряется.


Лучшее решение — обрабатывать создание сопрограмм в вашем классе ViewModel, чтобы отслеживать их и предотвращать утечку выполнения.

fun getUser(userId: String) {
    viewModelScope.launch(IO) {
        _user.postValue(repository.getUser(userId))
    }
}

И во фрагменте:

searchButton.setOnClickListener{
    viewModel.getUser(username.toString())
}

Это не работает, потому что ваша «структура MVVM» не соответствует ни рекомендациям MVVM, ни рекомендациям структурированного параллелизма, предусмотренным для сопрограмм.

    searchButton.setOnClickListener{
        CoroutineScope(IO).launch{ // <-- should be using a controlled scope
            viewModel.getUser(username.toString()) // <-- state belongs in the viewModel
        }
    }

Вместо этого он должен выглядеть так

searchButton.setOnClickListener {
    viewModel.onSearchButtonClicked()
}

username.doAfterTextChanged {
    viewModel.updateUsername(it)
}

И

class MyViewModel(
    private val application: Application,
    private val savedStateHandle: SavedStateHandle
): AndroidViewModel(application) {
    private val repository = (application as CustomApplication).repository

    private val username = savedStateHandle.getLiveData("username", "")

    fun updateUsername(username: String) {
        username.value = username
    }
    
    val user: LiveData<User?> = username.switchMap { userId -> 
        liveData(viewModelScope + Dispatchers.IO) {
            emit(repository.getUser(userId))
        }
    }
}

Теперь вы можете сделать user.observe(viewLifecycleOwner) { user -> ... }, и это должно сработать. Если вам действительно нужно получать только при нажатии кнопки, вы можете заменить liveData { обычным вызовом suspend fun, вызывающим из viewModelScope.launch {, и сохранить значение в LiveData.

Большое спасибо за ваш отзыв. Я не совсем понял, что состояние должно быть инкапсулировано ViewModel в MVVM, так что это помогает.

pingOfDoom 21.12.2020 16:17

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