Even though the next would answer my own question, for Database-Network logic where the data can mutate, this solution is not enough since it doesn't handle updates to data already saved in database.
Pardon the amount of abstract classes - I usually work for extension and reusability. Moreover, also pardon the mixture of Java and Kotlin code.. for some reason some of the libraries were not recognised in my project using Kotlin but feel free to use Kotlin in those cases.
So basically we needed to create a new DataSource class
abstract class MyDataSource<Value>: PositionalDataSource<Value>() {
override fun loadInitial(params: PositionalDataSource.LoadInitialParams, callback: PositionalDataSource.LoadInitialCallback<Value>) {
callback.onResult(getDataFromDatabase(params.requestedLoadSize, params.requestedStartPosition)?: listOf(), 0)
}
override fun loadRange(params: PositionalDataSource.LoadRangeParams, callback: PositionalDataSource.LoadRangeCallback<Value>) {
callback.onResult(getDataFromDatabase(params.loadSize, params.startPosition)?: listOf())
}
abstract fun getDataFromDatabase(limit: Int, offset: Int): List<Value>?
}
The implemented Concert data class:
class ConcertDataSource : MyDataSource<Concert>() {
override fun getDataFromDatabase(limit: Int, offset: Int): List<Concert>? {
return ConcertAA.getConcerts(limit, offset)
}
}
Then we need a DataSourceFactory that would be creating our DataSource instance:
abstract class MyDataSourceFactory<Key, Value> : DataSource.Factory<Key, Value>() {
val mutableLiveData: MutableLiveData<DataSource<Key, Value>>? = null
override fun create(): DataSource<Key, Value> {
val dataSource = createDataSource()
mutableLiveData?.postValue(dataSource)
return dataSource
}
abstract fun createDataSource(): DataSource<Key, Value>
}
And the implemented DataSourceFactory for the ConcertDataSource:
class ConcertDataSourceFactory : MyDataSourceFactory<Int, Concert>() {
override fun createDataSource(): DataSource<Int, Concert> {
return ConcertDataSource()
}
}
Then a ViewModel for the PagedList that will have our datasource:
public class ConcertViewModel extends ViewModel {
public final LiveData<PagedList<Concert>> concertList;
private FetchCallback callback;
private final int pageSize = 10
PagedList.BoundaryCallback<Concert> boundaryCallback = new PagedList.BoundaryCallback<Concert>(){
boolean frontLoaded =false;
public void onZeroItemsLoaded() {
// callback.fetch(...);
}
public void onItemAtFrontLoaded(@NonNull Concert itemAtFront) {
if(!frontLoaded) {
// callback.fetch(...);
}
frontLoaded = true;
}
public void onItemAtEndLoaded(@NonNull Concert itemAtEnd) {
// callback.fetch(...);
}
};
public ConcertViewModel(FetchCallback callback) {
this.callback = callback;
ConcertDataSourceFactory dataSourceFactory = new ConcertDataSourceFactory();
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(pageSize)
.setEnablePlaceholders(false)
.build();
concertList = new LivePagedListBuilder<>(dataSourceFactory, config).setBoundaryCallback(boundaryCallback).build();
}
public void refresh() {
conertList.getValue().getDataSource().invalidate();
}
}
We use a BoundaryCallback to listen whenever the PagedList requires to load more whenever it reached either of the sides of the list and our database data does't have any extra data stored. This will enable to fetch more data from the server (or any data provider). The fetch is called using a customised callback:
interface FetchCallback{
fun fetch(limit: Int?, concertIdOffset: String?)
}
Now, since we need to pass a callback to our ViewModel, we need to tell the ViewModelProviders how to create a new instance of ViewModel with parameters. This is done using a ViewModelProvider.Factory:
abstract class MyViewModelFactory(
private val callback: FetchCallback) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return createViewModel(callback)
}
abstract fun <T : ViewModel> createViewModel(callback: FetchCallback): T
}
And our implemented ViewModelFactory for Concert:
class ConcertViewModelFactory(callback: FetchCallback) : MyViewModelFactory(callback) {
override fun <T : ViewModel> createViewModel(callback: FetchCallback): T {
return ConcertViewModel(callback) as T
}
}
Finally, we initialise the ViewModel from our view (Activity, Fragment, etc):
val factory = ConcertViewModelFactory(
object: FetchCallback{
override fun fetch(limit: Int?, eventIdOffset: String?, level: RequestParameters.RequestLevel, showLoader: Boolean) {
//Do the call to the server
}
})
viewModel = ViewModelProviders.of(this, factory).get(ConcertViewModel::class.java)
adapter = ConcertAdapter()
viewModel.concertList.observe(viewLifecycleOwner, Observer(adapter::submitList))
This would be the answer to my question, however, note that (at least at the moment) the Paging Library for Android doesn't handle mutable data. For instance, if a concert has a list of bands that can change (e.g. one of the bands cannot make it to the concert), the logic cannot update the data saved in the database since it only asks for extra data to the server when it reaches either of the boundaries (top or bottom) of the data stored in the database. Hence, for my own purposes, this library is useless.