Implementing ConcatAdapter In Android - Tutorial

Implementing ConcatAdapter In Android - Tutorial

Let's say If I show you a UI like the following and ask you to design it, what would be the first thought process would come into your mind?

Implementing ConcatAdapter In Android - Tutorial

You might come up with a different set of ideas to do it. But the most common idea to do this would be using RecyclerView with multiple view types inside one recyclerView.

But with the above given the solution, the major problem comes up is managing a lot of view types. In the above example, we have three types of views, one with a container to contain name and description of the user, second is a list of user and third is a banner. But in a real-life example, there can be a lot of different views and not just three.

So how can we design a view in an easy way without having to manage different view types?

Concat Adapter is the solution for it.

In this, let us understand the ConcatAdapter by building the above screen.

Step 01.

First, let us add the dependency required by the concat adapter. It is a part of recyclerView. In the app's build.gradle add,

implementation "androidx.recyclerview:recyclerview:1.2.0-alpha04"
PS: The current build is in the alpha02 version.

Step 02.

For the above screen, we have three different types of Views. So, for that to structure the data we need to create three different data classes namely - User, MyDetail, Banner, inside the model package. Where User is used for structuring the data for a list of users. MyDetail is used for displaying the used for my user data and Banner is used for displaying the banner. User looks like,

data class User(
     val id: Int = 0,
     val name: String = "",
     val avatar: String = ""
)

MyDetail data class looks like,

data class MyDetail(
     val id: Int = 0,
     val name: String = "",
     val aboutMe: String = ""
)

and Banner looks like,

data class Banner(
     val bannerImage: Int = 0
)

Step 03:

In this, we will create a data source for a list of users to display the list. For that, we will create an object of DataSource and in that,

object DataSource {

    fun getUser() = ArrayList<User>().apply {
        add(User(1, "Himanshu", "https://s3.amazonaws.com/uifaces/faces/twitter/sunlandictwin/128.jpg"))
        add(User(2, "Alford Hoeger", "https://s3.amazonaws.com/uifaces/faces/twitter/mufaddal_mw/128.jpg"))
        add(User(3, "Terrance Halvorson", "https://s3.amazonaws.com/uifaces/faces/twitter/mufaddal_mw/128.jpg"))
        add(User(3, "Morgan McGlynn", "https://s3.amazonaws.com/uifaces/faces/twitter/allfordesign/128.jpg"))
        add(User(4, "Ms. Ramiro DuBuque", "https://s3.amazonaws.com/uifaces/faces/twitter/shaneIxD/128.jpg"))
        add(User(5, "Kolby Orn", "https://s3.amazonaws.com/uifaces/faces/twitter/johnsmithagency/128.jpg"))
        add(User(6, "Elijah Schoen MD", "https://s3.amazonaws.com/uifaces/faces/twitter/alxndrustinov/128.jpg"))

    }

    fun getBanner() = Banner(R.drawable.publication_site_cover)
}

Where getUser () will be used to get a list of User type, and getBanner () is used for getting the banner image from the drawable folder.

Step 04.

In RecyclerView, what happens is, Concat Adapter helps to display the RecyclerView Adapters in sequential order. So, now in our use-case first we will define three different adapters,

To display MyDetails, will create MyDetailAdapter,

class MyDetailAdapter(
        private val myDetail: MyDetail
) : RecyclerView.Adapter<MyDetailAdapter.DataViewHolder>() {

    class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: MyDetail) {

            itemView.textViewUser.text = user.name
            itemView.textViewAboutMe.text = user.aboutMe
        }
    }

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

    override fun getItemCount(): Int = 1

    override fun onBindViewHolder(holder: DataViewHolder, position: Int) =
            holder.bind(myDetail)

}

This adapter will be used for displaying MyUserDetails, basically the first section of the list. Where, R.layout.item_layout_my_detail looks like,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/black"
    android:orientation="vertical"
    android:paddingStart="16dp"
    android:paddingTop="16dp"
    android:paddingEnd="16dp"
    android:paddingBottom="16dp">


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUser"
        style="@style/TextAppearance.AppCompat.Large"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        tools:text="MindOrks" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewAboutMe"
        style="@style/TextAppearance.AppCompat.Small"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/darker_gray" />
</LinearLayout>

Then to display the list of user, we will create another adapter UsersAdapter,

class UsersAdapter(
        private val users: ArrayList<User>
) : RecyclerView.Adapter<UsersAdapter.DataViewHolder>() {

    class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: User) {
            itemView.textViewUserName.text = user.name
            Glide.with(itemView.imageViewAvatar.context)
                    .load(user.avatar)
                    .into(itemView.imageViewAvatar)
        }
    }

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

    override fun getItemCount(): Int = users.size

    override fun onBindViewHolder(holder: DataViewHolder, position: Int) =
            holder.bind(users[position])

}

This adapter will be used to display the list of the user, where R.layout.item_layout looks like,

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/imageViewAvatar"
        android:layout_width="60dp"
        android:layout_height="0dp"
        android:padding="4dp"
        app:civ_border_color="@android:color/black"
        app:civ_border_width="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUserName"
        style="@style/TextAppearance.AppCompat.Large"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="4dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        tools:text="MindOrks" />


