Paging library DataSource.Factory for multiple data sources

Nidhi Shah picture Nidhi Shah · Jul 1, 2018 · Viewed 8k times · Source

The new paging library allows us to specify a custom data source to use with data pagination. Paging library documentation and sample code on github show us how to create your custom data source instances by creating a subclass of DataSource.Factory like so:

class ConcertTimeDataSourceFactory(private val concertStartTime: Date) :
    DataSource.Factory<Date, Concert>() {
    val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
    override fun create(): DataSource<Date, Concert> {
        val source = ConcertTimeDataSource(concertStartTime)
        sourceLiveData.postValue(source)
        return source
    }
}

In a real app, you'd generally have multiple views with recyclerviews and hence multiple custom data sources. So, do you end up creating multiple implementations of DataSource.Factory per data source or is there a more generic solution?

Answer

Allan Veloso picture Allan Veloso · Apr 2, 2019

Not always.

If you are using other Android Architecture components or libraries that give it good support, in most cases the DataSource.Factory will be delivered as a result of a method call like Room database does.

If you really want a very generic one and have no problem with reflection:

class GenericFactory<K, R>(private val kClass: KClass<DataSource<K, R>>) : DataSource.Factory<K, R>() {
    override fun create(): DataSource<K, R> = kClass.java.newInstance()
}

Your example shows a DataSource.Factory that exposes the DataSource as a LiveData. This is just necessary in specific cases, for example, when the DataSource holds a retry method for the API call. In other cases, your DataSource.Factory will be as simple as 3 more lines in your DataSource:

class MySimpleDataSource<R> : PageKeyedDataSource<String, R>() {

    override fun loadBefore(params: LoadParams<String>,
                            callback: LoadCallback<String, R>) {
        // do your thing
    }

    override fun loadAfter(params: LoadParams<String>,
                           callback: LoadCallback<String, R>) {
        // do your thing
    }

    override fun loadInitial(params: LoadInitialParams<String>,
                             callback: LoadInitialCallback<String, R>) {
        // do your thing
    }

    class Factory<R> : DataSource.Factory<String, R>() {
        override fun create(): DataSource<String, R> = MySimpleDataSource<R>()
    }
}

I guess the most common case for custom DataSource.Factory is paginated REST API calls. In this case, you may just implement one generic DataSource and one DataSource.Factory that receives the request object and response callback as a lambda.

data class MyCollection<R>(
        var items: List<R>,
        var nextPageToken: String
)

data class MyData(
        var title: String = ""
)

abstract class SomeLibraryPagedClientRequest<R> {
    abstract fun setNextPageToken(token: String?): SomeLibraryPagedClientRequest<R>
    abstract fun enqueue(callback: (response: Response<R>) -> Unit): Unit
}

class MyRestApiDataSource(
        private val request: SomeLibraryPagedClientRequest<MyData>,
        private val handleResponse: (Response<R>) -> Unit
) : ItemKeyedDataSource<String, MyData>() {

    var nextPageToken: String = ""

    override fun getKey(item: MyData): String = nextPageToken

    override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<MyData>) {
    }

    override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<MyData>) {
        request.setNextPageToken(params.requestedInitialKey).enqueue { data ->
            nextPageToken = response.data.nextPageToken
            if(response.isSucefull) callback.onResult(response.data.items)
            handleResponse.invoke(response)
        }
    }

    override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<MyData>) {
        request.setNextPageToken(params.key).enqueue { response ->
            nextPageToken = response.data.nextPageToken
            if(response.isSucefull) callback.onResult(response.data.items)
            handleResponse.invoke(response)
        }
    }

    class Factory<R>(
        private val request: SomeLibraryPagedClientRequest<MyData>,
        private val handleResponse: (Response<R>) -> Unit
    ) : DataSource.Factory<String, R>() {
        override fun create(): DataSource<String, R> = MySimpleDataSource<R>()
    }
}