/* * 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.annotation.WorkerThread; import androidx.arch.core.util.Function; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; /** * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at * arbitrary page positions. *
* Extend PositionalDataSource if you can load pages of a requested size at arbitrary * positions, and provide a fixed item count. If your data source can't support loading arbitrary * requested page sizes (e.g. when network page size constraints are only known at runtime), use * either {@link PageKeyedDataSource} or {@link ItemKeyedDataSource} instead. *
* Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled} * PositionalDataSource requires counting the size of the data set. This allows pages to be tiled in * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}. * If placeholders are disabled, initialize with the two parameter * {@link LoadInitialCallback#onResult(List, int)}. *
* Room can generate a Factory of PositionalDataSources for you: *
* {@literal @}Dao * interface UserDao { * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC") * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc(); * }* * @param
* Note that this may not be within the bounds of your data set, it may need to be adjusted * before you execute your load. */ public final int requestedStartPosition; /** * Requested number of items to load. *
* Note that this may be larger than available data. */ public final int requestedLoadSize; /** * Defines page size acceptable for return values. *
* List of items passed to the callback must be an integer multiple of page size. */ public final int pageSize; /** * 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( int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled) { this.requestedStartPosition = requestedStartPosition; this.requestedLoadSize = requestedLoadSize; this.pageSize = pageSize; this.placeholdersEnabled = placeholdersEnabled; } } /** * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}. */ @SuppressWarnings("WeakerAccess") public static class LoadRangeParams { /** * Start position of data to load. *
* Returned data must start at this position. */ public final int startPosition; /** * Number of items to load. *
* Returned data must be of this size, unless at end of the list. */ public final int loadSize; public LoadRangeParams(int startPosition, int loadSize) { this.startPosition = startPosition; this.loadSize = loadSize; } } /** * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} * to return data, position, and count. *
* A callback should be called only once, and may throw if called again. *
* 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
* 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 the total size to the totalCount parameter. If placeholders are not
* requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead
* call {@link #onResult(List, int)}.
*
* @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 Note: This method can only be called when placeholders
* are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
*
* Call this method from your DataSource's {@code loadInitial} function to return data,
* if position is known but total size is not. If placeholders are requested, call the three
* parameter variant: {@link #onResult(List, int, int)}.
*
* @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 provided by this DataSource,
* pass {@code N}.
*/
public abstract void onResult(@NonNull List
* A callback should be called only once, and may throw if called again.
*
* 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
* This method is called to load the initial page(s) from the DataSource.
*
* Result list must be a multiple of pageSize to enable efficient tiling.
*
* @param params Parameters for initial load, including requested start position, load size, and
* page size.
* @param callback Callback that receives initial load data, including
* position and total data set size.
*/
@WorkerThread
public abstract void loadInitial(
@NonNull LoadInitialParams params,
@NonNull LoadInitialCallback
* This method is called to load additional pages from the DataSource after the
* LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
*
* Unlike {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, this method must return
* the number of items requested, at the position requested.
*
* @param params Parameters for load, including start position and load size.
* @param callback Callback that receives loaded data.
*/
@WorkerThread
public abstract void loadRange(@NonNull LoadRangeParams params,
@NonNull LoadRangeCallback
* The value computed by this function will do bounds checking, page alignment, and positioning
* based on initial load size requested.
*
* Example usage in a PositionalDataSource subclass:
*
* This function takes the requested load size, and bounds checks it against the value returned
* by {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
*
* Example usage in a PositionalDataSource subclass:
*
* class ItemDataSource extends PositionalDataSource<Item> {
* private int computeCount() {
* // actual count code here
* }
*
* private List<Item> loadRangeInternal(int startPosition, int loadCount) {
* // actual load code here
* }
*
* {@literal @}Override
* public void loadInitial({@literal @}NonNull LoadInitialParams params,
* {@literal @}NonNull LoadInitialCallback<Item> callback) {
* int totalCount = computeCount();
* int position = computeInitialLoadPosition(params, totalCount);
* int loadSize = computeInitialLoadSize(params, position, totalCount);
* callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
* }
*
* {@literal @}Override
* public void loadRange({@literal @}NonNull LoadRangeParams params,
* {@literal @}NonNull LoadRangeCallback<Item> callback) {
* callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
* }
* }
*
* @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
* including page size, and requested start/loadSize.
* @param totalCount Total size of the data set.
* @return Position to start loading at.
*
* @see #computeInitialLoadSize(LoadInitialParams, int, int)
*/
public static int computeInitialLoadPosition(@NonNull LoadInitialParams params,
int totalCount) {
int position = params.requestedStartPosition;
int initialLoadSize = params.requestedLoadSize;
int pageSize = params.pageSize;
int roundedPageStart = Math.round(position / pageSize) * pageSize;
// maximum start pos is that which will encompass end of list
int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize;
roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
// minimum start position is 0
roundedPageStart = Math.max(0, roundedPageStart);
return roundedPageStart;
}
/**
* Helper for computing an initial load size in
* {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
* computed ahead of loading.
*
* class ItemDataSource extends PositionalDataSource<Item> {
* private int computeCount() {
* // actual count code here
* }
*
* private List<Item> loadRangeInternal(int startPosition, int loadCount) {
* // actual load code here
* }
*
* {@literal @}Override
* public void loadInitial({@literal @}NonNull LoadInitialParams params,
* {@literal @}NonNull LoadInitialCallback<Item> callback) {
* int totalCount = computeCount();
* int position = computeInitialLoadPosition(params, totalCount);
* int loadSize = computeInitialLoadSize(params, position, totalCount);
* callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
* }
*
* {@literal @}Override
* public void loadRange({@literal @}NonNull LoadRangeParams params,
* {@literal @}NonNull LoadRangeCallback<Item> callback) {
* callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
* }
* }
*
* @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
* including page size, and requested start/loadSize.
* @param initialLoadPosition Value returned by
* {@link #computeInitialLoadPosition(LoadInitialParams, int)}
* @param totalCount Total size of the data set.
* @return Number of items to load.
*
* @see #computeInitialLoadPosition(LoadInitialParams, int)
*/
@SuppressWarnings("WeakerAccess")
public static int computeInitialLoadSize(@NonNull LoadInitialParams params,
int initialLoadPosition, int totalCount) {
return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
}
@SuppressWarnings("deprecation")
static class ContiguousWithoutPlaceholdersWrapper, List
, List