SmartRecyclerAdapter

Additional

Language
Java
Version
3.0.0 (Aug 31, 2019)
Created
May 30, 2017
Updated
Aug 31, 2019
Owner
Manne Öhlund (manneohlund)
Contributor
Manne Öhlund (manneohlund)
1
Activity
Badge
Generate
Download
Source code
APK file

Blurb

smart-recycler-adapter

Never code any boilerplate RecyclerAdapter again! This library will make it easy and painless to map your data item with a target ViewHolder.

Features

OnViewEventListener
ItemTouchHelper Swipe, Drag & Drop extensions
ViewTypeResolver
SmartStateHolder
Nested adapter
Pagination
DiffUtil

Release overview

Gradle

Add jcenter() or maven { url "https://dl.bintray.com/manneohlund/maven" } to your build.gradle under repositories

dependencies {  
  implementation 'io.github.manneohlund:smart-recycler-adapter:3.0.0'
}

Basic

Basic adapter creation

SmartRecyclerAdapter
  .items(items)
  .map(MoviePosterModel.class, PosterViewHolder.class)
  .map(MovieBannerModel.class, BannerViewHolder.class)
  .map(MovieModel.class, MovieViewHolder.class)
  .map(TopNewsModel.class, TopNewsViewHolder.class)
  .into(recyclerView);

SmartViewHolder

Just extend your ViewHolder class with SmartViewHolder and pass in the target type ex SmartViewHolder<Mail>. Note that the constructor can both take The The method unbind has an default implementation and is optional.parentView is the recyclerView.
View or ViewGroup as parameter, in this case PosterViewHolder(ViewGroup parentView) to avoid casting to ViewGroup while inflating.


Works with Android DataBinding! Just add the DataBinding LayoutInflater in super call. 🚀
public class PosterViewHolder extends SmartViewHolder<MoviePosterModel> {

  public PostViewHolder(ViewGroup parentView) { 
    super(LayoutInflater.from(parentView.getContext()).inflate(R.layout.poster_view, parentView, false)); 
  }
  
  @Override 
  public void bind(MoviePosterModel model) {
    Glide.with(imageView)
      .load(model.posterUrl)
      .into(imageView);
  }
  
  @Override 
  public void unbind() {
    Glide.with(imageView).clear(imageView);
  }
} 

View Events

You can easily assign events to views and add an OnViewEventListener to the SmartRecyclerAdapter for easy event handling.

SmartRecyclerAdapter
  .items(items)
  .map(MovieModel.class, MovieViewHolder.class)
  // Adds a basic `OnViewEventListener` to any `SmartViewHolder` extension that implements `ViewEventListenerHolder`
  .addViewEventListener((view, eventId, position) -> handleItemEvent())
  .into(recyclerView);

In your view holder, add eg OnClickListener to a view and call onViewEvent on the OnViewEventListener. Your ViewHolder must implements ViewEventListenerHolder to recieve the OnViewEventListener.

class MovieViewHolder 
    extends SmartViewHolder<MovieModel>
    implements SmartViewEventListenerHolder {

  private OnViewEventListener viewEventListener;

  @Override
  public void setOnViewEventListener(@NonNull OnViewEventListener viewEventListener) {
    this.viewEventListener = viewEventListener;
  }
  
  @Override
  public void bind(MovieModel movieModel) {
    imageView.setOnClickListener(view -> 
        viewEventListener.onViewEvent(view, R.id.action_play_movie, getAdapterPosition()));
  }
}

Predefined Listeners in the lib

If you are lazy and want to auto assign a predefined onClickListener and onLongClickListener with eventIds R.id.event_on_click and R.id.event_on_long_click,

Define listeners for MovieViewHolder

Default implemented view event id for OnItemClickListener is R.id.event_on_click. Default implemented view id for OnItemClickListener is R.id.undefined. R.id.undefined targets root view of the ViewHolder (ViewHolder.itemView).

interface OnMovieItemClickListener extends OnItemClickListener {
  @NonNull
  @Override
  default Class<? extends SmartViewHolder> getViewHolderType() {
    return MovieViewHolder.class;
  }
}

