Recycler View Internals - I: Birth Of ViewHolder

recyclerview Jul 28, 2021

Implementation of Recycler View ins and outs from Google-I/O 2016



Kotlin Weekly #261
onCreate Digest - Issue #67
onCreate Digest is a weekly newsletter with links to Android Development related content.
Special thanks to Matt M. for reviewing and providing feedbacks to improving the article

We all have been working with RecyclerViews for way long and are very familiar using its API to display our list efficiently, but many have just touched its surface on a very high level, few of us have dug deep into it and tried to figure how internally it works. I’m one of the same, I have a generalised idea of what might be happening but I never looked at its implementation from Android open source project (AOSP) but before I do that I wanted to figure out how others in the community have explored it.

After some poking around I was told by many developer to follow talk on RecyclerViews ins and outs - Google I/O 2016 for getting an idea of what RecyclerViews are doing under the hood. If you haven’t seen it yet, I would highly recommend you to watch this video, in-fact bookmark it you will be visiting it a lot in your android career.

It actually does a pretty nice job explaining various components of RecyclerView and those diagrams are ❤️ . But that is also the problem with the video, even though there are so many details in the video but everything is just  in form of diagrams and flows. We don’t see any code examples or implementations that we can relate to understand Recycler view more deeply.

Since that is not done, and I don’t want to hurt my soul by looking into AOSP, I have used decided to use my big 🧠 to implement these diagrams into workable code.

This video is divided into 4 part :

  • Birth Of ViewHolder
  • Adding View to UI
  • Death of ViewHolder
  • Animation on RecyclerView Operation

From the title of the article, you must have figured that I will be covering the first section i.e Birth Of View-Holder, before that lets look into RecyclerView Components.

RecycleView Components

First half of the video introduce us to the various components of the RecyclerView. Let’s make our classes accordingly to it.

p.s. In comments I have added responsibilities of each component.

/**
 *  Layout Manager :
 *  - Position the Views
 *  - Scrolling Views
 */
class LayoutManager

/**
 *  Recycler View :
 *  - Coordinator of all component
 *  - Interaction with Elements
 */
class RecyclerView

/**
 *  Adapter :
 *  - Create View and ViewHolder
 *  - binding data to ViewHolder
 *  - Multiple View Types
 *  - Recycle Recovery
 *  - notify data change and out of sync
 */
class Adapter

/**
 *  Item Animator :
 *  - Animates the views
 */
class ItemAnimator

/**
 *  View Holder :
 *  - item interaction handler
 */ 
class ViewHolder()

Enjoying the Post?

a clap is much appreciated if you enjoyed. No sign up or cost associated :)

If you want to support my content do consider dropping a tip/coffee/donation💰


You can clearly see Adapter are expressed as View Providers but do lot more things than that like

  • Binding data,
  • Notify data changes,
  • Recycle recovery,
  • Working with Multiple view types and more

Lets get started with the Implementation

Birth Of View Holder

There three possible scenario encountered in flow of creating of ViewHolder

  • View Cache Success
  • View Cache Failed, Recycler Pool Success
  • View Cache Failed and Recycler Pool Fails

View Cache Success ✅

In the first section, we learn that the LayoutManager requests RecyclerView for the View at a Position. Recycler is a very fast component because it tries to cache data on each step.

So instead of going through an expensive cycle of creating a new view, it will try to return it from the cache.

The scenario in which Recycler’s View Cache Pass i.e it will return cached View instance immediately satisfying layout manager request.

First flow use case results in the following component interaction,

 Component interactions :
    [Layout Manager] ---- getViewForPosition ----> [Recycler View]
    [Recycler View] ---- getViewForPosition ----> [View Cache]
    [View Cache] ---- cache returns View ✅ ----> [Recycler View]
    [Recycler View] ---- returns View ----> [Layout Manager]

You can follow the sequence to see flow of control.



In Program this translates to :

// layout manager calls function getViewForPosition over recycler 
// thus has a recycler.
class LayoutManager(private val recycler: Recycler) {
    fun getViewForPosition(pos: Int): View {
        return recycler.getView(pos)
    }
}
class RecyclerView(
    private val viewCache: ViewCache
) {
    fun getView(pos: Int): View {
        val viewOrNull = viewCache.getOrNull(pos)
        // view found then return view
        if (viewOrNull != null) {
            return viewOrNull
        }
        return TODO()
    }
}

class ViewCache {
    private val viewCache = mutableMapOf<Int, View>() // LRU cache
    fun getOrNull(pos: Int): View? = viewCache.get(pos)
}

