Creating Lists – How to Implement RecyclerView in the MVVM? Use DiffUtils
Using RecyclerView has already become a standard in the case of mobile apps. It’s flexible and offers many extension options. That’s the reason why it works so well when you have to display a list on smartphones or tablets.
Continuous development of this tool makes it more efficient and easier to implement. But it’s not all rosy. When there are many available libraries, architectures, and methods, it’s difficult to choose the best solutions.
Check out one of the methods we use at Holdapp.
What is RecyclerView?
Android RecyclerView is a library used to display a big amount of data. It reuses the views by injecting different data into them. This way you don’t waste resources for creating, storing, or deleting the views.
The mechanism is quite simple. You build the views based on a template (ViewHolder). They are filled with the data of the elements that should be currently displayed. Additionally, the RecyclerView library keeps something in a buffer – the downloaded views of the elements outside of the area that is currently displayed on a screen. The goal is to ensure that the app will run smoothly.
DiffUtil – what is it and why should you use it?
It’s a tool that makes it possible to calculate the difference between two lists. DiffUtil returns a list of operations that turn the first list into the second one. You can apply it to increase the effectiveness of the Adapter used in RecyclerView.
DiffUtil relies on Eugene W. Mayers’ algorithm. It calculates a minimal number of updates needed to turn the original list into the list with results. The algorithm alone doesn’t detect which elements have changed their positions, but DiffUtil will handle it.
What’s the role of ViewModel?
If you consider using Clean Architecture together with MVVM, updating the status of the list should be done in a ViewModel. It’s not only about making a list shorter or longer but also about changing the status of the displayed elements.
This change can be called out by making an operation on this element (e.g., tapping). The ViewModel updates the status of a list that is observed from the Activity or Fragment level. However, it must be informed on which element the action has been called out.
After changing the observed list, a new one gets injected again to the Adapter. Because of DiffUtil, the process of updating the list is more efficient. Only the elements that have changed their status get to be rebuilt.
Example
I am going to analyze a simple use case of the RecyclerView – displaying a list of tappable elements. Tapping triggers an action that changes the color of the presented text.
The layer of data passes on the list of elements that must be shown on a screen to the UI layer that displays it. It’s simple, right? Let’s take a look at it from the technical perspective – assuming that the recommended architecture and MVVM were used.
The layer responsible for displaying the elements is supposed to show the list based on the current status of the app. The status of the app (a.k.a. a list that should be displayed) is stored in the ViewModel. The list has been downloaded from the data layer by the Repository. ViewModel triggers the action related to the element of the list.
In a call-out, you have to include the information required to update the status (usually the entire element that triggers the action is passed on). Finally, the view should be updated (MVVM) based on the status.
Displaying the elements of the list
Let’s consider a situation when you don’t present all the information that the element downloaded from the Repository includes. Such an element can be very big and cover much unnecessary information and linkage.
In such a case, you rebuild the element downloaded from Repository, so it would only contain the information displayed on the list. Moreover, you need to add attributes related to displaying the element – if they aren’t related to the attribute of the element downloaded from the data layer. What does it look like in practice?
Preparing to implementation
For the implementation, I use Android Studio and the JetPack tools recommended by Google. But to show you how to play, I must build a sports ground first. This means I have to implement Activity, Fragment, ViewModel, Repository, and Model.
Below, I present only the fragments of code required to explain the described method of the RecyclerView.
data class User(
val id: Int,
val firstName: String,
val lastName: String,
val nickName: String,
val email: String,
val city: String,
val country: String,
val speaksEnglish: Boolean,
val motto: String,
val position: JobCategory
)
User data class implementation
As I’ve proposed before, I rebuild a detailed type to one that has only the field required to display a list and relate it to the base element.
data class UserPresentation(
val id: Int,
val firstName: String,
val lastName: String,
val isHighlighted: Boolean = false
)
UserPresentation data class implementation
UserPresentation is a data class that should be an immutable structure (according to the recommendations in the Kotlin language’s documentation). In order to change it, I need to make a deep copy. For this reason, the methods have been added that extend a list of elements of that type.
fun List<UserPresentation>.changeSingleSelectionOnListOfPresentationItems(
selectedItemId: Int
): List<UserPresentation> = this.map {
it.copy(
isHighlighted = if (it.id == selectedItemId) !it.isHighlighted else false
)
}
fun List<UserPresentation>.changeMultipleSelectionOnListOfPresentationItems(
selectedItemId: Int
): List<UserPresentation> = this.map {
it.copy(
isHighlighted = if (it.id == selectedItemId) !it.isHighlighted else it.isHighlighted
)
}
Extension methods in UserPresentationExtension file
Methods update the field responsible for displaying. The first method is used when only one element can be selected. The second one comes in handy when there’s a possibility to choose multiple elements.
Implementing RecyclerView
I need the adapter inside a fragment or activity.
private lateinit var listAdapter: UserRecyclerListAdapter
Adapter declaration as a property in the Fragment class
Then, I initiate a new field and assign a RecyclerView of the declared view to the adapter. Here, I also define the layoutManager. Optionally, I add a dividing element, DividerItemDecoration.
private fun initRecyclerView() = with(binding.clickableListFragmentRecyclerview) {
listAdapter = UserRecyclerListAdapter(::onUserSelected)
adapter = listAdapter
layoutManager = LinearLayoutManager(requireContext())
addItemDecoration(
DividerItemDecoration(context, (layoutManager as LinearLayoutManager).orientation)
)
}
RecyclerView initialization in Fragment
The method above is called out in onViewCreated.
Implementing DiffUtillCallback
object UserPresentationDiffUtilItemCallback :
DiffUtil.ItemCallback<UserPresentation>() {
override fun areItemsTheSame(oldItem: UserPresentation, newItem: UserPresentation): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: UserPresentation, newItem: UserPresentation): Boolean =
oldItem == newItem
}
UserPresentationDiffUtilCallback class implementation
When DiffUtillCallback was being implemented, the type of the compared elements was applied with the data class (I wonder if you know why).
Implementing Adapter
class UserRecyclerListAdapter(
private val onItemClick: (Int) -> Unit
) : ListAdapter<UserPresentation, ViewHolder>(
UserPresentationDiffUtilItemCallback
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemBinding = UserPresentationItemViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return UserPresentationViewHolder(itemBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
(holder as UserPresentationViewHolder).bind(position)
}
inner class UserPresentationViewHolder(
private val itemBinding: UserPresentationItemViewBinding
) : RecyclerView.ViewHolder(itemBinding.root){
fun bind(position: Int){
val bindingItem = getItem(position)
itemBinding.userPresentationItemFirstName.text = bindingItem.firstName
itemBinding.userPresentationItemLastName.text = bindingItem.lastName
updateHighlight(bindingItem.isHighlighted)
itemBinding.userPresentationItemRoot.setOnClickListener {
onItemClick(bindingItem.id)
}
}
private fun updateHighlight(isHighlighted: Boolean){
if ( isHighlighted ){
itemBinding.root.setCardBackgroundColor(
ContextCompat.getColor(itemView.context, R.color.red)
)
itemBinding.userPresentationItemFirstName.let {
it.setTypeface( it.typeface, Typeface.BOLD)
}
itemBinding.userPresentationItemLastName.typeface = Typeface.DEFAULT_BOLD
}
else {
itemBinding.root.setCardBackgroundColor(
ContextCompat.getColor(itemView.context, R.color.grey_light)
)
itemBinding.userPresentationItemFirstName.typeface = Typeface.DEFAULT
itemBinding.userPresentationItemLastName.typeface = Typeface.DEFAULT
}
}
}
}
Adapter class implementation
Cooperation – RecyclerView & ViewModel
private fun onUserSelected(presentationItemId: Int) {
viewModel.onSingleSelectUserPresentationClick(presentationItemId)
}
OnClick method inside SingleSelectFragment implementation
private fun onUserSelected(presentationItemId: Int){
viewModel.onMultipleSelectUserPresentationClick(presentationItemId)
}
OnClick method inside MultipleSelectFragment implementation
fun onSingleSelectUserPresentationClick(userId: Int) {
viewModelScope.launch(defaultDispatcher) {
userRepository.getUserById(userId).fold(
{ user ->
_stateRecyclerList.update { state ->
state.copy(
userPresentationsList = _stateRecyclerList.value.userPresentationsList
.changeSingleSelectionOnListOfPresentationItems(userId),
currentSelectedUser = user
)
}
},
{ _stateRecyclerList.update { state -> state.copy(error = it) } }
)
}
}
OnClick method for Single Select inside ViewModel implementation
fun onMultipleSelectUserPresentationClick(userId: Int) {
viewModelScope.launch(defaultDispatcher) {
userRepository.getUserById(userId).fold(
{ user ->
_stateRecyclerList.update { state ->
state.copy(
userPresentationsList = _stateRecyclerList.value.userPresentationsList
.changeMultipleSelectionOnListOfPresentationItems(userId),
currentSelectedUser = user
)
}
},
{ _stateRecyclerList.update { state -> state.copy(error = it) } }
)
}
}
OnClick method for Multiple Select inside ViewModel implementation
The results
In the illustrations below, you can see what you can achieve with a described implementation. On the first screen of both groups, there’s a sheet with empty values. When the element on the list is selected, the sheet fills out with the person’s data.
In the first group, you can choose only one element of the list. In the second group, you already have a few elements. In both cases, on the main screen, there is the data of the last tapped element. Double tap on the element deselects it.
I hope that now you know how to quickly implement RecyclerView with tappable elements. Last final tip – in the case of the method described in this article, it’s worth paying attention to the proper implementation of the Status and its updates. You’d also better check if the cooperation with DiffUtillCallback is being executed correctly.
You can find the code of the app above on our GitHub.
And that’s it! If you have any questions, write them down in the comment section, and I’ll try to help you out.