Adapster
Android library designed to enrich and make your RecyclerView adapters more SOLID
Adapster will help you make your RecyclerView adapters more manageable and overall enrich your RecyclerView epxerience.
Contents
- Demo
- Getting Started
- Basic RecyclerView-based Implementation
- Basic ListView-based Implementation
- Advanced Use
- Data Binding
- Contribution
- Hall of Fame
- License
Demo (YouTube)
Getting Started
- Make sure that you've added the
jcenter()
repository to your top-levelbuild.gradle
file.
buildscript {
//...
repositories {
//...
jcenter()
}
//...
}
- Add the library dependency to your module-level
build.gradle
file.
ext {
//...
adapsterLibraryVersion = "1.0.9"
}
dependencies {
//...
implementation "com.arthurivanets.adapster:adapster:$adapsterLibraryVersion"
}
- Enable the jetifier and androidX support in the top-level
gradle.properties
file.
//...
android.enableJetifier=true
android.useAndroidX=true
//....
- Update your
compileSdkVersion
in the module-levelbuild.gradle
file to 28+.
//...
android {
//...
compileSdkVersion 28
//...
}
//...
- Update your
com.android.support.appcompat.*
dependency to the newandroidx.appcompat.*
alternative.
//...
dependencies {
//...
implementation "androidx.appcompat:appcompat:1.0.2"
//...
}
//...
- Proceed with the implementation of your own adapter.
See: Basic RecyclerView-based Implementation and Basic ListView-based Implementation
Basic RecyclerView-based Implementation
Implementation of a basic RecyclerView-based concept involves 3 main steps - creation of concrete Item classes, creation of the adapter, and binding of the adapter to the RecyclerView.
Let's implement a basic RecyclerView-based concept by following the steps listed above:
- Creation of a concrete Item class that extends the
BaseItem<IM, VH, IR>
base class.
- Creating a model class
Article
Kotlin (click to expand)
data class Article(
val id : Int,
val title : String,
val text : String,
val imageUrl : String = ""
) {
val hasImage : Boolean
get() = !imageUrl.isBlank()
}
Java (click to expand)
public final class Article {
private int id;
private String title;
private String text;
private String imageUrl;
public Article() {
this.id = -1;
this.title = "";
this.text = "";
this.imageUrl = "";
}
// Setters and Getters...
public final boolean hasImage() {
return !TextUtils.isEmpty(this.imageUrl);
}
}
- Creating the
ArticleItem
item class for theArticle
model
Kotlin (click to expand)
class ArticleItem(itemModel : Article) : BaseItem<Article, ArticleItem.ViewHolder, ItemResources>(itemModel), Trackable<Int> {
override fun init(adapter : Adapter<out Item<out RecyclerView.ViewHolder, out ItemResources>>,
parent : ViewGroup,
inflater : LayoutInflater,
resources : ItemResources?) : ViewHolder {
// inflate (or create) your item view here and create a corresponding ViewHolder
// then return the created ViewHolder
}
override fun bind(adapter : Adapter<out Item<out RecyclerView.ViewHolder, out ItemResources>>,
viewHolder : ViewHolder,
resources : ItemResources?) {
super.bind(adapter, viewHolder, resources)
// bind the data here
// (use the itemModel associated with this item to access the data)
}
override fun getLayout() : Int {
// return a unique id, which will be used as a View Type for this item
// (you can use the item view layout id if this item's view is not going
// to be modified dynamically by adding new views to it, otherwise you will
// have to compose your own id to properly distinguish the item's view within your adapter)
return R.layout.article_item_layout
}
override fun getTrackKey() : Int {
// use the model's unique id to prevent the duplicates within the Adapter
// (this step is optional and is enabled by the implementation of the Trackable<KeyType> interface)
return itemModel.id
}
class ViewHolder(itemView : View) : BaseItem.ViewHolder<Article>(itemView) {
// look up (or initialize) your views here...
}
}
Java (click to expand)
public final class ArticleItem extends BaseItem<Article, ArticleItem.ViewHolder, ItemResources> implements Trackable<Integer> {
public ArticleItem(Article itemModel) {
super(itemModel);
}
@Override
public ViewHolder init(Adapter<? extends Item> adapter,
ViewGroup parent,
LayoutInflater inflater,
ItemResources resources) {
// inflate (or create) your item view here and create a corresponding ViewHolder
// then return the created ViewHolder
}
@Override
public void bind(Adapter<? extends Item> adapter,
ViewHolder viewHolder,
ItemResources resources) {
super.bind(adapter, viewHolder, resources);
// bind the data here
// (use the itemModel associated with this item to access the data)
}
@Override
public int getLayout() {
// return a unique id, which will be used as a View Type for this item
// (you can use the item view layout id if this item's view is not going
// to be modified dynamically by adding new views to it, otherwise you will
// have to compose your own id to properly distinguish the item's view within your adapter)
return R.layout.article_item_layout;
}
@Override
public int getTrackKey() {
// use the model's unique id to prevent the duplicates within the Adapter
// (this step is optional and is enabled by the implementation of the Trackable<KeyType> interface)
return getItemModel().getId();
}
public static class ViewHolder extends BaseItem.ViewHolder<Article> {
public ViewHolder(View itemView) {
super(itemView);
// look up (or initialize) your views here...
}
}
}
- Creation of the adapter that extends the
TrackableRecyclerViewAdapter<KT, IT, VH>
base class.
- Creating the
ArticlesRecyclerViewAdapter
Kotlin (click to expand)
class ArticlesRecyclerViewAdapter(
context : Context,
items : MutableList<ArticleItem>
) : TrackableRecyclerViewAdapter<Long, ArticleItem, ArticleItem.ViewHolder>(context, items) {
var onArticleItemClickListener : OnItemClickListener<ArticleItem>? = null
init {
setHasStableIds(true)
}
override fun assignListeners(holder : ArticleItem.ViewHolder, position : Int, item : ArticleItem) {
super.assignListeners(holder, position, item)
item.setOnItemClickListener(holder, onArticleItemClickListener)
}
}
Java (click to expand)
public final class ArticlesRecyclerViewAdapter extends TrackableRecyclerViewAdapter<Long, ArticleItem, ArticleItem.ViewHolder> {
private OnItemClickListener<ArticleItem> onArticleItemClickListener;
public ArticlesRecyclerViewAdapter(Context context, List<ArticleItem> items) {
super(context, items);
setHasStableIds(true);
}
@Override
public void assignListeners(ArticleItem.ViewHolder holder, int position, ArticleItem item) {
super.assignListeners(holder, position, item);
item.setOnItemClickListener(holder, onArticleItemClickListener);
}
public final void setOnItemClickListener(OnItemClickListener<ArticleItem> onArticleItemClickListener) {
this.onArticleItemClickListener = onArticleItemClickListener;
}
}
- Instantiation of the created adapter and its binding to the RecyclerView.
- Instantiating the
ArticlesRecyclerViewAdapter
, setting the Adapter-specific listeners, and binding the adapter to the RecyclerView
Kotlin (click to expand)
//...
private var items = ArrayList<ArticleItem>()
private lateinit var adapter : ArticlesRecyclerViewAdapter
//...
private fun initRecyclerView() {
//...
adapter = ArticlesRecyclerViewAdapter(this, items)
adapter.onArticleItemClickListener = OnItemClickListener { view, item, position ->
// handle the article item click
}
//...
recyclerView.adapter = adapter
}
//...
Java (click to expand)
//...
private List<ArticleItem> items = new ArrayList<>();
private RecyclerView recyclerView;
private ArticlesRecyclerViewAdapter adapter;
//...
private void initRecyclerView() {
//....
recyclerView = findViewById(R.id.recyclerView);
//...
adapter = new ArticlesRecyclerViewAdapter(this, items);
adpater.setOnArticleItemClickListener(new OnItemClickListener() {
@Override
public void onItemClicked(View view, ArticleItem item, int position) {
// handle the article item click
}
});
//...
recyclerView.setAdapter(adapter);
}
Basic ListView-based Implementation
Implementation of a basic ListView-based concept is identical to the Basic RecyclerView-based Implementation, with the exception of one thing - the adapter implementation.
In the ListView-based concept your adapter should be provided with a little more information about the nature of the items it's handling, such as the exact View Type Count the adapter is going to handle, and Item View Type for a given adapter position (This additional information is only required in cases when you have more than one View Type associated with the Adapter).
Here's the implementation of the ArticlesListViewAdapter
Kotlin (click to expand)
class ArticlesListViewAdapter(
context : Context,
items : MutableList<ArticleItem>
) : TrackableListViewAdapter<Long, ArticleItem, ArticleItem.ViewHolder>(context, items) {
var onItemClickListener : OnItemClickListener<ArticleItem>? = null
override fun assignListeners(holder : ArticleItem.ViewHolder, position : Int, item : ArticleItem) {
super.assignListeners(holder, position, item)
item.setOnItemClickListener(holder, onItemClickListener)
}
}
Java (click to expand)
public final class ArticlesListViewAdapter extends TrackableListViewAdapter<Long, ArticleItem, ArticleItem.ViewHolder> {
private OnItemClickListener<ArticleItem> onItemClickListener;
public ArticlesListViewAdapter(Context context, List<ArticleItem> items) {
super(context, items);
setHasStableIds(true);
}
@Override
public void assignListeners(ArticleItem.ViewHolder holder, int position, ArticleItem item) {
super.assignListeners(holder, position, item);
item.setOnItemClickListener(holder, onItemClickListener);
}
public final void setOnItemClickListener(OnItemClickListener<ArticleItem> onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}
Advanced Use
- Header and Footer Support
To add a Header and/or Footer to your RecyclerView you need to create an Item class that extends theBaseItem<IM, VH, IR>
and implement the marker interfaceHeader<VH>
(orFooter<VH>
) on it (but not both at the same time). Thereafter you will be able to provide the implementation for the interface methods.
The implementation of the aforementioned marker interfaces will allow the adapter to properly distinguish and handle the items; the Header Item will always be placed at the top, while the Footer - at the bottom.
IMPORTANT: To be able to properly utilize this functionality (Header and/or Footer support) you must first make sure that the adapter you're using has that support provided to you. An adapter that supports Header and/or Footer must implement the SupportsHeader<VH>
and/or SupportsFooter<VH>
interfaces and the corresponding interface methods. There's no need to worry about this nuance if you're using the library-provided adapter implementations (such as TrackableRecyclerViewAdapter<KT, IT, VH>
and TrackableListViewAdapter<KT, IT, VH>
), as they do provide the support for both the Header and Footer; otherwise, if you decide to implement your own adapter based on the contracts provided by the library you should consider implementing the aforementioned interfaces to provide the desired Header and/or Footer support.
Adapters that have the Header and/or Footer support provide the following list of convenience-methods:
Header-related addHeader(Header<VH>)
removeHeader()
setOnHeaderClickListener(OnItemClickListener<Header<VH>>)
Footer-related addFooter(Footer<VH>)
removeFooter()
setOnFooterClickListener(OnItemClickListener<Footer<VH>>)
These methods will help you manage the Header and/or Footer within your adapter.
Here's an example of how to create a Header Item and what changes need to take place in the Adapter.
- Creating a
TopicHeaderItem
Kotlin (click to expand)
class TopicItem(itemModel : Topic) : BaseItem<Topic, TopicItem.ViewHolder, ItemResources>(itemModel),
Header<BaseItem.ViewHolder<*>> {
override fun init(adapter : Adapter<out Item<out RecyclerView.ViewHolder, out ItemResources>>,
parent : ViewGroup,
inflater : LayoutInflater,
resources : ItemResources?) : ViewHolder {
return ViewHolder(inflater.inflate(
layout,
parent,
false
))
}
override fun bind(adapter : Adapter<out Item<out RecyclerView.ViewHolder, out ItemResources>>,
viewHolder : ViewHolder,
resources : ItemResources?) {
super.bind(adapter, viewHolder, resources)
viewHolder.nameTv.text = itemModel.name
Picasso.with(viewHolder.imageIv.context.applicationContext)
.load(itemModel.imageUrl)
.into(viewHolder.imageIv)
}
override fun setOnItemClickListener(viewHolder : BaseItem.ViewHolder<*>, onItemClickListener : OnItemClickListener<Header<BaseItem.ViewHolder<*>>>?) {
viewHolder.itemView.setOnClickListener(ItemClickListener(this, 0, onItemClickListener))
}
override fun getLayout() : Int {
return R.layout.topic_item_layout
}
class ViewHolder(itemView : View) : BaseItem.ViewHolder<Topic>(itemView) {
val imageIv = itemView.findViewById<ImageView>(R.id.imageIv)
val nameTv = itemView.findViewById<TextView>(R.id.nameTv)
}
}
Java (click to expand)
public final class TopicItem extends BaseItem<Topic, TopicItem.ViewHolder, ItemResources> implements
Header<BaseItem.ViewHolder> {
public TopicItem(Topic itemModel) {
super(itemModel);
}
@Override
public ViewHolder init(Adapter<? extends Item> adapter,
ViewGroup parent,
LayoutInflater inflater,
ItemResources resources) {
return new ViewHolder(inflater.inflate(
getLayout(),
parent,
false
));
}
@Override
public void bind(Adapter<? extends Item> adapter,
ViewHolder viewHolder,
ItemResources resources) {
super.bind(adapter, viewHolder, resources);
viewHolder.nameTv.setText(getItemModel().getName());
Picasso.with(viewHolder.imageIv.getContext().getApplicationContext())
.load(getItemModel().getImageUrl())
.into(viewHolder.imageIv);
}
@Override
public void setOnItemClickListener(BaseItem.ViewHolder viewHolder, OnItemClickListener<Header<BaseItem.ViewHolder>> onItemClickListener) {
viewHolder.itemView.setOnClickListener(new ItemClickListener(this, 0, onItemClickListener));
}
@Override
public int getLayout() {
return R.layout.topic_item_layout;
}
public static final class ViewHolder extends BaseItem.ViewHolder<Topic> {
ImageView imageIv;
TextView nameTv;
public ViewHolder(View itemView) {
super(itemView);
imageIv = itemView.findViewById(R.id.imageIv);
nameTv = itemView.findViewById(R.id.nameTv);
}
}
}
-
Altering the adapters - now we need to enable the Multi-item support (See Adapter multi-item support below)
-
Adapter multi-item support
To enable the support of multiple item types in your adapter you need to change adapter item type to a more general one (e.g.BaseItem
) (For ListView-based adapters you should also specify the exact View Type Count and the actual Item View Type for a given position).
Here's an example of a multi-item adapter that supports Header and Footer items.
Kotlin (click to expand)
class SimpleRecyclerViewAdapter(
context : Context,
items : MutableList<BaseItem<*, *, *>>
) : TrackableRecyclerViewAdapter<Long, BaseItem<*, *, *>, BaseItem.ViewHolder<*>>(context, items) {
var onArticleItemClickListener : OnItemClickListener<ArticleItem>? = null
var onTopicSuggestionItemClickListener : OnItemClickListener<TopicSuggestionItem>? = null
var onTopicSuggestionItemLongClickListener : OnItemLongClickListener<TopicSuggestionItem>? = null
var onFooterButtonClickListener : OnItemClickListener<FooterItem>? = null
init {
setHasStableIds(true)
}
override fun assignListeners(holder : BaseItem.ViewHolder<*>, position : Int, item : BaseItem<*, *, *>) {
super.assignListeners(holder, position, item)
when(item) {
is ArticleItem -> onArticleItemClickListener?.let { item.setOnItemClickListener((holder as ArticleItem.ViewHolder), it) }
is FooterItem -> onFooterButtonClickListener?.let { item.setOnButtonClickListener((holder as FooterItem.ViewHolder), it) }
is TopicSuggestionsItem -> {
onTopicSuggestionItemClickListener?.let { item.setOnItemClickListener((holder as TopicSuggestionsItem.ViewHolder), it) }
onTopicSuggestionItemLongClickListener?.let { item.setOnItemLongClickListener((holder as TopicSuggestionsItem.ViewHolder), it) }
}
}
}
}
Java (click to expand)
public final class SimpleRecyclerViewAdapter extends TrackableRecyclerViewAdapter<Long, BaseItem, BaseItem.ViewHolder> {
private OnItemClickListener<ArticleItem> onArticleItemClickListener;
private OnItemClickListener<TopicSuggestionItem> onTopicSuggestionItemClickListener;
private OnItemLongClickListener<TopicSuggestionItem> onTopicSuggestionItemLongClickListener;
private OnItemClickListener<FooterItem> onFooterButtonClickListener;
public SimpleRecyclerViewAdapter(Context context, List<BaseItem> items) {
super(context, items);
setHasStableIds(true);
}
@Override
public void assignListeners(BaseItem.ViewHolder holder, int position, BaseItem item) {
super.assignListeners(holder, position, item);
if(item instanceof ArticleItem) {
((ArticleItem) item).setOnItemClickListener(((ArticleItem.ViewHolder) holder), onArticleItemClickListener);
} else if(item instanceof FooterItem) {
((FooterItem) item).setOnButtonClickListener(((FooterItem.ViewHolder) holder), onFooterButtonClickListener);
}
}
// Listener setters...
}
The sample ListView-based multi-item adapter implementation can be found here SimpleListViewAdapter.kt
Data Binding
- Data Binding Support
First, make sure theadapster-databinding
dependency is added to yourbuild.gradle
file, as well as the coreadapster
one.
ext {
//...
adapsterLibraryVersion = "1.0.9"
}
dependencies {
//...
implementation "com.arthurivanets.adapster:adapster:$adapsterLibraryVersion"
implementation "com.arthurivanets.adapster:adapster-databinding:$adapsterLibraryVersion"
}
//TODO to be continued
- More uses
See the Sample app.
Contribution
See the CONTRIBUTING.md file.
Hall of Fame
Reminder | |
Owly | |
Stocks Exchange |
Using Adapster in your app and want it to get listed here? Email me at arthur.ivanets.l@gmail.com!
License
Adapster is licensed under the Apache 2.0 License.