0

How to know when to request the server for more items using the new android Paging Library of Architecture Components? For example, if I want to implement an endless scrolling where the data is loaded from server how do I know when I have to request more items.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
Damia Fuentes
  • 5,308
  • 6
  • 33
  • 65

1 Answers1

1

I use next class for implementing this feature:

package com.mlsdev.enjoymusic.data.repository;

import android.arch.paging.DataSource;
import android.arch.paging.TiledDataSource;
import android.arch.persistence.room.InvalidationTracker;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.util.Log;

import com.mlsdev.enjoymusic.data.local.DeezerDatabase;
import com.mlsdev.enjoymusic.data.local.Table;

import java.io.IOException;
import java.util.List;
import java.util.Set;

import retrofit2.Call;
import retrofit2.Response;

/**
 * Created by stafievsky on 09.10.17.
 */

public abstract class PagedNetworkBoundResource<ResultType, RequestType> extends TiledDataSource<ResultType> {

    private final InvalidationTracker.Observer mObserver;
    private DeezerDatabase db;

    public PagedNetworkBoundResource(DeezerDatabase db) {
        this.db = db;
        mObserver = new InvalidationTracker.Observer(Table.States.PLAY_STATE) {

            public void onInvalidated(@NonNull Set<String> tables) {
                invalidate();
            }
        };
        this.db.getInvalidationTracker().addWeakObserver(mObserver);
    }


    @Override
    public boolean isInvalid() {
        db.getInvalidationTracker().refreshVersionsSync();
        return super.isInvalid();
    }

    @Override
    public int countItems() {
        return DataSource.COUNT_UNDEFINED;
    }

    @Override
    public List<ResultType> loadRange(int startPosition, int count) {
        if (startPosition == 0 && count == 20) {
            clearDB();
        }
        fetchFromNetwork(startPosition, count);
        return loadFromDb(startPosition, count);
    }

    public abstract void clearDB();

    @WorkerThread
    private void fetchFromNetwork(int startPosition, int count) {
        if (createCall(startPosition, count) != null)
            try {
                Response<RequestType> response = createCall(startPosition, count).execute();
                if (response.isSuccessful() && response.code() == 200) {
                    saveCallResult(response.body());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

    @WorkerThread
    protected abstract void saveCallResult(@NonNull RequestType item);


    @WorkerThread
    protected abstract List<ResultType> loadFromDb(int startPosition, int count);

    @WorkerThread
    protected abstract Call<RequestType> createCall(int startPosition, int count);
}

And this implementation of this class:

public LiveData<PagedList<ChartAlbumDao.Album>> getAlbums() {

    return new LivePagedListProvider<Integer, ChartAlbumDao.Album>() {
        @Override
        protected DataSource<Integer, ChartAlbumDao.Album> createDataSource() {
            return new PagedNetworkBoundResource<ChartAlbumDao.Album, ModelList<ChartAlbumEntity>>(db) {

                @Override
                public void clearDB() {

                }

                @Override
                protected void saveCallResult(@NonNull ModelList<ChartAlbumEntity> item) {
                    if (item != null) {
                        chartAlbumDao.saveAlbums(item.getItems());
                    }
                }

                @NonNull
                @Override
                protected List<ChartAlbumDao.Album> loadFromDb(int startPosition, int count) {
                    return chartAlbumDao.loadAlbums(count, startPosition);
                }

                @NonNull
                @Override
                protected Call<ModelList<ChartAlbumEntity>> createCall(int startPosition, int count) {
                    return deezerService.getChartAlbums(startPosition, count);
                }
            };
        }

    }.create(0, new PagedList.Config.Builder()
            .setEnablePlaceholders(false)
            .setPageSize(20)
            .setInitialLoadSizeHint(20)
            .build());
}
Alex
  • 138
  • 8
  • When data is invalidated, is loadRange called again? If it is the case how do you control that data is not fetched again? If it is not the case how does TiledDataSource know which items to update? – Damia Fuentes Nov 16 '17 at 19:21
  • And in the loadFromDb method, what happens when the database doesn't have the items yet because it's the first time and you are fetching it from network? If loadRange method receive less items than count, paged list understand that is the end of the list. – Damia Fuentes Nov 16 '17 at 19:32
  • Hi, in my implementation I download items from server, save them into local database and after that load range from database. If I don't have network connection - step with remote server will be ignored and data will be loaded from db immediately. In my observer I check if data not null. When DataSource will be invalidated - LivePagedListProvided call `createDataSource()` method, and after that `loadRange(int,int)` will be called again but with saved state of your recyclerView (if you use Adapter from paging library). And Paging library know when we gone to the end of our data source. – Alex Nov 17 '17 at 08:54
  • But once loadRange is called again, the request to server is called again. Do you know if there is any way to handle this using Paging Library methods? (I know I can create a variable handling this but maybe pagin library can do it for me) – Damia Fuentes Nov 19 '17 at 21:49
  • @DamiaFuentes Paging can't do it from the box. Because Paging works only with the abstraction of `DataSource`, so that's why you should create your custom `DataSource` with handling functionality (like cache depend on timestamp). – Alex Nov 20 '17 at 07:17