RecycleView Adapter with Click Listener

April 17, 2020

park-slope.JPG

There are many ways to write a RecyclerView Adapter depending on the amount of the data it can handle, how often the data gets updated, etc. Lately, I’ve been using the following approach for basic use cases where Paging Library is not needed:

Adapter

class RepoAdapter(private val clickListener: (RepoOwner) -> Unit) :
    ListAdapter<Repo, RepoAdapter.RepoViewHolder>(RepoDiffCallback()) {
    
    override fun onCreateViewHolder(parent: ViewGroup, position: Int): RepoViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = RepoItemBinding.inflate(inflater, parent, false)
        return RepoViewHolder(binding, clickListener)
    }

    override fun onBindViewHolder(holder: RepoViewHolder, position: Int) {
      holder.bind(getItem(position))
    }
} 

class RepoViewHolder(
  private val binding: RepoItemBinding,
  private val clickListener: (RepoOwner) -> Unit
) : RecyclerView.ViewHolder(binding.root), View.OnClickListener  {
  
  private lateinit var item: Repo
  
  init {
    binding.repoOwner.setOnClickListener(this)
  }
  
  fun bind(item: Repo) {
    this.item = item
    binding.repoName.text = item.name
    binding.repoOwner.apply {
      text = item.owner.login
  }

  override fun onClick(v: View?) {
  	v?.let { clickListener.invoke(item.owner) }
  }
}

class RepoDiffCallback : DiffUtil.ItemCallback<Repo>()   

First, I am using a ListAdapter and a DiffCallback to avoid re-rendering all items in the Adapter when items are added, updated or removed.

Second, notice the click listener is passed into the Adapter’s constructor. The custom View Holder could be made an inner class so that it has access to the click listener but personally I think too much nesting makes the code less readable. Constructor “injection” (passing the click listener in the constructor to the ViewHolder gives us a blueprint what the ViewHolder is about.

Fragment

class ReposFragment : Fragment() {

    private val recyclerViewAdapter = RepoAdapter   

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.reposRecyclerView.adapter = recyclerViewAdapter

        viewModel.repos.observe(viewLifecycleOwner, Observer 
            
        )

        viewModel.lookupRepos()
    }

    private fun onRepoOwnerClicked(repoOwner: RepoOwner) {
        val action = ReposForQueryFragmentDirections
            .actionReposToUserDetails(repoOwner.login)
        findNavController().navigate(action)
    }
}

As you see, I am creating an Adapter and passing a click listener (lambda that calls onRepoOwnerClicked(owner)) into it. The actual click will be handled by the Fragment that created it.

This way all navigation, tracking, etc is done in a consistent manner—in Fragments themselves.

You can see the full source code including the navigation component setup here.

 
Previous
Previous

Making Android app Material

Next
Next

Managing local git branches