My case is as following: Supposing that I have an app where I show a list of users, and their profile pics (similar to whatsapp).
First I load the list of users, and observer the LiveData of users. The problem is, that the url of their profile pic, doesn't come with the getUsersList API. Instead, I have to do another network call on fly (while rendering the list), in order to retrieve the profile pic for a given User, let's say getUserPicByUserId.
Using, MVVM design pattern, I did this implementation:
In Fragment class I load the list of Users from
getUsersListAPI. For each user item,profilePicUrlis Null.In Adapter/ViewHolder class I check if
profilePicUrlis null. If so, using a listener(MyAdapterListener), I do a call togetUserPicAPI for the given User.When the response of
getUserPicAPI is ready, I update the user item in ViewHolder (see the lambda functiononUrlLoaded: (url: String) -> Unit) and load the image.
Problems: This approach is causing that all items get the same image, because the observer of userProfilePicLiveData is always listening for each user item. And every item will load the last retrieven url.
Adding:
viewModel.userProfilePicLiveData.removeObservers(lifecycleOwner) after callback onUrlLoaded(profilePicUrl) in Fragment will not work either.
I spent some time on this problem, but cannot find a solution.
What would be the appropriate approach for this scenario? How to perform a network call when rendering each RecyclerView item, and send the result back to the Adapter to update the view?
Here there is the simplified code of what I have done so far:
Model User
data class User(
val id: String,
val name: String,
val profilePicUrl: String? = null, // By default is null
...
)
profilePicUrl doesn't come with getUsersList API, so by default is Null.
Model UserProfilePic:
data class UserProfilePic(
val url: String
...
)
Implementation example of ViewModel class:
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
}
}
Adapter class:
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
}
}
}
}
Fragment class:
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)
})
}
}