Kotlin: A Sectioned Adapter for Android
For those that don’t know me, I’ve been a mobile developer at Hudl for over a year now. We recently started using Kotlin within our mobile app. There are a lot of posts out there about the benefits of Kotlin and I see no need to duplicate those. However what I would like to share is where I have seen the benefits first hand. Actual implementations where Kotlin’s language has proved useful. I don’t plan to post Kotlin articles regularly, but whenever I encounter an interesting implementation of Kotlin specifics then I will be sure to do so.
You may have noticed a lot of Android applications are trying to embrace Material Design. It is very common throughout all of Google’s own apps as well as many top chart apps as well.
At Hudl we are also trying to strive for great design. One of the most important challenges is finding a way to make sure your brand doesn’t fall between the Material cracks. If every app in the Play Store looked similar then “Be together, not the same” would not hold true anymore.
One of the tasks I had recently been given was to add subheaders to one of our views to give notice of the month and year of a particular group of items in a list (much like Inbox). First thing first is to check out the material docs.
The actual specs of the subheader were easy enough:
- 48dp tile height
- Roboto Medium 14sp Font
- Secondary Grey text color, or your applications primary color.
- 16dp of padding (unless you have a prominent icon or action then it should be left aligned with your text)
The next step was to create the subheader for our recycler adapter.
One thing to note is that we wanted to use RecyclerView. There are many libraries out there that provide list support, however a decent amount seemed clunky or provided more features then necessary. So lets break down the problem. What is a subheader? Just another item in your list, that is styled differently. We know that RecyclerViews allow for different types of items via the getItemViewType() method so this should be simple enough. To make it easier I went ahead and used the Delegate Adapter approach. One delegate to handle the items in our list, and another to handle the subheaders. After that all that was left was the math to determine if we are displaying a subheader or an actual item.
By now you might be asking “What about Kotlin? I thought I was going to learn something”. Well the benefit that Kotlin provided was through an Interface. Yes. An Interface.
https://kotlinlang.org/docs/reference/interfaces.html
Interfaces in Kotlin are very similar to Java 8. They can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.
Why did an Interface help? Most of the Sectioned Adapters in Libraries I’ve seen have come from extending another class, and that class extends RecyclerView. Sooner or later that class grows and we know that we can only extend from one class. What if you wanted to add Swipe-to-dismiss support? Thankfully we can implement many interfaces and compose each into its own responsibility.
/**
* An adapter that allows a RecyclerView to contain sections
* or subheaders much like the material docs describe.
* https://material.google.com/components/subheaders.html
*/
interface SectionedAdapter { /**
* A list of sections to display in adapter.
*/
var sections: SparseArray<AdapterSection>...
And we’re done! Almost. Variables in interfaces isn’t a new thing so wheres the benefit?
...
/**
* Returns true if the given position contains a section
*/
fun isSectionHeaderPosition(position: Int) =
sections[position] != null /**
* Determines the correct position based off of the number
* of currently displayed sections.
*/
fun sectionedPositionToPosition(sectionedPosition: Int): Int {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION
}
var offset = 0
for (i in 0..sections.size() - 1) {
if (sections.valueAt(i).sectionedPosition >
sectionedPosition) {
break
}
--offset
}
return sectionedPosition + offset
}...}
https://gist.github.com/jpshelley/4e0d4aa16c20e91a6c01e48d278b4282
The ability to create “Method Implementations” is the advantage.
If we wanted to achieve the same result in the Android/Java7 world we would need an abstract class to provide those methods for us. This allows us to maintain single responsibility for this class and not overcrowd our original adapter or a base class.
The power of the delegate adapter and method implementations make it really easy to add subheaders to our lists. It makes our code easier to read, easier to find potential bugs, and easer to test.
Hopefully this post showed you a little bit of the power behind Kotlin and maybe how to add subheaders to your next list.
Resources:
- Material Docs: https://material.google.com/components/subheaders.html
- Delegate Adapters: http://hannesdorfmann.com/android/adapter-delegates & https://medium.com/@juanchosaravia/keddit-part-4-recyclerview-delegate-adapters-data-classes-with-kotlin-9248f44327f7#.bfwiswuzl
- Kotlin Interfaces: https://kotlinlang.org/docs/reference/interfaces.html
- Sectioned Adapter Math: https://gist.github.com/gabrielemariotti/4c189fb1124df4556058