Logo

dev-resources.site

for different kinds of informations.

Android RecyclerView single choice adapter

Published at
12/6/2019
Categories
android
recyclerview
databinding
kotlin
Author
bigyan4424
Author
10 person written this
bigyan4424
open
Android RecyclerView single choice adapter

More often than not, we as android developers come across a requirement to display a list of objects and make that list selectable in a single choice mode. Over the time, several ways of implementations have been done. I wanted to share the approach that I took recently to achieve this functionality. This requires basic understanding of kotlin, data binding and a bit of mvvm.

The basic idea here is to keep track of the selected position in the adapter as observable value and right after this changes, call notifyItemChanged() for the oldPosition and the newPosition with will re-bind the user to the views that will in turn reverse the check-mark for the associated RadioButton. When the list item is clicked, it updates the selectedPosition to the position of the clicked list item.

User

    data class User(val name: String, val address: String, val age: Int)

RecyclerView Adapter

    class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder> {

        var users: List<User> = emptyList()
            set(value) {
                field = value
                notifyDataSetChanged()
            }

       // This keeps track of the currently selected position
       var selectedPosition by Delegates.observable(-1) { property, oldPos, newPos ->
            if (newPos in items.indices) {
                notifyItemChanged(oldPos)
                notifyItemChanged(newPos)
           }
       }

        override fun onCreateViewHolder(parent: ViewGroup, viewType:   Int): UserViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val viewBinding: ViewListItemBinding =
                        DataBindingUtil.inflate(layoutInflater,R.layout.view_list_item, parent, false)
        return UserViewHolder(viewBinding)
        }

       override fun getItemCount(): Int = users.size

       override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        if (position in users.indices){
            holder.bind(items[position], position == selectedPosition)
            holder.itemView.setOnClickListener { selectedPosition = position }
        }
    }

    inner class UserViewHolder(private val viewBinding: ViewListItemBinding) :
        RecyclerView.ViewHolder(viewBinding.root) {

        fun bind(user: User, selected: Boolean) {
            with(event) {
                viewBinding.tvUserName.text = name
                viewBinding.tvAddress.text = address
                viewBinding.tvAge.text = "$age"
                viewBinding.btnChecked.isChecked = selected
            }
        }
    }

View List Item

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools">
      <LinearLayout
        android:id="@+id/mainContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:focusable="true"
        android:foreground="?android:attr/selectableItemBackground"
        android:minHeight="72dp"
        android:orientation="horizontal"
        android:padding="16dp">

        <androidx.appcompat.widget.AppCompatRadioButton
          android:id="@+id/btnChecked"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="start|center_vertical"
          android:layout_marginEnd="@dimen/margin_8dp"
          android:clickable="false"
          tools:checked="true" />

          <LinearLayout
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
             android:layout_weight="1"
             android:orientation="vertical">

             <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tvUserName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="User User" />

             <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tvAddress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="United States" />

             <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tvAge"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="26" />
           </LinearLayout>

       </LinearLayout>
   </layout>

User Fragment

   class UserFragment : Fragment() {

     private lateinit binding: FragmentUserBinding
     private lateinit viewModel: UserViewModel
     private val adapter: UserAdapter = UserAdapter()     

     override onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
     super.onCreateView(inflater, container, savedInstanceState)
     binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user, container, false)

     binding.recyclerView.layoutManager = LinearLayoutManager(context)
     binding.recyclerView.adapter = adapter

     return binding.root
     }

     override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        // initialize UserViewModel here ... the viewModel exposes     `users` as LiveData which we observe here and assign to the adapter

        viewModel.users.observe(this, Observer { users ->
            adapter.users = users
        }
     }
   }

The source of data is the ViewModel. The view model fetches the users using some repository in the background thread and exposes that as LiveData. The Fragment can initialize the view model and observe the live data exposed by the view model. When it receives the data, it sets that data to the adapter which then binds that data to the RecyclerView. This is a pretty basic version of the implementation. We can take this approach and expand into multiple dimensions to achieve various different behaviors.

Please provide comments if you have questions or feedback.
Thank you!

recyclerview Article's
25 articles in total
Favicon
How to Avoid Blinking in Android Recycler View
Favicon
Introducing RVTimeTracker - RecyclerView Time Tracker
Favicon
RecyclerView Last Item Extra Bottom Margin
Favicon
RecyclerView pagination scroll listener in Android
Favicon
Mostrando os Pokémon com Coil em Android
Favicon
How to create an Expandable Item in Android RecyclerView?
Favicon
Listener de clique no RecyclerView com Kotlin
Favicon
Cloud Firestore Android example – CRUD Operations with RecyclerView
Favicon
Kotlin Firestore example - CRUD Operations with RecyclerView | Android
Favicon
6 Things You Might Not Know About RecyclerView
Favicon
Implementing ListView inside RecyclerView and observing LiveData
Favicon
The Simplest Recycler EmptyView
Favicon
Learn to make list using RecyclerView
Favicon
ExpandableRecyclerView: Expandable Items With Room Database in Kotlin
Favicon
Generic RecyclerViewAdapter
Favicon
Android RecyclerView StickyHeader without external library
Favicon
Doing Android Long Lists Effectively
Favicon
Android RecyclerView single choice adapter
Favicon
Scrolling... Scrolling... RecyclerView for Android with Kotlin!
Favicon
Small RecyclerView XML feature
Favicon
How to use a RecyclerView to show images from storage
Favicon
RecyclerView - II, adding onClick to list items
Favicon
GKE: Ingress Controllers
Favicon
RecyclerView - I
Favicon
Merge Multiple adapters with MergeAdapter

Featured ones: