diff --git a/app/build.gradle b/app/build.gradle index ff29277c82..b0cd82b332 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,6 +204,7 @@ configurations.all { // lifecycle-livedata: ComputableLiveData, MediatorLiveData, Transformations // lifecycle-livedata-core: LiveData, MutableLiveData, Observer + // paging-runtime: AsyncPagedListDiffer, LivePagedListBuilder, PagedListAdapter, PagedStorageDiffHelper } dependencies { @@ -303,7 +304,7 @@ dependencies { // https://mvnrepository.com/artifact/androidx.paging/paging-runtime // https://developer.android.com/jetpack/androidx/releases/paging - implementation "androidx.paging:paging-runtime:$paging_version" + //implementation "androidx.paging:paging-runtime:$paging_version" // https://mvnrepository.com/artifact/androidx.preference/preference implementation "androidx.preference:preference:$preference_version" diff --git a/app/src/main/java/androidx/paging/AsyncPagedListDiffer.java b/app/src/main/java/androidx/paging/AsyncPagedListDiffer.java new file mode 100644 index 0000000000..6640dc0c8c --- /dev/null +++ b/app/src/main/java/androidx/paging/AsyncPagedListDiffer.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2017 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.executor.ArchTaskExecutor; +import androidx.lifecycle.LiveData; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; + +/** + * Helper object for mapping a {@link PagedList} into a + * {@link androidx.recyclerview.widget.RecyclerView.Adapter RecyclerView.Adapter}. + *
+ * For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the + * differ directly. This diff class is exposed for complex cases, and where overriding an adapter + * base class to support paging isn't convenient. + *
+ * When consuming a {@link LiveData} of PagedList, you can observe updates and dispatch them + * directly to {@link #submitList(PagedList)}. The AsyncPagedListDiffer then can present this + * updating data set simply for an adapter. It listens to PagedList loading callbacks, and uses + * DiffUtil on a background thread to compute updates as new PagedLists are received. + *
+ * It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an + * adapter to acquire and present data objects. + *
+ * A complete usage pattern with Room would look like this: + *
+ * {@literal @}Dao + * interface UserDao { + * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") + * public abstract DataSource.Factory<Integer, User> usersByLastName(); + * } + * + * class MyViewModel extends ViewModel { + * public final LiveData<PagedList<User>> usersList; + * public MyViewModel(UserDao userDao) { + * usersList = new LivePagedListBuilder<>( + * userDao.usersByLastName(), /* page size {@literal *}/ 20).build(); + * } + * } + * + * class MyActivity extends AppCompatActivity { + * {@literal @}Override + * public void onCreate(Bundle savedState) { + * super.onCreate(savedState); + * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); + * RecyclerView recyclerView = findViewById(R.id.user_list); + * final UserAdapter adapter = new UserAdapter(); + * viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList)); + * recyclerView.setAdapter(adapter); + * } + * } + * + * class UserAdapter extends RecyclerView.Adapter<UserViewHolder> { + * private final AsyncPagedListDiffer<User> mDiffer + * = new AsyncPagedListDiffer(this, DIFF_CALLBACK); + * {@literal @}Override + * public int getItemCount() { + * return mDiffer.getItemCount(); + * } + * public void submitList(PagedList<User> pagedList) { + * mDiffer.submitList(pagedList); + * } + * {@literal @}Override + * public void onBindViewHolder(UserViewHolder holder, int position) { + * User user = mDiffer.getItem(position); + * if (user != null) { + * holder.bindTo(user); + * } else { + * // Null defines a placeholder item - AsyncPagedListDiffer will automatically + * // invalidate this row when the actual object is loaded from the database + * holder.clear(); + * } + * } + * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = + * new DiffUtil.ItemCallback<User>() { + * {@literal @}Override + * public boolean areItemsTheSame( + * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { + * // User properties may have changed if reloaded from the DB, but ID is fixed + * return oldUser.getId() == newUser.getId(); + * } + * {@literal @}Override + * public boolean areContentsTheSame( + * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { + * // NOTE: if you use equals, your object must properly override Object#equals() + * // Incorrectly returning false here will result in too many animations. + * return oldUser.equals(newUser); + * } + * } + * }+ * + * @param
+ * Note that this operates on both loaded items and null padding within the PagedList. + * + * @param index Index of item to get, must be >= 0, and < {@link #getItemCount()}. + * @return The item, or null, if a null placeholder is at the specified position. + */ + @SuppressWarnings("WeakerAccess") + @Nullable + public T getItem(int index) { + if (mPagedList == null) { + if (mSnapshot == null) { + throw new IndexOutOfBoundsException( + "Item count is zero, getItem() call is invalid"); + } else { + return mSnapshot.get(index); + } + } + + mPagedList.loadAround(index); + return mPagedList.get(index); + } + + /** + * Get the number of items currently presented by this Differ. This value can be directly + * returned to {@link RecyclerView.Adapter#getItemCount()}. + * + * @return Number of items being presented. + */ + @SuppressWarnings("WeakerAccess") + public int getItemCount() { + if (mPagedList != null) { + return mPagedList.size(); + } + + return mSnapshot == null ? 0 : mSnapshot.size(); + } + + /** + * Pass a new PagedList to the differ. + *
+ * If a PagedList is already present, a diff will be computed asynchronously on a background
+ * thread. When the diff is computed, it will be applied (dispatched to the
+ * {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
+ * {@link #getCurrentList() current list}.
+ *
+ * @param pagedList The new PagedList.
+ */
+ public void submitList(@Nullable final PagedList
+ * If a PagedList is already present, a diff will be computed asynchronously on a background
+ * thread. When the diff is computed, it will be applied (dispatched to the
+ * {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
+ * {@link #getCurrentList() current list}.
+ *
+ * The commit callback can be used to know when the PagedList is committed, but note that it
+ * may not be executed. If PagedList B is submitted immediately after PagedList A, and is
+ * committed directly, the callback associated with PagedList A will not be run.
+ *
+ * @param pagedList The new PagedList.
+ * @param commitCallback Optional runnable that is executed when the PagedList is committed, if
+ * it is committed.
+ */
+ @SuppressWarnings("ReferenceEquality")
+ public void submitList(@Nullable final PagedList
+ * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
+ * because a diff is computed asynchronously between the new list and the current list before
+ * updating the currentList value. May be null if no PagedList is being presented.
+ *
+ * @return The list currently being displayed, may be null.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @Nullable
+ public PagedList
+ * Position may not match passed item's position - if trying to query the key from a position
+ * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
+ */
+ abstract Key getKey(int position, Value item);
+
+ boolean supportsPageDropping() {
+ return true;
+ }
+}
diff --git a/app/src/main/java/androidx/paging/ContiguousPagedList.java b/app/src/main/java/androidx/paging/ContiguousPagedList.java
new file mode 100644
index 0000000000..99060671d8
--- /dev/null
+++ b/app/src/main/java/androidx/paging/ContiguousPagedList.java
@@ -0,0 +1,412 @@
+/*
+ * 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 static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.IntDef;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+class ContiguousPagedList
+ * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
+ * it loads more data, but the data loaded cannot be updated. If the underlying data set is
+ * modified, a new PagedList / DataSource pair must be created to represent the new data.
+ *
+ * To control how and when a PagedList queries data from its DataSource, see
+ * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
+ *
+ * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
+ * PagedList. For example, loading from network when the network's paging API doesn't provide
+ * updates.
+ *
+ * To page in data from a source that does provide updates, you can create a
+ * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
+ * data set occurs that makes the current snapshot invalid. For example, when paging a query from
+ * the Database, and the table being queried inserts or removes items. You can also use a
+ * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
+ * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
+ * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
+ * DataSource.
+ *
+ * If you have more granular update signals, such as a network API signaling an update to a single
+ * item in the list, it's recommended to load data from network into memory. Then present that
+ * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
+ * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
+ * snapshot can be created.
+ *
+ * Use {@link PageKeyedDataSource} if pages you load embed keys for loading adjacent pages. For
+ * example a network response that returns some items, and a next/previous page links.
+ *
+ * Use {@link ItemKeyedDataSource} if you need to use data from item {@code N-1} to load item
+ * {@code N}. For example, if requesting the backend for the next comments in the list
+ * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
+ * from a name-sorted database query requires the name and unique ID of the previous.
+ *
+ * Use {@link PositionalDataSource} if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
+ * PositionalDataSource is required to respect page size for efficient tiling. If you want to
+ * override page size (e.g. when network page size constraints are only known at runtime), use one
+ * of the other DataSource classes.
+ *
+ * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
+ * return {@code null} items in lists that it loads. This is so that users of the PagedList
+ * can differentiate unloaded placeholder items from content that has been paged in.
+ *
+ * @param
+ * Data-loading systems of an application or library can implement this interface to allow
+ * {@code LiveData
+ * The DataSource should invalidate itself if the snapshot is no longer valid. If a
+ * DataSource becomes invalid, the only way to query more data is to create a new DataSource
+ * from the Factory.
+ *
+ * {@link LivePagedListBuilder} for example will construct a new PagedList and DataSource
+ * when the current DataSource is invalidated, and pass the new PagedList through the
+ * {@code LiveData
+ * Same as {@link #mapByPage(Function)}, but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param
+ * Same as {@link #map(Function)}, but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param
+ * Same as {@link #map(Function)}, but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param
+ * Same as {@link #mapByPage(Function)}, but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param
+ * Used to signal when a DataSource a data source has become invalid, and that a new data source
+ * is needed to continue loading data.
+ */
+ public interface InvalidatedCallback {
+ /**
+ * Called when the data backing the list has become invalid. This callback is typically used
+ * to signal that a new data source is needed.
+ *
+ * This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid
+ * for the data source to invalidate itself during its load methods, or for an outside
+ * source to invalidate it.
+ */
+ @AnyThread
+ void onInvalidated();
+ }
+
+ private AtomicBoolean mInvalid = new AtomicBoolean(false);
+
+ private CopyOnWriteArrayList
+ * Once invalidated, a data source will not become valid again.
+ *
+ * A data source will only invoke its callbacks once - the first time {@link #invalidate()}
+ * is called, on that thread.
+ *
+ * @param onInvalidatedCallback The callback, will be invoked on thread that
+ * {@link #invalidate()} is called on.
+ */
+ @AnyThread
+ @SuppressWarnings("WeakerAccess")
+ public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ mOnInvalidatedCallbacks.add(onInvalidatedCallback);
+ }
+
+ /**
+ * Remove a previously added invalidate callback.
+ *
+ * @param onInvalidatedCallback The previously added callback.
+ */
+ @AnyThread
+ @SuppressWarnings("WeakerAccess")
+ public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
+ }
+
+ /**
+ * Signal the data source to stop loading, and notify its callback.
+ *
+ * If invalidate has already been called, this method does nothing.
+ */
+ @AnyThread
+ public void invalidate() {
+ if (mInvalid.compareAndSet(false, true)) {
+ for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
+ callback.onInvalidated();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the data source is invalid, and can no longer be queried for data.
+ *
+ * @return True if the data source is invalid, and can no longer return data.
+ */
+ @WorkerThread
+ public boolean isInvalid() {
+ return mInvalid.get();
+ }
+}
diff --git a/app/src/main/java/androidx/paging/ItemKeyedDataSource.java b/app/src/main/java/androidx/paging/ItemKeyedDataSource.java
new file mode 100644
index 0000000000..2e89ba6764
--- /dev/null
+++ b/app/src/main/java/androidx/paging/ItemKeyedDataSource.java
@@ -0,0 +1,375 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * The {@code InMemoryByItemRepository} in the
+ * PagingWithNetworkSample
+ * shows how to implement a network ItemKeyedDataSource using
+ * Retrofit, while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param
+ * 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.
+ *
+ * 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
+ * Returned data must begin directly adjacent to this position.
+ */
+ @NonNull
+ public final Key key;
+ /**
+ * Requested number of items to load.
+ *
+ * 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.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * 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.
+ *
+ * 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 data back through this method.
+ *
+ * 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
+ * A callback can be called only once, and will 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 ItemKeyedDataSource's
+ * {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
+ *
+ * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
+ * initialize without counting available data, or supporting placeholders.
+ *
+ * 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
+ * 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.
+ *
+ * {@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
+ * 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 {@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
+ * 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 {@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
+ * 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.
+ *
+ * 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
+ * The required parameters are in the constructor, so you can simply construct and build, or
+ * optionally enable extra features (such as initial load key, or BoundaryCallback).
+ *
+ * @param
+ * This method is a convenience for:
+ *
+ * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
+ * the previous generation so that data is loaded around the position already being observed.
+ *
+ * @param key Initial load key passed to the first PagedList/DataSource.
+ * @return this
+ */
+ @NonNull
+ public LivePagedListBuilder
+ * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
+ * method is not called, or {@code null} is passed, you will not be notified when each
+ * DataSource runs out of data to provide to its PagedList.
+ *
+ * If you are paging from a DataSource.Factory backed by local storage, you can set a
+ * BoundaryCallback to know when there is no more information to page from local storage.
+ * This is useful to page from the network when local storage is a cache of network data.
+ *
+ * Note that when using a BoundaryCallback with a {@code LiveData
+ * If not set, defaults to the Arch components I/O thread pool.
+ *
+ * @param fetchExecutor Executor for fetching data from DataSources.
+ * @return this
+ */
+ @SuppressWarnings("unused")
+ @NonNull
+ public LivePagedListBuilder
+ * No work (such as loading) is done immediately, the creation of the first PagedList is is
+ * deferred until the LiveData is observed.
+ *
+ * @return The LiveData of PagedLists
+ */
+ @NonNull
+ @SuppressLint("RestrictedApi")
+ public LiveData
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
+ * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ *
+ * The {@code InMemoryByPageRepository} in the
+ * PagingWithNetworkSample
+ * shows how to implement a network PageKeyedDataSource using
+ * Retrofit, while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param
+ * 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, Key, Key)} will be ignored.
+ */
+ public final boolean placeholdersEnabled;
+
+
+ public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
+ this.requestedLoadSize = requestedLoadSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)}.
+ *
+ * @param
+ * Returned data must begin directly adjacent to this position.
+ */
+ @NonNull
+ public final Key key;
+
+ /**
+ * Requested number of items to load.
+ *
+ * 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.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * If you can compute the number of items in the data set before and after the loaded range,
+ * call the five parameter {@link #onResult(List, int, int, Object, Object)} to pass that
+ * information. You can skip passing this information by calling the three parameter
+ * {@link #onResult(List, Object, Object)}, either if it's difficult to compute, or if
+ * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
+ * information will be ignored.
+ *
+ * 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 data back through this method.
+ *
+ * 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
+ * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
+ * initialize without counting available data, or supporting placeholders.
+ *
+ * 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 PageKeyedDataSource.
+ * @param previousPageKey Key for page before the initial load result, or {@code null} if no
+ * more data can be loaded before.
+ * @param nextPageKey Key for page after the initial load result, or {@code null} if no
+ * more data can be loaded after.
+ */
+ public abstract void onResult(@NonNull List
+ * A callback can be called only once, and will 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 PageKeyedDataSource's
+ * {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
+ *
+ * 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.
+ *
+ * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
+ * loaded a page in {@link #loadBefore(LoadParams, LoadCallback)}, pass the key for the
+ * previous page, or {@code null} if the loaded page is the first. If in
+ * {@link #loadAfter(LoadParams, LoadCallback)}, pass the key for the next page, or
+ * {@code null} if the loaded page is the last.
+ *
+ * @param data List of items loaded from the PageKeyedDataSource.
+ * @param adjacentPageKey Key for subsequent page load (previous page in {@link #loadBefore}
+ * / next page in {@link #loadAfter}), or {@code null} if there are
+ * no more pages to load in the current load direction.
+ */
+ public abstract void onResult(@NonNull List
+ * 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, Object, Object)}. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ *
+ * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
+ * altered or ignored.
+ *
+ * @param params Parameters for initial load, including requested load size.
+ * @param callback Callback that receives initial load data.
+ */
+ public abstract void loadInitial(@NonNull LoadInitialParams
+ * 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 load 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 {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ public abstract void loadBefore(@NonNull LoadParams
+ * 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 load 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 {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ public abstract void loadAfter(@NonNull LoadParamsLoading Pages
+ * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
+ * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
+ * Updating Paged Data
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
+ * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
+ * content update occurs. A DataSource must detect that it cannot continue loading its
+ * snapshot (for instance, when Database query notices a table being invalidated), and call
+ * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
+ * the new state of the Database query.
+ * Implementing a DataSource
+ * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
+ * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
+ *
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
+ * }
+ *
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
+ * page a large query with a PositionalDataSource.
+ *
+ * @param , List
, List
, List
, List> function, List source) {
+ List dest = function.apply(source);
+ if (dest.size() != source.size()) {
+ throw new IllegalStateException("Invalid Function " + function
+ + " changed return size. This is not supported.");
+ }
+ return dest;
+ }
+
+ // Since we currently rely on implementation details of two implementations,
+ // prevent external subclassing, except through exposed subclasses
+ DataSource() {
+ }
+
+ /**
+ * Applies the given function to each value emitted by the DataSource.
+ *
, List
, List
+ * LivePagedListBuilder(dataSourceFactory,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build())
+ *
+ *
+ * @param dataSourceFactory DataSource.Factory providing DataSource generations.
+ * @param pageSize Size of pages to load.
+ */
+ public LivePagedListBuilder(@NonNull DataSource.Factory, List