Android Development

Categorizing in RecyclerViews

April 3, 2018

We are all con¬≠stant¬≠ly cat¬≠e¬≠go¬≠riz¬≠ing bits of infor¬≠ma¬≠tion that we receive with¬≠out even real¬≠iz¬≠ing it. This allows us to think about things prac¬≠ti¬≠cal¬≠ly; such as under¬≠stand¬≠ing which tasks need to be com¬≠plet¬≠ed in the next hour, day, or week. But, when it comes to dis¬≠play¬≠ing infor¬≠ma¬≠tion in orga¬≠nized groups on a device‚Äôs screen, archi¬≠tec¬≠tural¬≠ly it can be a bit of a pain. So, how can we more effec¬≠tive¬≠ly group data in a RecyclerView?

Let’s make it hap­pen #

With a basic RecyclerView, we can cat¬≠e¬≠go¬≠rize data by over¬≠rid¬≠ing func¬≠tions in our imple¬≠men¬≠ta¬≠tion of RecyclerView.Adapter. By over¬≠rid¬≠ing getItemViewType, we can cre¬≠ate a dif¬≠fer¬≠ent type of RecyclerView.ViewHolder in our onCreateViewHolder. This is great! It allows us to eas¬≠i¬≠ly imple¬≠ment many dif¬≠fer¬≠ent types of views/‚Äčview hold¬≠ers all with¬≠in the same RecyclerView. But remem¬≠ber, with great pow¬≠er comes great respon¬≠si¬≠bil¬≠i¬≠ty. Man¬≠ag¬≠ing a data struc¬≠ture to han¬≠dle group¬≠ing can quick¬≠ly become error prone as you are like¬≠ly ref¬≠er¬≠enc¬≠ing a Map<Any, List<Any>>. Ref¬≠er¬≠enc¬≠ing an item at posi¬≠tion 101 can¬≠not be done with a sim¬≠ple index, but instead will require iter¬≠at¬≠ing through the groups to count their chil¬≠dren. In the end, you will have a basic struc¬≠ture like this.

class SomeRecyclerAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  /*
    * Remember, your logic would likely be more complicated to handle
    * a nested data structure. You would probably want to pull this out into its own
    * function so that it could also be used here, in getItemViewType,
    * and in onBindViewHolder as well.
    */
  override fun getItemCount(): Int {
    return data.size
  }

  override fun getItemViewType(position: Int): Int {
    return when (position) {
      0 -> TYPE_HEADER
      else -> TYPE_ITEM
    }
  }

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val inflater = parent.context.layoutInflater
    return when (viewType) {
      TYPE_HEADER -> HeaderViewHolder(inflater.inflate(R.layout.header, parent, false))
      else -> ItemViewHolder(inflater.inflate(R.layout.item, parent, false))
    }
  }

  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when (holder) {
      is HeaderViewHolder -> {
        // do some Header things
      }
      is ItemViewHolder -> {
        // do some Item things
      }
    }
  }
}

But wait, I want¬≠ed ani¬≠ma¬≠tions #

You may be think¬≠ing that what has been described so far is pret¬≠ty straight¬≠for¬≠ward. How¬≠ev¬≠er, I chal¬≠lenge you to con¬≠sid¬≠er how you would expand and col¬≠lapse your cat¬≠e¬≠gories. If you‚Äôre like me, your answer like¬≠ly requires you to manip¬≠u¬≠late your data struc¬≠ture, pos¬≠si¬≠bly add new vari¬≠ables to man¬≠age vis¬≠i¬≠bil¬≠i¬≠ty, con¬≠vert these changes into notifyItemRangeInserted and notifyItemRangeRemoved events, and in gen¬≠er¬≠al, make things complicated.

