/adapster

Android library designed to enrich and make your RecyclerView adapters more SOLID

Primary LanguageJavaApache License 2.0Apache-2.0

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.

Download Android Arsenal License Platform

Contents

Demo (YouTube)

YouTube Video

Getting Started

  1. Make sure that you've added the jcenter() repository to your top-level build.gradle file.
buildscript {
    //...
    repositories {
        //...
        jcenter()
    }
    //...
}
  1. Add the library dependency to your module-level build.gradle file.

Latest version: Download

ext {
    //...
    adapsterLibraryVersion = "1.0.9"
}

dependencies {
    //...
    implementation "com.arthurivanets.adapster:adapster:$adapsterLibraryVersion"
}
  1. Enable the jetifier and androidX support in the top-level gradle.properties file.
//...
android.enableJetifier=true
android.useAndroidX=true
//....
  1. Update your compileSdkVersion in the module-level build.gradle file to 28+.
//...
android {
    //...
    compileSdkVersion 28
    //...
}
//...
  1. Update your com.android.support.appcompat.* dependency to the new androidx.appcompat.* alternative.
//...
dependencies {
    //...
    implementation "androidx.appcompat:appcompat:1.0.2"
    //...
}
//...
  1. 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:

  1. 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 the Article 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...
        }

    }

}


  1. 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;
    }

}


  1. 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 the BaseItem<IM, VH, IR> and implement the marker interface Header<VH> (or Footer<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 the adapster-databinding dependency is added to your build.gradle file, as well as the core adapster one.
ext {
    //...
    adapsterLibraryVersion = "1.0.9"
}

dependencies {
    //...
    implementation "com.arthurivanets.adapster:adapster:$adapsterLibraryVersion"
    implementation "com.arthurivanets.adapster:adapster-databinding:$adapsterLibraryVersion"
}

//TODO to be continued

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.