Top Related Projects
Epoxy is an Android library for building complex screens in a RecyclerView
The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction...
"Favor composition over inheritance" for RecyclerView Adapters
Fast and versatile Adapter for RecyclerView which regroups several features into one library to considerably improve the user experience :-)
Flexible multiple types for Android RecyclerView.
Quick Overview
Groupie is an Android library that simplifies the process of creating complex RecyclerView layouts. It provides a flexible and powerful way to build lists with multiple view types, expandable groups, and headers/footers, reducing boilerplate code and improving maintainability.
Pros
- Simplifies the creation of complex RecyclerView layouts
- Supports multiple view types, expandable groups, and headers/footers
- Reduces boilerplate code and improves code readability
- Offers a flexible API for easy customization
Cons
- Limited to Android development
- May have a learning curve for developers new to the library
- Potential performance overhead for very large lists
- Requires additional dependency in the project
Code Examples
- Creating a simple item:
class SimpleItem(val name: String) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.findViewById<TextView>(R.id.name).text = name
}
override fun getLayout(): Int = R.layout.item_simple
}
- Creating an expandable group:
class ExpandableHeaderItem(private val title: String) : ExpandableItem, Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.findViewById<TextView>(R.id.title).text = title
}
override fun getLayout(): Int = R.layout.item_expandable_header
}
val expandableGroup = ExpandableGroup(ExpandableHeaderItem("Expandable Group"))
expandableGroup.add(SimpleItem("Child 1"))
expandableGroup.add(SimpleItem("Child 2"))
- Setting up a RecyclerView with Groupie:
val adapter = GroupAdapter<GroupieViewHolder>()
recyclerView.adapter = adapter
adapter.add(SimpleItem("Item 1"))
adapter.add(expandableGroup)
adapter.add(SimpleItem("Item 2"))
Getting Started
To use Groupie in your Android project, follow these steps:
- Add the dependency to your app's
build.gradle
file:
dependencies {
implementation 'com.xwray:groupie:2.9.0'
implementation 'com.xwray:groupie-viewbinding:2.9.0' // Optional, for view binding support
}
- Create your custom items by extending the
Item
class:
class CustomItem(private val text: String) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.findViewById<TextView>(R.id.text).text = text
}
override fun getLayout(): Int = R.layout.item_custom
}
- Set up your RecyclerView with Groupie:
val adapter = GroupAdapter<GroupieViewHolder>()
recyclerView.adapter = adapter
adapter.add(CustomItem("Hello, Groupie!"))
Competitor Comparisons
Epoxy is an Android library for building complex screens in a RecyclerView
Pros of Epoxy
- More comprehensive and feature-rich, offering a wider range of UI components and layouts
- Better performance optimization, especially for large lists and complex layouts
- Stronger type safety and compile-time checks
Cons of Epoxy
- Steeper learning curve due to its more complex API and concepts
- Requires more boilerplate code for simple use cases
- Heavier library with a larger footprint
Code Comparison
Groupie example:
Section().apply {
add(HeaderItem("Title"))
addAll(items.map { ContentItem(it) })
}
Epoxy example:
EpoxyRecyclerView.withModels {
header {
id("header")
title("Title")
}
items.forEach { item ->
content {
id(item.id)
data(item)
}
}
}
Both libraries aim to simplify RecyclerView implementations, but Epoxy offers more advanced features at the cost of increased complexity. Groupie focuses on simplicity and ease of use, making it a good choice for simpler list layouts. Epoxy shines in complex, dynamic UI scenarios and large-scale applications where performance and flexibility are crucial.
The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction...
Pros of FastAdapter
- More comprehensive feature set, including drag & drop, swipe-to-dismiss, and multi-select
- Better performance for large lists due to its optimized adapter implementation
- Extensive documentation and sample code available
Cons of FastAdapter
- Steeper learning curve due to its more complex API
- Less flexibility for custom item types compared to Groupie's simpler approach
- Heavier dependency, which may increase app size
Code Comparison
FastAdapter:
val fastAdapter = FastAdapter.with(itemAdapter)
recyclerView.adapter = fastAdapter
itemAdapter.add(SimpleItem("Item 1"))
itemAdapter.add(SimpleItem("Item 2"))
Groupie:
val adapter = GroupAdapter<GroupieViewHolder>()
recyclerView.adapter = adapter
adapter.add(ExpandableGroup(ExpandableHeaderItem()).apply {
add(ExpandableItem("Item 1"))
add(ExpandableItem("Item 2"))
})
Both libraries aim to simplify RecyclerView adapter creation, but FastAdapter offers more built-in features at the cost of complexity, while Groupie focuses on a simpler, more flexible approach for organizing items into groups. The choice between them depends on the specific needs of your project and your preference for either a feature-rich or a more lightweight solution.
"Favor composition over inheritance" for RecyclerView Adapters
Pros of AdapterDelegates
- More lightweight and focused on a single responsibility
- Easier to integrate into existing projects without major refactoring
- Provides a simpler API for basic use cases
Cons of AdapterDelegates
- Less feature-rich compared to Groupie's extensive functionality
- Requires more manual setup for complex layouts and item types
- Limited built-in support for advanced RecyclerView features
Code Comparison
AdapterDelegates:
class BookAdapterDelegate : AdapterDelegate<List<DisplayableItem>>() {
override fun isForViewType(items: List<DisplayableItem>, position: Int) =
items[position] is Book
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
BookViewHolder(parent)
override fun onBindViewHolder(items: List<DisplayableItem>, position: Int,
holder: RecyclerView.ViewHolder, payloads: List<Any>) {
val book = items[position] as Book
(holder as BookViewHolder).bind(book)
}
}
Groupie:
class BookItem(private val book: Book) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.apply {
title.text = book.title
author.text = book.author
}
}
override fun getLayout() = R.layout.item_book
}
Fast and versatile Adapter for RecyclerView which regroups several features into one library to considerably improve the user experience :-)
Pros of FlexibleAdapter
- More comprehensive and feature-rich, offering advanced functionalities like expandable items, headers, and footers
- Provides built-in support for animations and item decorations
- Offers extensive customization options for item views and behaviors
Cons of FlexibleAdapter
- Steeper learning curve due to its complexity and extensive feature set
- May be overkill for simpler RecyclerView implementations
- Requires more boilerplate code compared to Groupie's simpler approach
Code Comparison
Groupie:
class MyItem : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
// Bind data to views
}
override fun getLayout(): Int = R.layout.my_item
}
FlexibleAdapter:
class MyItem(model: MyModel) : AbstractFlexibleItem<MyViewHolder>() {
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: MyViewHolder, position: Int, payloads: List<*>) {
// Bind data to views
}
override fun getLayoutRes(): Int = R.layout.my_item
}
Both libraries aim to simplify RecyclerView implementations, but FlexibleAdapter offers more advanced features at the cost of increased complexity. Groupie focuses on simplicity and ease of use, making it more suitable for straightforward list implementations. The choice between the two depends on the specific requirements of your project and the level of customization needed.
Flexible multiple types for Android RecyclerView.
Pros of MultiType
- Simpler API with less boilerplate code
- More flexible item type registration system
- Better support for nested RecyclerViews
Cons of MultiType
- Less built-in functionality for common RecyclerView tasks
- Steeper learning curve for complex layouts
- Limited documentation compared to Groupie
Code Comparison
MultiType:
class TextItemViewBinder : ItemViewBinder<TextItem, TextItemViewBinder.ViewHolder>() {
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
return ViewHolder(inflater.inflate(R.layout.item_text, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, item: TextItem) {
holder.textView.text = item.text
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.text)
}
}
Groupie:
class TextItem(private val text: String) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.textView.text = text
}
override fun getLayout(): Int = R.layout.item_text
}
Both libraries aim to simplify RecyclerView implementation, but they take different approaches. MultiType focuses on a more flexible type system, while Groupie provides a rich set of built-in features for common RecyclerView patterns. The choice between them depends on the specific requirements of your project and personal preference.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
groupie
Groupie is a simple, flexible library for complex RecyclerView layouts.
Groupie lets you treat your content as logical groups and handles change notifications for you -- think sections with headers and footers, expandable groups, blocks of vertical columns, and much more. It makes it easy to handle asynchronous content updates and insertions and user-driven content changes. At the item level, it abstracts the boilerplate of item view types, item layouts, viewholders, and span sizes.
Try it out:
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
Groupie also has a support module for Android's view binding. This module also supports Android data binding, so if your project uses both data binding and view binding, you don't have to add the dependency on the data binding support module. Setup here.
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
Note:
If using groupie-viewbinding
in a databinding project is only available when using Android Gradle Plugin 3.6.0 or higher.
If using an older Gradle Plugin version with databinding the you can use the standalone groupie-databinding
library to generate view holders. Setup here.
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-databinding:$groupie_version"
You can also use Groupie with Java and your existing ViewHolders.
Which one to choose? It's up to you and what your project already uses. You can even use Kotlin and data binding together.* Or all your existing hand-written Java ViewHolders, and one new Kotlin item to try it out. Go crazy!
Get started
Use a GroupieAdapter
anywhere you would normally use a RecyclerView.Adapter
, and attach it to your RecyclerView as usual.
Kotlin
val adapter = GroupieAdapter()
recyclerView.adapter = adapter
Java
GroupieAdapter adapter = new GroupieAdapter();
recyclerView.setAdapter(adapter);
Groups
Groups are the building block of Groupie. An individual Item
(the unit which an adapter inflates and recycles) is a Group of 1. You can add Groups and Items interchangeably to the adapter.
Kotlin
groupAdapter += HeaderItem()
groupAdapter += CommentItem()
val section = Section()
section.setHeader(HeaderItem())
section.addAll(bodyItems)
groupAdapter += section
Java
groupAdapter.add(new HeaderItem());
groupAdapter.add(new CommentItem());
Section section = new Section();
section.setHeader(new HeaderItem());
section.addAll(bodyItems);
groupAdapter.add(section);
Modifying the contents of the GroupieAdapter in any way automatically sends change notifications. Adding an item calls notifyItemAdded()
; adding a group calls notifyItemRangeAdded()
, etc.
Modifying the contents of a Group automatically notifies its parent. When notifications reach the GroupieAdapter, it dispatches final change notifications. There's never a need to manually notify or keep track of indices, no matter how you structure your data.
section.removeHeader(); // results in a remove event for 1 item in the adapter, at position 2
There are a few simple implementations of Groups within the library:
Section
, a list of body content with an optional header group and footer group. It supports diffing and animating moves, updates and other changesExpandableGroup
, a single parent group with a list of body content that can be toggled hidden or shown.
Groupie tries not to assume what features your groups require. Instead, groups are flexible and composable. They can be combined and nested to arbitrary depth.
Life (and mobile design) is complicated, so groups are designed so that making new ones and defining their behavior is easy. You should make many small, simple, custom groups as the need strikes you.
You can implement the Group
interface directly if you want. However, in most cases, you can extend Section
or the base implementation, NestedGroup
. Section supports common RV paradigms like diffing, headers, footers, and placeholders. NestedGroup provides support for arbitrary nesting of groups, registering/unregistering listeners, and fine-grained change notifications to support animations and updating the adapter.
Items
Groupie abstracts away the complexity of multiple item view types. Each Item declares a view layout id, and gets a callback to bind
the inflated layout. That's all you need; you can add your new item directly to a GroupieAdapter
and call it a day.
Item with data binding:
The Item
class gives you simple callbacks to bind your model object to the generated binding. Because of data binding, there's no need to write a view holder.
public class SongItem extends BindableItem<SongBinding> {
public SongItem(Song song) {
this(song);
}
@Override public void bind(SongBinding binding, int position) {
binding.setSong(song);
}
@Override public int getLayout() {
return R.layout.song;
}
}
If you're converting existing ViewHolders, you can reference any named views (e.g. R.id.title
) directly from the binding instead.
@Override public void bind(SongBinding binding, int position) {
binding.title.setText(song.getTitle());
}
You can also mix and match BindableItem
and other Items
in the adapter, so you can leave legacy viewholders as they are by making an Item<MyExistingViewHolder>
.
Legacy item (your own ViewHolder)
You can leave legacy viewholders as they are by converting MyExistingViewHolder
to extend GroupieViewHolder
rather than RecyclerView.ViewHolder
. Make sure to change the imports to com.xwray.groupie.Item
and com.xwray.groupie.GroupieViewHolder
.
Finally, in your Item<MyExistingViewHolder>
, override
@Override
public MyExistingViewHolder createViewHolder(@NonNull View itemView) {
return new MyExistingViewHolder(itemView);
}
Note:
Items can also declare their own column span and whether they are draggable or swipeable.
Gradle setup
Kotlin
In your project level build.gradle
file, include:
buildscript {
ext.kotlin_version = '1.6.21'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
}
In new projects, the settings.gradle
file has a dependencyResolutionManagement
block, which needs to specify the repository as well:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' } // <--
jcenter() // Warning: this repository is going to shut down soon
}
}
In your app build.gradle
file, include:
implementation 'com.github.lisawray.groupie:groupie:$groupie_version'
View binding
Add to your app module's build.gradle
:
android {
buildFeatures {
viewBinding true
}
}
dependencies {
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
}
Then:
class MyLayoutItem: BindableItem<MyLayoutBinding>() {
override fun initializeViewBinding(view: View): MyLayoutBinding {
return MyLayoutBinding.bind(view)
}
// Other implementations...
}
Note:
If you use groupie-databinding
with data binding classes and your layouts have some variables or observable objects, don't forget to run executePendingBindings
at the last point in bind
.
Data binding
Add to your app module's build.gradle:
android {
buildFeatures {
dataBinding true
}
}
dependencies {
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-databinding:$groupie_version"
}
Then, just wrap each item layout in <layout>
tags. (The <data>
section is optional.)
layout/item_song.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="song" type="com.example.Song" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{song.title}"
tools:text="A Song Title" />
</FrameLayout>
</layout>
Bindings are only generated for layouts wrapped with
You can add a <data>
section to directly bind a model or ViewModel, but you don't have to. The generated view bindings alone are a huge time saver.
Kotlin AND data binding / view binding?
Sure, why not? Follow all the instructions from both sections above.
You only need to include the groupie-databinding
or groupie-viewbinding
dependency.
Contributing
Contributions you say? Yes please!
Bug report?
- If at all possible, please attach a minimal sample project or code which reproduces the bug.
- Screenshots are also a huge help if the problem is visual.
Send a pull request!
- If you're fixing a bug, please add a failing test or code that can reproduce the issue.
If you try it out, I'd love to know what you think. Please hit up Lisa at [first][last]@gmail.com or on Twitter at @lisawrayz.
Top Related Projects
Epoxy is an Android library for building complex screens in a RecyclerView
The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction...
"Favor composition over inheritance" for RecyclerView Adapters
Fast and versatile Adapter for RecyclerView which regroups several features into one library to considerably improve the user experience :-)
Flexible multiple types for Android RecyclerView.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot