RecyclerView FieldAdapter

While we still provide adapters for ListView, as you may already know, you should build your app using RecyclerViews, as they provide more flexibility.

Our class FieldAdapter extends the RecyclerView.Adapter class. Before seeing how to create a it we must introduce a few classes.

The ViewType class

link

Since a RecyclerView can display views of different types, we created this class to represent it. To create it we can simply call the constructor that accepts two parameters:

  • viewCreator: a function that, given a Context, creates a view of type V
  • viewBinder: a function that, given a view V and an item T, binds the item to the view

Example:

//Assuming we have a class Movie and a MovieView with the method setMovie() that accepts a Movie val movies : Field<List<Movie>> = getMovies() val movieViewType = ViewType( viewCreator = { context -> MovieView(context) }, viewBinder = { view, movie : Movie -> view.setMovie(movie) } )
//Assuming we have a class Movie and a MovieView with the method setMovie() Field<List<Movie>> movies = getMovies(); ViewType<Movie, MovieView> movieViewType = new ViewType<>( context -> new MovieView(context), //viewCreator (view, Movie movie) -> view.setMovie(movie) //viewBinder );

We can also pass an optional ID to represent this view type. If we omit it, an unique one will automatically be generated.

This class extends ViewType and, instead of the viewCreator/viewBinder couple, requires just a viewCreator, that this time accepts a view V and a Field<T?> for the item.

Example:

//Assuming we have a class Movie and a MovieView with the method setMovie() that accepts a Field<Movie?> val movieViewType = ViewTypeField( viewCreator = { context, movie : Field<Movie?> -> val view = MovieView(context) view.setMovie(movie); view } )
//Assuming we have a class Movie and a MovieView with the method setMovie() ViewTypeField<Movie, MovieView> movieViewType = new ViewTypeField<>( (context, Field<Movie> movie) -> { //viewCreator MovieView view = new MovieView(context); view.setMovie(movie); return movie; } );

As you can see the field has type T? representing the item it needs to display. The value is null the first time the view is instantiated, because it happens before it's known which item it needs to represent.

The ItemInfo class

link

This class holds the information about a single item. It contains an item of type T, a view type and an item ID of type Long. If the item ID is not provided it defaults to RecyclerView.NO_ID. The knowledge of this class will be needed when created adapters that can display multiple view types.

Creation of a FieldAdapter

link

The creation of this class passes through the FieldAdapter.of function, which has a few variants.

For a single view type

link

To create an adapter that handles a single view type all we need to pass is:

  • items: a Field<List<T>> containing the items to display
  • idProvider: a function that, given an item T, returns a unique ID for that item
  • viewType: a ViewType<T, *> that represents the single view type

There are also two functions that, instead of the view type, accept directly the viewCreator/viewBinder or the single viewCreator, as seen for the ViewType and ViewTypeField constructors.

Example:

val movies : Field<List<Movie>> = getMovies() //Assuming we have the movieViewType created before FieldAdapter.of( items = movies, idProvider = { it.id }, viewType = movieViewType )
Field<List<Movie>> movies = getMovies(); //Assuming we have the movieViewType created before FieldAdapter.of( movies, //items movie -> movie.getId(), //idProvider movieViewType //viewType );

For multiple view types

link

In order to create a FieldAdapter that is able to display multiple view types, the only needed parameter is a Field<List<ItemInfo<T>>>, since the ItemInfo class already contains all the necessary information.

Example:

//Assuming we have the following classes: // -Movie: that represents a film // -MovieView: with the method setMovie() that accepts a Movie // -Show: that represents a TV series // -ShowView: with the method setShow() that accepts a Show // -Media: class extended by both Movie and Show //We create the view type for a Movie... val movieViewType = ViewType( viewCreator = { context -> MovieView(context) }, viewBinder = { view, movie : Movie -> view.setMovie(movie) } ) //...and for a show val showViewType = ViewType( viewCreator = { context -> ShowView(context) }, viewBinder = { view, show : Show -> view.setShow(show) } ) val media : Field<List<Media>> = getMedia() FieldAdapter.of( items = media.map { //Here we transform each item in its corresponding ItemInfo when(it) { is Movie -> ItemInfo(it, it.id, movieViewType) is Show -> ItemInfo(it, it.id, showViewType) else -> throw IllegalStateException() } } )
//Assuming we have the following classes: // -Movie: that represents a film // -MovieView: with the method setMovie() that accepts a Movie // -Show: that represents a TV series // -ShowView: with the method setShow() that accepts a Show // -Media: class extended by both Movie and Show //We create the view type for a Movie... ViewType<Movie, MovieView> movieViewType = new ViewType<>( context -> new MovieView(context), //viewCreator (view, Movie movie) -> view.setMovie(movie) //viewBinder ); //...and for a show ViewType<Show, ShowView> showViewType = new ViewType<>( context -> new ShowView(context), //viewCreator (view, Show show) -> view.setShow(show) //viewBinder ); Field<List<Media>> media = getMedia(); FieldAdapter.of( IterableFieldUtils.map(media, item -> { //Here we transform each item in its corresponding ItemInfo if(it instanceof Movie) new ItemInfo<>(it, it.id, movieViewType) else if(it instanceof Show) new ItemInfo<>(it, it.id, showViewType) else throw new IllegalStateException(); }) );