From 5ff77cacd8fe348171ccde37430d7120b3f9490b Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 9 Jul 2020 08:14:51 +0200 Subject: [PATCH] Revert "Compile paging inline" This reverts commit 77130c5e0816e5072fc837b06040e98ee0806f35. --- app/build.gradle | 3 +- .../androidx/paging/AsyncPagedListDiffer.java | 447 ------- .../androidx/paging/ContiguousDataSource.java | 63 - .../androidx/paging/ContiguousPagedList.java | 412 ------ .../main/java/androidx/paging/DataSource.java | 408 ------ .../androidx/paging/ItemKeyedDataSource.java | 375 ------ .../java/androidx/paging/ListDataSource.java | 51 - .../androidx/paging/LivePagedListBuilder.java | 212 --- .../androidx/paging/PageKeyedDataSource.java | 445 ------- .../main/java/androidx/paging/PageResult.java | 105 -- .../main/java/androidx/paging/PagedList.java | 1174 ----------------- .../androidx/paging/PagedListAdapter.java | 244 ---- .../java/androidx/paging/PagedStorage.java | 649 --------- .../paging/PagedStorageDiffHelper.java | 231 ---- .../androidx/paging/PositionalDataSource.java | 576 -------- .../androidx/paging/SnapshotPagedList.java | 74 -- .../java/androidx/paging/TiledDataSource.java | 86 -- .../java/androidx/paging/TiledPagedList.java | 230 ---- .../paging/WrapperItemKeyedDataSource.java | 116 -- .../paging/WrapperPageKeyedDataSource.java | 96 -- .../paging/WrapperPositionalDataSource.java | 81 -- 21 files changed, 1 insertion(+), 6077 deletions(-) delete mode 100644 app/src/main/java/androidx/paging/AsyncPagedListDiffer.java delete mode 100644 app/src/main/java/androidx/paging/ContiguousDataSource.java delete mode 100644 app/src/main/java/androidx/paging/ContiguousPagedList.java delete mode 100644 app/src/main/java/androidx/paging/DataSource.java delete mode 100644 app/src/main/java/androidx/paging/ItemKeyedDataSource.java delete mode 100644 app/src/main/java/androidx/paging/ListDataSource.java delete mode 100644 app/src/main/java/androidx/paging/LivePagedListBuilder.java delete mode 100644 app/src/main/java/androidx/paging/PageKeyedDataSource.java delete mode 100644 app/src/main/java/androidx/paging/PageResult.java delete mode 100644 app/src/main/java/androidx/paging/PagedList.java delete mode 100644 app/src/main/java/androidx/paging/PagedListAdapter.java delete mode 100644 app/src/main/java/androidx/paging/PagedStorage.java delete mode 100644 app/src/main/java/androidx/paging/PagedStorageDiffHelper.java delete mode 100644 app/src/main/java/androidx/paging/PositionalDataSource.java delete mode 100644 app/src/main/java/androidx/paging/SnapshotPagedList.java delete mode 100644 app/src/main/java/androidx/paging/TiledDataSource.java delete mode 100644 app/src/main/java/androidx/paging/TiledPagedList.java delete mode 100644 app/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java delete mode 100644 app/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java delete mode 100644 app/src/main/java/androidx/paging/WrapperPositionalDataSource.java diff --git a/app/build.gradle b/app/build.gradle index 430ba3af17..e0e5740eff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -223,7 +223,6 @@ configurations.all { // lifecycle-livedata: ComputableLiveData, MediatorLiveData, Transformations // lifecycle-livedata-core: LiveData, MutableLiveData, Observer - // paging-runtime: AsyncPagedListDiffer, LivePagedListBuilder, PagedListAdapter, PagedStorageDiffHelper } dependencies { @@ -323,7 +322,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 deleted file mode 100644 index 6640dc0c8c..0000000000 --- a/app/src/main/java/androidx/paging/AsyncPagedListDiffer.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * 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 Type of the PagedLists this differ will receive. - */ -public class AsyncPagedListDiffer { - // updateCallback notifications must only be notified *after* new data and item count are stored - // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final ListUpdateCallback mUpdateCallback; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final AsyncDifferConfig mConfig; - - @SuppressWarnings("RestrictedApi") - Executor mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor(); - - /** - * Listener for when the current PagedList is updated. - * - * @param Type of items in PagedList - */ - public interface PagedListListener { - /** - * Called after the current PagedList has been updated. - * - * @param previousList The previous list, may be null. - * @param currentList The new current list, may be null. - */ - void onCurrentListChanged( - @Nullable PagedList previousList, @Nullable PagedList currentList); - } - - private final List> mListeners = new CopyOnWriteArrayList<>(); - - private boolean mIsContiguous; - - private PagedList mPagedList; - private PagedList mSnapshot; - - // Max generation of currently scheduled runnable - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mMaxScheduledGeneration; - - /** - * Convenience for {@code AsyncPagedListDiffer(new AdapterListUpdateCallback(adapter), - * new AsyncDifferConfig.Builder(diffCallback).build();} - * - * @param adapter Adapter that will receive update signals. - * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to - * compare items in the list. - */ - @SuppressWarnings("WeakerAccess") - public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter, - @NonNull DiffUtil.ItemCallback diffCallback) { - mUpdateCallback = new AdapterListUpdateCallback(adapter); - mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build(); - } - - @SuppressWarnings("WeakerAccess") - public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, - @NonNull AsyncDifferConfig config) { - mUpdateCallback = listUpdateCallback; - mConfig = config; - } - - private PagedList.Callback mPagedListCallback = new PagedList.Callback() { - @Override - public void onInserted(int position, int count) { - mUpdateCallback.onInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - mUpdateCallback.onRemoved(position, count); - } - - @Override - public void onChanged(int position, int count) { - // NOTE: pass a null payload to convey null -> item - mUpdateCallback.onChanged(position, count, null); - } - }; - - /** - * Get the item from the current PagedList at the specified index. - *

- * 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 pagedList) { - submitList(pagedList, null); - } - - /** - * 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}. - *

- * 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 pagedList, - @Nullable final Runnable commitCallback) { - if (pagedList != null) { - if (mPagedList == null && mSnapshot == null) { - mIsContiguous = pagedList.isContiguous(); - } else { - if (pagedList.isContiguous() != mIsContiguous) { - throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both" - + " contiguous and non-contiguous lists."); - } - } - } - - // incrementing generation means any currently-running diffs are discarded when they finish - final int runGeneration = ++mMaxScheduledGeneration; - - if (pagedList == mPagedList) { - // nothing to do (Note - still had to inc generation, since may have ongoing work) - if (commitCallback != null) { - commitCallback.run(); - } - return; - } - - final PagedList previous = (mSnapshot != null) ? mSnapshot : mPagedList; - - if (pagedList == null) { - int removedCount = getItemCount(); - if (mPagedList != null) { - mPagedList.removeWeakCallback(mPagedListCallback); - mPagedList = null; - } else if (mSnapshot != null) { - mSnapshot = null; - } - // dispatch update callback after updating mPagedList/mSnapshot - mUpdateCallback.onRemoved(0, removedCount); - onCurrentListChanged(previous, null, commitCallback); - return; - } - - if (mPagedList == null && mSnapshot == null) { - // fast simple first insert - mPagedList = pagedList; - pagedList.addWeakCallback(null, mPagedListCallback); - - // dispatch update callback after updating mPagedList/mSnapshot - mUpdateCallback.onInserted(0, pagedList.size()); - - onCurrentListChanged(null, pagedList, commitCallback); - return; - } - - if (mPagedList != null) { - // first update scheduled on this list, so capture mPages as a snapshot, removing - // callbacks so we don't have resolve updates against a moving target - mPagedList.removeWeakCallback(mPagedListCallback); - mSnapshot = (PagedList) mPagedList.snapshot(); - mPagedList = null; - } - - if (mSnapshot == null || mPagedList != null) { - throw new IllegalStateException("must be in snapshot state to diff"); - } - - final PagedList oldSnapshot = mSnapshot; - final PagedList newSnapshot = (PagedList) pagedList.snapshot(); - mConfig.getBackgroundThreadExecutor().execute(new Runnable() { - @Override - public void run() { - final DiffUtil.DiffResult result; - result = PagedStorageDiffHelper.computeDiff( - oldSnapshot.mStorage, - newSnapshot.mStorage, - mConfig.getDiffCallback()); - - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (mMaxScheduledGeneration == runGeneration) { - latchPagedList(pagedList, newSnapshot, result, - oldSnapshot.mLastLoad, commitCallback); - } - } - }); - } - }); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void latchPagedList( - @NonNull PagedList newList, - @NonNull PagedList diffSnapshot, - @NonNull DiffUtil.DiffResult diffResult, - int lastAccessIndex, - @Nullable Runnable commitCallback) { - if (mSnapshot == null || mPagedList != null) { - throw new IllegalStateException("must be in snapshot state to apply diff"); - } - - PagedList previousSnapshot = mSnapshot; - mPagedList = newList; - mSnapshot = null; - - // dispatch update callback after updating mPagedList/mSnapshot - PagedStorageDiffHelper.dispatchDiff(mUpdateCallback, - previousSnapshot.mStorage, newList.mStorage, diffResult); - - newList.addWeakCallback(diffSnapshot, mPagedListCallback); - - if (!mPagedList.isEmpty()) { - // Transform the last loadAround() index from the old list to the new list by passing it - // through the DiffResult. This ensures the lastKey of a positional PagedList is carried - // to new list even if no in-viewport item changes (AsyncPagedListDiffer#get not called) - // Note: we don't take into account loads between new list snapshot and new list, but - // this is only a problem in rare cases when placeholders are disabled, and a load - // starts (for some reason) and finishes before diff completes. - int newPosition = PagedStorageDiffHelper.transformAnchorIndex( - diffResult, previousSnapshot.mStorage, diffSnapshot.mStorage, lastAccessIndex); - - // Trigger load in new list at this position, clamped to list bounds. - // This is a load, not just an update of last load position, since the new list may be - // incomplete. If new list is subset of old list, but doesn't fill the viewport, this - // will likely trigger a load of new data. - mPagedList.loadAround(Math.max(0, Math.min(mPagedList.size() - 1, newPosition))); - } - - onCurrentListChanged(previousSnapshot, mPagedList, commitCallback); - } - - private void onCurrentListChanged( - @Nullable PagedList previousList, - @Nullable PagedList currentList, - @Nullable Runnable commitCallback) { - for (PagedListListener listener : mListeners) { - listener.onCurrentListChanged(previousList, currentList); - } - if (commitCallback != null) { - commitCallback.run(); - } - } - - /** - * Add a PagedListListener to receive updates when the current PagedList changes. - * - * @param listener Listener to receive updates. - * - * @see #getCurrentList() - * @see #removePagedListListener(PagedListListener) - */ - public void addPagedListListener(@NonNull PagedListListener listener) { - mListeners.add(listener); - } - - /** - * Remove a previously registered PagedListListener. - * - * @param listener Previously registered listener. - * @see #getCurrentList() - * @see #addPagedListListener(PagedListListener) - */ - public void removePagedListListener(@NonNull PagedListListener listener) { - mListeners.remove(listener); - } - - /** - * Returns the PagedList currently being displayed by the differ. - *

- * 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 getCurrentList() { - if (mSnapshot != null) { - return mSnapshot; - } - return mPagedList; - } -} diff --git a/app/src/main/java/androidx/paging/ContiguousDataSource.java b/app/src/main/java/androidx/paging/ContiguousDataSource.java deleted file mode 100644 index 5cde0af09a..0000000000 --- a/app/src/main/java/androidx/paging/ContiguousDataSource.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 java.util.concurrent.Executor; - -abstract class ContiguousDataSource extends DataSource { - @Override - boolean isContiguous() { - return true; - } - - abstract void dispatchLoadInitial( - @Nullable Key key, - int initialLoadSize, - int pageSize, - boolean enablePlaceholders, - @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver); - - abstract void dispatchLoadAfter( - int currentEndIndex, - @NonNull Value currentEndItem, - int pageSize, - @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver); - - abstract void dispatchLoadBefore( - int currentBeginIndex, - @NonNull Value currentBeginItem, - int pageSize, - @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver); - - /** - * Get the key from either the position, or item, or null if position/item invalid. - *

- * 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 deleted file mode 100644 index 99060671d8..0000000000 --- a/app/src/main/java/androidx/paging/ContiguousPagedList.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * 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 extends PagedList implements PagedStorage.Callback { - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final ContiguousDataSource mDataSource; - - @Retention(SOURCE) - @IntDef({READY_TO_FETCH, FETCHING, DONE_FETCHING}) - @interface FetchState {} - - private static final int READY_TO_FETCH = 0; - private static final int FETCHING = 1; - private static final int DONE_FETCHING = 2; - - @FetchState - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mPrependWorkerState = READY_TO_FETCH; - @FetchState - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mAppendWorkerState = READY_TO_FETCH; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mPrependItemsRequested = 0; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mAppendItemsRequested = 0; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean mReplacePagesWithNulls = false; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final boolean mShouldTrim; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - PageResult.Receiver mReceiver = new PageResult.Receiver() { - // Creation thread for initial synchronous load, otherwise main thread - // Safe to access main thread only state - no other thread has reference during construction - @AnyThread - @Override - public void onPageResult(@PageResult.ResultType int resultType, - @NonNull PageResult pageResult) { - if (pageResult.isInvalid()) { - detach(); - return; - } - - if (isDetached()) { - // No op, have detached - return; - } - - List page = pageResult.page; - if (resultType == PageResult.INIT) { - mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, - pageResult.positionOffset, ContiguousPagedList.this); - if (mLastLoad == LAST_LOAD_UNSPECIFIED) { - // Because the ContiguousPagedList wasn't initialized with a last load position, - // initialize it to the middle of the initial load - mLastLoad = - pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2; - } - } else { - // if we end up trimming, we trim from side that's furthest from most recent access - boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange(); - - // is the new page big enough to warrant pre-trimming (i.e. dropping) it? - boolean skipNewPage = mShouldTrim - && mStorage.shouldPreTrimNewPage( - mConfig.maxSize, mRequiredRemainder, page.size()); - - if (resultType == PageResult.APPEND) { - if (skipNewPage && !trimFromFront) { - // don't append this data, drop it - mAppendItemsRequested = 0; - mAppendWorkerState = READY_TO_FETCH; - } else { - mStorage.appendPage(page, ContiguousPagedList.this); - } - } else if (resultType == PageResult.PREPEND) { - if (skipNewPage && trimFromFront) { - // don't append this data, drop it - mPrependItemsRequested = 0; - mPrependWorkerState = READY_TO_FETCH; - } else { - mStorage.prependPage(page, ContiguousPagedList.this); - } - } else { - throw new IllegalArgumentException("unexpected resultType " + resultType); - } - - if (mShouldTrim) { - if (trimFromFront) { - if (mPrependWorkerState != FETCHING) { - if (mStorage.trimFromFront( - mReplacePagesWithNulls, - mConfig.maxSize, - mRequiredRemainder, - ContiguousPagedList.this)) { - // trimmed from front, ensure we can fetch in that dir - mPrependWorkerState = READY_TO_FETCH; - } - } - } else { - if (mAppendWorkerState != FETCHING) { - if (mStorage.trimFromEnd( - mReplacePagesWithNulls, - mConfig.maxSize, - mRequiredRemainder, - ContiguousPagedList.this)) { - mAppendWorkerState = READY_TO_FETCH; - } - } - } - } - } - - if (mBoundaryCallback != null) { - boolean deferEmpty = mStorage.size() == 0; - boolean deferBegin = !deferEmpty - && resultType == PageResult.PREPEND - && pageResult.page.size() == 0; - boolean deferEnd = !deferEmpty - && resultType == PageResult.APPEND - && pageResult.page.size() == 0; - deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); - } - } - }; - - static final int LAST_LOAD_UNSPECIFIED = -1; - - ContiguousPagedList( - @NonNull ContiguousDataSource dataSource, - @NonNull Executor mainThreadExecutor, - @NonNull Executor backgroundThreadExecutor, - @Nullable BoundaryCallback boundaryCallback, - @NonNull Config config, - final @Nullable K key, - int lastLoad) { - super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, - boundaryCallback, config); - mDataSource = dataSource; - mLastLoad = lastLoad; - - if (mDataSource.isInvalid()) { - detach(); - } else { - mDataSource.dispatchLoadInitial(key, - mConfig.initialLoadSizeHint, - mConfig.pageSize, - mConfig.enablePlaceholders, - mMainThreadExecutor, - mReceiver); - } - mShouldTrim = mDataSource.supportsPageDropping() - && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED; - } - - @MainThread - @Override - void dispatchUpdatesSinceSnapshot( - @NonNull PagedList pagedListSnapshot, @NonNull Callback callback) { - final PagedStorage snapshot = pagedListSnapshot.mStorage; - - final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended(); - final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended(); - - final int previousTrailing = snapshot.getTrailingNullCount(); - final int previousLeading = snapshot.getLeadingNullCount(); - - // Validate that the snapshot looks like a previous version of this list - if it's not, - // we can't be sure we'll dispatch callbacks safely - if (snapshot.isEmpty() - || newlyAppended < 0 - || newlyPrepended < 0 - || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0) - || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0) - || (mStorage.getStorageCount() - != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) { - throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" - + " to be a snapshot of this PagedList"); - } - - if (newlyAppended != 0) { - final int changedCount = Math.min(previousTrailing, newlyAppended); - final int addedCount = newlyAppended - changedCount; - - final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount(); - if (changedCount != 0) { - callback.onChanged(endPosition, changedCount); - } - if (addedCount != 0) { - callback.onInserted(endPosition + changedCount, addedCount); - } - } - if (newlyPrepended != 0) { - final int changedCount = Math.min(previousLeading, newlyPrepended); - final int addedCount = newlyPrepended - changedCount; - - if (changedCount != 0) { - callback.onChanged(previousLeading, changedCount); - } - if (addedCount != 0) { - callback.onInserted(0, addedCount); - } - } - } - - static int getPrependItemsRequested(int prefetchDistance, int index, int leadingNulls) { - return prefetchDistance - (index - leadingNulls); - } - - static int getAppendItemsRequested( - int prefetchDistance, int index, int itemsBeforeTrailingNulls) { - return index + prefetchDistance + 1 - itemsBeforeTrailingNulls; - } - - @MainThread - @Override - protected void loadAroundInternal(int index) { - int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index, - mStorage.getLeadingNullCount()); - int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index, - mStorage.getLeadingNullCount() + mStorage.getStorageCount()); - - mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested); - if (mPrependItemsRequested > 0) { - schedulePrepend(); - } - - mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested); - if (mAppendItemsRequested > 0) { - scheduleAppend(); - } - } - - @MainThread - private void schedulePrepend() { - if (mPrependWorkerState != READY_TO_FETCH) { - return; - } - mPrependWorkerState = FETCHING; - - final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset(); - - // safe to access first item here - mStorage can't be empty if we're prepending - final V item = mStorage.getFirstLoadedItem(); - mBackgroundThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (isDetached()) { - return; - } - if (mDataSource.isInvalid()) { - detach(); - } else { - mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize, - mMainThreadExecutor, mReceiver); - } - } - }); - } - - @MainThread - private void scheduleAppend() { - if (mAppendWorkerState != READY_TO_FETCH) { - return; - } - mAppendWorkerState = FETCHING; - - final int position = mStorage.getLeadingNullCount() - + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset(); - - // safe to access first item here - mStorage can't be empty if we're appending - final V item = mStorage.getLastLoadedItem(); - mBackgroundThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (isDetached()) { - return; - } - if (mDataSource.isInvalid()) { - detach(); - } else { - mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize, - mMainThreadExecutor, mReceiver); - } - } - }); - } - - @Override - boolean isContiguous() { - return true; - } - - @NonNull - @Override - public DataSource getDataSource() { - return mDataSource; - } - - @Nullable - @Override - public Object getLastKey() { - return mDataSource.getKey(mLastLoad, mLastItem); - } - - @MainThread - @Override - public void onInitialized(int count) { - notifyInserted(0, count); - // simple heuristic to decide if, when dropping pages, we should replace with placeholders - mReplacePagesWithNulls = - mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0; - } - - @MainThread - @Override - public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) { - // consider whether to post more work, now that a page is fully prepended - mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount; - mPrependWorkerState = READY_TO_FETCH; - if (mPrependItemsRequested > 0) { - // not done prepending, keep going - schedulePrepend(); - } - - // finally dispatch callbacks, after prepend may have already been scheduled - notifyChanged(leadingNulls, changedCount); - notifyInserted(0, addedCount); - - offsetAccessIndices(addedCount); - } - - @MainThread - @Override - public void onEmptyPrepend() { - mPrependWorkerState = DONE_FETCHING; - } - - @MainThread - @Override - public void onPageAppended(int endPosition, int changedCount, int addedCount) { - // consider whether to post more work, now that a page is fully appended - mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount; - mAppendWorkerState = READY_TO_FETCH; - if (mAppendItemsRequested > 0) { - // not done appending, keep going - scheduleAppend(); - } - - // finally dispatch callbacks, after append may have already been scheduled - notifyChanged(endPosition, changedCount); - notifyInserted(endPosition + changedCount, addedCount); - } - - @MainThread - @Override - public void onEmptyAppend() { - mAppendWorkerState = DONE_FETCHING; - } - - @MainThread - @Override - public void onPagePlaceholderInserted(int pageIndex) { - throw new IllegalStateException("Tiled callback on ContiguousPagedList"); - } - - @MainThread - @Override - public void onPageInserted(int start, int count) { - throw new IllegalStateException("Tiled callback on ContiguousPagedList"); - } - - @Override - public void onPagesRemoved(int startOfDrops, int count) { - notifyRemoved(startOfDrops, count); - } - - @Override - public void onPagesSwappedToPlaceholder(int startOfDrops, int count) { - notifyChanged(startOfDrops, count); - } -} diff --git a/app/src/main/java/androidx/paging/DataSource.java b/app/src/main/java/androidx/paging/DataSource.java deleted file mode 100644 index 60f3cfe51f..0000000000 --- a/app/src/main/java/androidx/paging/DataSource.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * 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.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import androidx.arch.core.util.Function; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Base class for loading pages of snapshot data into a {@link PagedList}. - *

- * 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. - *

Loading 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. - *

- * 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. - *

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. - *

- * 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. - *

Implementing a DataSource

- * To implement, extend one of the subclasses: {@link PageKeyedDataSource}, - * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}. - *

- * 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 Unique identifier for item loaded from DataSource. Often an integer to represent - * position in data set. Note - this is distinct from e.g. Room's {@code @PrimaryKey}. - * @param Value type loaded by the DataSource. - */ -@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety -public abstract class DataSource { - /** - * Factory for DataSources. - *

- * Data-loading systems of an application or library can implement this interface to allow - * {@code LiveData}s to be created. For example, Room can provide a - * DataSource.Factory for a given SQL query: - * - *

-     * {@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 Key identifying items in DataSource. - * @param Type of items in the list loaded by the DataSources. - */ - public abstract static class Factory { - /** - * Create a DataSource. - *

- * 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} to observers. - * - * @return the new DataSource. - */ - @NonNull - public abstract DataSource create(); - - /** - * Applies the given function to each value emitted by DataSources produced by this Factory. - *

- * 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 Type of items produced by the new DataSource, from the passed function. - * - * @return A new DataSource.Factory, which transforms items using the given function. - * - * @see #mapByPage(Function) - * @see DataSource#map(Function) - * @see DataSource#mapByPage(Function) - */ - @NonNull - public DataSource.Factory map( - @NonNull Function function) { - return mapByPage(createListFunction(function)); - } - - /** - * Applies the given function to each value emitted by DataSources produced by this Factory. - *

- * 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 Type of items produced by the new DataSource, from the passed function. - * - * @return A new DataSource.Factory, which transforms items using the given function. - * - * @see #map(Function) - * @see DataSource#map(Function) - * @see DataSource#mapByPage(Function) - */ - @NonNull - public DataSource.Factory mapByPage( - @NonNull final Function, List> function) { - return new Factory() { - @Override - public DataSource create() { - return Factory.this.create().mapByPage(function); - } - }; - } - } - - @NonNull - static Function, List> createListFunction( - final @NonNull Function innerFunc) { - return new Function, List>() { - @Override - public List apply(@NonNull List source) { - List out = new ArrayList<>(source.size()); - for (int i = 0; i < source.size(); i++) { - out.add(innerFunc.apply(source.get(i))); - } - return out; - } - }; - } - - static List convert(Function, 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. - *

- * 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 Type of items produced by the new DataSource, from the passed function. - * - * @return A new DataSource, which transforms items using the given function. - * - * @see #map(Function) - * @see DataSource.Factory#map(Function) - * @see DataSource.Factory#mapByPage(Function) - */ - @NonNull - public abstract DataSource mapByPage( - @NonNull Function, List> function); - - /** - * Applies the given function to each value emitted by the DataSource. - *

- * 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 Type of items produced by the new DataSource, from the passed function. - * - * @return A new DataSource, which transforms items using the given function. - * - * @see #mapByPage(Function) - * @see DataSource.Factory#map(Function) - * @see DataSource.Factory#mapByPage(Function) - */ - @NonNull - public abstract DataSource map( - @NonNull Function function); - - /** - * Returns true if the data source guaranteed to produce a contiguous set of items, - * never producing gaps. - */ - abstract boolean isContiguous(); - - static class LoadCallbackHelper { - static void validateInitialLoadParams(@NonNull List data, int position, int totalCount) { - if (position < 0) { - throw new IllegalArgumentException("Position must be non-negative"); - } - if (data.size() + position > totalCount) { - throw new IllegalArgumentException( - "List size + position too large, last item in list beyond totalCount."); - } - if (data.size() == 0 && totalCount > 0) { - throw new IllegalArgumentException( - "Initial result cannot be empty if items are present in data set."); - } - } - - @PageResult.ResultType - final int mResultType; - private final DataSource mDataSource; - final PageResult.Receiver mReceiver; - - // mSignalLock protects mPostExecutor, and mHasSignalled - private final Object mSignalLock = new Object(); - private Executor mPostExecutor = null; - private boolean mHasSignalled = false; - - LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType, - @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver receiver) { - mDataSource = dataSource; - mResultType = resultType; - mPostExecutor = mainThreadExecutor; - mReceiver = receiver; - } - - void setPostExecutor(Executor postExecutor) { - synchronized (mSignalLock) { - mPostExecutor = postExecutor; - } - } - - /** - * Call before verifying args, or dispatching actul results - * - * @return true if DataSource was invalid, and invalid result dispatched - */ - boolean dispatchInvalidResultIfInvalid() { - if (mDataSource.isInvalid()) { - dispatchResultToReceiver(PageResult.getInvalidResult()); - return true; - } - return false; - } - - void dispatchResultToReceiver(final @NonNull PageResult result) { - Executor executor; - synchronized (mSignalLock) { - if (mHasSignalled) { - throw new IllegalStateException( - "callback.onResult already called, cannot call again."); - } - mHasSignalled = true; - executor = mPostExecutor; - } - - if (executor != null) { - executor.execute(new Runnable() { - @Override - public void run() { - mReceiver.onPageResult(mResultType, result); - } - }); - } else { - mReceiver.onPageResult(mResultType, result); - } - } - } - - /** - * Invalidation callback for DataSource. - *

- * 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 mOnInvalidatedCallbacks = - new CopyOnWriteArrayList<>(); - - /** - * Add a callback to invoke when the DataSource is first invalidated. - *

- * 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 deleted file mode 100644 index 2e89ba6764..0000000000 --- a/app/src/main/java/androidx/paging/ItemKeyedDataSource.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * 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 Type of data used to query Value types out of the DataSource. - * @param Type of items being loaded by the DataSource. - */ -public abstract class ItemKeyedDataSource extends ContiguousDataSource { - - /** - * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}. - * - * @param Type of data used to query Value types out of the DataSource. - */ - @SuppressWarnings("WeakerAccess") - public static class LoadInitialParams { - /** - * Load items around this key, or at the beginning of the data set if {@code null} is - * passed. - *

- * 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 Type of data used to query Value types out of the DataSource. - */ - @SuppressWarnings("WeakerAccess") - public static class LoadParams { - /** - * Load items before/after this key. - *

- * 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 Type of items being loaded. - */ - public abstract static class LoadInitialCallback extends LoadCallback { - /** - * Called to pass initial load state from a DataSource. - *

- * 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 data, int position, int totalCount); - } - - - /** - * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} - * and {@link #loadAfter(LoadParams, LoadCallback)} to return data. - *

- * 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 Type of items being loaded. - */ - public abstract static class LoadCallback { - /** - * Called to pass loaded data from a DataSource. - *

- * 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 data); - } - - static class LoadInitialCallbackImpl extends LoadInitialCallback { - final LoadCallbackHelper mCallbackHelper; - private final boolean mCountingEnabled; - LoadInitialCallbackImpl(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled, - @NonNull PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver); - mCountingEnabled = countingEnabled; - } - - @Override - public void onResult(@NonNull List 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 data) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0)); - } - } - } - - static class LoadCallbackImpl extends LoadCallback { - final LoadCallbackHelper mCallbackHelper; - - LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type, - @Nullable Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>( - dataSource, type, mainThreadExecutor, receiver); - } - - @Override - public void onResult(@NonNull List 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 receiver) { - LoadInitialCallbackImpl 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 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 receiver) { - loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize), - new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver)); - } - - /** - * 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 - * {@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 params, - @NonNull LoadInitialCallback callback); - - /** - * Load list data after the key specified in {@link LoadParams#key 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 {@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 params, - @NonNull LoadCallback callback); - - /** - * Load list data before the key specified in {@link LoadParams#key 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 {@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 params, - @NonNull LoadCallback callback); - - /** - * 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 {@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} 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 ItemKeyedDataSource mapByPage( - @NonNull Function, List> function) { - return new WrapperItemKeyedDataSource<>(this, function); - } - - @NonNull - @Override - public final ItemKeyedDataSource map( - @NonNull Function function) { - return mapByPage(createListFunction(function)); - } -} diff --git a/app/src/main/java/androidx/paging/ListDataSource.java b/app/src/main/java/androidx/paging/ListDataSource.java deleted file mode 100644 index 481d5f00d7..0000000000 --- a/app/src/main/java/androidx/paging/ListDataSource.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 java.util.ArrayList; -import java.util.List; - -class ListDataSource extends PositionalDataSource { - private final List mList; - - public ListDataSource(List list) { - mList = new ArrayList<>(list); - } - - @Override - public void loadInitial(@NonNull LoadInitialParams params, - @NonNull LoadInitialCallback callback) { - final int totalCount = mList.size(); - - final int position = computeInitialLoadPosition(params, totalCount); - final int loadSize = computeInitialLoadSize(params, position, totalCount); - - // for simplicity, we could return everything immediately, - // but we tile here since it's expected behavior - List sublist = mList.subList(position, position + loadSize); - callback.onResult(sublist, position, totalCount); - } - - @Override - public void loadRange(@NonNull LoadRangeParams params, - @NonNull LoadRangeCallback callback) { - callback.onResult(mList.subList(params.startPosition, - params.startPosition + params.loadSize)); - } -} diff --git a/app/src/main/java/androidx/paging/LivePagedListBuilder.java b/app/src/main/java/androidx/paging/LivePagedListBuilder.java deleted file mode 100644 index 481619d6d9..0000000000 --- a/app/src/main/java/androidx/paging/LivePagedListBuilder.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 android.annotation.SuppressLint; - -import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.arch.core.executor.ArchTaskExecutor; -import androidx.lifecycle.ComputableLiveData; -import androidx.lifecycle.LiveData; - -import java.util.concurrent.Executor; - -/** - * Builder for {@code LiveData}, given a {@link DataSource.Factory} and a - * {@link PagedList.Config}. - *

- * 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 Type of input valued used to load data from the DataSource. Must be integer if - * you're using PositionalDataSource. - * @param Item type being presented. - */ -public final class LivePagedListBuilder { - private Key mInitialLoadKey; - private PagedList.Config mConfig; - private DataSource.Factory mDataSourceFactory; - private PagedList.BoundaryCallback mBoundaryCallback; - @SuppressLint("RestrictedApi") - private Executor mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor(); - - /** - * Creates a LivePagedListBuilder with required parameters. - * - * @param dataSourceFactory DataSource factory providing DataSource generations. - * @param config Paging configuration. - */ - public LivePagedListBuilder(@NonNull DataSource.Factory dataSourceFactory, - @NonNull PagedList.Config config) { - //noinspection ConstantConditions - if (config == null) { - throw new IllegalArgumentException("PagedList.Config must be provided"); - } - //noinspection ConstantConditions - if (dataSourceFactory == null) { - throw new IllegalArgumentException("DataSource.Factory must be provided"); - } - - mDataSourceFactory = dataSourceFactory; - mConfig = config; - } - - /** - * Creates a LivePagedListBuilder with required parameters. - *

- * This method is a convenience for: - *

-     * 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 dataSourceFactory, - int pageSize) { - this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build()); - } - - /** - * First loading key passed to the first PagedList/DataSource. - *

- * 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 setInitialLoadKey(@Nullable Key key) { - mInitialLoadKey = key; - return this; - } - - /** - * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load - * additional data from network when paging from local storage. - *

- * 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}, method calls - * on the callback may be dispatched multiple times - one for each PagedList/DataSource - * pair. If loading network data from a BoundaryCallback, you should prevent multiple - * dispatches of the same method from triggering multiple simultaneous network loads. - * - * @param boundaryCallback The boundary callback for listening to PagedList load state. - * @return this - */ - @SuppressWarnings("unused") - @NonNull - public LivePagedListBuilder setBoundaryCallback( - @Nullable PagedList.BoundaryCallback boundaryCallback) { - mBoundaryCallback = boundaryCallback; - return this; - } - - /** - * Sets executor used for background fetching of PagedLists, and the pages within. - *

- * 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 setFetchExecutor( - @NonNull Executor fetchExecutor) { - mFetchExecutor = fetchExecutor; - return this; - } - - /** - * Constructs the {@code LiveData}. - *

- * 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> build() { - return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, - ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); - } - - @AnyThread - @NonNull - @SuppressLint("RestrictedApi") - private static LiveData> create( - @Nullable final Key initialLoadKey, - @NonNull final PagedList.Config config, - @Nullable final PagedList.BoundaryCallback boundaryCallback, - @NonNull final DataSource.Factory dataSourceFactory, - @NonNull final Executor notifyExecutor, - @NonNull final Executor fetchExecutor) { - return new ComputableLiveData>(fetchExecutor) { - @Nullable - private PagedList mList; - @Nullable - private DataSource mDataSource; - - private final DataSource.InvalidatedCallback mCallback = - new DataSource.InvalidatedCallback() { - @Override - public void onInvalidated() { - invalidate(); - } - }; - - @SuppressWarnings("unchecked") // for casting getLastKey to Key - @Override - protected PagedList compute() { - @Nullable Key initializeKey = initialLoadKey; - if (mList != null) { - initializeKey = (Key) mList.getLastKey(); - } - - do { - if (mDataSource != null) { - mDataSource.removeInvalidatedCallback(mCallback); - } - - mDataSource = dataSourceFactory.create(); - mDataSource.addInvalidatedCallback(mCallback); - - mList = new PagedList.Builder<>(mDataSource, config) - .setNotifyExecutor(notifyExecutor) - .setFetchExecutor(fetchExecutor) - .setBoundaryCallback(boundaryCallback) - .setInitialKey(initializeKey) - .build(); - } while (mList.isDetached()); - return mList; - } - }.getLiveData(); - } -} diff --git a/app/src/main/java/androidx/paging/PageKeyedDataSource.java b/app/src/main/java/androidx/paging/PageKeyedDataSource.java deleted file mode 100644 index 076d791079..0000000000 --- a/app/src/main/java/androidx/paging/PageKeyedDataSource.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * 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.GuardedBy; -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 page-keyed content, where requests return keys for next/previous - * pages. - *

- * 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 Type of data used to query Value types out of the DataSource. - * @param Type of items being loaded by the DataSource. - */ -public abstract class PageKeyedDataSource extends ContiguousDataSource { - private final Object mKeyLock = new Object(); - - @Nullable - @GuardedBy("mKeyLock") - private Key mNextKey = null; - - @Nullable - @GuardedBy("mKeyLock") - private Key mPreviousKey = null; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void initKeys(@Nullable Key previousKey, @Nullable Key nextKey) { - synchronized (mKeyLock) { - mPreviousKey = previousKey; - mNextKey = nextKey; - } - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void setPreviousKey(@Nullable Key previousKey) { - synchronized (mKeyLock) { - mPreviousKey = previousKey; - } - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void setNextKey(@Nullable Key nextKey) { - synchronized (mKeyLock) { - mNextKey = nextKey; - } - } - - private @Nullable Key getPreviousKey() { - synchronized (mKeyLock) { - return mPreviousKey; - } - } - - private @Nullable Key getNextKey() { - synchronized (mKeyLock) { - return mNextKey; - } - } - - /** - * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}. - * - * @param Type of data used to query pages. - */ - @SuppressWarnings("WeakerAccess") - public static class LoadInitialParams { - /** - * 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, 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 Type of data used to query pages. - */ - @SuppressWarnings("WeakerAccess") - public static class LoadParams { - /** - * Load items before/after this key. - *

- * 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 Type of data used to query pages. - * @param Type of items being loaded. - */ - public abstract static class LoadInitialCallback { - /** - * Called to pass initial load state from a DataSource. - *

- * 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 data, int position, int totalCount, - @Nullable Key previousPageKey, @Nullable Key nextPageKey); - - /** - * Called to pass loaded data from a DataSource. - *

- * 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 data, @Nullable Key previousPageKey, - @Nullable Key nextPageKey); - } - - /** - * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and - * {@link #loadAfter(LoadParams, LoadCallback)} to return data. - *

- * 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 Type of data used to query pages. - * @param Type of items being loaded. - */ - public abstract static class LoadCallback { - - /** - * Called to pass loaded data from a DataSource. - *

- * 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 data, @Nullable Key adjacentPageKey); - } - - static class LoadInitialCallbackImpl extends LoadInitialCallback { - final LoadCallbackHelper mCallbackHelper; - private final PageKeyedDataSource mDataSource; - private final boolean mCountingEnabled; - LoadInitialCallbackImpl(@NonNull PageKeyedDataSource dataSource, - boolean countingEnabled, @NonNull PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>( - dataSource, PageResult.INIT, null, receiver); - mDataSource = dataSource; - mCountingEnabled = countingEnabled; - } - - @Override - public void onResult(@NonNull List data, int position, int totalCount, - @Nullable Key previousPageKey, @Nullable Key nextPageKey) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount); - - // setup keys before dispatching data, so guaranteed to be ready - mDataSource.initKeys(previousPageKey, nextPageKey); - - 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 data, @Nullable Key previousPageKey, - @Nullable Key nextPageKey) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - mDataSource.initKeys(previousPageKey, nextPageKey); - mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0)); - } - } - } - - static class LoadCallbackImpl extends LoadCallback { - final LoadCallbackHelper mCallbackHelper; - private final PageKeyedDataSource mDataSource; - LoadCallbackImpl(@NonNull PageKeyedDataSource dataSource, - @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>( - dataSource, type, mainThreadExecutor, receiver); - mDataSource = dataSource; - } - - @Override - public void onResult(@NonNull List data, @Nullable Key adjacentPageKey) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - if (mCallbackHelper.mResultType == PageResult.APPEND) { - mDataSource.setNextKey(adjacentPageKey); - } else { - mDataSource.setPreviousKey(adjacentPageKey); - } - mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0)); - } - } - } - - @Nullable - @Override - final Key getKey(int position, Value item) { - // don't attempt to persist keys, since we currently don't pass them to initial load - return null; - } - - @Override - boolean supportsPageDropping() { - /* To support page dropping when PageKeyed, we'll need to: - * - Stash keys for every page we have loaded (can id by index relative to loadInitial) - * - Drop keys for any page not adjacent to loaded content - * - And either: - * - Allow impl to signal previous page key: onResult(data, nextPageKey, prevPageKey) - * - Re-trigger loadInitial, and break assumption it will only occur once. - */ - return false; - } - - @Override - final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize, - boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - LoadInitialCallbackImpl callback = - new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver); - loadInitial(new LoadInitialParams(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 receiver) { - @Nullable Key key = getNextKey(); - if (key != null) { - loadAfter(new LoadParams<>(key, pageSize), - new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver)); - } else { - receiver.onPageResult(PageResult.APPEND, PageResult.getEmptyResult()); - } - } - - @Override - final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, - int pageSize, @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - @Nullable Key key = getPreviousKey(); - if (key != null) { - loadBefore(new LoadParams<>(key, pageSize), - new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver)); - } else { - receiver.onPageResult(PageResult.PREPEND, PageResult.getEmptyResult()); - } - } - - /** - * 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 - * {@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 params, - @NonNull LoadInitialCallback callback); - - /** - * Prepend page with the key specified by {@link LoadParams#key 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 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 params, - @NonNull LoadCallback callback); - - /** - * Append page with the key specified by {@link LoadParams#key 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 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 LoadParams params, - @NonNull LoadCallback callback); - - @NonNull - @Override - public final PageKeyedDataSource mapByPage( - @NonNull Function, List> function) { - return new WrapperPageKeyedDataSource<>(this, function); - } - - @NonNull - @Override - public final PageKeyedDataSource map( - @NonNull Function function) { - return mapByPage(createListFunction(function)); - } -} diff --git a/app/src/main/java/androidx/paging/PageResult.java b/app/src/main/java/androidx/paging/PageResult.java deleted file mode 100644 index e654a73627..0000000000 --- a/app/src/main/java/androidx/paging/PageResult.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.IntDef; -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.util.Collections; -import java.util.List; - -class PageResult { - /** - * Single empty instance to avoid allocations. - *

- * Note, distinct from {@link #INVALID_RESULT} because {@link #isInvalid()} checks instance. - */ - @SuppressWarnings("unchecked") - private static final PageResult EMPTY_RESULT = - new PageResult(Collections.emptyList(), 0); - - @SuppressWarnings("unchecked") - private static final PageResult INVALID_RESULT = - new PageResult(Collections.emptyList(), 0); - - @SuppressWarnings("unchecked") - static PageResult getEmptyResult() { - return EMPTY_RESULT; - } - - @SuppressWarnings("unchecked") - static PageResult getInvalidResult() { - return INVALID_RESULT; - } - - @Retention(SOURCE) - @IntDef({INIT, APPEND, PREPEND, TILE}) - @interface ResultType {} - - static final int INIT = 0; - - // contiguous results - static final int APPEND = 1; - static final int PREPEND = 2; - - // non-contiguous, tile result - static final int TILE = 3; - - @NonNull - public final List page; - @SuppressWarnings("WeakerAccess") - public final int leadingNulls; - @SuppressWarnings("WeakerAccess") - public final int trailingNulls; - @SuppressWarnings("WeakerAccess") - public final int positionOffset; - - PageResult(@NonNull List list, int leadingNulls, int trailingNulls, int positionOffset) { - this.page = list; - this.leadingNulls = leadingNulls; - this.trailingNulls = trailingNulls; - this.positionOffset = positionOffset; - } - - PageResult(@NonNull List list, int positionOffset) { - this.page = list; - this.leadingNulls = 0; - this.trailingNulls = 0; - this.positionOffset = positionOffset; - } - - @Override - public String toString() { - return "Result " + leadingNulls - + ", " + page - + ", " + trailingNulls - + ", offset " + positionOffset; - } - - public boolean isInvalid() { - return this == INVALID_RESULT; - } - - abstract static class Receiver { - @MainThread - public abstract void onPageResult(@ResultType int type, @NonNull PageResult pageResult); - } -} diff --git a/app/src/main/java/androidx/paging/PagedList.java b/app/src/main/java/androidx/paging/PagedList.java deleted file mode 100644 index 1d4a184efd..0000000000 --- a/app/src/main/java/androidx/paging/PagedList.java +++ /dev/null @@ -1,1174 +0,0 @@ -/* - * 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.AnyThread; -import androidx.annotation.IntRange; -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.annotation.WorkerThread; - -import java.lang.ref.WeakReference; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Lazy loading list that pages in immutable content from a {@link DataSource}. - *

- * A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}. - * Items can be accessed with {@link #get(int)}, and further loading can be triggered with - * {@link #loadAround(int)}. To display a PagedList, see {@link PagedListAdapter}, which enables the - * binding of a PagedList to a {@link androidx.recyclerview.widget.RecyclerView}. - *

Loading Data

- *

- * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads the - * first chunk of data from the DataSource immediately, and should for this reason be done on a - * background thread. The constructed PagedList may then be passed to and used on the UI thread. - * This is done to prevent passing a list with no loaded content to the UI thread, which should - * generally not be presented to the user. - *

- * A PagedList initially presents this first partial load as its content, and expands over time as - * content is loaded in. When {@link #loadAround} is called, items will be loaded in near the passed - * list index. If placeholder {@code null}s are present in the list, they will be replaced as - * content is loaded. If not, newly loaded items will be inserted at the beginning or end of the - * list. - *

- * PagedList can present data for an unbounded, infinite scrolling list, or a very large but - * countable list. Use {@link Config} to control how many items a PagedList loads, and when. - *

- * If you use {@link LivePagedListBuilder} to get a - * {@link androidx.lifecycle.LiveData}<PagedList>, it will initialize PagedLists on a - * background thread for you. - *

Placeholders

- *

- * There are two ways that PagedList can represent its not-yet-loaded data - with or without - * {@code null} placeholders. - *

- * With placeholders, the PagedList is always the full size of the data set. {@code get(N)} returns - * the {@code N}th item in the data set, or {@code null} if its not yet loaded. - *

- * Without {@code null} placeholders, the PagedList is the sublist of data that has already been - * loaded. The size of the PagedList is the number of currently loaded items, and {@code get(N)} - * returns the {@code N}th loaded item. This is not necessarily the {@code N}th item in the - * data set. - *

- * Placeholders have several benefits: - *

    - *
  • They express the full sized list to the presentation layer (often a - * {@link PagedListAdapter}), and so can support scrollbars (without jumping as pages are - * loaded or dropped) and fast-scrolling to any position, loaded or not. - *
  • They avoid the need for a loading spinner at the end of the loaded list, since the list - * is always full sized. - *
- *

- * They also have drawbacks: - *

    - *
  • Your Adapter needs to account for {@code null} items. This often means providing default - * values in data you bind to a {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}. - *
  • They don't work well if your item views are of different sizes, as this will prevent - * loading items from cross-fading nicely. - *
  • They require you to count your data set, which can be expensive or impossible, depending - * on your DataSource. - *
- *

- * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the - * DataSource does not count its data set in its initial load, or if {@code false} is passed to - * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}. - *

Mutability and Snapshots

- * A PagedList is mutable while loading, or ready to load from its DataSource. - * As loads succeed, a mutable PagedList will be updated via Runnables on the main thread. You can - * listen to these updates with a {@link Callback}. (Note that {@link PagedListAdapter} will listen - * to these to signal RecyclerView about the updates/changes). - *

- * If a PagedList attempts to load from an invalid DataSource, it will {@link #detach()} - * from the DataSource, meaning that it will no longer attempt to load data. It will return true - * from {@link #isImmutable()}, and a new DataSource / PagedList pair must be created to load - * further data. See {@link DataSource} and {@link LivePagedListBuilder} for how new PagedLists are - * created to represent changed data. - *

- * A PagedList snapshot is simply an immutable shallow copy of the current state of the PagedList as - * a {@code List}. It will reference the same inner items, and contain the same {@code null} - * placeholders, if present. - * - * @param The type of the entries in the list. - */ -public abstract class PagedList extends AbstractList { - - // Notes on threading: - // - // PagedList and its subclasses are passed and accessed on multiple threads, but are always - // owned by a single thread. During initialization, this is the creation thread, generally the - // fetchExecutor/fetchScheduler when using LiveData/RxJava. After initialization, the PagedList - // is owned by the main thread (or notifyScheduler, if overridden in RxJava). - // - // The only exception is detach()/isDetached(), which can be called from the fetch thread. - // However these methods simply wrap a atomic boolean, so are safe. - // - // The PageResult.Receiver that receives new data from the DataSource is always run on the - // owning thread. - - @NonNull - final Executor mMainThreadExecutor; - @NonNull - final Executor mBackgroundThreadExecutor; - @Nullable - final BoundaryCallback mBoundaryCallback; - @NonNull - final Config mConfig; - @NonNull - final PagedStorage mStorage; - - /** - * Last access location, in total position space (including offset). - *

- * Used by positional data - * sources to initialize loading near viewport - */ - int mLastLoad = 0; - T mLastItem = null; - - final int mRequiredRemainder; - - // if set to true, mBoundaryCallback is non-null, and should - // be dispatched when nearby load has occurred - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean mBoundaryCallbackBeginDeferred = false; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean mBoundaryCallbackEndDeferred = false; - - // lowest and highest index accessed by loadAround. Used to - // decide when mBoundaryCallback should be dispatched - private int mLowestIndexAccessed = Integer.MAX_VALUE; - private int mHighestIndexAccessed = Integer.MIN_VALUE; - - private final AtomicBoolean mDetached = new AtomicBoolean(false); - - private final ArrayList> mCallbacks = new ArrayList<>(); - - PagedList(@NonNull PagedStorage storage, - @NonNull Executor mainThreadExecutor, - @NonNull Executor backgroundThreadExecutor, - @Nullable BoundaryCallback boundaryCallback, - @NonNull Config config) { - mStorage = storage; - mMainThreadExecutor = mainThreadExecutor; - mBackgroundThreadExecutor = backgroundThreadExecutor; - mBoundaryCallback = boundaryCallback; - mConfig = config; - mRequiredRemainder = mConfig.prefetchDistance * 2 + mConfig.pageSize; - } - - /** - * Create a PagedList which loads data from the provided data source on a background thread, - * posting updates to the main thread. - * - * - * @param dataSource DataSource providing data to the PagedList - * @param notifyExecutor Thread that will use and consume data from the PagedList. - * Generally, this is the UI/main thread. - * @param fetchExecutor Data loading will be done via this executor - - * should be a background thread. - * @param boundaryCallback Optional boundary callback to attach to the list. - * @param config PagedList Config, which defines how the PagedList will load data. - * @param Key type that indicates to the DataSource what data to load. - * @param Type of items to be held and loaded by the PagedList. - * - * @return Newly created PagedList, which will page in data from the DataSource as needed. - */ - @NonNull - @SuppressWarnings("WeakerAccess") /* synthetic access */ - static PagedList create(@NonNull DataSource dataSource, - @NonNull Executor notifyExecutor, - @NonNull Executor fetchExecutor, - @Nullable BoundaryCallback boundaryCallback, - @NonNull Config config, - @Nullable K key) { - if (dataSource.isContiguous() || !config.enablePlaceholders) { - int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED; - if (!dataSource.isContiguous()) { - //noinspection unchecked - dataSource = (DataSource) ((PositionalDataSource) dataSource) - .wrapAsContiguousWithoutPlaceholders(); - if (key != null) { - lastLoad = (Integer) key; - } - } - ContiguousDataSource contigDataSource = (ContiguousDataSource) dataSource; - return new ContiguousPagedList<>(contigDataSource, - notifyExecutor, - fetchExecutor, - boundaryCallback, - config, - key, - lastLoad); - } else { - return new TiledPagedList<>((PositionalDataSource) dataSource, - notifyExecutor, - fetchExecutor, - boundaryCallback, - config, - (key != null) ? (Integer) key : 0); - } - } - - /** - * Builder class for PagedList. - *

- * DataSource, Config, main thread and background executor must all be provided. - *

- * A PagedList queries initial data from its DataSource during construction, to avoid empty - * PagedLists being presented to the UI when possible. It's preferred to present initial data, - * so that the UI doesn't show an empty list, or placeholders for a few frames, just before - * showing initial content. - *

- * {@link LivePagedListBuilder} does this creation on a background thread automatically, if you - * want to receive a {@code LiveData>}. - * - * @param Type of key used to load data from the DataSource. - * @param Type of items held and loaded by the PagedList. - */ - @SuppressWarnings("WeakerAccess") - public static final class Builder { - private final DataSource mDataSource; - private final Config mConfig; - private Executor mNotifyExecutor; - private Executor mFetchExecutor; - private BoundaryCallback mBoundaryCallback; - private Key mInitialKey; - - /** - * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}. - * - * @param dataSource DataSource the PagedList will load from. - * @param config Config that defines how the PagedList loads data from its DataSource. - */ - public Builder(@NonNull DataSource dataSource, @NonNull Config config) { - //noinspection ConstantConditions - if (dataSource == null) { - throw new IllegalArgumentException("DataSource may not be null"); - } - //noinspection ConstantConditions - if (config == null) { - throw new IllegalArgumentException("Config may not be null"); - } - mDataSource = dataSource; - mConfig = config; - } - - /** - * Create a PagedList.Builder with the provided {@link DataSource} and page size. - *

- * This method is a convenience for: - *

-         * PagedList.Builder(dataSource,
-         *         new PagedList.Config.Builder().setPageSize(pageSize).build());
-         * 
- * - * @param dataSource DataSource the PagedList will load from. - * @param pageSize Config that defines how the PagedList loads data from its DataSource. - */ - public Builder(@NonNull DataSource dataSource, int pageSize) { - this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build()); - } - /** - * The executor defining where page loading updates are dispatched. - * - * @param notifyExecutor Executor that receives PagedList updates, and where - * {@link Callback} calls are dispatched. Generally, this is the ui/main thread. - * @return this - */ - @NonNull - public Builder setNotifyExecutor(@NonNull Executor notifyExecutor) { - mNotifyExecutor = notifyExecutor; - return this; - } - - /** - * The executor used to fetch additional pages from the DataSource. - * - * Does not affect initial load, which will be done immediately on whichever thread the - * PagedList is created on. - * - * @param fetchExecutor Executor used to fetch from DataSources, generally a background - * thread pool for e.g. I/O or network loading. - * @return this - */ - @NonNull - public Builder setFetchExecutor(@NonNull Executor fetchExecutor) { - mFetchExecutor = fetchExecutor; - return this; - } - - /** - * The BoundaryCallback for out of data events. - *

- * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. - * - * @param boundaryCallback BoundaryCallback for listening to out-of-data events. - * @return this - */ - @SuppressWarnings("unused") - @NonNull - public Builder setBoundaryCallback( - @Nullable BoundaryCallback boundaryCallback) { - mBoundaryCallback = boundaryCallback; - return this; - } - - /** - * Sets the initial key the DataSource should load around as part of initialization. - * - * @param initialKey Key the DataSource should load around as part of initialization. - * @return this - */ - @NonNull - public Builder setInitialKey(@Nullable Key initialKey) { - mInitialKey = initialKey; - return this; - } - - /** - * Creates a {@link PagedList} with the given parameters. - *

- * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a - * DataSource posts all of its work (e.g. to a network thread), the PagedList will - * be immediately created as empty, and grow to its initial size when the initial load - * completes. - *

- * If the DataSource implements its load synchronously, doing the load work immediately in - * the loadInitial method, the PagedList will block on that load before completing - * construction. In this case, use a background thread to create a PagedList. - *

- * It's fine to create a PagedList with an async DataSource on the main thread, such as in - * the constructor of a ViewModel. An async network load won't block the initialLoad - * function. For a synchronous DataSource such as one created from a Room database, a - * {@code LiveData} can be safely constructed with {@link LivePagedListBuilder} - * on the main thread, since actual construction work is deferred, and done on a background - * thread. - *

- * While build() will always return a PagedList, it's important to note that the PagedList - * initial load may fail to acquire data from the DataSource. This can happen for example if - * the DataSource is invalidated during its initial load. If this happens, the PagedList - * will be immediately {@link PagedList#isDetached() detached}, and you can retry - * construction (including setting a new DataSource). - * - * @return The newly constructed PagedList - */ - @WorkerThread - @NonNull - public PagedList build() { - // TODO: define defaults, once they can be used in module without android dependency - if (mNotifyExecutor == null) { - throw new IllegalArgumentException("MainThreadExecutor required"); - } - if (mFetchExecutor == null) { - throw new IllegalArgumentException("BackgroundThreadExecutor required"); - } - - //noinspection unchecked - return PagedList.create( - mDataSource, - mNotifyExecutor, - mFetchExecutor, - mBoundaryCallback, - mConfig, - mInitialKey); - } - } - - /** - * Get the item in the list of loaded items at the provided index. - * - * @param index Index in the loaded item list. Must be >= 0, and < {@link #size()} - * @return The item at the passed index, or null if a null placeholder is at the specified - * position. - * - * @see #size() - */ - @Override - @Nullable - public T get(int index) { - T item = mStorage.get(index); - if (item != null) { - mLastItem = item; - } - return item; - } - - /** - * Load adjacent items to passed index. - * - * @param index Index at which to load. - */ - public void loadAround(int index) { - if (index < 0 || index >= size()) { - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); - } - - mLastLoad = index + getPositionOffset(); - loadAroundInternal(index); - - mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index); - mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index); - - /* - * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to - * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded, - * and accesses happen near the boundaries. - * - * Note: we post here, since RecyclerView may want to add items in response, and this - * call occurs in PagedListAdapter bind. - */ - tryDispatchBoundaryCallbacks(true); - } - - // Creation thread for initial synchronous load, otherwise main thread - // Safe to access main thread only state - no other thread has reference during construction - @AnyThread - void deferBoundaryCallbacks(final boolean deferEmpty, - final boolean deferBegin, final boolean deferEnd) { - if (mBoundaryCallback == null) { - throw new IllegalStateException("Can't defer BoundaryCallback, no instance"); - } - - /* - * If lowest/highest haven't been initialized, set them to storage size, - * since placeholders must already be computed by this point. - * - * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately - * if the initial load size is smaller than the prefetch window (see - * TiledPagedListTest#boundaryCallback_immediate()) - */ - if (mLowestIndexAccessed == Integer.MAX_VALUE) { - mLowestIndexAccessed = mStorage.size(); - } - if (mHighestIndexAccessed == Integer.MIN_VALUE) { - mHighestIndexAccessed = 0; - } - - if (deferEmpty || deferBegin || deferEnd) { - // Post to the main thread, since we may be on creation thread currently - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - // on is dispatched immediately, since items won't be accessed - //noinspection ConstantConditions - if (deferEmpty) { - mBoundaryCallback.onZeroItemsLoaded(); - } - - // for other callbacks, mark deferred, and only dispatch if loadAround - // has been called near to the position - if (deferBegin) { - mBoundaryCallbackBeginDeferred = true; - } - if (deferEnd) { - mBoundaryCallbackEndDeferred = true; - } - tryDispatchBoundaryCallbacks(false); - } - }); - } - } - - /** - * Call this when mLowest/HighestIndexAccessed are changed, or - * mBoundaryCallbackBegin/EndDeferred is set. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void tryDispatchBoundaryCallbacks(boolean post) { - final boolean dispatchBegin = mBoundaryCallbackBeginDeferred - && mLowestIndexAccessed <= mConfig.prefetchDistance; - final boolean dispatchEnd = mBoundaryCallbackEndDeferred - && mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance; - - if (!dispatchBegin && !dispatchEnd) { - return; - } - - if (dispatchBegin) { - mBoundaryCallbackBeginDeferred = false; - } - if (dispatchEnd) { - mBoundaryCallbackEndDeferred = false; - } - if (post) { - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); - } - }); - } else { - dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); - } - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void dispatchBoundaryCallbacks(boolean begin, boolean end) { - // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present - if (begin) { - //noinspection ConstantConditions - mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem()); - } - if (end) { - //noinspection ConstantConditions - mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem()); - } - } - - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - void offsetAccessIndices(int offset) { - // update last loadAround index - mLastLoad += offset; - - // update access range - mLowestIndexAccessed += offset; - mHighestIndexAccessed += offset; - } - - /** - * Returns size of the list, including any not-yet-loaded null padding. - * - * To get the number of loaded items, not counting placeholders, use {@link #getLoadedCount()}. - * - * @return Current total size of the list, including placeholders. - * - * @see #getLoadedCount() - */ - @Override - public int size() { - return mStorage.size(); - } - - /** - * Returns the number of items loaded in the PagedList. - * - * Unlike {@link #size()} this counts only loaded items, not placeholders. - *

- * If placeholders are {@link Config#enablePlaceholders disabled}, this method is equivalent to - * {@link #size()}. - * - * @return Number of items currently loaded, not counting placeholders. - * - * @see #size() - */ - public int getLoadedCount() { - return mStorage.getLoadedCount(); - } - - /** - * Returns whether the list is immutable. - * - * Immutable lists may not become mutable again, and may safely be accessed from any thread. - *

- * In the future, this method may return true when a PagedList has completed loading from its - * DataSource. Currently, it is equivalent to {@link #isDetached()}. - * - * @return True if the PagedList is immutable. - */ - @SuppressWarnings("WeakerAccess") - public boolean isImmutable() { - return isDetached(); - } - - /** - * Returns an immutable snapshot of the PagedList in its current state. - * - * If this PagedList {@link #isImmutable() is immutable} due to its DataSource being invalid, it - * will be returned. - * - * @return Immutable snapshot of PagedList data. - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public List snapshot() { - if (isImmutable()) { - return this; - } - return new SnapshotPagedList<>(this); - } - - abstract boolean isContiguous(); - - /** - * Return the Config used to construct this PagedList. - * - * @return the Config of this PagedList - */ - @NonNull - public Config getConfig() { - return mConfig; - } - - /** - * Return the DataSource that provides data to this PagedList. - * - * @return the DataSource of this PagedList. - */ - @NonNull - public abstract DataSource getDataSource(); - - /** - * Return the key for the position passed most recently to {@link #loadAround(int)}. - *

- * When a PagedList is invalidated, you can pass the key returned by this function to initialize - * the next PagedList. This ensures (depending on load times) that the next PagedList that - * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do - * this for you. - * - * @return Key of position most recently passed to {@link #loadAround(int)}. - */ - @Nullable - public abstract Object getLastKey(); - - /** - * True if the PagedList has detached the DataSource it was loading from, and will no longer - * load new data. - *

- * A detached list is {@link #isImmutable() immutable}. - * - * @return True if the data source is detached. - */ - @SuppressWarnings("WeakerAccess") - public boolean isDetached() { - return mDetached.get(); - } - - /** - * Detach the PagedList from its DataSource, and attempt to load no more data. - *

- * This is called automatically when a DataSource load returns null, which is a - * signal to stop loading. The PagedList will continue to present existing data, but will not - * initiate new loads. - */ - @SuppressWarnings("WeakerAccess") - public void detach() { - mDetached.set(true); - } - - /** - * Position offset of the data in the list. - *

- * If data is supplied by a {@link PositionalDataSource}, the item returned from - * get(i) has a position of i + getPositionOffset(). - *

- * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it - * doesn't use positions, returns 0. - */ - public int getPositionOffset() { - return mStorage.getPositionOffset(); - } - - /** - * Adds a callback, and issues updates since the previousSnapshot was created. - *

- * If previousSnapshot is passed, the callback will also immediately be dispatched any - * differences between the previous snapshot, and the current state. For example, if the - * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls, - * 12 items, 3 nulls, the callback would immediately receive a call of - * onChanged(14, 2). - *

- * This allows an observer that's currently presenting a snapshot to catch up to the most recent - * version, including any changes that may have been made. - *

- * The callback is internally held as weak reference, so PagedList doesn't hold a strong - * reference to its observer, such as a {@link PagedListAdapter}. If an adapter were held with a - * strong reference, it would be necessary to clear its PagedList observer before it could be - * GC'd. - * - * @param previousSnapshot Snapshot previously captured from this List, or null. - * @param callback Callback to dispatch to. - * - * @see #removeWeakCallback(Callback) - */ - @SuppressWarnings("WeakerAccess") - public void addWeakCallback(@Nullable List previousSnapshot, @NonNull Callback callback) { - if (previousSnapshot != null && previousSnapshot != this) { - - if (previousSnapshot.isEmpty()) { - if (!mStorage.isEmpty()) { - // If snapshot is empty, diff is trivial - just notify number new items. - // Note: occurs in async init, when snapshot taken before init page arrives - callback.onInserted(0, mStorage.size()); - } - } else { - PagedList storageSnapshot = (PagedList) previousSnapshot; - - //noinspection unchecked - dispatchUpdatesSinceSnapshot(storageSnapshot, callback); - } - } - - // first, clean up any empty weak refs - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - final Callback currentCallback = mCallbacks.get(i).get(); - if (currentCallback == null) { - mCallbacks.remove(i); - } - } - - // then add the new one - mCallbacks.add(new WeakReference<>(callback)); - } - /** - * Removes a previously added callback. - * - * @param callback Callback, previously added. - * @see #addWeakCallback(List, Callback) - */ - @SuppressWarnings("WeakerAccess") - public void removeWeakCallback(@NonNull Callback callback) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - final Callback currentCallback = mCallbacks.get(i).get(); - if (currentCallback == null || currentCallback == callback) { - // found callback, or empty weak ref - mCallbacks.remove(i); - } - } - } - - void notifyInserted(int position, int count) { - if (count != 0) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - final Callback callback = mCallbacks.get(i).get(); - if (callback != null) { - callback.onInserted(position, count); - } - } - } - } - - void notifyChanged(int position, int count) { - if (count != 0) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - final Callback callback = mCallbacks.get(i).get(); - - if (callback != null) { - callback.onChanged(position, count); - } - } - } - } - - void notifyRemoved(int position, int count) { - if (count != 0) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - final Callback callback = mCallbacks.get(i).get(); - - if (callback != null) { - callback.onRemoved(position, count); - } - } - } - } - - /** - * Dispatch updates since the non-empty snapshot was taken. - * - * @param snapshot Non-empty snapshot. - * @param callback Callback for updates that have occurred since snapshot. - */ - abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList snapshot, - @NonNull Callback callback); - - abstract void loadAroundInternal(int index); - - /** - * Callback signaling when content is loaded into the list. - *

- * Can be used to listen to items being paged in and out. These calls will be dispatched on - * the executor defined by {@link Builder#setNotifyExecutor(Executor)}, which is generally - * the main/UI thread. - */ - public abstract static class Callback { - /** - * Called when null padding items have been loaded to signal newly available data, or when - * data that hasn't been used in a while has been dropped, and swapped back to null. - * - * @param position Position of first newly loaded items, out of total number of items - * (including padded nulls). - * @param count Number of items loaded. - */ - public abstract void onChanged(int position, int count); - - /** - * Called when new items have been loaded at the end or beginning of the list. - * - * @param position Position of the first newly loaded item (in practice, either - * 0 or size - 1. - * @param count Number of items loaded. - */ - public abstract void onInserted(int position, int count); - - /** - * Called when items have been removed at the end or beginning of the list, and have not - * been replaced by padded nulls. - * - * @param position Position of the first newly loaded item (in practice, either - * 0 or size - 1. - * @param count Number of items loaded. - */ - @SuppressWarnings("unused") - public abstract void onRemoved(int position, int count); - } - - /** - * Configures how a PagedList loads content from its DataSource. - *

- * Use a Config {@link Builder} to construct and define custom loading behavior, such as - * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}. - */ - public static class Config { - /** - * When {@link #maxSize} is set to {@code MAX_SIZE_UNBOUNDED}, the maximum number of items - * loaded is unbounded, and pages will never be dropped. - */ - @SuppressWarnings("WeakerAccess") - public static final int MAX_SIZE_UNBOUNDED = Integer.MAX_VALUE; - - /** - * Size of each page loaded by the PagedList. - */ - public final int pageSize; - - /** - * Prefetch distance which defines how far ahead to load. - *

- * If this value is set to 50, the paged list will attempt to load 50 items in advance of - * data that's already been accessed. - * - * @see PagedList#loadAround(int) - */ - @SuppressWarnings("WeakerAccess") - public final int prefetchDistance; - - /** - * Defines whether the PagedList may display null placeholders, if the DataSource provides - * them. - */ - @SuppressWarnings("WeakerAccess") - public final boolean enablePlaceholders; - - /** - * Defines the maximum number of items that may be loaded into this pagedList before pages - * should be dropped. - *

- * {@link PageKeyedDataSource} does not currently support dropping pages - when - * loading from a {@code PageKeyedDataSource}, this value is ignored. - * - * @see #MAX_SIZE_UNBOUNDED - * @see Builder#setMaxSize(int) - */ - public final int maxSize; - - /** - * Size hint for initial load of PagedList, often larger than a regular page. - */ - @SuppressWarnings("WeakerAccess") - public final int initialLoadSizeHint; - - Config(int pageSize, int prefetchDistance, - boolean enablePlaceholders, int initialLoadSizeHint, int maxSize) { - this.pageSize = pageSize; - this.prefetchDistance = prefetchDistance; - this.enablePlaceholders = enablePlaceholders; - this.initialLoadSizeHint = initialLoadSizeHint; - this.maxSize = maxSize; - } - - /** - * Builder class for {@link Config}. - *

- * You must at minimum specify page size with {@link #setPageSize(int)}. - */ - public static final class Builder { - static final int DEFAULT_INITIAL_PAGE_MULTIPLIER = 3; - - private int mPageSize = -1; - private int mPrefetchDistance = -1; - private int mInitialLoadSizeHint = -1; - private boolean mEnablePlaceholders = true; - private int mMaxSize = MAX_SIZE_UNBOUNDED; - - /** - * Defines the number of items loaded at once from the DataSource. - *

- * Should be several times the number of visible items onscreen. - *

- * Configuring your page size depends on how your data is being loaded and used. Smaller - * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally - * improve loading throughput, to a point - * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost). - *

- * If you're loading data for very large, social-media style cards that take up most of - * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're - * displaying dozens of items in a tiled grid, which can present items during a scroll - * much more quickly, consider closer to 100. - * - * @param pageSize Number of items loaded at once from the DataSource. - * @return this - */ - @NonNull - public Builder setPageSize(@IntRange(from = 1) int pageSize) { - if (pageSize < 1) { - throw new IllegalArgumentException("Page size must be a positive number"); - } - mPageSize = pageSize; - return this; - } - - /** - * Defines how far from the edge of loaded content an access must be to trigger further - * loading. - *

- * Should be several times the number of visible items onscreen. - *

- * If not set, defaults to page size. - *

- * A value of 0 indicates that no list items will be loaded until they are specifically - * requested. This is generally not recommended, so that users don't observe a - * placeholder item (with placeholders) or end of list (without) while scrolling. - * - * @param prefetchDistance Distance the PagedList should prefetch. - * @return this - */ - @NonNull - public Builder setPrefetchDistance(@IntRange(from = 0) int prefetchDistance) { - mPrefetchDistance = prefetchDistance; - return this; - } - - /** - * Pass false to disable null placeholders in PagedLists using this Config. - *

- * If not set, defaults to true. - *

- * A PagedList will present null placeholders for not-yet-loaded content if two - * conditions are met: - *

- * 1) Its DataSource can count all unloaded items (so that the number of nulls to - * present is known). - *

- * 2) placeholders are not disabled on the Config. - *

- * Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList - * (often a {@link PagedListAdapter}) doesn't need to account for null items. - *

- * If placeholders are disabled, not-yet-loaded content will not be present in the list. - * Paging will still occur, but as items are loaded or removed, they will be signaled - * as inserts to the {@link PagedList.Callback}. - * {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading, - * though a {@link PagedListAdapter} may still receive change events as a result of - * PagedList diffing. - * - * @param enablePlaceholders False if null placeholders should be disabled. - * @return this - */ - @SuppressWarnings("SameParameterValue") - @NonNull - public Builder setEnablePlaceholders(boolean enablePlaceholders) { - mEnablePlaceholders = enablePlaceholders; - return this; - } - - /** - * Defines how many items to load when first load occurs. - *

- * This value is typically larger than page size, so on first load data there's a large - * enough range of content loaded to cover small scrolls. - *

- * When using a {@link PositionalDataSource}, the initial load size will be coerced to - * an integer multiple of pageSize, to enable efficient tiling. - *

- * If not set, defaults to three times page size. - * - * @param initialLoadSizeHint Number of items to load while initializing the PagedList. - * @return this - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public Builder setInitialLoadSizeHint(@IntRange(from = 1) int initialLoadSizeHint) { - mInitialLoadSizeHint = initialLoadSizeHint; - return this; - } - - /** - * Defines how many items to keep loaded at once. - *

- * This can be used to cap the number of items kept in memory by dropping pages. This - * value is typically many pages so old pages are cached in case the user scrolls back. - *

- * This value must be at least two times the - * {@link #setPrefetchDistance(int)} prefetch distance} plus the - * {@link #setPageSize(int) page size}). This constraint prevent loads from being - * continuously fetched and discarded due to prefetching. - *

- * The max size specified here best effort, not a guarantee. In practice, if maxSize - * is many times the page size, the number of items held by the PagedList will not grow - * above this number. Exceptions are made as necessary to guarantee: - *

    - *
  • Pages are never dropped until there are more than two pages loaded. Note that - * a DataSource may not be held strictly to - * {@link Config#pageSize requested pageSize}, so two pages may be larger than - * expected. - *
  • Pages are never dropped if they are within a prefetch window (defined to be - * {@code pageSize + (2 * prefetchDistance)}) of the most recent load. - *
- *

- * {@link PageKeyedDataSource} does not currently support dropping pages - when - * loading from a {@code PageKeyedDataSource}, this value is ignored. - *

- * If not set, defaults to {@code MAX_SIZE_UNBOUNDED}, which disables page dropping. - * - * @param maxSize Maximum number of items to keep in memory, or - * {@code MAX_SIZE_UNBOUNDED} to disable page dropping. - * @return this - * - * @see Config#MAX_SIZE_UNBOUNDED - * @see Config#maxSize - */ - @NonNull - public Builder setMaxSize(@IntRange(from = 2) int maxSize) { - mMaxSize = maxSize; - return this; - } - - /** - * Creates a {@link Config} with the given parameters. - * - * @return A new Config. - */ - @NonNull - public Config build() { - if (mPrefetchDistance < 0) { - mPrefetchDistance = mPageSize; - } - if (mInitialLoadSizeHint < 0) { - mInitialLoadSizeHint = mPageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER; - } - if (!mEnablePlaceholders && mPrefetchDistance == 0) { - throw new IllegalArgumentException("Placeholders and prefetch are the only ways" - + " to trigger loading of more data in the PagedList, so either" - + " placeholders must be enabled, or prefetch distance must be > 0."); - } - if (mMaxSize != MAX_SIZE_UNBOUNDED) { - if (mMaxSize < mPageSize + mPrefetchDistance * 2) { - throw new IllegalArgumentException("Maximum size must be at least" - + " pageSize + 2*prefetchDist, pageSize=" + mPageSize - + ", prefetchDist=" + mPrefetchDistance + ", maxSize=" + mMaxSize); - } - } - - return new Config(mPageSize, mPrefetchDistance, - mEnablePlaceholders, mInitialLoadSizeHint, mMaxSize); - } - } - } - - /** - * Signals when a PagedList has reached the end of available data. - *

- * When local storage is a cache of network data, it's common to set up a streaming pipeline: - * Network data is paged into the database, database is paged into UI. Paging from the database - * to UI can be done with a {@code LiveData}, but it's still necessary to know when - * to trigger network loads. - *

- * BoundaryCallback does this signaling - when a DataSource runs out of data at the end of - * the list, {@link #onItemAtEndLoaded(Object)} is called, and you can start an async network - * load that will write the result directly to the database. Because the database is being - * observed, the UI bound to the {@code LiveData} will update automatically to - * account for the new items. - *

- * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to - * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple - * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should - * avoid triggering it again while the load is ongoing. - *

- * The database + network Repository in the - * PagingWithNetworkSample - * shows how to implement a network BoundaryCallback using - * Retrofit, while - * handling swipe-to-refresh, network errors, and retry. - *

Requesting Network Data

- * BoundaryCallback only passes the item at front or end of the list when out of data. This - * makes it an easy fit for item-keyed network requests, where you can use the item passed to - * the BoundaryCallback to request more data from the network. In these cases, the source of - * truth for next page to load is coming from local storage, based on what's already loaded. - *

- * If you aren't using an item-keyed network API, you may be using page-keyed, or page-indexed. - * If this is the case, the paging library doesn't know about the page key or index used in the - * BoundaryCallback, so you need to track it yourself. You can do this in one of two ways: - *

Local storage Page key
- * If you want to perfectly resume your query, even if the app is killed and resumed, you can - * store the key on disk. Note that with a positional/page index network API, there's a simple - * way to do this, by using the {@code listSize} as an input to the next load (or - * {@code listSize / NETWORK_PAGE_SIZE}, for page indexing). - *

- * The current list size isn't passed to the BoundaryCallback though. This is because the - * PagedList doesn't necessarily know the number of items in local storage. Placeholders may be - * disabled, or the DataSource may not count total number of items. - *

- * Instead, for these positional cases, you can query the database for the number of items, and - * pass that to the network. - *

In-Memory Page key
- * Often it doesn't make sense to query the next page from network if the last page you fetched - * was loaded many hours or days before. If you keep the key in memory, you can refresh any time - * you start paging from a network source. - *

- * Store the next key in memory, inside your BoundaryCallback. When you create a new - * BoundaryCallback when creating a new {@code LiveData}/{@code Observable} of - * {@code PagedList}, refresh data. For example, - * in the - * Paging Codelab, the GitHub network page index is stored in memory. - * - * @param Type loaded by the PagedList. - */ - @MainThread - public abstract static class BoundaryCallback { - /** - * Called when zero items are returned from an initial load of the PagedList's data source. - */ - public void onZeroItemsLoaded() {} - - /** - * Called when the item at the front of the PagedList has been loaded, and access has - * occurred within {@link Config#prefetchDistance} of it. - *

- * No more data will be prepended to the PagedList before this item. - * - * @param itemAtFront The first item of PagedList - */ - public void onItemAtFrontLoaded(@NonNull T itemAtFront) {} - - /** - * Called when the item at the end of the PagedList has been loaded, and access has - * occurred within {@link Config#prefetchDistance} of it. - *

- * No more data will be appended to the PagedList after this item. - * - * @param itemAtEnd The first item of PagedList - */ - public void onItemAtEndLoaded(@NonNull T itemAtEnd) {} - } -} diff --git a/app/src/main/java/androidx/paging/PagedListAdapter.java b/app/src/main/java/androidx/paging/PagedListAdapter.java deleted file mode 100644 index cfc8aa5116..0000000000 --- a/app/src/main/java/androidx/paging/PagedListAdapter.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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.recyclerview.widget.AdapterListUpdateCallback; -import androidx.recyclerview.widget.AsyncDifferConfig; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -/** - * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from - * {@link PagedList}s in a {@link RecyclerView}. - *

- * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common - * default behavior for item counting, and listening to PagedList update callbacks. - *

- * While using a LiveData<PagedList> is an easy way to provide data to the adapter, it isn't - * required - you can use {@link #submitList(PagedList)} when new lists are available. - *

- * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on - * a background thread to compute fine grained updates as new PagedLists are received. - *

- * Handles both the internal paging of the list as more data is loaded, and updates in the form of - * new PagedLists. - *

- * 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);
- *         UserAdapter<User> adapter = new UserAdapter();
- *         viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
- *         recyclerView.setAdapter(adapter);
- *     }
- * }
- *
- * class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
- *     public UserAdapter() {
- *         super(DIFF_CALLBACK);
- *     }
- *     {@literal @}Override
- *     public void onBindViewHolder(UserViewHolder holder, int position) {
- *         User user = getItem(position);
- *         if (user != null) {
- *             holder.bindTo(user);
- *         } else {
- *             // Null defines a placeholder item - PagedListAdapter 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);
- *         }
- *     }
- * }
- * - * Advanced users that wish for more control over adapter behavior, or to provide a specific base - * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging - * events to adapter-friendly callbacks. - * - * @param Type of the PagedLists this Adapter will receive. - * @param A class that extends ViewHolder that will be used by the adapter. - */ -public abstract class PagedListAdapter - extends RecyclerView.Adapter { - final AsyncPagedListDiffer mDiffer; - private final AsyncPagedListDiffer.PagedListListener mListener = - new AsyncPagedListDiffer.PagedListListener() { - @Override - public void onCurrentListChanged( - @Nullable PagedList previousList, @Nullable PagedList currentList) { - PagedListAdapter.this.onCurrentListChanged(currentList); - PagedListAdapter.this.onCurrentListChanged(previousList, currentList); - } - }; - - /** - * Creates a PagedListAdapter with default threading and - * {@link androidx.recyclerview.widget.ListUpdateCallback}. - * - * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading - * behavior. - * - * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to - * compare items in the list. - */ - protected PagedListAdapter(@NonNull DiffUtil.ItemCallback diffCallback) { - mDiffer = new AsyncPagedListDiffer<>(this, diffCallback); - mDiffer.addPagedListListener(mListener); - } - - protected PagedListAdapter(@NonNull AsyncDifferConfig config) { - mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config); - mDiffer.addPagedListListener(mListener); - } - - /** - * Set the new list to be displayed. - *

- * If a list is already being displayed, a diff will be computed on a background thread, which - * will dispatch Adapter.notifyItem events on the main thread. - * - * @param pagedList The new list to be displayed. - */ - public void submitList(@Nullable PagedList pagedList) { - mDiffer.submitList(pagedList); - } - - /** - * Set the new list to be displayed. - *

- * If a list is already being displayed, a diff will be computed on a background thread, which - * will dispatch Adapter.notifyItem events on the main thread. - *

- * 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 list to be displayed. - * @param commitCallback Optional runnable that is executed when the PagedList is committed, if - * it is committed. - */ - public void submitList(@Nullable PagedList pagedList, - @Nullable final Runnable commitCallback) { - mDiffer.submitList(pagedList, commitCallback); - } - - @Nullable - protected T getItem(int position) { - return mDiffer.getItem(position); - } - - @Override - public int getItemCount() { - return mDiffer.getItemCount(); - } - - /** - * Returns the PagedList currently being displayed by the Adapter. - *

- * 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. - * - * @see #onCurrentListChanged(PagedList, PagedList) - */ - @Nullable - public PagedList getCurrentList() { - return mDiffer.getCurrentList(); - } - - /** - * Called when the current PagedList is updated. - *

- * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't - * needed (such as when the first list is passed, or the list is cleared). In either case, - * PagedListAdapter will simply call - * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. - *

- * This method will notbe called when the Adapter switches from presenting a PagedList - * to a snapshot version of the PagedList during a diff. This means you cannot observe each - * PagedList via this method. - * - * @deprecated Use the two argument variant instead: - * {@link #onCurrentListChanged(PagedList, PagedList)} - * - * @param currentList new PagedList being displayed, may be null. - * - * @see #getCurrentList() - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - public void onCurrentListChanged(@Nullable PagedList currentList) { - } - - /** - * Called when the current PagedList is updated. - *

- * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't - * needed (such as when the first list is passed, or the list is cleared). In either case, - * PagedListAdapter will simply call - * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. - *

- * This method will notbe called when the Adapter switches from presenting a PagedList - * to a snapshot version of the PagedList during a diff. This means you cannot observe each - * PagedList via this method. - * - * @param previousList PagedList that was previously displayed, may be null. - * @param currentList new PagedList being displayed, may be null. - * - * @see #getCurrentList() - */ - public void onCurrentListChanged( - @Nullable PagedList previousList, @Nullable PagedList currentList) { - } -} diff --git a/app/src/main/java/androidx/paging/PagedStorage.java b/app/src/main/java/androidx/paging/PagedStorage.java deleted file mode 100644 index c644234c7e..0000000000 --- a/app/src/main/java/androidx/paging/PagedStorage.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * 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 java.util.AbstractList; -import java.util.ArrayList; -import java.util.List; - -/** - * Class holding the pages of data backing a PagedList, presenting sparse loaded data as a List. - *

- * It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds - * data, and does not have any notion of the ideas of async loads, or prefetching. - */ -final class PagedStorage extends AbstractList { - /** - * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item - * in that position is already loading. We use a singleton placeholder list that is distinct - * from Collections.emptyList() for safety. - */ - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private static final List PLACEHOLDER_LIST = new ArrayList(); - - // Always set - private int mLeadingNullCount; - /** - * List of pages in storage. - * - * Two storage modes: - * - * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled(). - * Safe to access any item in any page. - * - * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true. - * mPages may have nulls, or placeholder (empty) pages while content is loading. - */ - private final ArrayList> mPages; - private int mTrailingNullCount; - - private int mPositionOffset; - /** - * Number of loaded items held by {@link #mPages}. When tiling, doesn't count unloaded pages in - * {@link #mPages}. If tiling is disabled, same as {@link #mStorageCount}. - * - * This count is the one used for trimming. - */ - private int mLoadedCount; - - /** - * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in - * {@link #mPages} may be null, but this value still counts them. - */ - private int mStorageCount; - - // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set - private int mPageSize; - - private int mNumberPrepended; - private int mNumberAppended; - - PagedStorage() { - mLeadingNullCount = 0; - mPages = new ArrayList<>(); - mTrailingNullCount = 0; - mPositionOffset = 0; - mLoadedCount = 0; - mStorageCount = 0; - mPageSize = 1; - mNumberPrepended = 0; - mNumberAppended = 0; - } - - PagedStorage(int leadingNulls, List page, int trailingNulls) { - this(); - init(leadingNulls, page, trailingNulls, 0); - } - - private PagedStorage(PagedStorage other) { - mLeadingNullCount = other.mLeadingNullCount; - mPages = new ArrayList<>(other.mPages); - mTrailingNullCount = other.mTrailingNullCount; - mPositionOffset = other.mPositionOffset; - mLoadedCount = other.mLoadedCount; - mStorageCount = other.mStorageCount; - mPageSize = other.mPageSize; - mNumberPrepended = other.mNumberPrepended; - mNumberAppended = other.mNumberAppended; - } - - PagedStorage snapshot() { - return new PagedStorage<>(this); - } - - private void init(int leadingNulls, List page, int trailingNulls, int positionOffset) { - mLeadingNullCount = leadingNulls; - mPages.clear(); - mPages.add(page); - mTrailingNullCount = trailingNulls; - - mPositionOffset = positionOffset; - mLoadedCount = page.size(); - mStorageCount = mLoadedCount; - - // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled - // even if it will break if nulls convert. - mPageSize = page.size(); - - mNumberPrepended = 0; - mNumberAppended = 0; - } - - void init(int leadingNulls, @NonNull List page, int trailingNulls, int positionOffset, - @NonNull Callback callback) { - init(leadingNulls, page, trailingNulls, positionOffset); - callback.onInitialized(size()); - } - - @Override - public T get(int i) { - if (i < 0 || i >= size()) { - throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size()); - } - - // is it definitely outside 'mPages'? - int localIndex = i - mLeadingNullCount; - if (localIndex < 0 || localIndex >= mStorageCount) { - return null; - } - - int localPageIndex; - int pageInternalIndex; - - if (isTiled()) { - // it's inside mPages, and we're tiled. Jump to correct tile. - localPageIndex = localIndex / mPageSize; - pageInternalIndex = localIndex % mPageSize; - } else { - // it's inside mPages, but page sizes aren't regular. Walk to correct tile. - // Pages can only be null while tiled, so accessing page count is safe. - pageInternalIndex = localIndex; - final int localPageCount = mPages.size(); - for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) { - int pageSize = mPages.get(localPageIndex).size(); - if (pageSize > pageInternalIndex) { - // stop, found the page - break; - } - pageInternalIndex -= pageSize; - } - } - - List page = mPages.get(localPageIndex); - if (page == null || page.size() == 0) { - // can only occur in tiled case, with untouched inner/placeholder pages - return null; - } - return page.get(pageInternalIndex); - } - - /** - * Returns true if all pages are the same size, except for the last, which may be smaller - */ - boolean isTiled() { - return mPageSize > 0; - } - - int getLeadingNullCount() { - return mLeadingNullCount; - } - - int getTrailingNullCount() { - return mTrailingNullCount; - } - - int getStorageCount() { - return mStorageCount; - } - - int getNumberAppended() { - return mNumberAppended; - } - - int getNumberPrepended() { - return mNumberPrepended; - } - - int getPageCount() { - return mPages.size(); - } - - int getLoadedCount() { - return mLoadedCount; - } - - interface Callback { - void onInitialized(int count); - void onPagePrepended(int leadingNulls, int changed, int added); - void onPageAppended(int endPosition, int changed, int added); - void onPagePlaceholderInserted(int pageIndex); - void onPageInserted(int start, int count); - void onPagesRemoved(int startOfDrops, int count); - void onPagesSwappedToPlaceholder(int startOfDrops, int count); - void onEmptyPrepend(); - void onEmptyAppend(); - } - - int getPositionOffset() { - return mPositionOffset; - } - - int getMiddleOfLoadedRange() { - return mLeadingNullCount + mPositionOffset + mStorageCount / 2; - } - - @Override - public int size() { - return mLeadingNullCount + mStorageCount + mTrailingNullCount; - } - - int computeLeadingNulls() { - int total = mLeadingNullCount; - final int pageCount = mPages.size(); - for (int i = 0; i < pageCount; i++) { - List page = mPages.get(i); - if (page != null && page != PLACEHOLDER_LIST) { - break; - } - total += mPageSize; - } - return total; - } - - int computeTrailingNulls() { - int total = mTrailingNullCount; - for (int i = mPages.size() - 1; i >= 0; i--) { - List page = mPages.get(i); - if (page != null && page != PLACEHOLDER_LIST) { - break; - } - total += mPageSize; - } - return total; - } - - // ---------------- Trimming API ------------------- - // Trimming is always done at the beginning or end of the list, as content is loaded. - // In addition to trimming pages in the storage, we also support pre-trimming pages (dropping - // them just before they're added) to avoid dispatching an add followed immediately by a trim. - // - // Note - we avoid trimming down to a single page to reduce chances of dropping page in - // viewport, since we don't strictly know the viewport. If trim is aggressively set to size of a - // single page, trimming while the user can see a page boundary is dangerous. To be safe, we - // just avoid trimming in these cases entirely. - - private boolean needsTrim(int maxSize, int requiredRemaining, int localPageIndex) { - List page = mPages.get(localPageIndex); - return page == null || (mLoadedCount > maxSize - && mPages.size() > 2 - && page != PLACEHOLDER_LIST - && mLoadedCount - page.size() >= requiredRemaining); - } - - boolean needsTrimFromFront(int maxSize, int requiredRemaining) { - return needsTrim(maxSize, requiredRemaining, 0); - } - - boolean needsTrimFromEnd(int maxSize, int requiredRemaining) { - return needsTrim(maxSize, requiredRemaining, mPages.size() - 1); - } - - boolean shouldPreTrimNewPage(int maxSize, int requiredRemaining, int countToBeAdded) { - return mLoadedCount + countToBeAdded > maxSize - && mPages.size() > 1 - && mLoadedCount >= requiredRemaining; - } - - boolean trimFromFront(boolean insertNulls, int maxSize, int requiredRemaining, - @NonNull Callback callback) { - int totalRemoved = 0; - while (needsTrimFromFront(maxSize, requiredRemaining)) { - List page = mPages.remove(0); - int removed = (page == null) ? mPageSize : page.size(); - totalRemoved += removed; - mStorageCount -= removed; - mLoadedCount -= (page == null) ? 0 : page.size(); - } - - if (totalRemoved > 0) { - if (insertNulls) { - // replace removed items with nulls - int previousLeadingNulls = mLeadingNullCount; - mLeadingNullCount += totalRemoved; - callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved); - } else { - // simply remove, and handle offset - mPositionOffset += totalRemoved; - callback.onPagesRemoved(mLeadingNullCount, totalRemoved); - } - } - return totalRemoved > 0; - } - - boolean trimFromEnd(boolean insertNulls, int maxSize, int requiredRemaining, - @NonNull Callback callback) { - int totalRemoved = 0; - while (needsTrimFromEnd(maxSize, requiredRemaining)) { - List page = mPages.remove(mPages.size() - 1); - int removed = (page == null) ? mPageSize : page.size(); - totalRemoved += removed; - mStorageCount -= removed; - mLoadedCount -= (page == null) ? 0 : page.size(); - } - - if (totalRemoved > 0) { - int newEndPosition = mLeadingNullCount + mStorageCount; - if (insertNulls) { - // replace removed items with nulls - mTrailingNullCount += totalRemoved; - callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved); - } else { - // items were just removed, signal - callback.onPagesRemoved(newEndPosition, totalRemoved); - } - } - return totalRemoved > 0; - } - - // ---------------- Contiguous API ------------------- - - T getFirstLoadedItem() { - // safe to access first page's first item here: - // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - return mPages.get(0).get(0); - } - - T getLastLoadedItem() { - // safe to access last page's last item here: - // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - List page = mPages.get(mPages.size() - 1); - return page.get(page.size() - 1); - } - - void prependPage(@NonNull List page, @NonNull Callback callback) { - final int count = page.size(); - if (count == 0) { - // Nothing returned from source, stop loading in this direction - callback.onEmptyPrepend(); - return; - } - if (mPageSize > 0 && count != mPageSize) { - if (mPages.size() == 1 && count > mPageSize) { - // prepending to a single item - update current page size to that of 'inner' page - mPageSize = count; - } else { - // no longer tiled - mPageSize = -1; - } - } - - mPages.add(0, page); - mLoadedCount += count; - mStorageCount += count; - - final int changedCount = Math.min(mLeadingNullCount, count); - final int addedCount = count - changedCount; - - if (changedCount != 0) { - mLeadingNullCount -= changedCount; - } - mPositionOffset -= addedCount; - mNumberPrepended += count; - - callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount); - } - - void appendPage(@NonNull List page, @NonNull Callback callback) { - final int count = page.size(); - if (count == 0) { - // Nothing returned from source, stop loading in this direction - callback.onEmptyAppend(); - return; - } - - if (mPageSize > 0) { - // if the previous page was smaller than mPageSize, - // or if this page is larger than the previous, disable tiling - if (mPages.get(mPages.size() - 1).size() != mPageSize - || count > mPageSize) { - mPageSize = -1; - } - } - - mPages.add(page); - mLoadedCount += count; - mStorageCount += count; - - final int changedCount = Math.min(mTrailingNullCount, count); - final int addedCount = count - changedCount; - - if (changedCount != 0) { - mTrailingNullCount -= changedCount; - } - mNumberAppended += count; - callback.onPageAppended(mLeadingNullCount + mStorageCount - count, - changedCount, addedCount); - } - - // ------------------ Non-Contiguous API (tiling required) ---------------------- - - /** - * Return true if the page at the passed position would be the first (if trimFromFront) or last - * page that's currently loading. - */ - boolean pageWouldBeBoundary(int positionOfPage, boolean trimFromFront) { - if (mPageSize < 1 || mPages.size() < 2) { - throw new IllegalStateException("Trimming attempt before sufficient load"); - } - - if (positionOfPage < mLeadingNullCount) { - // position represent page in leading nulls - return trimFromFront; - } - - if (positionOfPage >= mLeadingNullCount + mStorageCount) { - // position represent page in trailing nulls - return !trimFromFront; - } - - int localPageIndex = (positionOfPage - mLeadingNullCount) / mPageSize; - - // walk outside in, return false if we find non-placeholder page before localPageIndex - if (trimFromFront) { - for (int i = 0; i < localPageIndex; i++) { - if (mPages.get(i) != null) { - return false; - } - } - } else { - for (int i = mPages.size() - 1; i > localPageIndex; i--) { - if (mPages.get(i) != null) { - return false; - } - } - } - - // didn't find another page, so this one would be a boundary - return true; - } - - void initAndSplit(int leadingNulls, @NonNull List multiPageList, - int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) { - - int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize; - for (int i = 0; i < pageCount; i++) { - int beginInclusive = i * pageSize; - int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize); - - List sublist = multiPageList.subList(beginInclusive, endExclusive); - - if (i == 0) { - // Trailing nulls for first page includes other pages in multiPageList - int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size(); - init(leadingNulls, sublist, initialTrailingNulls, positionOffset); - } else { - int insertPosition = leadingNulls + beginInclusive; - insertPage(insertPosition, sublist, null); - } - } - callback.onInitialized(size()); - } - - void tryInsertPageAndTrim( - int position, - @NonNull List page, - int lastLoad, - int maxSize, - int requiredRemaining, - @NonNull Callback callback) { - boolean trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED; - boolean trimFromFront = lastLoad > getMiddleOfLoadedRange(); - - boolean pageInserted = !trim - || !shouldPreTrimNewPage(maxSize, requiredRemaining, page.size()) - || !pageWouldBeBoundary(position, trimFromFront); - - if (pageInserted) { - insertPage(position, page, callback); - } else { - // trim would have us drop the page we just loaded - swap it to null - int localPageIndex = (position - mLeadingNullCount) / mPageSize; - mPages.set(localPageIndex, null); - - // note: we also remove it, so we don't have to guess how large a 'null' page is later - mStorageCount -= page.size(); - if (trimFromFront) { - mPages.remove(0); - mLeadingNullCount += page.size(); - } else { - mPages.remove(mPages.size() - 1); - mTrailingNullCount += page.size(); - } - } - - if (trim) { - if (trimFromFront) { - trimFromFront(true, maxSize, requiredRemaining, callback); - } else { - trimFromEnd(true, maxSize, requiredRemaining, callback); - } - } - } - - public void insertPage(int position, @NonNull List page, @Nullable Callback callback) { - final int newPageSize = page.size(); - if (newPageSize != mPageSize) { - // differing page size is OK in 2 cases, when the page is being added: - // 1) to the end (in which case, ignore new smaller size) - // 2) only the last page has been added so far (in which case, adopt new bigger size) - - int size = size(); - boolean addingLastPage = position == (size - size % mPageSize) - && newPageSize < mPageSize; - boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1 - && newPageSize > mPageSize; - - // OK only if existing single page, and it's the last one - if (!onlyEndPagePresent && !addingLastPage) { - throw new IllegalArgumentException("page introduces incorrect tiling"); - } - if (onlyEndPagePresent) { - mPageSize = newPageSize; - } - } - - int pageIndex = position / mPageSize; - - allocatePageRange(pageIndex, pageIndex); - - int localPageIndex = pageIndex - mLeadingNullCount / mPageSize; - - List oldPage = mPages.get(localPageIndex); - if (oldPage != null && oldPage != PLACEHOLDER_LIST) { - throw new IllegalArgumentException( - "Invalid position " + position + ": data already loaded"); - } - mPages.set(localPageIndex, page); - mLoadedCount += newPageSize; - if (callback != null) { - callback.onPageInserted(position, newPageSize); - } - } - - void allocatePageRange(final int minimumPage, final int maximumPage) { - int leadingNullPages = mLeadingNullCount / mPageSize; - - if (minimumPage < leadingNullPages) { - for (int i = 0; i < leadingNullPages - minimumPage; i++) { - mPages.add(0, null); - } - int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize; - mStorageCount += newStorageAllocated; - mLeadingNullCount -= newStorageAllocated; - - leadingNullPages = minimumPage; - } - if (maximumPage >= leadingNullPages + mPages.size()) { - int newStorageAllocated = Math.min(mTrailingNullCount, - (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize); - for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) { - mPages.add(mPages.size(), null); - } - mStorageCount += newStorageAllocated; - mTrailingNullCount -= newStorageAllocated; - } - } - - public void allocatePlaceholders(int index, int prefetchDistance, - int pageSize, Callback callback) { - if (pageSize != mPageSize) { - if (pageSize < mPageSize) { - throw new IllegalArgumentException("Page size cannot be reduced"); - } - if (mPages.size() != 1 || mTrailingNullCount != 0) { - // not in single, last page allocated case - can't change page size - throw new IllegalArgumentException( - "Page size can change only if last page is only one present"); - } - mPageSize = pageSize; - } - - final int maxPageCount = (size() + mPageSize - 1) / mPageSize; - int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0); - int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1); - - allocatePageRange(minimumPage, maximumPage); - int leadingNullPages = mLeadingNullCount / mPageSize; - for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) { - int localPageIndex = pageIndex - leadingNullPages; - if (mPages.get(localPageIndex) == null) { - //noinspection unchecked - mPages.set(localPageIndex, PLACEHOLDER_LIST); - callback.onPagePlaceholderInserted(pageIndex); - } - } - } - - public boolean hasPage(int pageSize, int index) { - // NOTE: we pass pageSize here to avoid in case mPageSize - // not fully initialized (when last page only one loaded) - int leadingNullPages = mLeadingNullCount / pageSize; - - if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) { - return false; - } - - List page = mPages.get(index - leadingNullPages); - - return page != null && page != PLACEHOLDER_LIST; - } - - @Override - public String toString() { - StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount - + ", storage " + mStorageCount - + ", trailing " + getTrailingNullCount()); - - for (int i = 0; i < mPages.size(); i++) { - ret.append(" ").append(mPages.get(i)); - } - return ret.toString(); - } -} diff --git a/app/src/main/java/androidx/paging/PagedStorageDiffHelper.java b/app/src/main/java/androidx/paging/PagedStorageDiffHelper.java deleted file mode 100644 index 886bfd1259..0000000000 --- a/app/src/main/java/androidx/paging/PagedStorageDiffHelper.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListUpdateCallback; - -/** - * Methods for computing and applying DiffResults between PagedLists. - * - * To minimize the amount of diffing caused by placeholders, we only execute DiffUtil in a reduced - * 'diff space' - in the range (computeLeadingNulls..size-computeTrailingNulls). - * - * This allows the diff of a PagedList, e.g.: - * 100 nulls, placeholder page, (empty page) x 5, page, 100 nulls - * - * To only inform DiffUtil about single loaded page in this case, by pruning all other nulls from - * consideration. - * - * @see PagedStorage#computeLeadingNulls() - * @see PagedStorage#computeTrailingNulls() - */ -class PagedStorageDiffHelper { - private PagedStorageDiffHelper() { - } - - static DiffUtil.DiffResult computeDiff( - final PagedStorage oldList, - final PagedStorage newList, - final DiffUtil.ItemCallback diffCallback) { - final int oldOffset = oldList.computeLeadingNulls(); - final int newOffset = newList.computeLeadingNulls(); - - final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls(); - final int newSize = newList.size() - newOffset - newList.computeTrailingNulls(); - - return DiffUtil.calculateDiff(new DiffUtil.Callback() { - @Nullable - @Override - public Object getChangePayload(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); - if (oldItem == null || newItem == null) { - return null; - } - return diffCallback.getChangePayload(oldItem, newItem); - } - - @Override - public int getOldListSize() { - return oldSize; - } - - @Override - public int getNewListSize() { - return newSize; - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); - if (oldItem == newItem) { - return true; - } - //noinspection SimplifiableIfStatement - if (oldItem == null || newItem == null) { - return false; - } - return diffCallback.areItemsTheSame(oldItem, newItem); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); - if (oldItem == newItem) { - return true; - } - //noinspection SimplifiableIfStatement - if (oldItem == null || newItem == null) { - return false; - } - - return diffCallback.areContentsTheSame(oldItem, newItem); - } - }, true); - } - - private static class OffsettingListUpdateCallback implements ListUpdateCallback { - private final int mOffset; - private final ListUpdateCallback mCallback; - - OffsettingListUpdateCallback(int offset, ListUpdateCallback callback) { - mOffset = offset; - mCallback = callback; - } - - @Override - public void onInserted(int position, int count) { - mCallback.onInserted(position + mOffset, count); - } - - @Override - public void onRemoved(int position, int count) { - mCallback.onRemoved(position + mOffset, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - mCallback.onMoved(fromPosition + mOffset, toPosition + mOffset); - } - - @Override - public void onChanged(int position, int count, Object payload) { - mCallback.onChanged(position + mOffset, count, payload); - } - } - - /** - * TODO: improve diffing logic - * - * This function currently does a naive diff, assuming null does not become an item, and vice - * versa (so it won't dispatch onChange events for these). It's similar to passing a list with - * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert - * for changed nulls at the beginning / end of the list. - * - * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we - * handle this by passing the snapshot to the callback, and dispatching those changes - * immediately after dispatching this diff. - */ - static void dispatchDiff(ListUpdateCallback callback, - final PagedStorage oldList, - final PagedStorage newList, - final DiffUtil.DiffResult diffResult) { - - final int trailingOld = oldList.computeTrailingNulls(); - final int trailingNew = newList.computeTrailingNulls(); - final int leadingOld = oldList.computeLeadingNulls(); - final int leadingNew = newList.computeLeadingNulls(); - - if (trailingOld == 0 - && trailingNew == 0 - && leadingOld == 0 - && leadingNew == 0) { - // Simple case, dispatch & return - diffResult.dispatchUpdatesTo(callback); - return; - } - - // First, remove or insert trailing nulls - if (trailingOld > trailingNew) { - int count = trailingOld - trailingNew; - callback.onRemoved(oldList.size() - count, count); - } else if (trailingOld < trailingNew) { - callback.onInserted(oldList.size(), trailingNew - trailingOld); - } - - // Second, remove or insert leading nulls - if (leadingOld > leadingNew) { - callback.onRemoved(0, leadingOld - leadingNew); - } else if (leadingOld < leadingNew) { - callback.onInserted(0, leadingNew - leadingOld); - } - - // apply the diff, with an offset if needed - if (leadingNew != 0) { - diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback)); - } else { - diffResult.dispatchUpdatesTo(callback); - } - } - - /** - * Given an oldPosition representing an anchor in the old data set, computes its new position - * after the diff, or a guess if it no longer exists. - */ - static int transformAnchorIndex(@NonNull DiffUtil.DiffResult diffResult, - @NonNull PagedStorage oldList, @NonNull PagedStorage newList, final int oldPosition) { - final int oldOffset = oldList.computeLeadingNulls(); - - // diffResult's indices starting after nulls, need to transform to diffutil indices - // (see also dispatchDiff(), which adds this offset when dispatching) - int diffIndex = oldPosition - oldOffset; - - final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls(); - - // if our anchor is non-null, use it or close item's position in new list - if (diffIndex >= 0 && diffIndex < oldSize) { - // search outward from old position for position that maps - for (int i = 0; i < 30; i++) { - int positionToTry = diffIndex + (i / 2 * (i % 2 == 1 ? -1 : 1)); - - // reject if (null) item was not passed to DiffUtil, and wouldn't be in the result - if (positionToTry < 0 || positionToTry >= oldList.getStorageCount()) { - continue; - } - - try { - int result = diffResult.convertOldPositionToNew(positionToTry); - if (result != -1) { - // also need to transform from diffutil output indices to newList - return result + newList.getLeadingNullCount(); - } - } catch (IndexOutOfBoundsException e) { - // Rare crash, just give up the search for the old item - break; - } - } - } - - // not anchored to an item in new list, so just reuse position (clamped to newList size) - return Math.max(0, Math.min(oldPosition, newList.size() - 1)); - } -} diff --git a/app/src/main/java/androidx/paging/PositionalDataSource.java b/app/src/main/java/androidx/paging/PositionalDataSource.java deleted file mode 100644 index d54cd7c906..0000000000 --- a/app/src/main/java/androidx/paging/PositionalDataSource.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * 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 Type of items being loaded by the PositionalDataSource. - */ -public abstract class PositionalDataSource extends DataSource { - - /** - * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}. - */ - @SuppressWarnings("WeakerAccess") - public static class LoadInitialParams { - /** - * Initial load position requested. - *

- * 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 Type of items being loaded. - */ - public abstract static class LoadInitialCallback { - /** - * Called to pass initial load state from a DataSource. - *

- * 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 data, int position, int totalCount); - - /** - * Called to pass initial load state from a DataSource without total count, - * when placeholders aren't requested. - *

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 data, int position); - } - - /** - * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)} - * to return data. - *

- * 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 Type of items being loaded. - */ - public abstract static class LoadRangeCallback { - /** - * Called to pass loaded data from {@link #loadRange(LoadRangeParams, LoadRangeCallback)}. - * - * @param data List of items loaded from the DataSource. Must be same size as requested, - * unless at end of list. - */ - public abstract void onResult(@NonNull List data); - } - - static class LoadInitialCallbackImpl extends LoadInitialCallback { - final LoadCallbackHelper mCallbackHelper; - private final boolean mCountingEnabled; - private final int mPageSize; - - LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled, - int pageSize, PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver); - mCountingEnabled = countingEnabled; - mPageSize = pageSize; - if (mPageSize < 1) { - throw new IllegalArgumentException("Page size must be non-negative"); - } - } - - @Override - public void onResult(@NonNull List data, int position, int totalCount) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount); - if (position + data.size() != totalCount - && data.size() % mPageSize != 0) { - throw new IllegalArgumentException("PositionalDataSource requires initial load" - + " size to be a multiple of page size to support internal tiling." - + " loadSize " + data.size() + ", position " + position - + ", totalCount " + totalCount + ", pageSize " + mPageSize); - } - - if (mCountingEnabled) { - int trailingUnloadedCount = totalCount - position - data.size(); - mCallbackHelper.dispatchResultToReceiver( - new PageResult<>(data, position, trailingUnloadedCount, 0)); - } else { - // Only occurs when wrapped as contiguous - mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position)); - } - } - } - - @Override - public void onResult(@NonNull List data, int position) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - if (position < 0) { - throw new IllegalArgumentException("Position must be non-negative"); - } - if (data.isEmpty() && position != 0) { - throw new IllegalArgumentException( - "Initial result cannot be empty if items are present in data set."); - } - if (mCountingEnabled) { - throw new IllegalStateException("Placeholders requested, but totalCount not" - + " provided. Please call the three-parameter onResult method, or" - + " disable placeholders in the PagedList.Config"); - } - mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position)); - } - } - } - - static class LoadRangeCallbackImpl extends LoadRangeCallback { - private LoadCallbackHelper mCallbackHelper; - private final int mPositionOffset; - LoadRangeCallbackImpl(@NonNull PositionalDataSource dataSource, - @PageResult.ResultType int resultType, int positionOffset, - Executor mainThreadExecutor, PageResult.Receiver receiver) { - mCallbackHelper = new LoadCallbackHelper<>( - dataSource, resultType, mainThreadExecutor, receiver); - mPositionOffset = positionOffset; - } - - @Override - public void onResult(@NonNull List data) { - if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) { - mCallbackHelper.dispatchResultToReceiver(new PageResult<>( - data, 0, 0, mPositionOffset)); - } - } - } - - final void dispatchLoadInitial(boolean acceptCount, - int requestedStartPosition, int requestedLoadSize, int pageSize, - @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver receiver) { - LoadInitialCallbackImpl callback = - new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver); - - LoadInitialParams params = new LoadInitialParams( - requestedStartPosition, requestedLoadSize, pageSize, acceptCount); - loadInitial(params, 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); - } - - final void dispatchLoadRange(@PageResult.ResultType int resultType, int startPosition, - int count, @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - LoadRangeCallback callback = new LoadRangeCallbackImpl<>( - this, resultType, startPosition, mainThreadExecutor, receiver); - if (count == 0) { - callback.onResult(Collections.emptyList()); - } else { - loadRange(new LoadRangeParams(startPosition, count), callback); - } - } - - /** - * Load initial list data. - *

- * 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 callback); - - /** - * Called to load a range of data from the DataSource. - *

- * 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 callback); - - @Override - boolean isContiguous() { - return false; - } - - @NonNull - ContiguousDataSource wrapAsContiguousWithoutPlaceholders() { - return new ContiguousWithoutPlaceholdersWrapper<>(this); - } - - /** - * Helper for computing an initial position in - * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be - * computed ahead of loading. - *

- * 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: - *

-     * 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 pageStart = position / pageSize * pageSize; - - // maximum start pos is that which will encompass end of list - int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize; - pageStart = Math.min(maximumLoadPage, pageStart); - - // minimum start position is 0 - pageStart = Math.max(0, pageStart); - - return pageStart; - } - - /** - * Helper for computing an initial load size in - * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be - * computed ahead of loading. - *

- * 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 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 - extends ContiguousDataSource { - @NonNull - final PositionalDataSource mSource; - - ContiguousWithoutPlaceholdersWrapper( - @NonNull PositionalDataSource source) { - mSource = source; - } - - @Override - public void addInvalidatedCallback( - @NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.addInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void removeInvalidatedCallback( - @NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.removeInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void invalidate() { - mSource.invalidate(); - } - - @Override - public boolean isInvalid() { - return mSource.isInvalid(); - } - - @NonNull - @Override - public DataSource mapByPage( - @NonNull Function, List> function) { - throw new UnsupportedOperationException( - "Inaccessible inner type doesn't support map op"); - } - - @NonNull - @Override - public DataSource map( - @NonNull Function function) { - throw new UnsupportedOperationException( - "Inaccessible inner type doesn't support map op"); - } - - @Override - void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize, - boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - - if (position == null) { - position = 0; - } else { - // snap load size to page multiple (minimum two) - initialLoadSize = (Math.max(initialLoadSize / pageSize, 2)) * pageSize; - - // move start pos so that the load is centered around the key, not starting at it - final int idealStart = position - initialLoadSize / 2; - position = Math.max(0, idealStart / pageSize * pageSize); - } - - // Note enablePlaceholders will be false here, but we don't have a way to communicate - // this to PositionalDataSource. This is fine, because only the list and its position - // offset will be consumed by the LoadInitialCallback. - mSource.dispatchLoadInitial(false, position, initialLoadSize, - pageSize, mainThreadExecutor, receiver); - } - - @Override - void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, - @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - int startIndex = currentEndIndex + 1; - mSource.dispatchLoadRange( - PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver); - } - - @Override - void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, - int pageSize, @NonNull Executor mainThreadExecutor, - @NonNull PageResult.Receiver receiver) { - int startIndex = currentBeginIndex - 1; - if (startIndex < 0) { - // trigger empty list load - mSource.dispatchLoadRange( - PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver); - } else { - int loadSize = Math.min(pageSize, startIndex + 1); - startIndex = startIndex - loadSize + 1; - mSource.dispatchLoadRange( - PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver); - } - } - - @Override - Integer getKey(int position, Value item) { - return position; - } - - } - - @NonNull - @Override - public final PositionalDataSource mapByPage( - @NonNull Function, List> function) { - return new WrapperPositionalDataSource<>(this, function); - } - - @NonNull - @Override - public final PositionalDataSource map(@NonNull Function function) { - return mapByPage(createListFunction(function)); - } -} diff --git a/app/src/main/java/androidx/paging/SnapshotPagedList.java b/app/src/main/java/androidx/paging/SnapshotPagedList.java deleted file mode 100644 index 42d8cfe629..0000000000 --- a/app/src/main/java/androidx/paging/SnapshotPagedList.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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; - -class SnapshotPagedList extends PagedList { - private final boolean mContiguous; - private final Object mLastKey; - private final DataSource mDataSource; - - SnapshotPagedList(@NonNull PagedList pagedList) { - super(pagedList.mStorage.snapshot(), - pagedList.mMainThreadExecutor, - pagedList.mBackgroundThreadExecutor, - null, - pagedList.mConfig); - mDataSource = pagedList.getDataSource(); - mContiguous = pagedList.isContiguous(); - mLastLoad = pagedList.mLastLoad; - mLastKey = pagedList.getLastKey(); - } - - @Override - public boolean isImmutable() { - return true; - } - - @Override - public boolean isDetached() { - return true; - } - - @Override - boolean isContiguous() { - return mContiguous; - } - - @Nullable - @Override - public Object getLastKey() { - return mLastKey; - } - - @NonNull - @Override - public DataSource getDataSource() { - return mDataSource; - } - - @Override - void dispatchUpdatesSinceSnapshot(@NonNull PagedList storageSnapshot, - @NonNull Callback callback) { - } - - @Override - void loadAroundInternal(int index) { - } -} diff --git a/app/src/main/java/androidx/paging/TiledDataSource.java b/app/src/main/java/androidx/paging/TiledDataSource.java deleted file mode 100644 index 5f864219aa..0000000000 --- a/app/src/main/java/androidx/paging/TiledDataSource.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.RestrictTo; -import androidx.annotation.WorkerThread; - -import java.util.Collections; -import java.util.List; - -// NOTE: Room 1.0 depends on this class, so it should not be removed until -// we can require a version of Room that uses PositionalDataSource directly -/** - * @param Type loaded by the TiledDataSource. - * - * @deprecated Use {@link PositionalDataSource} - * @hide - */ -@SuppressWarnings("DeprecatedIsStillUsed") -@Deprecated -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public abstract class TiledDataSource extends PositionalDataSource { - - @WorkerThread - public abstract int countItems(); - - @Override - boolean isContiguous() { - return false; - } - - @Nullable - @WorkerThread - public abstract List loadRange(int startPosition, int count); - - @Override - public void loadInitial(@NonNull LoadInitialParams params, - @NonNull LoadInitialCallback callback) { - int totalCount = countItems(); - if (totalCount == 0) { - callback.onResult(Collections.emptyList(), 0, 0); - return; - } - - // bound the size requested, based on known count - final int firstLoadPosition = computeInitialLoadPosition(params, totalCount); - final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount); - - // convert from legacy behavior - List list = loadRange(firstLoadPosition, firstLoadSize); - if (list != null && list.size() == firstLoadSize) { - callback.onResult(list, firstLoadPosition, totalCount); - } else { - // null list, or size doesn't match request - // The size check is a WAR for Room 1.0, subsequent versions do the check in Room - invalidate(); - } - } - - @Override - public void loadRange(@NonNull LoadRangeParams params, - @NonNull LoadRangeCallback callback) { - List list = loadRange(params.startPosition, params.loadSize); - if (list != null) { - callback.onResult(list); - } else { - invalidate(); - } - } -} diff --git a/app/src/main/java/androidx/paging/TiledPagedList.java b/app/src/main/java/androidx/paging/TiledPagedList.java deleted file mode 100644 index e68869e486..0000000000 --- a/app/src/main/java/androidx/paging/TiledPagedList.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import java.util.List; -import java.util.concurrent.Executor; - -class TiledPagedList extends PagedList - implements PagedStorage.Callback { - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final PositionalDataSource mDataSource; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - PageResult.Receiver mReceiver = new PageResult.Receiver() { - // Creation thread for initial synchronous load, otherwise main thread - // Safe to access main thread only state - no other thread has reference during construction - @AnyThread - @Override - public void onPageResult(@PageResult.ResultType int type, - @NonNull PageResult pageResult) { - if (pageResult.isInvalid()) { - detach(); - return; - } - - if (isDetached()) { - // No op, have detached - return; - } - - if (type != PageResult.INIT && type != PageResult.TILE) { - throw new IllegalArgumentException("unexpected resultType" + type); - } - - List page = pageResult.page; - if (mStorage.getPageCount() == 0) { - mStorage.initAndSplit( - pageResult.leadingNulls, page, pageResult.trailingNulls, - pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this); - } else { - mStorage.tryInsertPageAndTrim( - pageResult.positionOffset, - page, - mLastLoad, - mConfig.maxSize, - mRequiredRemainder, - TiledPagedList.this); - } - - if (mBoundaryCallback != null) { - boolean deferEmpty = mStorage.size() == 0; - boolean deferBegin = !deferEmpty - && pageResult.leadingNulls == 0 - && pageResult.positionOffset == 0; - int size = size(); - boolean deferEnd = !deferEmpty - && ((type == PageResult.INIT && pageResult.trailingNulls == 0) - || (type == PageResult.TILE - && (pageResult.positionOffset + mConfig.pageSize >= size))); - deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); - } - } - }; - - @WorkerThread - TiledPagedList(@NonNull PositionalDataSource dataSource, - @NonNull Executor mainThreadExecutor, - @NonNull Executor backgroundThreadExecutor, - @Nullable BoundaryCallback boundaryCallback, - @NonNull Config config, - int position) { - super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, - boundaryCallback, config); - mDataSource = dataSource; - - final int pageSize = mConfig.pageSize; - mLastLoad = position; - - if (mDataSource.isInvalid()) { - detach(); - } else { - final int firstLoadSize = - (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize; - - final int idealStart = position - firstLoadSize / 2; - final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize); - - mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize, - pageSize, mMainThreadExecutor, mReceiver); - } - } - - @Override - boolean isContiguous() { - return false; - } - - @NonNull - @Override - public DataSource getDataSource() { - return mDataSource; - } - - @Nullable - @Override - public Object getLastKey() { - return mLastLoad; - } - - @Override - protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList pagedListSnapshot, - @NonNull Callback callback) { - //noinspection UnnecessaryLocalVariable - final PagedStorage snapshot = pagedListSnapshot.mStorage; - - if (snapshot.isEmpty() - || mStorage.size() != snapshot.size()) { - throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" - + " to be a snapshot of this PagedList"); - } - - // loop through each page and signal the callback for any pages that are present now, - // but not in the snapshot. - final int pageSize = mConfig.pageSize; - final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize; - final int pageCount = mStorage.getPageCount(); - for (int i = 0; i < pageCount; i++) { - int pageIndex = i + leadingNullPages; - int updatedPages = 0; - // count number of consecutive pages that were added since the snapshot... - while (updatedPages < mStorage.getPageCount() - && mStorage.hasPage(pageSize, pageIndex + updatedPages) - && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) { - updatedPages++; - } - // and signal them all at once to the callback - if (updatedPages > 0) { - callback.onChanged(pageIndex * pageSize, pageSize * updatedPages); - i += updatedPages - 1; - } - } - } - - @Override - protected void loadAroundInternal(int index) { - mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this); - } - - @Override - public void onInitialized(int count) { - notifyInserted(0, count); - } - - @Override - public void onPagePrepended(int leadingNulls, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); - } - - @Override - public void onPageAppended(int endPosition, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); - } - - @Override - public void onEmptyPrepend() { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); - } - - @Override - public void onEmptyAppend() { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); - } - - @Override - public void onPagePlaceholderInserted(final int pageIndex) { - // placeholder means initialize a load - mBackgroundThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (isDetached()) { - return; - } - final int pageSize = mConfig.pageSize; - - if (mDataSource.isInvalid()) { - detach(); - } else { - int startPosition = pageIndex * pageSize; - int count = Math.min(pageSize, mStorage.size() - startPosition); - mDataSource.dispatchLoadRange( - PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver); - } - } - }); - } - - @Override - public void onPageInserted(int start, int count) { - notifyChanged(start, count); - } - - @Override - public void onPagesRemoved(int startOfDrops, int count) { - notifyRemoved(startOfDrops, count); - } - - @Override - public void onPagesSwappedToPlaceholder(int startOfDrops, int count) { - notifyChanged(startOfDrops, count); - } -} diff --git a/app/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java b/app/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java deleted file mode 100644 index dc9f908139..0000000000 --- a/app/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.arch.core.util.Function; - -import java.util.IdentityHashMap; -import java.util.List; - -class WrapperItemKeyedDataSource extends ItemKeyedDataSource { - private final ItemKeyedDataSource mSource; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Function, List> mListFunction; - - private final IdentityHashMap mKeyMap = new IdentityHashMap<>(); - - WrapperItemKeyedDataSource(ItemKeyedDataSource source, - Function, List> listFunction) { - mSource = source; - mListFunction = listFunction; - } - - @Override - public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.addInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.removeInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void invalidate() { - mSource.invalidate(); - } - - @Override - public boolean isInvalid() { - return mSource.isInvalid(); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - List convertWithStashedKeys(List source) { - List dest = convert(mListFunction, source); - synchronized (mKeyMap) { - // synchronize on mKeyMap, since multiple loads may occur simultaneously. - // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap) - for (int i = 0; i < dest.size(); i++) { - mKeyMap.put(dest.get(i), mSource.getKey(source.get(i))); - } - } - return dest; - } - - @Override - public void loadInitial(@NonNull LoadInitialParams params, - final @NonNull LoadInitialCallback callback) { - mSource.loadInitial(params, new LoadInitialCallback() { - @Override - public void onResult(@NonNull List data, int position, int totalCount) { - callback.onResult(convertWithStashedKeys(data), position, totalCount); - } - - @Override - public void onResult(@NonNull List data) { - callback.onResult(convertWithStashedKeys(data)); - } - }); - } - - @Override - public void loadAfter(@NonNull LoadParams params, - final @NonNull LoadCallback callback) { - mSource.loadAfter(params, new LoadCallback() { - @Override - public void onResult(@NonNull List data) { - callback.onResult(convertWithStashedKeys(data)); - } - }); - } - - @Override - public void loadBefore(@NonNull LoadParams params, - final @NonNull LoadCallback callback) { - mSource.loadBefore(params, new LoadCallback() { - @Override - public void onResult(@NonNull List data) { - callback.onResult(convertWithStashedKeys(data)); - } - }); - } - - @NonNull - @Override - public K getKey(@NonNull B item) { - synchronized (mKeyMap) { - return mKeyMap.get(item); - } - } -} diff --git a/app/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java b/app/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java deleted file mode 100644 index 6658df10d3..0000000000 --- a/app/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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; - -class WrapperPageKeyedDataSource extends PageKeyedDataSource { - private final PageKeyedDataSource mSource; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Function, List> mListFunction; - - WrapperPageKeyedDataSource(PageKeyedDataSource source, - Function, List> listFunction) { - mSource = source; - mListFunction = listFunction; - } - - @Override - public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.addInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.removeInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void invalidate() { - mSource.invalidate(); - } - - @Override - public boolean isInvalid() { - return mSource.isInvalid(); - } - - @Override - public void loadInitial(@NonNull LoadInitialParams params, - final @NonNull LoadInitialCallback callback) { - mSource.loadInitial(params, new LoadInitialCallback() { - @Override - public void onResult(@NonNull List data, int position, int totalCount, - @Nullable K previousPageKey, @Nullable K nextPageKey) { - callback.onResult(convert(mListFunction, data), position, totalCount, - previousPageKey, nextPageKey); - } - - @Override - public void onResult(@NonNull List data, @Nullable K previousPageKey, - @Nullable K nextPageKey) { - callback.onResult(convert(mListFunction, data), previousPageKey, nextPageKey); - } - }); - } - - @Override - public void loadBefore(@NonNull LoadParams params, - final @NonNull LoadCallback callback) { - mSource.loadBefore(params, new LoadCallback() { - @Override - public void onResult(@NonNull List data, @Nullable K adjacentPageKey) { - callback.onResult(convert(mListFunction, data), adjacentPageKey); - } - }); - } - - @Override - public void loadAfter(@NonNull LoadParams params, - final @NonNull LoadCallback callback) { - mSource.loadAfter(params, new LoadCallback() { - @Override - public void onResult(@NonNull List data, @Nullable K adjacentPageKey) { - callback.onResult(convert(mListFunction, data), adjacentPageKey); - } - }); - } -} diff --git a/app/src/main/java/androidx/paging/WrapperPositionalDataSource.java b/app/src/main/java/androidx/paging/WrapperPositionalDataSource.java deleted file mode 100644 index 3a265d6eee..0000000000 --- a/app/src/main/java/androidx/paging/WrapperPositionalDataSource.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.arch.core.util.Function; - -import java.util.List; - -class WrapperPositionalDataSource extends PositionalDataSource { - private final PositionalDataSource mSource; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Function, List> mListFunction; - - WrapperPositionalDataSource(PositionalDataSource source, - Function, List> listFunction) { - mSource = source; - mListFunction = listFunction; - } - - @Override - public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.addInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { - mSource.removeInvalidatedCallback(onInvalidatedCallback); - } - - @Override - public void invalidate() { - mSource.invalidate(); - } - - @Override - public boolean isInvalid() { - return mSource.isInvalid(); - } - - @Override - public void loadInitial(@NonNull LoadInitialParams params, - final @NonNull LoadInitialCallback callback) { - mSource.loadInitial(params, new LoadInitialCallback() { - @Override - public void onResult(@NonNull List data, int position, int totalCount) { - callback.onResult(convert(mListFunction, data), position, totalCount); - } - - @Override - public void onResult(@NonNull List data, int position) { - callback.onResult(convert(mListFunction, data), position); - } - }); - } - - @Override - public void loadRange(@NonNull LoadRangeParams params, - final @NonNull LoadRangeCallback callback) { - mSource.loadRange(params, new LoadRangeCallback() { - @Override - public void onResult(@NonNull List data) { - callback.onResult(convert(mListFunction, data)); - } - }); - } -}