#android #mvvm #android-recyclerview #android-adapter #android-livedata
#Android #mvvm #android-recyclerview #android-адаптер #android-livedata
Вопрос:
Мой случай следующий: предположим, что у меня есть приложение, в котором я показываю список пользователей и фотографии их профилей (аналогично WhatsApp). Сначала я загружаю список пользователей и наблюдаю за LiveData
пользователями. Проблема в том, что URL-адрес их изображения профиля не поставляется с getUsersList
API. Вместо этого мне нужно выполнить еще один сетевой вызов на лету (при рендеринге списка), чтобы, скажем, получить изображение профиля для данного пользователя getUserPicByUserId
.
Используя шаблон проектирования MVVM, я выполнил эту реализацию:
-
В классе Fragment я загружаю список пользователей из
getUsersList
API. Для каждого пользовательского элементаprofilePicUrl
равно нулю. -
В классе Adapter / ViewHolder я проверяю, является ли
profilePicUrl
значение null. Если это так, используя прослушиватель (MyAdapterListener
), я выполняю вызовgetUserPic
API для данного пользователя. -
Когда ответ
getUserPic
API готов, я обновляю пользовательский элемент в ViewHolder (см. лямбда-функциюonUrlLoaded: (url: String) -> Unit
) и загружаю изображение.
Проблемы: Этот подход приводит к тому, что все элементы получают одинаковое изображение, потому что наблюдатель userProfilePicLiveData
всегда прослушивает каждый пользовательский элемент. И каждый элемент будет загружать последний повторный URL.
Добавление:
viewModel.userProfilePicLiveData.removeObservers(lifecycleOwner)
после обратного вызова onUrlLoaded(profilePicUrl)
во фрагменте также не будет работать.
Я потратил некоторое время на эту проблему, но не могу найти решение.
Какой подход был бы подходящим для этого сценария? Как выполнить сетевой вызов при рендеринге каждого RecyclerView
элемента и отправить результат обратно на адаптер для обновления представления?
Здесь есть упрощенный код того, что я сделал до сих пор:
Модель пользователя
data class User(
val id: String,
val name: String,
val profilePicUrl: String? = null, // By default is null
...
)
profilePicUrl
не поставляется с getUsersList
API, поэтому по умолчанию является Null
.
Модель UserProfilePic:
data class UserProfilePic(
val url: String
...
)
Пример реализации класса ViewModel:
class MyViewModel: ViewModel() {
val usersListLiveData = LiveData<List<User>>
val userProfilePicLiveData = LiveData<UserProfilePic>
fun loadUsers() {
// Network call...
usersListLiveData.value = usersList
}
fun loadProfilePicByUserId(userId: String) {
// Network call...
userProfilePicLiveData.value = userProfilePic
}
}
Класс адаптера:
class RecyclerViewAdapter(val usersList: List<User>): RecyclerView.Adapter<MyViewHolder>() {
interface MyAdapterListener {
fun onLoadProfilePicUrl(
user: User,
onUrlLoaded: (url: String) -> Unit
)
}
class HomeVenueViewHolder(
val listener: MyAdapterListener
) : RecyclerView.ViewHolder() {
fun bind(user: User) {
// Fill view list item ...
if (user.profilePicUrl is Null ) {
listener.onLoadProfilePicUrl(user) { url ->
user.profilePicUrl = url
// Load Image From the Url
}
} else {
// Valid url: Load image
}
}
}
}
Класс фрагмента:
class MyFragment: Fragment(), MyAdapterListener {
val viewModel: MyViewModel
viewModel.usersListLiveData.observe(viewLifecycleOwner, { usersList ->
// Setup adapter and show list
})
fun loadUsers() {
viewModel.loadUsers()
}
override fun onLoadProfilePicUrl(
user: User,
onUrlLoaded: (url: String) -> Unit
) {
viewModel.loadProfilePicByUserId(user.id)
viewModel.userProfilePicLiveData.observe(viewLifecycleOwner, { profilePicUrl ->
// This is a callback to Adapter
onUrlLoaded(profilePicUrl)
})
}
}
Комментарии:
1. почему ваш API возвращает пользователя без его изображения? это не похоже на полноценный пользовательский объект, вероятно, не имеет отношения к вашему вопросу
2. @a_local_nobody Для этого ничего не нужно делать. Услуга предлагается третьей стороной, и я должен найти решение с учетом этих ограничений.
3. Почему бы просто не подождать, пока вы не выполните оба вызова API, прежде чем отправлять данные на адаптер?
4. @GavinWright Каким образом? Я пытался сделать это на
Interceptor
уровне. Повторение для каждого пользователя и выполнениеgetProfilePic
для каждого пользователя для завершенияUser
объекта. Но это привело к значительной задержке… Есть ли лучшее решение для этого?
Ответ №1:
Ваша ViewModel должна обновлять список, хранящийся в, usersListLiveData
каждый раз loadProfilePicByUserId(userId: String)
, когда извлекает новое изображение профиля из API. Ваш фрагмент должен отслеживать usersListLiveData
и уведомлять адаптер при изменении данных (используя adapter.notifyDataSetChanged()
или эквивалент).
Одно предостережение заключается в том, что User
это сложный объект, и usersListLiveData
это список этих сложных объектов. Вам не обойтись простым изменением одного свойства profilePicUrl
в соответствующем элементе списка. Вам нужно вызвать что-то вроде usersListLiveData.value = newUserList
, иначе наблюдатель MutableLiveData не сработает. Наблюдатели не будут срабатывать, когда вы просто измените одно свойство сложного объекта. Вам нужно вызвать setValue()
или postValue()
.