</androidx.constraintlayout.widget.ConstraintLayout>

and at last, will create another Adapter to show the banner at the end of User's list. For that will create BannerAdapter , which looks like

class BannerAdapter(
        private val banner: Banner
) : RecyclerView.Adapter<BannerAdapter.DataViewHolder>() {

    class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(banner: Banner) {
            Glide.with(itemView.imageViewBanner.context)
                    .load(user.bannerImage)
                    .into(itemView.imageViewBanner)
        }
    }

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

    override fun getItemCount(): Int = 1

    override fun onBindViewHolder(holder: DataViewHolder, position: Int) =
            holder.bind(banner)

}

and the R.layout.item_layout_banner looks like,

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/imageViewBanner"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Now, we are done designing three different types of adapters, which are responsible for displaying the three types of views in a recyclerView.

Step05.

Now, to use the adapters we create in ConcatAdapter, first, we will create an activity called MainActivity which will be our launcher activity as well.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Now, in the R.layout.activity_main , First, we will add a recyclerView. It will look like,

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now, we will add four variables in MainActivity,

lateinit var adapter: ConcatAdapter
lateinit var myDetailAdapter: MyDetailAdapter
lateinit var userVerticalAdapter: UsersAdapter
lateinit var bannerAdapter: BannerAdapter

we will also create another variable which will be structured according to MyDetail data class and contain my details,

val myDetail = MyDetail(1, "Himanshu Singh", "I am an writer and Open Source contributor in MindOrks.")

and lastly, we will create, a function called,

setupDataInRecyclerView()

and will call this in onCreate of Activity. Now, in the function, we will initialize the variables and set them to adapter .

private fun setupDataInRecyclerView() {
    recyclerView.layoutManager = LinearLayoutManager(this)
    userVerticalAdapter = UsersAdapter(DataSource.getUser())
    bannerAdapter = BannerAdapter(DataSource.getBanner())
    myDetailAdapter = MyDetailAdapter(myDetail)
    adapter = ConcatAdapter(myDetailAdapter,  userVerticalAdapter,bannerAdapter)
    recyclerView.adapter = adapter
}

Here, we will initialize the adapters, UserAdapter , BannerAdapter, and MyDetailAdapter and pass the data from the DataSource object.

Now, we will initialize the layout manager for the recyclerView using,

recyclerView.layoutManager = LinearLayoutManager(this) 

and, at last, we will create a ConcatAdapter and pass the adapters in its constructor.

Note: The Sequence on which we will pass the adapter, the ConcatAdapter will draw the list in the same sequence in the recyclerView.

And when we run the app, we will get the desired output on the screen.

Implementing ConcatAdapter In Android - Tutorial

This is how you can design a recyclerView with multiple view types in sequential order by using data from individual adapters.

Point to remember:

  • For example, let us say that we need to use the same recyclerView adapter multiple times in a recyclerView, then we can create multiple instances for the same adapter and add it in the constructor of ConcatAdapter.
  • We should implement the logic in individual adapters.
  • When we update data in any adapter using the notifyDataSetChanged() then, concat adapter will also call its notifyDataSetChanged()
  • In place of passing the adapters individually, we can pass the list of adapters in the constructor of ConcatAdapter. So, here in our example, we can create a list of adapter like,
val listOfAdapters = listOf<RecyclerView.Adapter<out RecyclerView.ViewHolder>>(myDetailAdapter, userVerticalAdapter, bannerAdapter)

and then in the constructor, we can pass like,

adapter = ConcatAdapter(listOfAdapters)

Now, let's talk about a use case

  1. In general, we basically use one view holder for one RecyclerView Adapter. But in some cases when we need to re-use the view holder for multiple adapters then,

we need to pass a config in the constructor where we need to set the isolateViewTypes to false

To create a config we use,

val configBuilder = ConcatAdapter.Config.Builder()
configBuilder.setIsolateViewTypes(false)

and then in ConcatAdapter, we pass it like,

val adapter = ConcatAdapter(configBuilder.build(),myDetailAdapter, userVerticalAdapter, bannerAdapter)

Similarly, here as well we can pass the list of adapter like,

val adapter = ConcatAdapter(configBuilder.build(),listOfAdapters)

2. Let say, we don't want to pass the adapter in the constructor and want to add it later in the Concat Adapter. To add an adapter we use,

val adapter = ConcatAdapter(listOfAdapters)
adapter.addAdapter(bannerAdapter)

This will add the adapter at the last position in the list. Now, let's say that we want to add the adapter at a specific position then we will pass the position alongside it,

adapter.addAdapter(0,bannerAdapter)

Here, we have added an adapter at the 0th position i.e the start of the list. Now, the recyclerView looks like,

Implementing ConcatAdapter In Android - Tutorial

3. Similarly to remove an adapter we call,

adapter.removeAdapter(//adapter)

4. To get the number of items in the recyclerView combining all the adapters we use,

adapter.itemCount

5. To get the list of adapters added in Concat Adapter wee call,

adapter.adapters

This is how you can get started with ConcatAdapter and build a recyclerView with multiple different views.

You can find the project here.

Happy learning.

Team MindOrks :)