Initial placeholder with Paging Architecture Components

Viewed 3789

I'm using the Paging Architecture Components in my project to load a list from the network (no database usage yet). My DataSource is a PositionalDataSource subclass, and my PagedList.Config looks like this:

PagedList.Config config = new PagedList.Config.Builder()
        .setPageSize(10)
        .setInitialLoadSizeHint(20)
        .setPrefetchDistance(20)
        .setEnablePlaceholders(true)
        .build();

Placeholders are enabled, and I handle null ViewHolders in my PagedListAdapter subclass to display placeholder items differently. The placeholders work fine when loading additional items at the end of the list, but I also want to display a placeholder item before the first few items are loaded (like a regular loading indicator).

Is there a way to do this with the Paging library? I tried to call LoadInitialCallback.onResult() with a position of 1 instead of 0, but this only creates a placeholder item before the first real item, and that placeholder item never disappears.

2 Answers

I realize this question is rather old but I answer this, since this is where I ended up from googling the issue . In v3 there are LoadStateAdapter which you can add to Header and Footer for Prepend and Append load states. Initial load will be the refresh state which does not seem to be included but we can simply copy the functionality and extend it to include Refresh:

I simply copied withLoadStateHeaderAndFooter and added a third adapter that could be used for refresh

private fun PagingAdapter.withLoadStateAll(
        header: LoadStateAdapter<*>,
        footer: LoadStateAdapter<*>,
        refresh: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            header.loadState = loadStates.prepend
            footer.loadState = loadStates.append
            refresh.loadState = loadStates.refresh
        }
        return ConcatAdapter(refresh,header, this, footer)
    }

then you can simply register this to your Recycler as per the documentation and it'll show during initial load.

recycler.adapter = adapter.withLoadStateAll(LoadAdapter(),LoadAdapter(),LoadAdapter())

If you need to show more than one item during initial load simply copy LoadStateAdapter and change the number of items from 1 to n:

class InitialLoadAdapter(private val num : Int) : RecyclerView.Adapter<LoadViewHolder>() {

    var loadState: LoadState = LoadState.NotLoading(endOfPaginationReached = false)
        set(loadState) {
            if (field != loadState) {
                val oldItem = displayLoadStateAsItem(field)
                val newItem = displayLoadStateAsItem(loadState)

                if (oldItem && !newItem) {
                    notifyItemRangeRemoved(0,num)
                } else if (newItem && !oldItem) {
                    notifyItemRangeInserted(0,num)
                } else if (oldItem && newItem) {
                    notifyItemRangeChanged(0,num)
                }
                field = loadState
            }
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LoadViewHolder {
        return LoadViewHolder(parent)
    }
    override fun onBindViewHolder(holder: LoadViewHolder, position: Int) {}

    override fun getItemViewType(position: Int): Int = getStateViewType(loadState)

    override fun getItemCount(): Int = if (displayLoadStateAsItem(loadState)) num else 0

    private fun getStateViewType(loadState: LoadState): Int = 0

    private fun displayLoadStateAsItem(loadState: LoadState): Boolean {
        return loadState is LoadState.Loading || loadState is LoadState.Error
    }
}

There's a method from where you should pass the data set size, I think that it is what you are missing.

public abstract static class LoadInitialCallback<Key, Value> {
        /**
         * Called to pass initial load state from a DataSource.
         * <p>
         * Call this method from your DataSource's {@code loadInitial} function to return data,
         * and inform how many placeholders should be shown before and after. If counting is cheap
         * to compute (for example, if a network load returns the information regardless), it's
         * recommended to pass data back through this method.
         * <p>
         * It is always valid to pass a different amount of data than what is requested. Pass an
         * empty list if there is no more data to load.
         *
         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
         *             is treated as empty, and no further loads will occur.
         * @param position Position of the item at the front of the list. If there are {@code N}
         *                 items before the items in data that can be loaded from this DataSource,
         *                 pass {@code N}.
         * @param totalCount Total number of items that may be returned from this DataSource.
         *                   Includes the number in the initial {@code data} parameter
         *                   as well as any items that can be loaded in front or behind of
         *                   {@code data}.
         */
        public abstract void onResult(@NonNull List<Value> data, int position, int totalCount,
                @Nullable Key previousPageKey, @Nullable Key nextPageKey);

I tried that but I am not been able to see the previews neither.

Check http://youtube.com/watch?v=BE5bsyGGLf4

Hope this helps

Related