RecyclerView has become a standard element in any kind of Android app. It’s fast and efficient – at least for the user. For developers, however, it’s an entirely different story.
You have to manage so many things – create adapters, keep track of multiple view types and span sizes (when you’re using a GridLayoutManager). Let’s not forget that sometimes you want to use expandable groups with headers… Oh and you also have to manage your overall sanity when something breaks in the process!
Luckily there is a simple RecyclerView solution which works out-of-the-box but it can also be heavily customized if you want to play with it. Groupie is an open-source Android library aimed at helping you get rid of any RecyclerView related headaches.
Groupie organizes items into groups – Sections and ExpandableGroups. It supports the use of headers and updating the groups with changed items could not be simpler – just call update() and you don’t even need to call notifyItemMoved() or any of that stuff.
This post contains all the code that’s been written in this YouTube video.
You can also check out this GitHub repository: https://github.com/ResoCoder/Groupie-LibraryTutorial
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
androidExtensions {
experimental = true
}
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.resocoder.groupielibrarytutorial"
minSdkVersion 19
targetSdkVersion 27
buildToolsVersion "27.0.2"
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:27.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.xwray:groupie:2.0.3'
compile 'com.xwray:groupie-kotlin-android-extensions:2.0.3'
implementation 'com.android.support:cardview-v7:27.0.2'
}
item_fancy.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
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/item_fancy_cardView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="4dp"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="4dp"
app:cardElevation="4dp">
<TextView
android:id="@+id/item_fancy_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="1"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
android:textSize="50sp"/>
</android.support.v7.widget.CardView>
FancyItem.kt
package com.resocoder.groupielibrarytutorial
import android.support.annotation.ColorInt
import com.xwray.groupie.kotlinandroidextensions.Item
import com.xwray.groupie.kotlinandroidextensions.ViewHolder
import kotlinx.android.synthetic.main.item_fancy.*
class FancyItem(@ColorInt private val color: Int,
private val number: Int)
: Item(){
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.item_fancy_cardView.setCardBackgroundColor(color)
viewHolder.item_fancy_number.text = number.toString()
}
override fun getLayout() = R.layout.item_fancy
override fun getSpanSize(spanCount: Int, position: Int) = spanCount / 3
}
item_expandable_header.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_expandable_header_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/item_expandable_header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_centerHorizontal="true"
android:textStyle="bold"
android:textColor="?attr/colorAccent"
tools:text="Group 1"/>
<ImageView
android:id="@+id/item_expandable_header_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/item_expandable_header_title"
app:srcCompat="@drawable/ic_keyboard_arrow_down_black_24dp"
android:tint="@color/colorAccent"/>
</RelativeLayout>
ExpandableHeaderItem.kt
package com.resocoder.groupielibrarytutorial
import com.xwray.groupie.ExpandableGroup
import com.xwray.groupie.ExpandableItem
import com.xwray.groupie.kotlinandroidextensions.Item
import com.xwray.groupie.kotlinandroidextensions.ViewHolder
import kotlinx.android.synthetic.main.item_expandable_header.*
class ExpandableHeaderItem(private val title: String)
: Item(), ExpandableItem{
private lateinit var expandableGroup: ExpandableGroup
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.item_expandable_header_title.text = title
viewHolder.item_expandable_header_icon.setImageResource(getRotatedIconResId())
viewHolder.item_expandable_header_root.setOnClickListener {
expandableGroup.onToggleExpanded()
viewHolder.item_expandable_header_icon.setImageResource(getRotatedIconResId())
}
}
override fun getLayout() = R.layout.item_expandable_header
override fun setExpandableGroup(onToggleListener: ExpandableGroup) {
expandableGroup = onToggleListener
}
private fun getRotatedIconResId() =
if (expandableGroup.isExpanded)
R.drawable.ic_keyboard_arrow_up_black_24dp
else
R.drawable.ic_keyboard_arrow_down_black_24dp
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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="com.resocoder.groupielibrarytutorial.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_sync_black_24dp"
android:tint="@android:color/white"/>
</android.support.design.widget.CoordinatorLayout>
MainActivity.kt
package com.resocoder.groupielibrarytutorial
import android.graphics.Color
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.GridLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.xwray.groupie.ExpandableGroup
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.ViewHolder
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*
class MainActivity : AppCompatActivity() {
private val excitingSection = Section()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val boringFancyItems = generateFancyItems(6)
val excitingFancyItems = generateFancyItems(12)
val groupAdapter = GroupAdapter<ViewHolder>().apply {
spanCount = 3
}
recycler_view.apply {
layoutManager = GridLayoutManager(this@MainActivity, groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup
}
adapter = groupAdapter
}
ExpandableGroup(ExpandableHeaderItem("Boring Group"), true).apply {
add(Section(boringFancyItems))
groupAdapter.add(this)
}
ExpandableGroup(ExpandableHeaderItem("Exciting Group"), false).apply {
excitingSection.addAll(excitingFancyItems)
add(excitingSection)
groupAdapter.add(this)
}
fab.setOnClickListener {
excitingFancyItems.shuffle()
excitingSection.update(excitingFancyItems)
}
}
private fun generateFancyItems(count: Int): MutableList<FancyItem>{
val rnd = Random()
return MutableList(count){
val color = Color.argb(255, rnd.nextInt(256),
rnd.nextInt(256), rnd.nextInt(256))
FancyItem(color, rnd.nextInt(100))
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}


This was a great tutorial.
For me, it was better than the official documentation.
How do I implement an onCLicklistener for the item in my activity or fragment.
I am using a viewmodel for my fragment and I want to load a ‘details’ fragment when an item is clicked.
Hi Matej,
I want to send multiple lists to myItem class. For single list, I am using the map function that iterates over the list and give me my model object and I set the data to views in Item class easily but I want to send another list. How can this be achieved??
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Your article helped me a lot, is there any more related content? Thanks!
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?
Your article helped me a lot, is there any more related content? Thanks!
Can you be more specific about the content of your enticle? After reading it, I still have some doubts. Hope you can help me.