Saurabh Thorat
Saurabh Thorat

Reputation: 20734

Paging library returns empty list initially

I'm using Paging library to paginate a list of items I'm retrieving from my server. Initially, when my fragment is loaded, it returns an empty list. But after changing fragments and going back to that fragment, I can see the list loaded. After debugging I saw that data was actually being fetched, but an empty list was passed to my fragment.

ItemDataSource:

@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Item> callback) {
    apiService.getItems(OFFSET)
    .enqueue(new Callback<ItemWrapper>() {
        @Override
        public void onResponse(@NonNull Call<ItemWrapper> call,@NonNull Response<ItemWrapper> response) {
            callback.onResult(response.body().getItems(), null, OFFSET + 25);
        }

        @Override
        public void onFailure(@NonNull Call<ItemWrapper> call,@NonNull Throwable t) {
            t.printStackTrace();
        }
    });
}

@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Item> callback) {

}

@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Item> callback) {
    apiService.getItems(params.key)
            .enqueue(new Callback<ItemWrapper>() {
                @Override
                public void onResponse(@NonNull Call<ItemWrapper> call,@NonNull Response<ItemWrapper> response) {
                    Integer key = response.body().getItems().isEmpty() ? null : params.key + 25;
                    callback.onResult(response.body().getItems(), key);
                }

                @Override
                public void onFailure(@NonNull Call<ItemWrapper> call,@NonNull Throwable t) {
                    t.printStackTrace();
                }
            });
}

ItemDataSourceFactory:

@Override
public DataSource create() {
    ItemDataSource itemDataSource = new ItemDataSource();
    itemLiveDataSource.postValue(itemDataSource);
    return itemDataSource;
}

public MutableLiveData<ItemDataSource> getItemLiveDataSource() {
    return itemLiveDataSource;
}

ItemViewModel:

private LiveData<ItemDataSource> liveDataSource;
private LiveData<PagedList<Item>> itemPagedList;

private ItemViewModel(Application application) {
    ItemDataSourceFactory factory = new ItemDataSourceFactory();
    liveDataSource = factory.getItemLiveDataSource();

    PagedList.Config config = (new PagedList.Config.Builder())
                .setEnablePlaceholders(false)
                .setPageSize(ItemDataSource.LIMIT).build();

    itemPagedList = (new LivePagedListBuilder(factory, config)).build();
}

public LiveData<PagedList<Item>> getItems() {
    return itemPagedList;
}

Fragment:

ItemViewModel itemViewModel = ViewModelProviders.of(this).get(ItemViewModel.class);
itemViewModel.getItems.observe(this, items -> {
    adapter.submitList(items);
})

Upvotes: 21

Views: 5038

Answers (6)

Chompions Sawelo
Chompions Sawelo

Reputation: 43

I've been having this same problem as well for a few days until finally I found the solution. But just to note I used Paging version 3, so this might not answer this question directly, but might help anyone who's still struggling in blank recyclerView initially

In your fragment where you applied PagingAdapter, use loadStateFlow on the adapter to observe changes in loadState. You can place this in onCreateView for Fragment

val pagingAdapter = PagingAdapter()
lifecycleScope.launch {
        pagingAdapter.loadStateFlow.collect { loadState ->
            if (loadState.prepend.endOfPaginationReached) {
             // Apply this if PagingDataAdapter start binding data in view holder
                println("APPLYING ADAPTER")
                binding.recyclerView.adapter = pagingAdapter
                cancel() // Cancel this flow after applying adapter
            }
        }
    }

Upvotes: 0

Anton Malyshev
Anton Malyshev

Reputation: 8871

Just as in the question I was loading the data asynchronously inside loadInitial, but the data did not come to the adapter when callback was called.

Solved just by updating the library version from androidx.paging:paging-runtime-ktx:2.1.2 to androidx.paging:paging-runtime-ktx:3.0.0.

Upvotes: 0

Conti
Conti

Reputation: 1007

If, like me, anyone is using an asynchronous RxJava/Kotlin call in loadInitial. I finally figured out a solution after many painful hours.

I tried using a delayed Handler (500ms) in the Observer method but it was fickle and didn't work in every scenario. No matter how much I tried to make it synchronous using setFetcher and Rx observeOn it wouldn't consistently work.

My solution was to use .blockingSubscribe in my Observable. My data fetcher was using a Socket library that had its own concurrency out of my scope, so I couldn't guarantee that I could make the process wholly synchronous as Paging requires. (A process which needs better documentation IMO). Anyway, here's my solution, hopefully it helps others with same issue:

    override fun loadInitial(
            params: LoadInitialParams<Int>,
            callback: LoadInitialCallback<Int, ResultItem>
    ) {
       mySocketClientRxRequest()
                .subscribe ({
                    callback.onResult(it.resultItems 1, 2)
                },{
                    it.printStackTrace()
                })
    }

to

    override fun loadInitial(
            params: LoadInitialParams<Int>,
            callback: LoadInitialCallback<Int, ResultItem>
    ) {
       mySocketClientRxRequest()
                .blockingSubscribe ({
                    callback.onResult(it.resultItems 1, 2)
                },{
                    it.printStackTrace()
                })
    }

Upvotes: 1

Alexander L.
Alexander L.

Reputation: 165

Yassin Ajdi is right. loadinitial() calls immediately on the same thread where PagedList is created on. As your API is async, the method runs empty for the first time

Upvotes: 2

Yassin Ajdi
Yassin Ajdi

Reputation: 1550

Not 100% sure, but I think this is because you are running an asynchronous request. try to change it to run synchronously for loadInitial() like so request.execute()

Upvotes: 9

Edward Codarcea
Edward Codarcea

Reputation: 115

I also had this problem once and I still can't figure it out why it does not work for some fragments. The solution I found, aldo being more like a fast sketchy fix is to load the fragment twice.

Upvotes: 2

Related Questions