Top Related Projects
The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction...
Epoxy is an Android library for building complex screens in a RecyclerView
Flexible multiple types for Android RecyclerView.
An Adapter that allows a RecyclerView to be split into Sections with headers and/or footers. Each Section can have its state controlled individually.
Groupie helps you display and manage complex RecyclerView layouts.
Quick Overview
AdapterDelegates is a lightweight Android library that simplifies the implementation of complex RecyclerView adapters. It promotes a modular approach by allowing developers to split the adapter logic into smaller, reusable components called delegates, each responsible for handling a specific view type.
Pros
- Improves code organization and maintainability by separating adapter logic into smaller, focused components
- Enhances reusability of adapter code across different RecyclerViews
- Simplifies the handling of multiple view types in a single adapter
- Supports Kotlin DSL for more concise and readable code
Cons
- Introduces a slight learning curve for developers unfamiliar with the delegate pattern
- May add a small overhead in terms of object creation for very simple adapters
- Requires additional setup compared to traditional RecyclerView adapters
- Limited documentation and examples for advanced use cases
Code Examples
- Creating a simple delegate:
class UserAdapterDelegate : AdapterDelegate<List<DisplayableItem>>() {
override fun isForViewType(items: List<DisplayableItem>, position: Int): Boolean {
return items[position] is UserItem
}
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
return UserViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false))
}
override fun onBindViewHolder(items: List<DisplayableItem>, position: Int,
holder: RecyclerView.ViewHolder, payloads: List<Any>) {
val userItem = items[position] as UserItem
val userViewHolder = holder as UserViewHolder
userViewHolder.bind(userItem)
}
}
- Setting up the adapter with delegates:
val adapter = AdapterDelegatesManager<List<DisplayableItem>>()
.addDelegate(UserAdapterDelegate())
.addDelegate(HeaderAdapterDelegate())
.addDelegate(FooterAdapterDelegate())
.into(ListDelegationAdapter<List<DisplayableItem>>())
recyclerView.adapter = adapter
- Using Kotlin DSL to create delegates:
val adapter = adapterDelegatesManager {
adapterDelegate<UserItem, DisplayableItem> {
layout = R.layout.item_user
on<UserViewHolder> {
create { UserViewHolder(it) }
bind { holder, item -> holder.bind(item) }
}
}
}
Getting Started
- Add the dependency to your
build.gradle
file:
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
- Create your adapter delegates and set up the adapter:
val adapter = adapterDelegatesManager {
adapterDelegate<YourItem, DisplayableItem> {
layout = R.layout.your_item_layout
on<YourViewHolder> {
create { YourViewHolder(it) }
bind { holder, item -> holder.bind(item) }
}
}
}.into(ListDelegationAdapter<List<DisplayableItem>>())
recyclerView.adapter = adapter
- Use the adapter with your RecyclerView and update the data as needed:
adapter.items = yourListOfItems
adapter.notifyDataSetChanged()
Competitor Comparisons
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
- Extensive documentation and samples
- Active development and community support
Cons of FastAdapter
- Steeper learning curve due to more complex API
- Larger library size, which may impact app size
- Potentially overkill for simpler use cases
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)
}
FastAdapter:
class BookItem : AbstractItem<BookItem.ViewHolder>() {
override val type: Int = R.id.fastadapter_book_item
override val layoutRes: Int = R.layout.item_book
override fun getViewHolder(v: View): ViewHolder = ViewHolder(v)
class ViewHolder(view: View) : FastAdapter.ViewHolder<BookItem>(view) {
override fun bindView(item: BookItem, payloads: List<Any>) {
// Binding logic here
}
}
}
Both libraries offer solutions for creating RecyclerView adapters, but FastAdapter provides a more feature-rich experience at the cost of increased complexity. AdapterDelegates focuses on a simpler, delegation-based approach, which may be preferable for less complex use cases or when minimizing dependencies is a priority.
Epoxy is an Android library for building complex screens in a RecyclerView
Pros of Epoxy
- More comprehensive solution for building complex RecyclerViews
- Supports data binding and view binding out of the box
- Offers automatic diffing and animations for smoother list updates
Cons of Epoxy
- Steeper learning curve due to its more complex API
- Requires more boilerplate code for simple use cases
- Heavier dependency with a larger footprint in the app
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.inflate(R.layout.item_book))
}
Epoxy:
@EpoxyModelClass(layout = R.layout.item_book)
abstract class BookModel : EpoxyModelWithHolder<BookHolder>() {
@EpoxyAttribute lateinit var book: Book
override fun bind(holder: BookHolder) {
holder.bindBook(book)
}
}
Both libraries aim to simplify RecyclerView adapter creation, but Epoxy offers a more feature-rich solution at the cost of increased complexity. AdapterDelegates focuses on a simpler, more lightweight approach to adapter delegation.
Flexible multiple types for Android RecyclerView.
Pros of MultiType
- Simpler API with less boilerplate code
- Built-in support for diffing and payload comparison
- More flexible item type registration system
Cons of MultiType
- Less granular control over view creation and binding
- May require more manual type checking in some cases
- Slightly steeper learning curve for complex scenarios
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)
}
}
AdapterDelegates:
class TextAdapterDelegate : AdapterDelegate<List<DisplayableItem>>() {
override fun isForViewType(items: List<DisplayableItem>, position: Int): Boolean {
return items[position] is TextItem
}
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
return TextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false))
}
override fun onBindViewHolder(items: List<DisplayableItem>, position: Int, holder: RecyclerView.ViewHolder, payloads: List<Any>) {
val textItem = items[position] as TextItem
(holder as TextViewHolder).textView.text = textItem.text
}
}
Both libraries offer efficient ways to handle multiple view types in RecyclerViews, with MultiType focusing on simplicity and AdapterDelegates providing more fine-grained control.
An Adapter that allows a RecyclerView to be split into Sections with headers and/or footers. Each Section can have its state controlled individually.
Pros of SectionedRecyclerViewAdapter
- Built-in support for sectioned data, simplifying the creation of complex list layouts
- Provides a clean API for managing sections, including headers and footers
- Offers helper methods for common section operations (e.g., add, remove, move)
Cons of SectionedRecyclerViewAdapter
- Less flexible for non-sectioned data or custom view types
- May have a steeper learning curve for developers unfamiliar with sectioned layouts
- Potentially higher memory usage due to section management overhead
Code Comparison
SectionedRecyclerViewAdapter:
Section section = new Section(headerResId, footerResId);
section.addItem(new Item("Item 1"));
sectionAdapter.addSection("section1", section);
AdapterDelegates:
AdapterDelegatesManager<List<Object>> manager = new AdapterDelegatesManager<>();
manager.addDelegate(new ItemAdapterDelegate());
manager.addDelegate(new HeaderAdapterDelegate());
Both libraries aim to simplify RecyclerView adapter management, but they take different approaches. SectionedRecyclerViewAdapter focuses on sectioned data, while AdapterDelegates provides a more general-purpose solution for handling multiple view types. The choice between them depends on the specific requirements of your project and the complexity of your list layouts.
Groupie helps you display and manage complex RecyclerView layouts.
Pros of Groupie
- Offers a more comprehensive solution for complex RecyclerViews, including built-in support for expandable groups and sections
- Provides a simpler API for handling different view types, reducing boilerplate code
- Includes built-in support for asynchronous diffing, improving performance for large datasets
Cons of Groupie
- May be overkill for simpler RecyclerView implementations, adding unnecessary complexity
- Less flexible for custom implementations compared to AdapterDelegates' more modular approach
- Steeper learning curve due to its more opinionated structure and additional features
Code Comparison
AdapterDelegates:
class BookAdapterDelegate : AdapterDelegate<List<DisplayableItem>>() {
override fun isForViewType(items: List<DisplayableItem>, position: Int): Boolean {
return items[position] is Book
}
// ... other methods
}
Groupie:
class BookItem(private val book: Book) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
// Bind book data to views
}
// ... other methods
}
Both libraries aim to simplify RecyclerView adapter creation, but Groupie provides a more opinionated and feature-rich approach, while AdapterDelegates offers a more flexible and modular solution. The choice between them depends on the specific requirements of your project and personal preferences.
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
AdapterDelegates
Read the motivation for this project in my blog post.
The idea of this library is to build your adapters by composing reusable components.
Favor composition over inheritance
The idea is that you define an AdapterDelegate for each view type. This delegate is responsible for creating ViewHolder and binding ViewHolder for a certain viewtype. Then you can compose your RecyclerView Adapter by registering the AdapterDelegates that you really need.
Changelog
See releases section
Quickstart: Kotlin DSL
There are 2 artifacts for kotlin users that allow you to write Adapter Delegates more convenient by providing a DSL
:
Dependencies
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
// If you use Kotlin Android Extensions and synthetic properties (alternative to findViewById())
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:4.3.2'
// If you use ViewBinding
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
How to use it
fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegate<Cat, Animal>(R.layout.item_cat) {
// This is the initializer block where you initialize the ViewHolder.
// Its called one time only in onCreateViewHolder.
// this is where you can call findViewById() and setup click listeners etc.
val name : TextView = findViewById(R.id.name)
name.setClickListener { itemClickedListener(item) } // Item is automatically set for you. It's set lazily though (set in onBindViewHolder()). So only use it for deferred calls like clickListeners.
bind { diffPayloads -> // diffPayloads is a List<Any> containing the Payload from your DiffUtils
// This is called anytime onBindViewHolder() is called
name.text = item.name // Item is of type Cat and is the current bound item.
}
}
In case you want to use kotlin android extensions and synthetic properties (as alternative to findViewById()) use adapterDelegateLayoutContainer
instead of adapterDelegate
like this:
fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegateLayoutContainer<Cat, Animal>(R.layout.item_cat) {
name.setClickListener { itemClickedListener(item) } // no need for findViewById(). Name is imported as synthetic property from kotlinx.android.synthetic.main.item_cat
bind { diffPayloads ->
name.text = item.name
}
}
If you use adapterDelegateLayoutContainer()
don't forget to add
androidExtensions {
experimental = true
}
to your build.gradle to enable LayoutContainer.
In case you want to use ViewBinding\DataBinding use adapterDelegateViewBinding
instead of adapterDelegate
like this:
fun cat2AdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegateViewBinding<Cat, DisplayableItem, ItemCatBinding>(
{ layoutInflater, root -> ItemCatBinding.inflate(layoutInflater, root, false) }
) {
binding.name.setOnClickListener {
itemClickedListener(item)
}
bind {
binding.name.text = item.name
}
}
You have to specify if a specific AdapterDelegate is responsible for a specific item.
Per default this is done with an instanceof
check like item instanceof Cat
.
You can override this if you want to handle it in a custom way by setting the on
lambda
and return true or false:
adapterDelegate<Cat, Animal> (
layout = R.layout.item_cat,
on = { item: Animal, items: List, position: Int ->
if (item is Cat && position == 0)
true // return true: this adapterDelegate handles it
else
false // return false
}
){
...
bind { ... }
}
The same on
parameter is available for adapterDelegateLayoutContainer()
and adapterDelegateViewBinding()
DSL.
Compose your Adapter
Finally, you can compose your RecyclerView Adapter
by registering your AdapterDelegates like this:
val adapter = ListDelegationAdapter<List<Animal>>(
catAdapterDelegate(...),
dogAdapterDelegate(),
snakeAdapterDelegate()
)
fun
vs. val
You could define your AdapterDelegate also as TopLevel val
like this:
// top level property inside CatDelegate.kt
val catDelegate = adapterDelegate<Cat, Animal> {
...
bind { ... }
}
but a top level val is a static field at the end so that this adapter delegate will be kept for the lifetime of your application in memory. Therefore, we would recommend to prefer write your AdapterDelegate as a function and call this function to actually instantiate the AdapterDelegate. Then the AdapterDelegate can be garbage collected as soon as the user leaves the screen the AdapterDelegate is used in.
// top level function inside CatDelegate.kt
fun catAdapterDelegate() = adapterDelegate<Cat, Animal> {
...
bind { ... }
}
Dependencies if you don't use Kotlin DSL
This library is available on maven central:
implementation 'com.hannesdorfmann:adapterdelegates4:4.3.2'
Please note that since 4.0 the group id has been changed to adapterdelegates4
.
Snapshot
implementation 'com.hannesdorfmann:adapterdelegates4:4.3.3-SNAPSHOT'
You also have to add the url to the snapshot repository:
allprojects {
repositories {
...
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
How to use it in Java
The idea of this library is to build your adapters by composing reusable components.
Favor composition over inheritance
The idea is that you define an AdapterDelegate
for each view type. This delegate is responsible for creating ViewHolder and binding ViewHolder for a certain viewtype.
An AdapterDelegate
get added to an AdapterDelegatesManager
. This manager is the man in the middle between RecyclerView.Adapter
and each AdapterDelegate
.
For example:
public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {
private LayoutInflater inflater;
public CatAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
@Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
}
@Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
@NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
CatViewHolder vh = (CatViewHolder) holder;
Cat cat = (Cat) items.get(position);
vh.name.setText(cat.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
}
}
}
Please note that onBindViewHolder()
last parameter payloads
is null unless you use from adapter.notify
. There are more methods like onViewRecycled(ViewHolder)
, onFailedToRecycleView(ViewHolder)
,
onViewAttachedToWindow(ViewHolder)
and onViewDetachedFromWindow(ViewHolder)
you can override in your AdapterDelegate
class.
Then an AnimalAdapter
could look like this:
public class AnimalAdapter extends RecyclerView.Adapter {
private AdapterDelegatesManager<List<Animal>> delegatesManager;
private List<Animal> items;
public AnimalAdapter(Activity activity, List<Animal> items) {
this.items = items;
delegatesManager = new AdapterDelegatesManager<>();
// AdapterDelegatesManager internally assigns view types integers
delegatesManager.addDelegate(new CatAdapterDelegate(activity))
.addDelegate(new DogAdapterDelegate(activity))
.addDelegate(new GeckoAdapterDelegate(activity));
// You can explicitly assign integer view type if you want to
delegatesManager.addDelegate(23, new SnakeAdapterDelegate(activity));
}
@Override public int getItemViewType(int position) {
return delegatesManager.getItemViewType(items, position);
}
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return delegatesManager.onCreateViewHolder(parent, viewType);
}
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
delegatesManager.onBindViewHolder(items, position, holder);
}
@Override public int getItemCount() {
return items.size();
}
}
Reducing boilerplate code
As you have seen in the code snippet above this may require to write the same boiler plate code again and again to hook in AdapterDelegatesManager
to Adapter
.
This can be reduced by extending either from ListDelegationAdapter
if the data source the adapter displays is java.util.List<?>
or AbsDelegationAdapter
which is a more general one (not limited to java.util.List
)
ListDelegationAdapter
For example the same AnimalAdapter
from above could be simplified as follows by extending from ListDelegationAdapter
:
public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {
public AnimalAdapter(Activity activity, List<Animal> items) {
// DelegatesManager is a protected Field in ListDelegationAdapter
delegatesManager.addDelegate(new CatAdapterDelegate(activity))
.addDelegate(new DogAdapterDelegate(activity))
.addDelegate(new GeckoAdapterDelegate(activity))
.addDelegate(23, new SnakeAdapterDelegate(activity));
// Set the items from super class.
setItems(items);
}
}
AbsListItemAdapterDelegate
Also you may have noticed that you often have to write boilerplate code to cast items and ViewHolders when working with list of items as adapters dataset source.
AbsListItemAdapterDelegate
can help you here. Let's take this class to create a CatListItemAdapterDelegate
similar to the CatAdapterDelegate
from top of this page but without the code for casting items.
public class CatListItemAdapterDelegate extends AbsListItemAdapterDelegate<Cat, Animal, CatViewHolder> {
private LayoutInflater inflater;
public CatAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
@Override public boolean isForViewType(Animal item, List<Animal> items, int position) {
return item instanceof Cat;
}
@Override public CatViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
}
@Override public void onBindViewHolder(Cat item, CatViewHolder vh, @Nullable List<Object> payloads) {
vh.name.setText(item.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
}
}
}
As you see, instead of writing code that casts list item to Cat we can use AbsListItemAdapterDelegate
to do the same job (by declaring generic types).
DiffUtil & ListAdapter = AsyncListDifferDelegationAdapter
Support library 27.0.1 introduced ListAdapter
- the new extension of RecyclerView.Adapter
that uses AsyncListDiffer
internally. It does calculating diff in the background thread by default and does all particular animations for you items collection. Hence you don't need carry about notify*
methods, AsyncListDiffer
does all the job for you. And AdapterDelegates supports it too.
This library offers the equivalent to ListAdapter
which is called AsyncListDifferDelegationAdapter
that can be used together with any regular AdapterDelegate
.
public class DiffAdapter extends AsyncListDifferDelegationAdapter<Animal> {
public DiffAdapter() {
super(DIFF_CALLBACK) // Your diff callback for diff utils
delegatesManager
.addDelegate(new DogAdapterDelegate());
.addDelegate(new CatAdapterDelegate());
}
}
Pagination
There is an additional artifact for the pagination library:
implementation 'com.hannesdorfmann:adapterdelegates4-pagination:4.3.2'
Use PagedListDelegationAdapter
.
Fallback AdapterDelegate
What if your adapter's data source contains a certain element you don't have registered an AdapterDelegate
for? In this case the AdapterDelegateManager
will throw an exception at runtime. However, this is not always what you want. You can specify a fallback AdapterDelegate
that will be used if no other AdapterDelegate
has been found to handle a certain view type.
AdapterDelegate fallbackDelegate = ...;
adapterDelegateManager.setFallbackDelegate( fallbackDelegate );
Note also that boolean return type of isForViewType()
of a fallback delegate will be ignored (will not be take into account). So it doesn't matter if you return true or false. You can use AbsFallbackAdapterDelegate
that already implements isForViewType()
so that you only have to override onCreateViewHolder()
and onBindViewHolder()
for your fallback adapter delegate.
Version 3.x to 4.0 migration
AdapterDelegates3
uses com.android.support:recyclerview-v7:x.y.z
whereas AdapterDelegates4
uses
androidx.recyclerview:recyclerview:1.0.0
.
Migration should be easy. Just use IntelliJ IDE or Android Studio 'Replace in Path' (can be found inside Edit
main menu then Find
submenu):
Replace com.hannesdorfmann.adapterdelegates3
with com.hannesdorfmann.adapterdelegates4
.
You might also have to replace android.support.v7.widget.RecyclerView
with androidx.recyclerview.widget.RecyclerView
and
android.support.annotation.NonNull
with androidx.annotation.NonNull
.
License
Copyright 2015 Hannes Dorfmann
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Top Related Projects
The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction...
Epoxy is an Android library for building complex screens in a RecyclerView
Flexible multiple types for Android RecyclerView.
An Adapter that allows a RecyclerView to be split into Sections with headers and/or footers. Each Section can have its state controlled individually.
Groupie helps you display and manage complex RecyclerView layouts.
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