If you don’t know what is TODO() checkout my article from here :

Kotlin- TODO or Die
It’s important to have the feature implemented first and then later focus on its optimisation. As a software engineer, our itch to find out what’s the best and effective approach to do something…

View Cache Failed ❌, Recycler Pool Success ✅

When View Cache Fails to return a view for a given position, it goes through series of steps to create a new view.

Let's break these steps down :

  1. RecyclerView after receiving failure from ViewCache , ask Adapter to return ViewType for the position.
  2. After receiving ViewType, RecyclerView checks its RecyclerPool to see if cache of ViewHolder is present or not.
  3. If RecyclerPool Passes i.e. returns a ViewHolder, then RecyclerView takes that ViewHolder to the Adapter, and tries to bind new data to it.
  4. Once bound RecyclerView takes ViewHolder’s View and return it to the Layout Manager.

Told you View Creation is expensive task. Let’s see component interaction in this process :

    Component interactions :
    [Layout Manager] ---- getViewForPosition ----> [Recycler View]
    [Recycler View] ---- getViewForPosition ----> [View Cache]
    [View Cache] ---- cache returns null ❌ ----> [Recycler View]
    [Recycler View] ---- getViewType ----> [Adapter]
    [Adapter] ---- ViewType ----> [Recycler View]
    [Recycler View] ---- getViewHolderByType ----> [RecyclerPool]
    [RecyclerPool] ---- ViewHolder ----> [Recycler View]
    [Recycler View] ---- bindViewHolder ----> [Adapter]
    [Adapter] ---- View ----> [Recycler View]
    [Recycler View] ---- View ----> [Layout Manager]

You can follow sequence  to see flow of control :


Enjoying the Post?

a clap is much appreciated if you enjoyed. No sign up or cost associated :)

If you want to support my content do consider dropping a tip/coffee/donation💰


In Program this translates to :


class LayoutManager(private val recycler: Recycler) {
    fun getViewForPosition(pos: Int): View {
        return recycler.getView(pos)
    }
}

class RecyclerView(
    private val adapter: Adapter,
    private val recyclerPool: RecyclerPool,
    private val viewCache: ViewCache
) {

    fun getView(pos: Int): View {
        val viewOrNull = viewCache.getOrNull(pos)
        // view found then return view
        if (viewOrNull != null) {
            return viewOrNull
        }
        // if view not found in cache get view type
        val viewType = adapter.getViewType(pos)
        // from view type get viewholder from cache 
        val holderOrNull = recyclerPool.getViewHolderByType(viewType)
        // view holder found -> bind data and return view
        if (holderOrNull != null) {
            adapter.bindViewHolder(holderOrNull, pos)
            return holderOrNull.itemView
        }
        return TODO()
    }
}

class ViewCache {
    private val viewCache = mutableMapOf<Int, View>() // LRU cache
    fun getOrNull(pos: Int): View? = viewCache.get(pos)
}

class RecyclerPool {
    private val cache = mutableMapOf<ViewType, ViewHolder>() // LRU cache
    fun getViewHolderByType(viewType: ViewType): ViewHolder? {
        return cache.get(viewType)
    }
}

abstract class Adapter {
    abstract fun getViewType(pos: Int): ViewType
    abstract fun bindViewHolder(holder: ViewHolder,pos:Int)
}

IMO, API is coming out nicely designed just by following the diagrams in the Video.


View Cache failed ❌, Recycler Pool failed ❌

RecyclerPool is a cache of Viewholders, what will happen if the Viewholder you are looking for isn’t there in cache?

Let’s see :

  1. If RecyclerPool fails, it will return null to RecyclerView
  2. RecyclerView will go to Adapter and request it to create new ViewHolder from the ViewType.
  3. Adapter will create a new ViewHolder and bind it with the data for the requested position.
  4. Adapter will return ViewHolder to RecyclerView and RecyclerView return the View back to LayoutManger.

So only one step is extra when RecyclerPool fails, Adapter will create new Viewholder and binds the data.

Lets see the component interaction :

  Component interactions :
    [Layout Manager] ---- getViewForPosition ----> [Recycler View]
    [Recycler View] ---- getViewForPosition ----> [View Cache]
    [View Cache] ---- cache returns null ❌ ----> [Recycler View]
    [Recycler View] ---- getViewType ----> [Adapter]
    [Adapter] ---- ViewType ----> [Recycler View]
    [Recycler View] ---- getViewHolderByType ----> [Recycler Pool]
    [Recycler Pool] ---- returns ViewHolder ❌ ----> [Recycler View]
    [Recycler View] ---- createViewHolder ----> [Adapter]
    [Adapter] ---- bindViewHolder ----> [Adapter]
    [Adapter] ---- ViewHolder ----> [Recycler View]
    [Recycler View] ---- View ----> [Layout Manager]

