Back to List

How to Build a Custom Android ListAdapter

Michael Fazio Michael Fazio  |  
Mar 30, 2021
 

Michael Fazio is a tech speaker and senior software developer at Skyline Technologies - a Core BTS Company. He’s been the Android lead for multiple billion-dollar companies and has seen the Android development process evolve from far too many Activities and a nigh unusable emulator to the actually enjoyable dev experience of Kotlin + Jetpack.

This is a snippet from his new book, "Kotlin and Android Development featuring Jetpack", that covers how to build a ListAdapter class for a native (Kotlin-based) Android app.
 

Create a Custom List Adapter

The PlayerSummaryAdapter class is responsible for managing all the PlayerSummary items in our list and handling how they’re displayed. We use a custom RecyclerView.ViewHolder inner class (meaning it lives inside PlayerSummaryAdapter) to bind a PlayerSummary item to the layout, then the RecyclerView library handles the rest. All we need to do in PlayerSummaryAdapter is tell the RecyclerView what to do when creating and binding a new ViewHolder, plus how to tell the difference between PlayerSummary items in the list.

After creating PlayerSummaryAdapter in the adapters package, first up is the PlayerSummaryViewHolder inner class. The PlayerSummaryAdapter class both contains and depends on this class, so we’ll create it first then wrap PlayerSummaryAdapter around it. The PlayerSummaryViewHolder class inherits from RecyclerView.ViewHolder and has a single function, bind(), which takes in a PlayerSummary object.

The bind() function doesn’t do much other than assign binding.playerSummary to the item value. The binding value is an instance of PlayerSummaryListItemBinding, which was generated by the data binding library when we added the generic <layout> tag to the player_summary_list_item.xml file. The item value, then, is the PlayerSummary object coming into the method. Once that assignment is complete, bind() then ensures bindings are executed so the data shows up properly with the executePendingBindings() function.

inner class PlayerSummaryViewHolder(
    private val binding: PlayerSummaryListItemBinding
) : RecyclerView.ViewHolder(binding.root) {

    fun bind(item: PlayerSummary) {
        binding.apply {
            playerSummary = item
            executePendingBindings()
        }
    }
}

The PlayerSummaryAdapter class around this inner class inherits from ListAdapter, which takes two type parameters and a DiffUtil.ItemCallback instance. The type parameters are the type of item in the list (PlayerSummary) and the type of ViewHolder for those items (PlayerSummaryAdapter.PlayerSummaryViewHolder). The callback piece is a new private class at the end of the file (uncreatively) called PlayerSummaryDiffCallback. That class looks like this:

private class PlayerSummaryDiffCallback :
    DiffUtil.ItemCallback<PlayerSummary>() {

    override fun areItemsTheSame(
        oldItem: PlayerSummary,
        newItem: PlayerSummary
    ): Boolean = oldItem.id == newItem.id

    override fun areContentsTheSame(
        oldItem: PlayerSummary,
        newItem: PlayerSummary
    ): Boolean = oldItem == newItem
}

With both PlayerSummaryDiffCallback and PlayerSummaryViewHolder ready, we can get PlayerSummaryAdapter created. This class, which inherits from ListAdapter, will also contain a few overridden functions that we’ll create in a bit. The class declaration plus the other class and function from before together look like this:

class PlayerSummaryAdapter :
    ListAdapter<PlayerSummary, PlayerSummaryAdapter.PlayerSummaryViewHolder>(
        PlayerSummaryDiffCallback()
    ) {

    //Overridden functions will go here in a bit.

    inner class PlayerSummaryViewHolder(
        private val binding: PlayerSummaryListItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: PlayerSummary) {
            binding.apply {
                playerSummary = item
                executePendingBindings()
            }
        }
    }
}

private class PlayerSummaryDiffCallback :
    DiffUtil.ItemCallback<PlayerSummary>() {

    override fun areItemsTheSame(
        oldItem: PlayerSummary,
        newItem: PlayerSummary
    ): Boolean = oldItem.id == newItem.id

    override fun areContentsTheSame(
        oldItem: PlayerSummary,
        newItem: PlayerSummary
    ): Boolean = oldItem == newItem
}

There should be an error with the PlayerSummaryAdapter as written since we’ve yet to implement the two abstract functions from ListAdapter: onCreateViewHolder() and onBindViewHolder(). Both functions are effectively one step so we can get them done pretty quickly.

onCreateViewHolder() needs to know how to build instances of PlayerSummaryViewHolder. That means we’re inflating our layout using the DataBindingUtil class as we have done a few times in this book, sending that into a new PlayerSummaryViewHolder instance, and returning that from the function.

