public abstract class

ItemKeyedDataSource<Key, Value>

extends androidx.paging.ContiguousDataSource<java.lang.Object, java.lang.Object>

 java.lang.Object

androidx.paging.DataSource<java.lang.Object, java.lang.Object>

↳androidx.paging.ContiguousDataSource<java.lang.Object, java.lang.Object>

↳androidx.paging.ItemKeyedDataSource<Key, Value>

Overview

Incremental data loader for paging keyed content, where loaded content uses previously loaded items as input to future loads.

Implement a DataSource using ItemKeyedDataSource if you need to use data from item N - 1 to load item N. This is common, for example, in sorted database queries where attributes of the item such just before the next query define how to execute it.

The InMemoryByItemRepository in the PagingWithNetworkSample shows how to implement a network ItemKeyedDataSource using Retrofit, while handling swipe-to-refresh, network errors, and retry.

Summary

Constructors
publicItemKeyedDataSource()

Methods
public abstract java.lang.ObjectgetKey(java.lang.Object item)

Return a key associated with the given item.

public abstract voidloadAfter(ItemKeyedDataSource.LoadParams<java.lang.Object> params, ItemKeyedDataSource.LoadCallback<java.lang.Object> callback)

Load list data after the key specified in LoadParams.key.

public abstract voidloadBefore(ItemKeyedDataSource.LoadParams<java.lang.Object> params, ItemKeyedDataSource.LoadCallback<java.lang.Object> callback)

Load list data before the key specified in LoadParams.key.

public abstract voidloadInitial(ItemKeyedDataSource.LoadInitialParams<java.lang.Object> params, ItemKeyedDataSource.LoadInitialCallback<java.lang.Object> callback)

Load initial data.

public abstract DataSource<java.lang.Object, java.lang.Object>map(Function<java.lang.Object, java.lang.Object> function)

Applies the given function to each value emitted by the DataSource.

public abstract DataSource<java.lang.Object, java.lang.Object>mapByPage(Function<java.util.List, java.util.List> function)

Applies the given function to each value emitted by the DataSource.

from DataSource<Key, Value>addInvalidatedCallback, invalidate, isInvalid, removeInvalidatedCallback
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ItemKeyedDataSource()

Methods

public abstract void loadInitial(ItemKeyedDataSource.LoadInitialParams<java.lang.Object> params, ItemKeyedDataSource.LoadInitialCallback<java.lang.Object> callback)

Load initial data.

This method is called first to initialize a PagedList with data. If it's possible to count the items that can be loaded by the DataSource, it's recommended to pass the loaded data to the callback via the three-parameter ItemKeyedDataSource.LoadInitialCallback.onResult(List, int, int). This enables PagedLists presenting data from this source to display placeholders to represent unloaded items.

ItemKeyedDataSource.LoadInitialParams.requestedInitialKey and ItemKeyedDataSource.LoadInitialParams.requestedLoadSize are hints, not requirements, so they may be altered or ignored. Note that ignoring the requestedInitialKey can prevent subsequent PagedList/DataSource pairs from initializing at the same location. If your data source never invalidates (for example, loading from the network without the network ever signalling that old data must be reloaded), it's fine to ignore the initialLoadKey and always start from the beginning of the data set.

Parameters:

params: Parameters for initial load, including initial key and requested size.
callback: Callback that receives initial load data.

public abstract void loadAfter(ItemKeyedDataSource.LoadParams<java.lang.Object> params, ItemKeyedDataSource.LoadCallback<java.lang.Object> callback)

Load list data after the key specified in LoadParams.key.

It's valid to return a different list size than the page size if it's easier, e.g. if your backend defines page sizes. It is generally safer to increase the number loaded than reduce.

Data may be passed synchronously during the loadAfter method, or deferred and called at a later time. Further loads going down will be blocked until the callback is called.

