21

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);
})
Saurabh Thorat
  • 18,131
  • 5
  • 53
  • 70
  • Could you add the code of adapter#submitList() please? Perhaps its just something trivial like not doing a notifyDataChanged. Also you should try debugging to see how far the data goes, since - as you wrote - it is fetched from the server. – Ridcully Dec 10 '18 at 05:52
  • @Ridcully I'm using the predefined `submitList()` method of PagedListAdapter. I didn't override it. – Saurabh Thorat Dec 10 '18 at 06:15
  • Were you able to resolve the issue? Also, will be great if you can share what worked for you. – ABS Aug 21 '19 at 21:16
  • @ABS I rewrote this entire code from scratch and it started working. But I couldn't pinpoint exactly where was the problem. – Saurabh Thorat Aug 22 '19 at 04:43

6 Answers6

9

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()

Yassin Ajdi
  • 1,540
  • 14
  • 13
2

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.

2

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

Alexander L.
  • 165
  • 2
  • 13
1

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()
                })
    }
Conti
  • 1,017
  • 1
  • 11
  • 15
  • you're not canceling requests at all with this approach – Kibotu Apr 27 '21 at 13:50
  • @Kibotu you could also argue I shouldn't be using blocking rx call in production, but it is what it is, a workaround for a fundamental flaw in Paging 2 library. Though out of interest, what kind of cancellation would you expect? – Conti Apr 28 '21 at 06:36
  • imagine you have a search where you have to invalidate on each letter, then you're not interested in most requests if the user types. sidenote: the blocking is ok since we're already on arcio thread by pagination library, bad part is that even with setting own executioner, the empty list part stays – Kibotu Apr 29 '21 at 12:07
0

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.

Anton Malyshev
  • 8,686
  • 2
  • 27
  • 45
0

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
            }
        }
    }