SmartRecyclerAdapter will automatically bind an View.OnClickListener to a view with id R.id.movie_info_button.

interface OnMovieInfoButtonClickListener extends OnItemClickListener {
  @NonNull
  @Override
  default Class<? extends SmartViewHolder> getViewHolderType() {
    return MovieViewHolder.class;
  }
  
  @Override
  default int getViewId() {
    return R.id.movie_info_button;
  }
}

And add event listener to SmartRecyclerAdapter builder.

SmartRecyclerAdapter
  .items(items)
  .map(HeaderModel.class, HeaderViewHolder.class)
  .map(MovieModel.class, MovieViewHolder.class)
  .map(MovieTrailerModel.class, MovieTrailerViewHolder.class)
  // Adds `OnItemClickListener` and auto binds `View.OnClickListener` on all ViewHolders.
  .addViewEventListener((OnItemClickListener) (view, eventId, position) -> handleEvent(eventId))
  // Adds event listener for MovieViewHolder only and overrides any generic `OnItemClickListener`
  .addViewEventListener((OnMovieItemClickListener) (view, eventId, position) -> playMovie())
  // Adds event listener for MovieViewHolder only and auto binds `View.OnClickListener` on view with id `R.id.movie_info_button`
  .addViewEventListener((OnMovieInfoButtonClickListener) (view, eventId, position) -> showMovieInfo(position))
  .into(recyclerView);

Adapter creation with ViewTypeResolver

If you want to bind one data type with different view holders depending on some attribute you can set a ViewTypeResolver. Note .map() call not needed in this case but you can combine if you want to.

SmartRecyclerAdapter
  .items(items)
  .setViewTypeResolver((item, position) -> {
    if (item instanceof MovieTrailerModel) { 
      return MovieTrailerViewHolder.class;
    } else if (item instanceof MovieModel && ((MovieModel)item).isRatedR()) { 
      return RMovieViewHolder.class; 
    } return MovieViewHolder.class; // Add default view if needed, else SmartRecyclerAdapter will look at the base `.map` mapping
  })
  .into(recyclerView);

New nested SmartRecyclerAdapter from v2.0.0

New in SmartRecyclerAdapter v2.0.0 is support for nested recycler adapter. Now you can easily build complex nested adapters without hustle and have full control of the adapter in your view controlling Fragment or Activity. Use the new create() method instead of the into(recyclerView) to create just the SmartRecyclerAdapter then set the adapter to the recycler view in your ViewHolder. Just implement the SmartAdapterHolder interface in your ViewHolder and SmartRecyclerAdapter will handle the mapping.

1. Create your nested SmartRecyclerAdapter

SmartRecyclerAdapter myWatchListSmartMovieAdapter = SmartRecyclerAdapter
  .items(myWatchListItems)
  .map(MovieModel.class, ThumbViewHolder.class)
  .addViewEventListener((OnItemClickListener) (view, eventId, position) -> playMovie())
  .create();

2. Map myWatchListSmartMovieAdapter with MyWatchListViewHolder

SmartRecyclerAdapter
  .items(items)
  .map(MoviePosterModel.class, PosterViewHolder.class)
  .map(MyWatchListModel.class, MyWatchListViewHolder.class)
  .map(MyWatchListViewHolder.class, myWatchListSmartMovieAdapter)
  .into(recyclerView);

3. Map myWatchListSmartMovieAdapter to MyWatchListViewHolder

class MyWatchListViewHolder
    extends SmartViewHolder<MyWatchListModel>
    implements SmartAdapterHolder {
    
  // Constructor here
    
  @Override
  public void setSmartRecyclerAdapter(SmartRecyclerAdapter smartRecyclerAdapter) {
    recyclerView.setLayoutManager(new LinearLayoutManager(context, HORIZONTAL, false));
    recyclerView.setAdapter(smartRecyclerAdapter);
  }

  public void bind(MyWatchListModel myWatchListModel) {
    // bind model data to views
  }
    
  public void unbind() {
    // optional unbinding of view data model
  }
}

SmartEndlessScrollRecyclerAdapter

