Paging library DataSource.Factory for multiple data sources
Asked Answered
D

3

8

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?

Dunderhead answered 1/7, 2018 at 13:37 Comment(3)
I am also looking for answer to this question? Have you found one yet?Tuna
Nope, we have one DataSourceFactory per data source as of now.Dunderhead
I found the solution. Check here #54769284Tijuanatike
P
0

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>()
    }
}
Purdy answered 1/4, 2019 at 22:25 Comment(0)
A
0

We can create multiple instances of DataSource.Factory class which holds multilpe LiveData objects.

First create instance of factory and viewmodel in main activity then write a switch condition or if else ladder for choosing data source from DataSource.Factory class.

In switch condition you need to call factory.create(viewmodel).getLiveData method

For example

switch (service){
        case 1:
            final Adapter adapter = new Adapter();
            factory.create(viewModel.getClass()).getPagedECListLiveData().observe((LifecycleOwner) activity, new Observer<PagedList<ECRecord>>() {
                @Override
                public void onChanged(@Nullable PagedList<ECRecord> ecRecords) {
                    Adapter.submitList(ecRecords);
                }
            });
            recyclerView.setAdapter(adapter);
            break;
        case 2:
            final CAdapter cadapter = new CAdapter();
            factory.create(viewModel.getClass()).getPagedSTListLiveData().observe((LifecycleOwner) activity, new Observer<PagedList<STRecord>>() {
                @Override
                public void onChanged(@Nullable PagedList<STRecord> stRecords) {
                    ECTAdapter.submitList(stRecords);
                }
            });
            recyclerView.setAdapter(cadapter);
            break;
}

Happy Coding :)

Atalie answered 10/12, 2019 at 15:39 Comment(0)
D
-3

As seen in the Guide to the app architecture it's advised to have a single source of truth so no matter how many data sources you have you should only have one single source of truth.

Examples used in the Paging Library all rely on this fact and that is why paging library support Room by default. But it don't means you have to use database, as a matter of fact:

In this model, the database serves as the single source of truth, and other parts of the app access it via the repository. Regardless of whether you use a disk cache, we recommend that your repository designate a data source as the single source of truth to the rest of your app.

P.S: Even if you don't want to designate a single source of truth you don't have to define multiple DataSource you can just implement a custom data source that combine multiple stream of data to create a displayable list of items. For example:

public class MentionKeyedDataSource extends ItemKeyedDataSource<Long, Mention> {

    private Repository repository;
    ...
    private List<Mention> cachedItems;

    public MentionKeyedDataSource(Repository repository, ..., List<Mention> cachedItems){
        super();

        this.repository = repository;
        ...
        this.cachedItems = new ArrayList<>(cachedItems);
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Long> params, final @NonNull ItemKeyedDataSource.LoadInitialCallback<Mention> callback) {
        Observable.just(cachedItems)
                .filter(() -> return cachedItems != null && !cachedItems.isEmpty())
                .switchIfEmpty(repository.getItems(params.requestedLoadSize))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }
    ...
Droshky answered 3/7, 2018 at 5:24 Comment(1)
This doesn't explain the question at all.Anticathode

© 2022 - 2024 — McMap. All rights reserved.