Image for post
Image for post

Update recycler view content without refreshing the data.

Miguel J. Sesma
Jan 15, 2019 · 4 min read

Avoid graphical glitches by modifying specific ViewHolder items without redrawing them.

Showing information in a list, or a set of cards is probably the most commonly used graphical widget in any app. The process we usually follow is:

  • Create a model class with all the information needed in the view.
  • Fill a list of those model classes with the data to be shown and pass it to the RecyclerView adapter.
  • If something changes, we update that list and tell the adapter to calculate the differences (DiffUtil) and show the new data.

DiffUtil is a great improvement over the old notifyDataSetChanged method that forced the layout manager to redraw all the visible items. With DiffUtil only the changed items need to be redrawn. But what if all the items in a list need a small change?

Sometimes we need to update the content of the RecyclerView cells, the ViewHolders, but we don’t want to add more information to our model or refresh the information in the adapter and do an expensive DiffUtil calculation. If the change affects several cells, it can cause the whole list flicker.

Image for post
Image for post

In the above gif, a surgeon searches for ‘knee’ related surgical procedures in our Touch surgery app. As the user writes, the text is highlighted in every displayed row. This is the simplest case: every item must receive the same information. this effect can be implemented with an observable that every row can observe, so we won’t need specific data for each item and the list can be fast scrolled and every title will still show the correct search highlight because even recycled rows will be subscribed to the observable.


// Code in the ViewHolder that subscribes to the observable
override fun setSearchObservable(searchObservable: Observable<String>): Disposable {
return searchObservable
.subscribe { text ->
searchText = text

And the adapter:

//code in the adapter that sets the observable at View Holder creation time
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val viewHolder: ProcedureViewHolder = .....

return viewHolder

With only a few lines of code, the ViewHolder observes the search text, unnecessary updates are prevented with debounce and distinct operators, and calls the setTitle() method. The code in the adapter is even simpler: in the onCreateViewHolder() method the observable is set and the disposable is added to a CompositeDisposable.

This system works perfectly when we need to send the same information to every row, but it does not scale to sending different data to each row or only a few of them.

Let’s create a progress indicator for our items. We will need to change the indicator icon and update the progress bar with a percentage value. This percentage can be different for each item, of course.

Image for post
Image for post

RecyclerView adapter has overloads of notifyItemChanged and notifyItemRangeChanged methods with a payload: Object parameter that will allow us to pass an object to one or several items without completely redrawing them.

Both notifyItemChanged and notifyItemRangeChanged adapter methods are item change events, not structural change events. This means that if the modified item(s) are already bound to a ViewHolder, the updated item will be bound to the same ViewHolder that is already painted. So we only need to set a specific value for the element in the item we want to change.


// Code in the view Holder
fun bind(model: phaseModel){
...// Draw the whole item

fun setProgress(value: Int){

And the adapter:

// Code in the adapter
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
when {
payloads.isEmpty() -> holder.bind(models[position]
else -> holder.setprogress(payloads[0] as Int)

fun setProgress(progresses: List<Int>) {
progresses.forEachIndexed { i, _ ->
notifyItemChanged(i, it)

Calling notifyItemChanged and notifyItemRangeChanged with a payload parameter, a progress percentage in the example, will cause onBindViewHolder to be called for that item with a list of all the payloads set for that item. We can select to draw the whole item or only update an element of it depending on this list being empty. Passing a null payload to notifyItemChanged or notifyItemRangeChanged will clear all payloads for that item.

This technique can be used for multiple purposes like selecting or expanding items or showing animations inside a card.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store