Rescue View Sanity in RecyclerView!
keeping your sanity intact by releasing the view listeners on the holders
In development, we come across various challenges which demand displaying large amounts of data sets that can be scrolled very efficiently without risking the performance cost is where the
RecyclerView comes into the picture.
An advanced and flexible version of ListView, that encourages
Memory Management by
recycling the views as it uses ViewHolder that does the magic in reducing the ViewCreation by keeping the
MainThread away from a CPU Intensive Task, thus improving
But since it recycles items, Views that have the toggling behavior (checkbox, switch…etc) end up losing their current state (selected by the user) on scrolling, resulting in unpredictable behavior inside the RecyclerView.
In order to keep the sanity intact of the view, one might be forced to use the Data Structure (like HashMap, SparseBooleanArray… etc), and that’s where my patience starts to run out.
We’ve come far across it so I am gonna show you how I’ve preserved the current state of the checkbox using
data classes in Kotlin :
- Let’s first create a data class to hold the data
data class ListItem(val checkboxTextValue: String, val checkboxPreviousState: Boolean, var checkboxCurrentState: Boolean = checkboxPreviousState)
- checkboxPreviousState: holds the state of
checkboxprior to user interaction, a
valproperty (read-only boolean variable)
- checkboxCurrentState: holds the state of the
checkboxafter the user interaction
varproperty (you can mutate the state)
- default initialization with the checkboxPreviousState
2. The Adapter class looks like :
3. The ViewHolder class looks like :
But, a loophole still persists in the ViewHolder class which is :
The ViewHolder entails that the
setOnCheckedChangeListener will execute when you click the checkbox but in real — it is firing at a time you’re not expecting.
The ViewHolder that was previously associated with the checked/unchecked checkbox is going to be
recycled and then
re-used to display another view that might be checked/unchecked. At that point,
onBindViewHolder will be called which will trigger
item_checkbox.isChecked = textListItem.checkboxCurrentState
and ended up triggering the
setOnCheckedChangeListener on each item while scrolling down the list, resulting in the changes in the state of views, which were checked previously too.
So, in order to put the situation right, we need to set the state change listener of a checkbox to
null as the first thing to prevent the default execution of
setOnCheckedChangeListener inside the
Though, to eliminate the scenario we do have several other options — one of them is by calling the click listener of a
checkbox rather than listening to the events from
setOnClickListener will fire the event — only when the
setOnCheckedChangeListener which will fire the event every time the
checkbox state mutates.
Moving to the next option that we have in a platter is by setting the listener inside the init block — which is another handy option to opt by, but this approach holds the faults of its own as it
caches the view references which will turn out to be the prime reason for memory leaks in android.
The references to the previous views won’t be flushed even after the data is refreshed.
The next one is to release the resources as soon as the view is out of the window in
onViewDetachedFromWindow(VH holder) always follows a matching
onViewAttachedToWindow(VH holder). It's called in the exact moment when the view holder is becoming visible or invisible (attach or detach calls).
viewHolder was detached but not recycled yet, it's possible it can receive
onViewAttachedToWindow(ViewHolder) call again without needing to rebind data with
Also, Yes! You can always release the resources in the
onViewRecycled(VH holder) method which is called right before clearing ViewHolder’s internal data and sending it to the
RecycledViewPool which gives you the benefit of memory recollection by dumping the cached data consumed by the resources.
Well, it is highly recommended — If an item view has large or expensive data bound to it such as large bitmaps, this may be a good place to release those resources.
With these solutions, I have eliminated the possible holes that could become a pain for the app. I had encountered the same scenario which leads me to come up with these resolutions, though the door’s always open to exploring for more.
For just a simple view like this, I’d rather recommend you use any of the above approaches but the fifth one would be a reliable solution in case of the complex views too.
I hope that reading this will be as useful for you as writing it was for me. The link to the project has been added below.
This is my very first technical article on Medium after being motivated by my colleague who is blessedly good at coding. Chetan Garg helped me out during my findings and if you wanna dive deep into Kotlin, read his articles.
Stay Safe… Enjoy Life & Keep Coding!