At that point, if you‚Äôre any¬≠thing like me you will have tried a few libraries or maybe even attempt¬≠ed to roll your own. For me, Groupie was the clear front-run¬≠ner in terms of func¬≠tion¬≠al¬≠i¬≠ty and sim¬≠plic¬≠i¬≠ty but just had one issue; the col¬≠lapse ani¬≠ma¬≠tion was just not right ( full dis¬≠clo¬≠sure, I cre¬≠at¬≠ed an issue only to real¬≠ize the error was in my code ). So, I want¬≠ed to under¬≠stand how it worked and hope¬≠ful¬≠ly, pro¬≠vide the fix.

Let’s recon­sid­er what we would like in our code:

  • We want a data struc¬≠ture that is easy to under¬≠stand and manipulate.
  • We want to avoid han¬≠dling mul¬≠ti¬≠ple types when creating/‚Äčbinding views
  • We want to remain eas¬≠i¬≠ly manageable.

With that in mind, I ded¬≠i¬≠cat¬≠ed a week¬≠end to craft¬≠ing the per¬≠fect solu¬≠tion. What I end¬≠ed up with was a func¬≠tion¬≠al solu¬≠tion (Mac¬≠Grouper), and the under¬≠stand¬≠ing that I should prob¬≠a¬≠bly have looked at my own code before blam¬≠ing Groupie for my prob¬≠lems. The premise of Groupie, and Mac¬≠Grouper for that mat¬≠ter, is that a Group (or cat¬≠e¬≠go¬≠ry) is real¬≠ly just an Item that con¬≠tains a List<Item>. When the GroupAdapter (dis¬≠play¬≠ing a List<Item>) reports its size, it is able to ask all of the Items for their size (and they report back 1+).

if (isExpanded) { children.size + 1 } else 1

This means your RecyclerView.Adapter only real¬≠ly cares about Groups. A Group knows its num¬≠ber of chil¬≠dren and han¬≠dles vis¬≠i¬≠bil¬≠i¬≠ty changes. This means it can make the appro¬≠pri¬≠ate item range change calls auto¬≠mat¬≠i¬≠cal¬≠ly for you.

The mag­ic of it all is very clean code that is easy to under­stand. For the full exam­ple refer to my gist to cre­ate an Expand­ableList­Frag­ment.

val phones = mapOf(
  "Google" to listOf(
    "Pixel",
    "Pixel XL",
    "Pixel 2",
    "Pixel 2 XL"
  ),
  "HTC" to listOf(
    "Nexus One"
  ),
  "Huawei" to listOf(
    "Nexus 6P"
  )
)

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

  val layoutManager = GridLayoutManager(activity, groupAdapter.spanCount)
  layoutManager.spanSizeLookup = groupAdapter.spanSizeLookup

  phones.forEach { (brand, phones) ->
    val brandItem = BrandItem(brand)
    val expandableGroup = ExpandableGroup(brandItem)
    phones.forEach { phone ->
      expandableGroup.add(PhoneItem(phone))
    }
    groupAdapter.add(expandableGroup)
  }

  recyclerView.layoutManager = layoutManager
  recyclerView.adapter = groupAdapter
}

All this is to say, I high¬≠ly rec¬≠om¬≠mend giv¬≠ing Groupie a shot for any¬≠thing that might require a com¬≠plex RecyclerView lay¬≠out. The pro¬≠vid¬≠ed exam¬≠ples are an excel¬≠lent way to get start¬≠ed or check out my exam¬≠ple gist for an Expand¬≠ableList¬≠Frag¬≠ment.

Scott Schmitz
Scott Schmitz
Software Developer

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

The value of AR for business leaders (and when not to bother)
Business Development iOS

The value of AR for business leaders (and when not to bother)

April 24, 2024

Should you leverage AR for your new digital products? Should you build an app for Apple’s Vision Pro? Discover four common use cases for AR and when to focus your energy elsewhere.

Read more
The 5 Minute Accessibility Strategy
Android Development iOS

The 5 Minute Accessibility Strategy

May 18, 2023

We discuss how you can make a plan in just 5 minutes to provide accessibility in your mobile app.

Read more
How brain-computer-interfaces could improve human health
Business

How brain-computer-interfaces could improve human health

May 13, 2024

Read more
View more articles