Follow the sequence for flow of control :



In Program this translates to :


class LayoutManager(private val recycler: Recycler) {
    fun getViewForPosition(pos: Int): View {
        return recycler.getView(pos)
    }
}

class Recycler(
    private val adapter: Adapter,
    private val recyclerPool: RecyclerPool,
    private val viewCache: ViewCache
) {

    fun getView(pos: Int): View {
        val viewOrNull = viewCache.getOrNull(pos)
        // view found then return view
        if (viewOrNull != null) {
            return viewOrNull
        }
        val viewType = adapter.getViewType(pos)
        val holderOrNull = recyclerPool.getViewHolderByType(viewType)
        // view holder found -> bind and return view
        if (holderOrNull != null) {
            adapter.bindViewHolder(holderOrNull,pos)
            return holderOrNull.itemView
        }

        // create viewholder and bind viewholder and return view
        val holder = adapter.createViewHolder(viewType)
        adapter.bindViewHolder(holder,pos)
        return holder.itemView
    }
}

class ViewCache {
    private val viewCache = mutableMapOf<Int, View>()
    fun getOrNull(pos: Int): View? = viewCache.get(pos)
    fun put(pos: Int, view: View) {
        viewCache.put(pos, view)
    }
}

abstract class Adapter {
    abstract fun getViewType(pos: Int): ViewType
    abstract fun bindViewHolder(holder: ViewHolder,pos:Int)
    abstract fun createViewHolder(viewType: ViewType): ViewHolder
}

Adapter and ViewHolder is of abstract type, if you want to see how will their implementation looks like check the code below 👇:


/**
 * Represent ViewTypes which user provides
 */
sealed class ViewType {
    object ListItemHeader : ViewType()
    object ListItemContent : ViewType()
}

/**
 * Test View Holders
 */
class ContentHeader(view: View) : ViewHolder(view) {
    fun bind(){...}
}
class ContentContent(view: View) : ViewHolder(view) {
    fun bind(){...}
}

/**
 * Implementation of Adapter Class
 */

class PractieAdapter : Adapter() {

    /**
     * Similar to getItemViewType() of Recycler Adapter
     */
    override fun getViewType(pos: Int): ViewType {
        return when (pos) {
            0 -> ViewType.ListItemHeader
            else -> ViewType.ListItemContent
        }
    }

    /**
     * Similar to onBindViewHolder() of Recycler Adapter
     */
    override fun bindViewHolder(holder: ViewHolder, pos:Int) {
        when (holder) {
            is ContentHeader -> holder.bind()
            is ContentContent -> holder.bind()
        }
    }

    /**
     * Similar to onCreateViewHolder() of Recycler Adapter
     */
    override fun createViewHolder(viewType: ViewType): ViewHolder {
        when (viewType) {
            ViewType.ListItemContent -> TODO()
            ViewType.ListItemHeader -> TODO()
        }
    }
}

Conclusion

Alrighty, the design logically appears to be correct till the point where the birth of the view holder is explained in the video.

Hope you like my implementation, if you don’t please suggest improvement in my repository where i’m experimenting with the code :

GitHub - ch8n/Recycler-View-Internals: Recycler View Internals | How to create your own recycler view
Recycler View Internals | How to create your own recycler view - GitHub - ch8n/Recycler-View-Internals: Recycler View Internals | How to create your own recycler view

In the second part of the article, we will continue with how the view of the layout manager is added to the UI. So stay tuned.

If you like my content and want to see more, click right here to see more 👇

About 👨🏻‍💻
@ch8nLinktree. Make your link do more.Linktree [https://linktr.ee/ch8n]Hey bud 🙌, Welcome to my personal blogging site! This is a place where I keep all of my tips and tricks regarding Android Development , Kotlin and Java, which I have gathered from my experience and various sources. So this is …

Until next time, Happy Hacking! 👨🏻‍💻


Enjoying the Post?

a clap is much appreciated if you enjoyed. No sign up or cost associated :)

If you want to support my content do consider dropping a tip/coffee/donation💰


Reach me out :

Chetan Gupta
Software engineer 👨‍💻, Kotlin 🚀 Android 📱.



Chetan gupta

Hi there! call me Ch8n, I'm a mobile technology enthusiast! love Android #kotlinAlltheWay, CodingMantra: #cleanCoder #TDD #SOLID #designpatterns

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.