summaryrefslogtreecommitdiff
path: root/android/arch/paging/ContiguousPagedList.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/arch/paging/ContiguousPagedList.java')
-rw-r--r--android/arch/paging/ContiguousPagedList.java408
1 files changed, 258 insertions, 150 deletions
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index 7835dbe3..d8907c3b 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,136 +16,101 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ContiguousPagedList<T> extends NullPaddedList<T> {
+
+ private final ContiguousDataSource<?, T> mDataSource;
+ private final Executor mMainThreadExecutor;
+ private final Executor mBackgroundThreadExecutor;
+ private final Config mConfig;
-class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
- private final ContiguousDataSource<K, V> mDataSource;
private boolean mPrependWorkerRunning = false;
private boolean mAppendWorkerRunning = false;
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- @SuppressWarnings("unchecked")
- private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage;
-
- private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() {
- @AnyThread
- @Override
- public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) {
- // NOTE: if we're already on main thread, this can delay page receive by a frame
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onPageResult(pageResult);
- }
- });
- }
+ private int mLastLoad = 0;
+ private T mLastItem = null;
- @MainThread
- @Override
- public void onPageResult(@NonNull PageResult<K, V> pageResult) {
- if (pageResult.page == null) {
- detach();
- return;
- }
+ private AtomicBoolean mDetached = new AtomicBoolean(false);
- if (isDetached()) {
- // No op, have detached
- return;
- }
+ private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
- Page<K, V> page = pageResult.page;
- if (pageResult.type == PageResult.INIT) {
- mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
- pageResult.positionOffset, ContiguousPagedList.this);
- notifyInserted(0, mKeyedStorage.size());
- } else if (pageResult.type == PageResult.APPEND) {
- mKeyedStorage.appendPage(page, ContiguousPagedList.this);
- } else if (pageResult.type == PageResult.PREPEND) {
- mKeyedStorage.prependPage(page, ContiguousPagedList.this);
- }
- }
- };
-
- ContiguousPagedList(
- @NonNull ContiguousDataSource<K, V> dataSource,
+ @WorkerThread
+ <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- @NonNull Config config,
- final @Nullable K key) {
- super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
- mDataSource = dataSource;
-
- // blocking init just triggers the initial load on the construction thread -
- // Could still be posted with callback, if desired.
- mDataSource.loadInitial(key,
- mConfig.mInitialLoadSizeHint,
- mConfig.mEnablePlaceholders,
- mReceiver);
- }
-
- @MainThread
- @Override
- void dispatchUpdatesSinceSnapshot(
- @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
-
- final PagedStorage<?, V> 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 (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;
+ Config config,
+ @Nullable K key) {
+ super();
- final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
+ mDataSource = dataSource;
+ mMainThreadExecutor = mainThreadExecutor;
+ mBackgroundThreadExecutor = backgroundThreadExecutor;
+ mConfig = config;
+ NullPaddedList<T> initialState = dataSource.loadInitial(
+ key, config.mInitialLoadSizeHint, config.mEnablePlaceholders);
+
+ if (initialState != null) {
+ mPositionOffset = initialState.getPositionOffset();
+
+ mLeadingNullCount = initialState.getLeadingNullCount();
+ mList = new ArrayList<>(initialState.mList);
+ mTrailingNullCount = initialState.getTrailingNullCount();
+
+ if (initialState.getLeadingNullCount() == 0
+ && initialState.getTrailingNullCount() == 0
+ && config.mPrefetchDistance < 1) {
+ throw new IllegalArgumentException("Null padding is required to support the 0"
+ + " prefetch case - require either null items or prefetching to fetch"
+ + " beyond initial load.");
}
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
+
+ if (initialState.size() != 0) {
+ mLastLoad = mLeadingNullCount + mList.size() / 2;
+ mLastItem = mList.get(mList.size() / 2);
}
+ } else {
+ mList = new ArrayList<>();
+ detach();
+ }
+ if (mList.size() == 0) {
+ // Empty initial state, so don't try and fetch data.
+ mPrependWorkerRunning = true;
+ mAppendWorkerRunning = true;
}
- 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);
- }
+ @Override
+ public T get(int index) {
+ T item = super.get(index);
+ if (item != null) {
+ mLastItem = item;
}
+ return item;
}
- @MainThread
@Override
- protected void loadAroundInternal(int index) {
- int prependItems = mConfig.mPrefetchDistance - (index - mStorage.getLeadingNullCount());
- int appendItems = index + mConfig.mPrefetchDistance
- - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
+ public void loadAround(int index) {
+ mLastLoad = index + mPositionOffset;
+
+ int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
+ int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
@@ -158,6 +123,21 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
}
+ @Override
+ public int getLoadedCount() {
+ return mList.size();
+ }
+
+ @Override
+ public int getLeadingNullCount() {
+ return mLeadingNullCount;
+ }
+
+ @Override
+ public int getTrailingNullCount() {
+ return mTrailingNullCount;
+ }
+
@MainThread
private void schedulePrepend() {
if (mPrependWorkerRunning) {
@@ -165,17 +145,29 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
mPrependWorkerRunning = true;
- 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.getFirstContiguousItem();
+ final int position = mLeadingNullCount + mPositionOffset;
+ final T item = mList.get(0);
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (isDetached()) {
+ if (mDetached.get()) {
return;
}
- mDataSource.loadBefore(position, item, mConfig.mPageSize, mReceiver);
+
+ final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize);
+ if (data != null) {
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mDetached.get()) {
+ return;
+ }
+ prependImpl(data);
+ }
+ });
+ } else {
+ detach();
+ }
}
});
}
@@ -187,44 +179,56 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
mAppendWorkerRunning = true;
- 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.getLastContiguousItem();
+ final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset;
+ final T item = mList.get(mList.size() - 1);
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (isDetached()) {
+ if (mDetached.get()) {
return;
}
- mDataSource.loadAfter(position, item, mConfig.mPageSize, mReceiver);
+
+ final List<T> data = mDataSource.loadAfter(position, item, mConfig.mPageSize);
+ if (data != null) {
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mDetached.get()) {
+ return;
+ }
+ appendImpl(data);
+ }
+ });
+ } else {
+ detach();
+ }
}
});
}
- @Override
- boolean isContiguous() {
- return true;
- }
+ @MainThread
+ private void prependImpl(List<T> before) {
+ final int count = before.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
- @Nullable
- @Override
- public Object getLastKey() {
- return mDataSource.getKey(mLastLoad, mLastItem);
- }
+ Collections.reverse(before);
+ mList.addAll(0, before);
- @MainThread
- @Override
- public void onInitialized(int count) {
- notifyInserted(0, count);
- }
+ final int changedCount = Math.min(mLeadingNullCount, count);
+ final int addedCount = count - changedCount;
- @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;
+ if (changedCount != 0) {
+ mLeadingNullCount -= changedCount;
+ }
+ mPositionOffset -= addedCount;
+ mNumberPrepended += count;
+
+
+ // only try to post more work after fully prepended (with offsets / null counts updated)
+ mPrependItemsRequested -= count;
mPrependWorkerRunning = false;
if (mPrependItemsRequested > 0) {
// not done prepending, keep going
@@ -232,16 +236,39 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
// finally dispatch callbacks, after prepend may have already been scheduled
- notifyChanged(leadingNulls, changedCount);
- notifyInserted(0, addedCount);
+ for (WeakReference<Callback> weakRef : mCallbacks) {
+ Callback callback = weakRef.get();
+ if (callback != null) {
+ if (changedCount != 0) {
+ callback.onChanged(mLeadingNullCount, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(0, addedCount);
+ }
+ }
+ }
}
@MainThread
- @Override
- public void onPageAppended(int endPosition, int changedCount, int addedCount) {
- // consider whether to post more work, now that a page is fully appended
+ private void appendImpl(List<T> after) {
+ final int count = after.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
+
+ mList.addAll(after);
- mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
+ final int changedCount = Math.min(mTrailingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mTrailingNullCount -= changedCount;
+ }
+ mNumberAppended += count;
+
+ // only try to post more work after fully appended (with null counts updated)
+ mAppendItemsRequested -= count;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
@@ -249,19 +276,100 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
// finally dispatch callbacks, after append may have already been scheduled
- notifyChanged(endPosition, changedCount);
- notifyInserted(endPosition + changedCount, addedCount);
+ for (WeakReference<Callback> weakRef : mCallbacks) {
+ Callback callback = weakRef.get();
+ if (callback != null) {
+ final int endPosition = mLeadingNullCount + mList.size() - count;
+ if (changedCount != 0) {
+ callback.onChanged(endPosition, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(endPosition + changedCount, addedCount);
+ }
+ }
+ }
}
- @MainThread
@Override
- public void onPagePlaceholderInserted(int pageIndex) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+ public boolean isImmutable() {
+ // TODO: return true if had nulls, and now getLoadedCount() == size(). Is that safe?
+ // Currently we don't prevent DataSources from returning more items than their null counts
+ return isDetached();
}
- @MainThread
@Override
- public void onPageInserted(int start, int count) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+ public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
+ @NonNull Callback callback) {
+ NullPaddedList<T> snapshot = (NullPaddedList<T>) previousSnapshot;
+ if (snapshot != this && snapshot != null) {
+ final int newlyAppended = mNumberAppended - snapshot.getNumberAppended();
+ final int newlyPrepended = mNumberPrepended - 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 (newlyAppended < 0
+ || newlyPrepended < 0
+ || mTrailingNullCount != Math.max(previousTrailing - newlyAppended, 0)
+ || mLeadingNullCount != Math.max(previousLeading - newlyPrepended, 0)
+ || snapshot.getLoadedCount() + newlyAppended + newlyPrepended != mList.size()) {
+ throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ + " to be a snapshot of this list");
+ }
+
+ if (newlyAppended != 0) {
+ final int changedCount = Math.min(previousTrailing, newlyAppended);
+ final int addedCount = newlyAppended - changedCount;
+
+ final int endPosition =
+ snapshot.getLeadingNullCount() + snapshot.getLoadedCount();
+ 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);
+ }
+ }
+ }
+ mCallbacks.add(new WeakReference<>(callback));
+ }
+
+ @Override
+ public void removeWeakCallback(@NonNull Callback callback) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback currentCallback = mCallbacks.get(i).get();
+ if (currentCallback == null || currentCallback == callback) {
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ @Override
+ public boolean isDetached() {
+ return mDetached.get();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void detach() {
+ mDetached.set(true);
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mDataSource.getKey(mLastLoad, mLastItem);
}
}