Making PagedListAdapter even easier!

Why I decided to develop a library on top of the PagedListAdapter library and how you can use it.

Thomas Hosty Cundell
4 min readMar 1, 2019

I wanted to implement a smooth scrolling list in my app and decided the PagedListAdapter was the solution. Using it though you can encounter some issues.

What if you’re going to receive 2 different objects in your response feed with a network call? Not exactly easily fixable.

The next issue was, if you have no control over the API and the API doesn’t have a filter implemented, you need to filter client side and in the app. It’s not ideal as a developer but it is what happens at times.

So it was for this reason, once solved, I decided to build a library that can sit on top of PagedListAdapter and make it easier for a developer.

Introducing NettyPaging

NettyPaging is designed so that all you have to do is pass a Retrofit call to the library and then you receive the response so you can manipulate the data however you please.
What makes it especially nice is that it allows to hit 2 or more (completely up to you) different APIs as well.

So lets get started

First we need to add the dependency.
Add jitpack as a dependency to your project gradle.

allprojects {
repositories {
google()
jcenter()
maven {
url 'https://jitpack.io'
}
}
}

Then add the library to your app gradle

implementation 'com.github.CostyHundell:NettyPaging:1.0.0'

Using the library

Hopefully you have a basic knowledge of the PagedListAdapter and how to set this up if not this article by Anitaa Murthy is a good starter. You will need to use retrofit2 and retrofitRxJava.

implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

Instead of using PagedKeyDataSource in that example we use the libraries new class NettyPagedDataSource. And we will also replace PagedListAdapter with NettyPagedListAdapter.

And we will then get something like this:

class TestDataSource: NettyPagerDataSource(NUMBER_OF_APIS) {
override var single: Single<NettyResponse>
get() = TODO("not implemented")
set(value) {}

override fun onLoadAfterError(error: Throwable) {
TODO("not implemented")
}

override fun onLoadAfterSuccess(
callback: LoadCallback<Int, NettyItem>,
response: NettyResponse,
params: LoadParams<Int>
) {
TODO("not implemented")
}

override fun onLoadInitialError(error: Throwable) {
TODO("not implemented")
}

override fun onLoadInitialSuccess(callback: LoadInitialCallback<Int, NettyItem>, response: NettyResponse) {
TODO("not implemented")
}
}

But there’s a few things that also need to be done. As you can see we have a NettyItem this is an interface that we must use when creating the objects that our app is going to display in a list. For Example :

data class NewsArticle(val source: String,
val author: String,
val title: String,
val description: String,
val url: String,
val imageUrl: String,
val publishedAt: String,
val content: String): NettyItem {
// Must have unique viewType
override fun getItemViewType(): Int = Constant.NEWS_ARTICLE_TYPE
}

data class Advert(val id: String,
val url: String): : NettyItem {
// Must have unique viewType
override fun getItemViewType(): Int = Constant.AD_VIEW_TYPE
}

As well as doing the same for the Api response except implementing NettyResponse:

data class NewsResponse(val status: String,
val totalResults: Int,
val articles: List<NewsArticle>) : NettyResponse {
override fun getResponseType(): Int = Constant.NEWS_RESPONSE
}

We also need to register a type adapter into our GsonConverterFactory we can implement our own but if we have a response that is a json object already we can use the DeserializeResponse class provided in the library like this:

val adapter = DeserializeResponse(NewsResponse::class.java)

So what this now allows us to do is call two different APIs that get two different responses but once finished for the user it will look like one list.

Note: This means that our RetrofitAPI call should return a NettyResponse for all API calls not the response we’re.

The first thing to do is set the initial single variable.

override var single = NewsRetrofitApi().getArticles()

Then in onLoadInitialSuccess() we deal with the response

var list = mutableListOf<NettyItem>()

override fun onLoadInitialSuccess(callback: LoadInitialCallback<Int, NettyItem>, response: NettyResponse) {
when (response.getResponseType()) {
Constant.NEWS_RESPONSE -> {
response as NewsResponse
list = response.articles.toMutableList()
}
...
}


if (callsMade == NUMBER_OF_APIS) {
postInitial(callback, list, PAGE_SIZE + 1)
} else {
setNextInitialCall(true, PAGE_SIZE, manageApis(), callback).runNext()
}
}

The callsMade variable increments every time a new API call is made and is reset once we have made the amount of calls defined in the constructor.

Above we can see that we have a variable called list and if it’s of type NEWS_RESPONSE we’re just setting that to the list of articles from the response. If it is not the first call we add the result to the original list and then shuffle the whole list (this isn’t shown here but it’s what is done in the example app). You can do what ever you want with your results this is just an example. The same happens for onLoadAfterSuccess().

manageMyApis() is a method that if not overriden will return null. It does not have to be used as you can build your own method to determine which api to use. But this is the idea:

override fun manageApis(): Single<Response> = when (callsMade) {
1 -> NewsRetrofitApi().getArticles()
else -> AdsRetroFitApi().getAds()
}

And then once you’ve implemented this, run the app and you should see your list of different feeds look like on normal feed. And it will be done with ease.

--

--

Thomas Hosty Cundell
Thomas Hosty Cundell

Written by Thomas Hosty Cundell

Sports Perfomance Analyst @ HC Sports Analytics. Contributor to @AllStatsArentWe and former Android Engineer hcsportsanalytics.co.uk

No responses yet