If data cannot be loaded (for example, if the request is invalid, or the data would be stale and inconsistent, it is valid to call DataSource.invalidate() to invalidate the data source, and prevent further loading.

Parameters:

params: Parameters for the load, including the key to load after, and requested size.
callback: Callback that receives loaded data.

public abstract void loadBefore(ItemKeyedDataSource.LoadParams<java.lang.Object> params, ItemKeyedDataSource.LoadCallback<java.lang.Object> callback)

Load list data before the key specified in LoadParams.key.

It's valid to return a different list size than the page size if it's easier, e.g. if your backend defines page sizes. It is generally safer to increase the number loaded than reduce.

Note: Data returned will be prepended just before the key passed, so if you vary size, ensure that the last item is adjacent to the passed key.

Data may be passed synchronously during the loadBefore method, or deferred and called at a later time. Further loads going up will be blocked until the callback is called.

If data cannot be loaded (for example, if the request is invalid, or the data would be stale and inconsistent, it is valid to call DataSource.invalidate() to invalidate the data source, and prevent further loading.

Parameters:

params: Parameters for the load, including the key to load before, and requested size.
callback: Callback that receives loaded data.

public abstract java.lang.Object getKey(java.lang.Object item)

Return a key associated with the given item.

If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique integer ID, you would return item.getID() here. This key can then be passed to ItemKeyedDataSource.loadBefore(ItemKeyedDataSource.LoadParams, ItemKeyedDataSource.LoadCallback) or ItemKeyedDataSource.loadAfter(ItemKeyedDataSource.LoadParams, ItemKeyedDataSource.LoadCallback) to load additional items adjacent to the item passed to this function.

If your key is more complex, such as when you're sorting by name, then resolving collisions with integer ID, you'll need to return both. In such a case you would use a wrapper class, such as Pair or, in Kotlin, data class Key(val name: String, val id: Int)

Parameters:

item: Item to get the key from.

Returns:

Key associated with given item.

public abstract DataSource<java.lang.Object, java.lang.Object> mapByPage(Function<java.util.List, java.util.List> function)

Applies the given function to each value emitted by the DataSource.

Same as DataSource.map(Function), but allows for batch conversions.

Parameters:

function: Function that runs on each loaded page, returning items of a potentially new type.

Returns:

A new DataSource, which transforms items using the given function.

See also: DataSource.map(Function), DataSource.Factory.map(Function), DataSource.Factory.mapByPage(Function, List>)

public abstract DataSource<java.lang.Object, java.lang.Object> map(Function<java.lang.Object, java.lang.Object> function)

Applies the given function to each value emitted by the DataSource.

Same as DataSource.mapByPage(Function, List>), but operates on individual items.

Parameters:

function: Function that runs on each loaded item, returning items of a potentially new type.

Returns:

A new DataSource, which transforms items using the given function.

See also: DataSource.mapByPage(Function, List>), DataSource.Factory.map(Function), DataSource.Factory.mapByPage(Function, List>)

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.paging;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;

import java.util.List;
import java.util.concurrent.Executor;

/**
 * Incremental data loader for paging keyed content, where loaded content uses previously loaded
 * items as input to future loads.
 * <p>
 * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
 * to load item {@code N}. This is common, for example, in sorted database queries where
 * attributes of the item such just before the next query define how to execute it.
 * <p>
 * The {@code InMemoryByItemRepository} in the
 * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
 * shows how to implement a network ItemKeyedDataSource using
 * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
 * handling swipe-to-refresh, network errors, and retry.
 *
 * @param <Key> Type of data used to query Value types out of the DataSource.
 * @param <Value> Type of items being loaded by the DataSource.
 */
public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {

    /**
     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
     *
     * @param <Key> Type of data used to query Value types out of the DataSource.
     */
    @SuppressWarnings("WeakerAccess")
    public static class LoadInitialParams<Key> {
        /**
         * Load items around this key, or at the beginning of the data set if {@code null} is
         * passed.
         * <p>
         * Note that this key is generally a hint, and may be ignored if you want to always load
         * from the beginning.
         */
        @Nullable
        public final Key requestedInitialKey;

        /**
         * Requested number of items to load.
         * <p>
         * Note that this may be larger than available data.
         */
        public final int requestedLoadSize;

        /**
         * Defines whether placeholders are enabled, and whether the total count passed to
         * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
         */
        public final boolean placeholdersEnabled;


        public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
                boolean placeholdersEnabled) {
            this.requestedInitialKey = requestedInitialKey;
            this.requestedLoadSize = requestedLoadSize;
            this.placeholdersEnabled = placeholdersEnabled;
        }
    }

    /**
     * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)}
     * and {@link #loadAfter(LoadParams, LoadCallback)}.
     *
     * @param <Key> Type of data used to query Value types out of the DataSource.
     */
    @SuppressWarnings("WeakerAccess")
    public static class LoadParams<Key> {
        /**
         * Load items before/after this key.
         * <p>
         * Returned data must begin directly adjacent to this position.
         */
        @NonNull
        public final Key key;
        /**
         * Requested number of items to load.
         * <p>
         * Returned page can be of this size, but it may be altered if that is easier, e.g. a
         * network data source where the backend defines page size.
         */
        public final int requestedLoadSize;

        public LoadParams(@NonNull Key key, int requestedLoadSize) {
            this.key = key;
            this.requestedLoadSize = requestedLoadSize;
        }
    }

    /**
     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
     * to return data and, optionally, position/count information.
     * <p>
     * A callback can be called only once, and will throw if called again.
     * <p>
     * If you can compute the number of items in the data set before and after the loaded range,
     * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
     * can skip passing this information by calling the single parameter {@link #onResult(List)},
     * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
     * {@code false}, so the positioning information will be ignored.
     * <p>
     * It is always valid for a DataSource loading method that takes a callback to stash the
     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
     * temporary, recoverable error states (such as a network error that can be retried).
     *
     * @param <Value> Type of items being loaded.
     */
    public abstract static class LoadInitialCallback<Value> extends LoadCallback<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);
    }


    /**
     * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
     * and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
     * <p>
     * A callback can be called only once, and will throw if called again.
     * <p>
     * It is always valid for a DataSource loading method that takes a callback to stash the
     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
     * temporary, recoverable error states (such as a network error that can be retried).
     *
     * @param <Value> Type of items being loaded.
     */
    public abstract static class LoadCallback<Value> {
        /**
         * Called to pass loaded data from a DataSource.
         * <p>
         * Call this method from your ItemKeyedDataSource's
         * {@link #loadBefore(LoadParams, LoadCallback)} and
         * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
         * <p>
         * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
         * initialize without counting available data, or supporting placeholders.
         * <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 ItemKeyedDataSource.
         */
        public abstract void onResult(@NonNull List<Value> data);
    }

    static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
        final LoadCallbackHelper<Value> mCallbackHelper;
        private final boolean mCountingEnabled;
        LoadInitialCallbackImpl(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled,
                @NonNull PageResult.Receiver<Value> receiver) {
            mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
            mCountingEnabled = countingEnabled;
        }

        @Override
        public void onResult(@NonNull List<Value> data, int position, int totalCount) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);

                int trailingUnloadedCount = totalCount - position - data.size();
                if (mCountingEnabled) {
                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
                            data, position, trailingUnloadedCount, 0));
                } else {
                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
                }
            }
        }

        @Override
        public void onResult(@NonNull List<Value> data) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
        }
    }

    static class LoadCallbackImpl<Value> extends LoadCallback<Value> {
        final LoadCallbackHelper<Value> mCallbackHelper;

        LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
                @Nullable Executor mainThreadExecutor,
                @NonNull PageResult.Receiver<Value> receiver) {
            mCallbackHelper = new LoadCallbackHelper<>(
                    dataSource, type, mainThreadExecutor, receiver);
        }

        @Override
        public void onResult(@NonNull List<Value> data) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
        }
    }

    @Nullable
    @Override
    final Key getKey(int position, Value item) {
        if (item == null) {
            return null;
        }

        return getKey(item);
    }

    @Override
    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        LoadInitialCallbackImpl<Value> callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);

        // If initialLoad's callback is not called within the body, we force any following calls
        // to post to the UI thread. This constructor may be run on a background thread, but
        // after constructor, mutation must happen on UI thread.
        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }

    @Override
    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
            int pageSize, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),
                new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
    }

    @Override
    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
            int pageSize, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),
                new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
    }

    /**
     * Load initial data.
     * <p>
     * This method is called first to initialize a PagedList with data. If it's possible to count
     * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
     * the callback via the three-parameter
     * {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
     * presenting data from this source to display placeholders to represent unloaded items.
     * <p>
     * {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
     * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
     * {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
     * initializing at the same location. If your data source never invalidates (for example,
     * loading from the network without the network ever signalling that old data must be reloaded),
     * it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
     * data set.
     *
     * @param params Parameters for initial load, including initial key and requested size.
     * @param callback Callback that receives initial load data.
     */
    public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
            @NonNull LoadInitialCallback<Value> callback);

    /**
     * Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
     * <p>
     * It's valid to return a different list size than the page size if it's easier, e.g. if your
     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
     * <p>
     * Data may be passed synchronously during the loadAfter method, or deferred and called at a
     * later time. Further loads going down will be blocked until the callback is called.
     * <p>
     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
     * and prevent further loading.
     *
     * @param params Parameters for the load, including the key to load after, and requested size.
     * @param callback Callback that receives loaded data.
     */
    public abstract void loadAfter(@NonNull LoadParams<Key> params,
            @NonNull LoadCallback<Value> callback);

    /**
     * Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
     * <p>
     * It's valid to return a different list size than the page size if it's easier, e.g. if your
     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
     * <p>
     * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
     * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
     * <p>
     * Data may be passed synchronously during the loadBefore method, or deferred and called at a
     * later time. Further loads going up will be blocked until the callback is called.
     * <p>
     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
     * and prevent further loading.
     *
     * @param params Parameters for the load, including the key to load before, and requested size.
     * @param callback Callback that receives loaded data.
     */
    public abstract void loadBefore(@NonNull LoadParams<Key> params,
            @NonNull LoadCallback<Value> callback);

    /**
     * Return a key associated with the given item.
     * <p>
     * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
     * integer ID, you would return {@code item.getID()} here. This key can then be passed to
     * {@link #loadBefore(LoadParams, LoadCallback)} or
     * {@link #loadAfter(LoadParams, LoadCallback)} to load additional items adjacent to the item
     * passed to this function.
     * <p>
     * If your key is more complex, such as when you're sorting by name, then resolving collisions
     * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
     * such as {@code Pair<String, Integer>} or, in Kotlin,
     * {@code data class Key(val name: String, val id: Int)}
     *
     * @param item Item to get the key from.
     * @return Key associated with given item.
     */
    @NonNull
    public abstract Key getKey(@NonNull Value item);

    @NonNull
    @Override
    public final <ToValue> ItemKeyedDataSource<Key, ToValue> mapByPage(
            @NonNull Function<List<Value>, List<ToValue>> function) {
        return new WrapperItemKeyedDataSource<>(this, function);
    }

    @NonNull
    @Override
    public final <ToValue> ItemKeyedDataSource<Key, ToValue> map(
            @NonNull Function<Value, ToValue> function) {
        return mapByPage(createListFunction(function));
    }
}