У меня есть приложение для 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, и я не уверен, что делаю неправильно.
Нет необходимости использовать 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, так что это помогает.