RecyclerView FieldAdapter
While we still provide adapters for ListView
, as you may already know, you should build your app using RecyclerView
s, 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.
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 aContext
, creates a view of typeV
viewBinder
: a function that, given a viewV
and an itemT
, 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.
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.
The creation of this class passes through the FieldAdapter.of
function, which has a few variants.
To create an adapter that handles a single view type all we need to pass is:
items
: aField<List<T>>
containing the items to displayidProvider
: a function that, given an itemT
, returns a unique ID for that itemviewType
: aViewType<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
);
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();
})
);