Paging with Epoxy
Paging Library in Android
Paging library helps loading and displaying chunks of data from various datasources efficiently. You can get plenty of resources of how to use the paging library on android. This article is not about paging library usage. You can find the articles about paging here, here and here and many more.
Why Epoxy?
Epoxy is android library developed by AirBnB which simplifies the RecyclerView Adapter implementation by taking the declarative approach. More about Epoxy can be found here and here.
Whats this article about?
With paging library if we want to implement the loading indicator and error indicators in adapter, it turns out to be complex and ugly. Don’t believe me look at the line numbers from 62 to 94 from android paging sample.
Problem statement
Create an android application to show the top rated movies in a vertical list from the TMDB apis.
Show me the code
package com.vk.latestmovies.epoxy
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import com.vk.latestmovies.service.Movie
/**
* EpoxyController which works with PagedLists
*/
class MovieListEpoxyController : PagedListEpoxyController<Movie>() {
private var isError: Boolean = false
var error: String? = ""
set(value) {
field = value?.let {
isError = true
it
} ?: run {
isError = false
null
}
if (isError) {
requestModelBuild()
}
}
var isLoading = false
set(value) {
field = value
if (field) {
requestModelBuild()
}
}
/**
* Create the EpoxyViewModels
*/
override fun buildItemModel(currentPosition: Int, item: Movie?): EpoxyModel<*> {
item?.let {
//Movie Item View Model
return MovieItemModel_()
.id("movie${currentPosition}")
.title(item.title ?: "Unknown")
.description(item.overview ?: "Uknown")
.thumbnailUrl("http://image.tmdb.org/t/p/w185/${item.posterPath}")
} ?: run {
//Loading View Model
return LoadingEpoxyModel_()
.id("loading")
}
}
/**
* Adding models
*/
override fun addModels(models: List<EpoxyModel<*>>) {
if (isError) {
super.addModels(
models.plus(
//Error View Model
ErrorEpoxyModel_()
.id("Error")
.errorStr(error)
).filter { !(it is LoadingEpoxyModel_) }
)
} else if (isLoading) {
super.addModels(
models.plus(
//Error View Model
LoadingEpoxyModel_()
.id("loading")
).distinct()
)
} else {
super.addModels(models.distinct())
}
}
override fun onExceptionSwallowed(exception: RuntimeException) {
}
}
and call setError from the call site like this
viewModel.getNetworkState().observe(this, Observer<ApiState> { apiState ->
apiState?.let {
when (it) {
ApiState.Loading -> {
//removes error view from List
pagedListController.error = null
pagedListController.isLoading = true
}
ApiState.Success -> {
//removes Error view
pagedListController.error = null
pagedListController.isLoading = false
}
is ApiState.Error -> {
pagedListController.isLoading = false
//sets Error view from list
pagedListController.error = it.error.localizedMessage
}
}
}
})
complete source code can be found here. Leave your comments and feedback below.
The screen looks like this.