override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int
): PlayerSummaryViewHolder =
    PlayerSummaryViewHolder(
        DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.player_summary_list_item,
            parent,
            false
        )
    )

onBindViewHolder() is even more straightforward as it uses a PlayerSummaryViewHolder instance from onCreateViewHolder(), then sends a PlayerSummary item into the bind() function. We use the getItem() function from the ListAdapter class to get the correct PlayerSummary based on where we are in the list. This is a major advantage of inheriting from the ListAdapter class - it does almost all the work for us as far as handling the items and retrieving the correct one.

override fun onBindViewHolder(
    viewHolder: PlayerSummaryViewHolder,
    position: Int
) {
    viewHolder.bind(getItem(position))
}

The PlayerSummaryAdapter is now ready for use, so we can head over to the RankingsFragment class to get everything connected.
 

Connect Adapter to RecyclerView

Here, we’re expanding on what we set up earlier with RankingsFragment. Inside the onCreateView() function, we instantiate a PlayerSummaryAdapter object, then assign that to the RecyclerView. Retrieving that RecyclerView object turns out to be easier than previous times we’ve gotten view components because the entire view we inflated earlier is a <RecyclerView>. As a result, we can convert the view value into a RecyclerView instance, then assign the adapter property. We’re also going to add an ItemDecoration to the RecyclerView, which adds light gray lines between each row.

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val view = inflater.inflate(R.layout.fragment_rankings, container, false)
    val playerSummaryAdapter = PlayerSummaryAdapter()

    if (view is RecyclerView) {
        with(view) {
            adapter = playerSummaryAdapter

            addItemDecoration(
                DividerItemDecoration(context, LinearLayoutManager.VERTICAL)
            )
        }
    }

    return view
}

This is another great example of smart casting in Kotlin that we first saw in Chapter 5. Since we checked that view is an instance of RecyclerView, view is treated in that entire block as a RecyclerView instance without having to create a new value.

Also, we normally would have assigned a value to the layoutManager property on RecyclerView like this:

layoutManager = LinearLayoutManager(context)

However, it wasn’t required since we already handled setting a LayoutManager in the <RecyclerView> tag inside fragment_rankings.xml.

The RecyclerView is now complete and has an assigned adapter to handle all its data. The last piece we need to cover here is how to get that data from the database into the PlayerSummaryAdapter. To do that, we’re going to create RankingsViewModel and observe a LiveData value from there.
 

Thanks for Reading

Hopefully you enjoyed this preview of "Kotlin and Android Development featuring Jetpack" and gained some insight into custom ListAdapter classes in Android.

Want to see more about the Penny Drop app or Android development? "Kotlin and Android Development featuring Jetpack" can be found in eBook form (PDF/ePub/mobi) on PragProg.com and soon as a physical book from various booksellers.

For more info about the RecyclerView and ListAdapter classes, check out the "Create dynamic lists with RecyclerView" article from the Android Developer team.

 

Love our Blogs?

Sign up to get notified of new Skyline posts.

 


Related Content


Spring 2019 Kentico User Group
Apr 17, 2019
Location: Waukesha County Technical College - Pewaukee Campus - 800 Main Street, Pewaukee, Wisconsin 53072 - Building: Q, Room: Q361
Blog Article
Why I Stopped Testing My Code (and You Should Too)
Todd TaylorTodd Taylor  |  
Feb 23, 2021
About the author: Todd M. Taylor is a Senior Software Engineer with a passion for producing quality software using Microsoft Azure technologies.   I confess that the title of this blog post is clickbait. I feel a little ashamed that I've stooped so low to get your attention.But as...
Blog Article
Developer Playbook for Getting Started with DevOps
Bob SchommerBob Schommer  |  
Jan 26, 2021
About the author: Bob Schommer is a Senior Consultant with over 10 years of agile experience who has trained over 300 professionals on agile and DevOps practices. He has coached and advised large and small organizations to optimize their software delivery chain. The buzzword to end all buzzwords...
Blog Article
Manager Playbook for Getting Started with DevOps
Bob SchommerBob Schommer  |  
Jan 12, 2021
About the author: Bob Schommer is a Senior Consultant with over 10 years of agile experience who has trained over 300 professionals on agile and DevOps practices. He has coached and advised large and small organizations to optimize their software delivery chain. Agile practices and DevOps...
Blog Article
Executive Playbook for Getting Started with DevOps
Bob SchommerBob Schommer  |  
Dec 22, 2020
About the author: Bob Schommer is a Senior Consultant with over 10 years of agile experience who has trained over 300 professionals on agile and DevOps practices. He has coached and advised large and small organizations to optimize their software delivery chain. You have probably heard about...