A popular feature in apps is to have endless scrolling with pagination, in other words load more items when user has scrolled to bottom. With SmartEndlessScrollRecyclerAdapter you can achieve this.

1. Create adapter

SmartEndlessScrollRecyclerAdapter endlessScrollAdapter = SmartEndlessScrollRecyclerAdapter
  .items(items)
  .map(MovieModel.class, MovieViewHolder.class)
  .into(recyclerView);

2. Set OnLoadMoreListener to your SmartEndlessScrollRecyclerAdapter

Called when scrolled to the last item and loading view is visible.

endlessScrollAdapter.setOnLoadMoreListener(() -> {
  endlessScrollAdapter.addItems(moreItems);
});

More SmartEndlessScrollRecyclerAdapter features

Enable/Disable endless scrolling and thus removing the loading view. endlessScrollAdapter.setEndlessScrollEnabled(false);

You can also set your custom loading/loadmore view. endlessScrollAdapter.setCustomLoadMoreLayoutResource(R.layout.your_custom_loadmore_view);

More

For more samples test out the sample app and see the source code.

RecyclableViewHolder

Sometimes a ViewHolder created by the Adapter cannot be recycled due to its transient state. In order to fix this is to implement Re in your SmartViewHolder extension so that upon receiving this callback, Adapter can clear the animation(s) that effect the View's transient state and return true so that the View can be recycled.

class MovieViewHolder 
    extends SmartViewHolder
    implements RecyclableViewHolder {
  @Override
  public boolean onFailedToRecycleView() {
    // Clear animations or other stuff
    return true; 
  }
}

OnViewAttachedToWindowListener and OnViewDetachedFromWindowListener

If you want to catch when the view is attached and detached from the window in your ViewHolder you can implement OnViewAttachedToWindowListener and OnViewDetachedFromWindowListener in your SmartViewHolder extension. Becoming detached from the window is not necessarily a permanent condition the consumer of an Adapter's views may choose to cache views offscreen while they are not visible, attaching and detaching them as appropriate.

public class MovieViewHolder 
    extends SmartViewHolder 
    implements OnViewAttachedToWindowListener, 
               OnViewDetachedFromWindowListener { 

  @Override
  public void onViewAttachedToWindow() {
    // Restore
  }

  @Override
  public void onViewDetachedFromWindow() {
    // Cache
  }
}

Migrations

More guides coming to the Wiki Page

ViewEvent Migration

Old way before v3.0.0

Variable parameter overloading with many different addViewEventListener calls.

.addViewEventListener(
    MovieViewHolder.class,
    R.id.event_on_click,
    (view, eventId, position) -> playMovie())
class MovieViewHolder
    extends SmartAutoEventViewHolder<MyWatchListModel>
    implements SmartAdapterHolder {}

New in v3.0.0

Create an OnItemClickListener for MovieViewHolder. SmartAutoEvent implementations has been removed so no need for ex MovieViewHolder to extend SmartAutoEventViewHolder.

interface OnMovieItemClickListener extends OnItemClickListener {
  @NonNull
  @Override
  default Class<? extends SmartViewHolder> getViewHolderType() {
    return MovieViewHolder.class;
  }
}

Add listener to the SmartAdapterBuilder.

.addViewEventListener((OnMovieItemClickListener) (view, eventId, position) -> playMovie())

More SmartRecyclerAdapter features

SmartRecyclerAdapter adapter = SmartRecyclerAdapter
    .items(items)
    .map(MovieModel.class, MovieViewHolder.class)
    .into(recyclerView);

// We can add more data
adapter.addItems(items);

// Add data at index with animation
adapter.addItem(0, item);

// Add data at index without animation
adapter.addItem(0, item, false);

// Remove item at index with animation
adapter.removeItem(0);

// Remove item at index without animation
adapter.removeItem(0, false);

// Replace item at index with animation
adapter.replaceItem(0, item);

// Replace item at index without animation
adapter.replaceItem(0, item, false);

// Get items by type
adapter.getItems(MovieModel.class);

// Delete all items in the list
adapter.clear();