Convert Figma logo to code with AI

lisawray logogroupie

Groupie helps you display and manage complex RecyclerView layouts.

3,663
291
3,663
68

Top Related Projects

8,494

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

  1. 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
}
  1. 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"))
  1. 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:

  1. 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
}
  1. 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
}
  1. Set up your RecyclerView with Groupie:
val adapter = GroupAdapter<GroupieViewHolder>()
recyclerView.adapter = adapter

adapter.add(CustomItem("Hello, Groupie!"))

Competitor Comparisons

8,494

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 Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

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 changes
  • ExpandableGroup, 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 tags, so there's no need to convert the rest of your project (unless you want to).

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.