Convert paging-common to .kt
DataSource.BaseResult is made public since Kotlin disallows changing
visibility of a derived class from its base class or different
visibility between a function and its parameters.
Test: ./gradlew paging:paging-common:check
Change-Id: Ia89cb55fdc2e74e681e299103aba1fba5b8b5abc
diff --git a/paging/common/api/2.2.0-alpha01.ignore b/paging/common/api/2.2.0-alpha01.ignore
index e81e503..6d27f48 100644
--- a/paging/common/api/2.2.0-alpha01.ignore
+++ b/paging/common/api/2.2.0-alpha01.ignore
@@ -1,7 +1,9 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.paging.PagedList#isContiguous():
+ Added method androidx.paging.PagedList.isContiguous()
+
+
ChangedAbstract: androidx.paging.PagedList#detach():
Method androidx.paging.PagedList.detach has changed 'abstract' qualifier
ChangedAbstract: androidx.paging.PagedList#isDetached():
Method androidx.paging.PagedList.isDetached has changed 'abstract' qualifier
-
-
diff --git a/paging/common/api/2.2.0-alpha01.txt b/paging/common/api/2.2.0-alpha01.txt
index cf51b44..f5eea3a 100644
--- a/paging/common/api/2.2.0-alpha01.txt
+++ b/paging/common/api/2.2.0-alpha01.txt
@@ -2,20 +2,34 @@
package androidx.paging {
public abstract class DataSource<Key, Value> {
- method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ method protected final java.util.concurrent.Executor getExecutor();
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public boolean isRetryableError(Throwable);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
- method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method public boolean isRetryableError(Throwable error);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+ method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ property protected final java.util.concurrent.Executor executor;
+ property @WorkerThread public boolean isInvalid;
+ }
+
+ public static class DataSource.BaseResult<Value> {
+ ctor protected DataSource.BaseResult(java.util.List<? extends Value> data, Object? prevKey, Object? nextKey, int leadingNulls, int trailingNulls, int offset, boolean counted);
+ method public final boolean getCounted();
+ method public final int getLeadingNulls();
+ method public final Object? getNextKey();
+ method public final int getOffset();
+ method public final Object? getPrevKey();
+ method public final int getTrailingNulls();
+ field public final java.util.List<Value> data;
}
public abstract static class DataSource.Factory<Key, Value> {
ctor public DataSource.Factory();
- method public abstract androidx.paging.DataSource<Key!,Value!> create();
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public abstract androidx.paging.DataSource<Key,Value> create();
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public static interface DataSource.InvalidatedCallback {
@@ -24,113 +38,108 @@
public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
ctor public ItemKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadInitialCallback();
- method public abstract void onResult(java.util.List<Value!>, int, int);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
}
public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
}
public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenableItemKeyedDataSource();
- method public abstract Key? getKey(Value);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract Key getKey(Value item);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenableItemKeyedDataSource.InitialResult<V> {
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data);
}
public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
public static class ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenableItemKeyedDataSource.Result<V> {
- ctor public ListenableItemKeyedDataSource.Result(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.Result<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<? extends V> data);
}
public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenablePageKeyedDataSource();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, int, int, Key?, Key?);
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, Key?, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
}
public static class ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenablePageKeyedDataSource.Result<Key, Value> {
- ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value!>, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public ListenablePositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ method public static final int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
}
- public static class ListenablePositionalDataSource.InitialResult<V> {
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position);
}
public static class ListenablePositionalDataSource.LoadInitialParams {
- ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
field public final int pageSize;
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
@@ -138,92 +147,101 @@
}
public static class ListenablePositionalDataSource.LoadRangeParams {
- ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
field public final int loadSize;
field public final int startPosition;
}
- public static class ListenablePositionalDataSource.RangeResult<V> {
- ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.RangeResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<? extends V> data);
}
public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
ctor public PageKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key!,Value!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, int, int, Key?, Key?);
- method public abstract void onResult(java.util.List<Value!>, Key?, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
}
public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public PageKeyedDataSource.LoadParams(Key, int);
+ ctor public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
- method public void addWeakCallback(java.util.List<T!>?, androidx.paging.PagedList.Callback);
- method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+ method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public abstract void detach();
- method public T? get(int);
+ method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
- method public abstract androidx.paging.DataSource<?,T!> getDataSource();
+ method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
+ method public int getSize();
+ method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
- method public void loadAround(int);
- method public void removeWeakCallback(androidx.paging.PagedList.Callback);
- method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void loadAround(int index);
+ method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+ method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public void retry();
- method public int size();
- method public java.util.List<T!> snapshot();
+ method public java.util.List<T> snapshot();
+ property public androidx.paging.PagedList.Config config;
+ property public abstract androidx.paging.DataSource<?,T> dataSource;
+ property public abstract boolean isContiguous;
+ property public abstract boolean isDetached;
+ property public boolean isImmutable;
+ property public abstract Object? lastKey;
+ property public int loadedCount;
+ property public int positionOffset;
+ property public int size;
}
@MainThread public abstract static class PagedList.BoundaryCallback<T> {
ctor public PagedList.BoundaryCallback();
- method public void onItemAtEndLoaded(T);
- method public void onItemAtFrontLoaded(T);
+ method public void onItemAtEndLoaded(T? itemAtEnd);
+ method public void onItemAtFrontLoaded(T? itemAtFront);
method public void onZeroItemsLoaded();
}
public static final class PagedList.Builder<Key, Value> {
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, androidx.paging.PagedList.Config);
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, int);
- method @Deprecated @WorkerThread public androidx.paging.PagedList<Value!> build();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value!>!> buildAsync();
- method public androidx.paging.PagedList.Builder<Key!,Value!> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value!>?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setFetchExecutor(java.util.concurrent.Executor);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setInitialKey(Key?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setNotifyExecutor(java.util.concurrent.Executor);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+ method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+ method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+ method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
}
public abstract static class PagedList.Callback {
ctor public PagedList.Callback();
- method public abstract void onChanged(int, int);
- method public abstract void onInserted(int, int);
- method public abstract void onRemoved(int, int);
+ method public abstract void onChanged(int position, int count);
+ method public abstract void onInserted(int position, int count);
+ method public abstract void onRemoved(int position, int count);
}
public static class PagedList.Config {
@@ -238,11 +256,11 @@
public static final class PagedList.Config.Builder {
ctor public PagedList.Config.Builder();
method public androidx.paging.PagedList.Config build();
- method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
- method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int);
- method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int);
+ method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+ method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+ method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+ method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+ method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
public enum PagedList.LoadState {
@@ -254,7 +272,7 @@
}
public static interface PagedList.LoadStateListener {
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType, androidx.paging.PagedList.LoadState, Throwable?);
+ method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
}
public enum PagedList.LoadType {
@@ -263,37 +281,50 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
+ public final class PagedListConfigKt {
+ ctor public PagedListConfigKt();
+ method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
ctor public PositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
- method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> map(androidx.arch.core.util.Function<T!,V!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> mapByPage(androidx.arch.core.util.Function<java.util.List<T!>!,java.util.List<V!>!>);
+ method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
+ method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+ method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+ method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
}
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>, int, int);
- method public abstract void onResult(java.util.List<T!>, int);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+ method public abstract void onResult(java.util.List<? extends T> data, int position);
}
public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
- ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data);
}
public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
- ctor public PositionalDataSource.LoadRangeParams(int, int);
+ ctor public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+ }
+
+}
+
+package androidx.paging.futures {
+
+ public final class Futures {
+ ctor public Futures();
}
}
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index cf51b44..f5eea3a 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -2,20 +2,34 @@
package androidx.paging {
public abstract class DataSource<Key, Value> {
- method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ method protected final java.util.concurrent.Executor getExecutor();
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public boolean isRetryableError(Throwable);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
- method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method public boolean isRetryableError(Throwable error);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+ method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ property protected final java.util.concurrent.Executor executor;
+ property @WorkerThread public boolean isInvalid;
+ }
+
+ public static class DataSource.BaseResult<Value> {
+ ctor protected DataSource.BaseResult(java.util.List<? extends Value> data, Object? prevKey, Object? nextKey, int leadingNulls, int trailingNulls, int offset, boolean counted);
+ method public final boolean getCounted();
+ method public final int getLeadingNulls();
+ method public final Object? getNextKey();
+ method public final int getOffset();
+ method public final Object? getPrevKey();
+ method public final int getTrailingNulls();
+ field public final java.util.List<Value> data;
}
public abstract static class DataSource.Factory<Key, Value> {
ctor public DataSource.Factory();
- method public abstract androidx.paging.DataSource<Key!,Value!> create();
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public abstract androidx.paging.DataSource<Key,Value> create();
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public static interface DataSource.InvalidatedCallback {
@@ -24,113 +38,108 @@
public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
ctor public ItemKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadInitialCallback();
- method public abstract void onResult(java.util.List<Value!>, int, int);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
}
public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
}
public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenableItemKeyedDataSource();
- method public abstract Key? getKey(Value);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract Key getKey(Value item);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenableItemKeyedDataSource.InitialResult<V> {
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data);
}
public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
public static class ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenableItemKeyedDataSource.Result<V> {
- ctor public ListenableItemKeyedDataSource.Result(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.Result<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<? extends V> data);
}
public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenablePageKeyedDataSource();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, int, int, Key?, Key?);
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, Key?, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
}
public static class ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenablePageKeyedDataSource.Result<Key, Value> {
- ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value!>, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public ListenablePositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ method public static final int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
}
- public static class ListenablePositionalDataSource.InitialResult<V> {
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position);
}
public static class ListenablePositionalDataSource.LoadInitialParams {
- ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
field public final int pageSize;
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
@@ -138,92 +147,101 @@
}
public static class ListenablePositionalDataSource.LoadRangeParams {
- ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
field public final int loadSize;
field public final int startPosition;
}
- public static class ListenablePositionalDataSource.RangeResult<V> {
- ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.RangeResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<? extends V> data);
}
public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
ctor public PageKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key!,Value!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, int, int, Key?, Key?);
- method public abstract void onResult(java.util.List<Value!>, Key?, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
}
public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public PageKeyedDataSource.LoadParams(Key, int);
+ ctor public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
- method public void addWeakCallback(java.util.List<T!>?, androidx.paging.PagedList.Callback);
- method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+ method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public abstract void detach();
- method public T? get(int);
+ method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
- method public abstract androidx.paging.DataSource<?,T!> getDataSource();
+ method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
+ method public int getSize();
+ method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
- method public void loadAround(int);
- method public void removeWeakCallback(androidx.paging.PagedList.Callback);
- method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void loadAround(int index);
+ method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+ method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public void retry();
- method public int size();
- method public java.util.List<T!> snapshot();
+ method public java.util.List<T> snapshot();
+ property public androidx.paging.PagedList.Config config;
+ property public abstract androidx.paging.DataSource<?,T> dataSource;
+ property public abstract boolean isContiguous;
+ property public abstract boolean isDetached;
+ property public boolean isImmutable;
+ property public abstract Object? lastKey;
+ property public int loadedCount;
+ property public int positionOffset;
+ property public int size;
}
@MainThread public abstract static class PagedList.BoundaryCallback<T> {
ctor public PagedList.BoundaryCallback();
- method public void onItemAtEndLoaded(T);
- method public void onItemAtFrontLoaded(T);
+ method public void onItemAtEndLoaded(T? itemAtEnd);
+ method public void onItemAtFrontLoaded(T? itemAtFront);
method public void onZeroItemsLoaded();
}
public static final class PagedList.Builder<Key, Value> {
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, androidx.paging.PagedList.Config);
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, int);
- method @Deprecated @WorkerThread public androidx.paging.PagedList<Value!> build();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value!>!> buildAsync();
- method public androidx.paging.PagedList.Builder<Key!,Value!> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value!>?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setFetchExecutor(java.util.concurrent.Executor);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setInitialKey(Key?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setNotifyExecutor(java.util.concurrent.Executor);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+ method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+ method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+ method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
}
public abstract static class PagedList.Callback {
ctor public PagedList.Callback();
- method public abstract void onChanged(int, int);
- method public abstract void onInserted(int, int);
- method public abstract void onRemoved(int, int);
+ method public abstract void onChanged(int position, int count);
+ method public abstract void onInserted(int position, int count);
+ method public abstract void onRemoved(int position, int count);
}
public static class PagedList.Config {
@@ -238,11 +256,11 @@
public static final class PagedList.Config.Builder {
ctor public PagedList.Config.Builder();
method public androidx.paging.PagedList.Config build();
- method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
- method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int);
- method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int);
+ method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+ method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+ method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+ method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+ method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
public enum PagedList.LoadState {
@@ -254,7 +272,7 @@
}
public static interface PagedList.LoadStateListener {
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType, androidx.paging.PagedList.LoadState, Throwable?);
+ method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
}
public enum PagedList.LoadType {
@@ -263,37 +281,50 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
+ public final class PagedListConfigKt {
+ ctor public PagedListConfigKt();
+ method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
ctor public PositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
- method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> map(androidx.arch.core.util.Function<T!,V!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> mapByPage(androidx.arch.core.util.Function<java.util.List<T!>!,java.util.List<V!>!>);
+ method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
+ method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+ method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+ method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
}
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>, int, int);
- method public abstract void onResult(java.util.List<T!>, int);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+ method public abstract void onResult(java.util.List<? extends T> data, int position);
}
public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
- ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data);
}
public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
- ctor public PositionalDataSource.LoadRangeParams(int, int);
+ ctor public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+ }
+
+}
+
+package androidx.paging.futures {
+
+ public final class Futures {
+ ctor public Futures();
}
}
diff --git a/paging/common/api/restricted_2.2.0-alpha01.ignore b/paging/common/api/restricted_2.2.0-alpha01.ignore
new file mode 100644
index 0000000..e7785e82
--- /dev/null
+++ b/paging/common/api/restricted_2.2.0-alpha01.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedDeprecatedClass: androidx.paging.TiledDataSource:
+ Removed deprecated class androidx.paging.TiledDataSource
diff --git a/paging/common/api/restricted_2.2.0-alpha01.txt b/paging/common/api/restricted_2.2.0-alpha01.txt
index c7a8fc2..6161bbb 100644
--- a/paging/common/api/restricted_2.2.0-alpha01.txt
+++ b/paging/common/api/restricted_2.2.0-alpha01.txt
@@ -1,31 +1,49 @@
// Signature format: 3.0
package androidx.paging {
- @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
- ctor @Deprecated public TiledDataSource();
- method @Deprecated @WorkerThread public abstract int countItems();
- method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method @Deprecated @WorkerThread public abstract java.util.List<T!>? loadRange(int, int);
- method @Deprecated public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+
+ public abstract class DataSource<Key, Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void initExecutor(java.util.concurrent.Executor executor);
}
+
+
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Integer! getKeyInternal$lintWithKotlin(T item);
+ }
+
+ public abstract class PagedList<T> extends java.util.AbstractList<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final androidx.paging.PagedStorage<T> getStorage();
+ }
+
+
+
+
}
package androidx.paging.futures {
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class DirectExecutor implements java.util.concurrent.Executor {
- method public void execute(Runnable);
- field public static androidx.paging.futures.DirectExecutor INSTANCE;
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class DirectExecutor implements java.util.concurrent.Executor {
+ method public void execute(Runnable runnable);
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface FutureCallback<V> {
- method public void onError(Throwable);
- method public void onSuccess(V!);
+ method public void onError(Throwable throwable);
+ method public void onSuccess(V? value);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
- method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V!>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
- method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O!> transform(com.google.common.util.concurrent.ListenableFuture<I!>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
+ public final class Futures {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<? extends V>, androidx.paging.futures.FutureCallback<? super V> callback, java.util.concurrent.Executor executor);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<? extends I>, androidx.arch.core.util.Function<? super I,? extends O> function, java.util.concurrent.Executor executor);
}
}
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index c7a8fc2..6161bbb 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -1,31 +1,49 @@
// Signature format: 3.0
package androidx.paging {
- @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
- ctor @Deprecated public TiledDataSource();
- method @Deprecated @WorkerThread public abstract int countItems();
- method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method @Deprecated @WorkerThread public abstract java.util.List<T!>? loadRange(int, int);
- method @Deprecated public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+
+ public abstract class DataSource<Key, Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void initExecutor(java.util.concurrent.Executor executor);
}
+
+
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Integer! getKeyInternal$lintWithKotlin(T item);
+ }
+
+ public abstract class PagedList<T> extends java.util.AbstractList<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final androidx.paging.PagedStorage<T> getStorage();
+ }
+
+
+
+
}
package androidx.paging.futures {
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class DirectExecutor implements java.util.concurrent.Executor {
- method public void execute(Runnable);
- field public static androidx.paging.futures.DirectExecutor INSTANCE;
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class DirectExecutor implements java.util.concurrent.Executor {
+ method public void execute(Runnable runnable);
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface FutureCallback<V> {
- method public void onError(Throwable);
- method public void onSuccess(V!);
+ method public void onError(Throwable throwable);
+ method public void onSuccess(V? value);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
- method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V!>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
- method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O!> transform(com.google.common.util.concurrent.ListenableFuture<I!>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
+ public final class Futures {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<? extends V>, androidx.paging.futures.FutureCallback<? super V> callback, java.util.concurrent.Executor executor);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<? extends I>, androidx.arch.core.util.Function<? super I,? extends O> function, java.util.concurrent.Executor executor);
}
}
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index c643f56..2156399 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -34,10 +34,13 @@
compile("androidx.annotation:annotation:1.1.0")
compile(ARCH_CORE_COMMON)
compile("androidx.concurrent:concurrent-futures:1.0.0-alpha02")
+ implementation(KOTLIN_STDLIB)
testCompile(JUNIT)
testCompile(MOCKITO_CORE)
- testCompile(KOTLIN_STDLIB)
+ testImplementation MOCKITO_KOTLIN, {
+ exclude group: 'org.mockito' // to keep control on the mockito version
+ }
testCompile(GUAVA)
testImplementation project(':internal-testutils-common')
}
diff --git a/paging/common/ktx/api/2.2.0-alpha01.ignore b/paging/common/ktx/api/2.2.0-alpha01.ignore
new file mode 100644
index 0000000..063ab48
--- /dev/null
+++ b/paging/common/ktx/api/2.2.0-alpha01.ignore
@@ -0,0 +1,2 @@
+RemovedClass: androidx.paging.PagedListConfigKt:
+ Removed class androidx.paging.PagedListConfigKt
diff --git a/paging/common/ktx/api/2.2.0-alpha01.txt b/paging/common/ktx/api/2.2.0-alpha01.txt
index 6d9cc829..66e463b 100644
--- a/paging/common/ktx/api/2.2.0-alpha01.txt
+++ b/paging/common/ktx/api/2.2.0-alpha01.txt
@@ -1,11 +1,6 @@
// Signature format: 3.0
package androidx.paging {
- public final class PagedListConfigKt {
- ctor public PagedListConfigKt();
- method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
- }
-
public final class PagedListKt {
ctor public PagedListKt();
method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
diff --git a/paging/common/ktx/api/current.txt b/paging/common/ktx/api/current.txt
index 6d9cc829..66e463b 100644
--- a/paging/common/ktx/api/current.txt
+++ b/paging/common/ktx/api/current.txt
@@ -1,11 +1,6 @@
// Signature format: 3.0
package androidx.paging {
- public final class PagedListConfigKt {
- ctor public PagedListConfigKt();
- method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
- }
-
public final class PagedListKt {
ctor public PagedListKt();
method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
diff --git a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt b/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
index 383a10f..d957dc7 100644
--- a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
+++ b/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
@@ -34,7 +34,7 @@
* @param initialKey Key the DataSource should load around as part of initialization.
*/
@Suppress("FunctionName")
-fun <Key, Value> PagedList(
+fun <Key : Any, Value : Any> PagedList(
dataSource: DataSource<Key, Value>,
config: PagedList.Config,
notifyExecutor: Executor,
diff --git a/paging/common/ktx/src/test/java/PagedListTest.kt b/paging/common/ktx/src/test/java/PagedListTest.kt
index ac5e511..ef5d93d 100644
--- a/paging/common/ktx/src/test/java/PagedListTest.kt
+++ b/paging/common/ktx/src/test/java/PagedListTest.kt
@@ -30,8 +30,8 @@
val pagedList = PagedList(
dataSource = dataSource,
config = config,
- fetchExecutor = DirectExecutor.INSTANCE,
- notifyExecutor = DirectExecutor.INSTANCE
+ fetchExecutor = DirectExecutor,
+ notifyExecutor = DirectExecutor
)
assertEquals(dataSource, pagedList.dataSource)
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
deleted file mode 100644
index d5ad40d..0000000
--- a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
+++ /dev/null
@@ -1,357 +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.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback,
- Pager.PageConsumer<V> {
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final DataSource<K, V> mDataSource;
-
- @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;
-
- /**
- * Given a page result, apply or drop it, and return whether more loading is needed.
- */
- @Override
- public boolean onPageResult(@NonNull LoadType type,
- @NonNull DataSource.BaseResult<V> pageResult) {
- boolean continueLoading = false;
- @NonNull List<V> page = pageResult.data;
-
-
- // 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 (type == LoadType.END) {
- if (skipNewPage && !trimFromFront) {
- // don't append this data, drop it
- mAppendItemsRequested = 0;
- } else {
- mStorage.appendPage(page, ContiguousPagedList.this);
- mAppendItemsRequested -= page.size();
- if (mAppendItemsRequested > 0 && page.size() != 0) {
- continueLoading = true;
- }
- }
- } else if (type == LoadType.START) {
- if (skipNewPage && trimFromFront) {
- // don't append this data, drop it
- mPrependItemsRequested = 0;
- } else {
- mStorage.prependPage(page, ContiguousPagedList.this);
- mPrependItemsRequested -= page.size();
- if (mPrependItemsRequested > 0 && page.size() != 0) {
- continueLoading = true;
- }
- }
- } else {
- throw new IllegalArgumentException("unexpected result type " + type);
- }
-
- if (mShouldTrim) {
- // Try and trim, but only if the side being trimmed isn't actually fetching.
- // For simplicity (both of impl here, and contract w/ DataSource) we don't
- // allow fetches in same direction - this means reading the load state is safe.
- if (trimFromFront) {
- if (mPager.mLoadStateManager.getStart() != LoadState.LOADING) {
- if (mStorage.trimFromFront(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- // trimmed from front, ensure we can fetch in that dir
- mPager.mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
- }
- }
- } else {
- if (mPager.mLoadStateManager.getEnd() != LoadState.LOADING) {
- if (mStorage.trimFromEnd(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- mPager.mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
- }
- }
- }
- }
-
- triggerBoundaryCallback(type, page);
- return continueLoading;
- }
-
- @Override
- public void onStateChanged(@NonNull LoadType type, @NonNull LoadState state,
- @Nullable Throwable error) {
- dispatchStateChange(type, state, error);
- }
-
- private void triggerBoundaryCallback(@NonNull LoadType type, @NonNull List<V> page) {
- if (mBoundaryCallback != null) {
- boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty
- && type == LoadType.START
- && page.size() == 0;
- boolean deferEnd = !deferEmpty
- && type == LoadType.END
- && page.size() == 0;
- deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
- }
- }
-
- @NonNull
- private final Pager mPager;
-
- @Override
- public void retry() {
- super.retry();
- mPager.retry();
-
- if (mRefreshRetryCallback != null
- && mPager.mLoadStateManager.getRefresh() == LoadState.RETRYABLE_ERROR) {
- // Loading the next PagedList failed, signal the retry callback.
- mRefreshRetryCallback.run();
- }
- }
-
- static final int LAST_LOAD_UNSPECIFIED = -1;
-
- ContiguousPagedList(
- @NonNull DataSource<K, V> dataSource,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @Nullable BoundaryCallback<V> boundaryCallback,
- @NonNull Config config,
- @NonNull DataSource.BaseResult<V> initialResult,
- int lastLoad) {
- super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
- boundaryCallback, config);
- mDataSource = dataSource;
- mLastLoad = lastLoad;
- mPager = new Pager<>(config, dataSource, mainThreadExecutor, backgroundThreadExecutor,
- this, mStorage, initialResult);
-
- if (config.enablePlaceholders) {
- // Placeholders enabled, pass raw data to storage init
- mStorage.init(initialResult.leadingNulls, initialResult.data,
- initialResult.trailingNulls, initialResult.offset, this);
- } else {
- // If placeholder are disabled, avoid passing leading/trailing nulls,
- // since DataSource may have passed them anyway
- mStorage.init(0, initialResult.data,
- 0, initialResult.offset + initialResult.leadingNulls, this);
- }
-
- mShouldTrim = mDataSource.supportsPageDropping()
- && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
-
- 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 = initialResult.leadingNulls + initialResult.offset
- + initialResult.data.size() / 2;
- }
- triggerBoundaryCallback(LoadType.REFRESH, initialResult.data);
- }
-
- @Override
- void dispatchCurrentLoadState(LoadStateListener listener) {
- mPager.mLoadStateManager.dispatchCurrentLoadState(listener);
- }
-
- @Override
- void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {
- mPager.mLoadStateManager.setState(LoadType.REFRESH, loadState, error);
- }
-
- @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 (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) {
- mPager.trySchedulePrepend();
- }
-
- mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
- if (mAppendItemsRequested > 0) {
- mPager.tryScheduleAppend();
- }
- }
-
- @Override
- public boolean isDetached() {
- return mPager.isDetached();
- }
-
- @Override
- public void detach() {
- mPager.detach();
- }
-
- @Override
- boolean isContiguous() {
- return true;
- }
-
- @NonNull
- @Override
- public DataSource<?, V> 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) {
-
- // finally dispatch callbacks, after prepend may have already been scheduled
- notifyChanged(leadingNulls, changedCount);
- notifyInserted(0, addedCount);
-
- offsetAccessIndices(addedCount);
- }
-
- @MainThread
- @Override
- public void onPageAppended(int endPosition, int changedCount, int addedCount) {
- // finally dispatch callbacks, after append may have already been scheduled
- notifyChanged(endPosition, changedCount);
- notifyInserted(endPosition + changedCount, addedCount);
- }
-
-
- @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/paging/common/src/main/java/androidx/paging/DataSource.java b/paging/common/src/main/java/androidx/paging/DataSource.java
deleted file mode 100644
index 2313653..0000000
--- a/paging/common/src/main/java/androidx/paging/DataSource.java
+++ /dev/null
@@ -1,545 +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 com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-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}.
- * <p>
- * 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.
- * <h4>Loading Pages</h4>
- * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
- * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
- * <p>
- * 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.
- * <h4>Updating Paged Data</h4>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <h4>Implementing a DataSource</h4>
- * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
- * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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 <Key> 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> Value type loaded by the DataSource.
- */
-@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
-public abstract class DataSource<Key, Value> {
- /**
- * Factory for DataSources.
- * <p>
- * Data-loading systems of an application or library can implement this interface to allow
- * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
- * DataSource.Factory for a given SQL query:
- *
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract DataSource.Factory<Integer, User> usersByLastName();
- * }
- * </pre>
- * 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> Key identifying items in DataSource.
- * @param <Value> Type of items in the list loaded by the DataSources.
- */
- public abstract static class Factory<Key, Value> {
- /**
- * Create a DataSource.
- * <p>
- * 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.
- * <p>
- * {@link androidx.paging.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<PagedList>} to observers.
- *
- * @return the new DataSource.
- */
- @NonNull
- public abstract DataSource<Key, Value> create();
-
- /**
- * Applies the given function to each value emitted by DataSources produced by this Factory.
- * <p>
- * 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 <ToValue> 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 <ToValue> DataSource.Factory<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-
- /**
- * Applies the given function to each value emitted by DataSources produced by this Factory.
- * <p>
- * 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 <ToValue> 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 <ToValue> DataSource.Factory<Key, ToValue> mapByPage(
- @NonNull final Function<List<Value>, List<ToValue>> function) {
- return new Factory<Key, ToValue>() {
- @Override
- public DataSource<Key, ToValue> create() {
- return Factory.this.create().mapByPage(function);
- }
- };
- }
- }
-
- @NonNull
- static <X, Y> Function<List<X>, List<Y>> createListFunction(
- final @NonNull Function<X, Y> innerFunc) {
- return new Function<List<X>, List<Y>>() {
- @Override
- public List<Y> apply(@NonNull List<X> source) {
- List<Y> out = new ArrayList<>(source.size());
- for (int i = 0; i < source.size(); i++) {
- out.add(innerFunc.apply(source.get(i)));
- }
- return out;
- }
- };
- }
-
- static <A, B> List<B> convert(Function<List<A>, List<B>> function, List<A> source) {
- List<B> dest = function.apply(source);
- if (dest.size() != source.size()) {
- throw new IllegalStateException("Invalid Function " + function
- + " changed return size. This is not supported.");
- }
- return dest;
- }
-
-
- /**
- * Applies the given function to each value emitted by the DataSource.
- * <p>
- * 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 <ToValue> 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 <ToValue> DataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperDataSource<>(this, function);
- }
-
- /**
- * Applies the given function to each value emitted by the DataSource.
- * <p>
- * 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 <ToValue> 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 <ToValue> DataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-
- /**
- * Returns true if the data source guaranteed to produce a contiguous set of items,
- * never producing gaps.
- */
- boolean isContiguous() {
- return true;
- }
-
- boolean supportsPageDropping() {
- return true;
- }
-
- /**
- * Invalidation callback for DataSource.
- * <p>
- * 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.
- * <p>
- * 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<InvalidatedCallback> mOnInvalidatedCallbacks =
- new CopyOnWriteArrayList<>();
-
- /**
- * Add a callback to invoke when the DataSource is first invalidated.
- * <p>
- * Once invalidated, a data source will not become valid again.
- * <p>
- * 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 invalidates the
- * DataSource.
- */
- @AnyThread
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- //noinspection ConstantConditions
- if (onInvalidatedCallback == null) {
- throw new IllegalArgumentException("onInvalidatedCallback must be non-null");
- }
- mOnInvalidatedCallbacks.add(onInvalidatedCallback);
- }
-
- /**
- * Remove a previously added invalidate callback.
- *
- * @param onInvalidatedCallback The previously added callback.
- */
- @AnyThread
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
- }
-
- /**
- * Signal the data source to stop loading, and notify its callback.
- * <p>
- * 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();
- }
-
- enum LoadType {
- INITIAL,
- START,
- END
- }
-
- @SuppressWarnings("WeakerAccess")
- static class Params<K> {
- @NonNull
- public final LoadType type;
- /* can be NULL for init, otherwise non-null */
- @Nullable
- public final K key;
- public final int initialLoadSize;
- public final boolean placeholdersEnabled;
- public final int pageSize;
-
- Params(@NonNull LoadType type, @Nullable K key, int initialLoadSize,
- boolean placeholdersEnabled, int pageSize) {
- this.type = type;
- this.key = key;
- this.initialLoadSize = initialLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
- this.pageSize = pageSize;
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- static class BaseResult<Value> {
- @SuppressWarnings("unchecked")
- static <T> BaseResult<T> empty() {
- return (BaseResult<T>) EMPTY;
- }
-
- private static final BaseResult<Object> EMPTY =
- new BaseResult<>(Collections.emptyList(), null, null, 0, 0, 0, true);
-
- public final List<Value> data;
- public final Object prevKey;
- public final Object nextKey;
- public final int leadingNulls;
- public final int trailingNulls;
- public final int offset;
- /**
- * Set to true if the result is an initial load that is passed totalCount
- */
- public final boolean counted;
-
- protected BaseResult(List<Value> data, Object prevKey, Object nextKey, int leadingNulls,
- int trailingNulls, int offset, boolean counted) {
- this.data = data;
- this.prevKey = prevKey;
- this.nextKey = nextKey;
- this.leadingNulls = leadingNulls;
- this.trailingNulls = trailingNulls;
- this.offset = offset;
- this.counted = counted;
- validate();
- }
-
- <ToValue> BaseResult(@NonNull BaseResult<ToValue> result,
- @NonNull Function<List<ToValue>, List<Value>> function) {
- data = convert(function, result.data);
- prevKey = result.prevKey;
- nextKey = result.nextKey;
- leadingNulls = result.leadingNulls;
- trailingNulls = result.trailingNulls;
- offset = result.offset;
- counted = result.counted;
- validate();
- }
-
- private int position() {
- // only one of leadingNulls / offset may be used
- return leadingNulls + offset;
- }
-
- static final int TOTAL_COUNT_UNKNOWN = -1;
-
- int totalCount() {
- // only one of leadingNulls / offset may be used
- if (counted) {
- return position() + data.size() + trailingNulls;
- } else {
- return TOTAL_COUNT_UNKNOWN;
- }
-
- }
-
- void validate() {
- if (leadingNulls < 0 || offset < 0) {
- throw new IllegalArgumentException("Position must be non-negative");
- }
- if (data.isEmpty() && (leadingNulls != 0 || trailingNulls != 0)) {
- throw new IllegalArgumentException("Initial result cannot be empty if items are"
- + " present in data set.");
- }
- if (trailingNulls < 0) {
- throw new IllegalArgumentException(
- "List size + position too large, last item in list beyond totalCount.");
- }
- }
-
- void validateForInitialTiling(int pageSize) {
- if (!counted) {
- throw new IllegalStateException("Placeholders requested, but totalCount not"
- + " provided. Please call the three-parameter onResult method, or"
- + " disable placeholders in the PagedList.Config");
- }
- if (trailingNulls != 0
- && data.size() % pageSize != 0) {
- int totalCount = leadingNulls + data.size() + trailingNulls;
- throw new IllegalArgumentException("PositionalDataSource requires initial load size"
- + " to be a multiple of page size to support internal tiling. loadSize "
- + data.size() + ", position " + leadingNulls + ", totalCount " + totalCount
- + ", pageSize " + pageSize);
- }
- if (position() % pageSize != 0) {
- throw new IllegalArgumentException("Initial load must be pageSize aligned."
- + "Position = " + position() + ", pageSize = " + pageSize);
- }
- }
-
- @SuppressWarnings("EqualsHashCode")
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof BaseResult)) {
- return false;
- }
- BaseResult other = (BaseResult) o;
- return data.equals(other.data)
- && PagedList.equalsHelper(prevKey, other.prevKey)
- && PagedList.equalsHelper(nextKey, other.nextKey)
- && leadingNulls == other.leadingNulls
- && trailingNulls == other.trailingNulls
- && offset == other.offset
- && counted == other.counted;
- }
- }
-
- enum KeyType {
- POSITIONAL,
- PAGE_KEYED,
- ITEM_KEYED,
- }
-
- @NonNull
- final KeyType mType;
-
- // Since we currently rely on implementation details of two implementations,
- // prevent external subclassing, except through exposed subclasses
- DataSource(@NonNull KeyType type) {
- mType = type;
- }
-
- abstract ListenableFuture<? extends BaseResult<Value>> load(
- @NonNull Params<Key> params);
-
- @Nullable
- abstract Key getKey(@NonNull Value item);
-
- @Nullable
- final Key getKey(int lastLoad, @Nullable Value item) {
- if (mType == KeyType.POSITIONAL) {
- //noinspection unchecked
- return (Key) ((Integer) lastLoad);
- }
- if (item == null) {
- return null;
- }
- return getKey(item);
- }
-
- /**
- * Determine whether an error passed to a loading method is retryable.
- *
- * @param error Throwable returned from an attempted load from this DataSource.
- * @return true if the error is retryable, otherwise false.
- */
- public boolean isRetryableError(@NonNull Throwable error) {
- return false;
- }
-
- final void initExecutor(@NonNull Executor executor) {
- mExecutor = executor;
- }
-
- /**
- * Null until loadInitial is called by PagedList construction
- */
- @Nullable
- private Executor mExecutor;
-
- @NonNull
- Executor getExecutor() {
- if (mExecutor == null) {
- throw new IllegalStateException(
- "This DataSource has not been passed to a PagedList, has no executor yet.");
- }
- return mExecutor;
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/InitialPagedList.java b/paging/common/src/main/java/androidx/paging/InitialPagedList.java
deleted file mode 100644
index 2589bdf..0000000
--- a/paging/common/src/main/java/androidx/paging/InitialPagedList.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2019 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.paging.futures.DirectExecutor;
-
-/**
- * InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
- *
- * It's used solely for listening to {@link PagedList.LoadType#REFRESH} loading events, and retrying
- * any errors that occur during initial load.
- */
-class InitialPagedList<K, V> extends ContiguousPagedList<K, V> {
- @Nullable
- private K mInitialKey;
-
- InitialPagedList(
- @NonNull DataSource<K, V> dataSource,
- @NonNull Config config,
- @Nullable K initialKey) {
- super(dataSource,
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
- null,
- config,
- DataSource.BaseResult.<V>empty(),
- /* no previous load, so pass 0 */ 0);
- mInitialKey = initialKey;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mInitialKey;
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
deleted file mode 100644
index bba7477d..0000000
--- a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
+++ /dev/null
@@ -1,348 +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 androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for paging keyed content, where loaded content uses previously loaded
- * items as input to future loads.
- * <p>
- * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
- * to load item {@code N}. This is common, for example, in uniquely sorted database queries where
- * attributes of the item such just before the next query define how to execute it.
- * <p>
- * The {@code InMemoryByItemRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network ItemKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @see ListenableItemKeyedDataSource
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ItemKeyedDataSource<Key, Value> extends
- ListenableItemKeyedDataSource<Key, Value> {
-
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- @SuppressWarnings("WeakerAccess")
- public static class LoadInitialParams<Key> extends
- ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
- boolean placeholdersEnabled) {
- super(requestedInitialKey, requestedLoadSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)}
- * and {@link #loadAfter(LoadParams, LoadCallback)}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- @SuppressWarnings("WeakerAccess")
- public static class LoadParams<Key> extends ListenableItemKeyedDataSource.LoadParams<Key> {
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- super(key, requestedLoadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data and, optionally, position/count information.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * If you can compute the number of items in the data set before and after the loaded range,
- * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
- * can skip passing this information by calling the single parameter {@link #onResult(List)},
- * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
- * {@code false}, so the positioning information will be ignored.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<Value> extends LoadCallback<Value> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be loaded from this DataSource,
- * pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public abstract void onResult(@NonNull List<Value> data, int position, int totalCount);
- }
-
- /**
- * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
- * and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadCallback<Value> {
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this method from your ItemKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
- * <p>
- * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
- * initialize without counting available data, or supporting placeholders.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the ItemKeyedDataSource.
- */
- public abstract void onResult(@NonNull List<Value> data);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
- * {@link #loadBefore(LoadParams, LoadCallback)}, or
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<Value>> loadInitial(
- final @NonNull ListenableItemKeyedDataSource.LoadInitialParams<Key> params) {
- final ResolvableFuture<InitialResult<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadInitialCallback<Value> callback = new LoadInitialCallback<Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount) {
- future.set(new InitialResult<>(data, position, totalCount));
- }
-
- @Override
- public void onResult(@NonNull List<Value> data) {
- future.set(new InitialResult<>(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(new LoadInitialParams<>(
- params.requestedInitialKey,
- params.requestedLoadSize,
- params.placeholdersEnabled),
- callback);
- }
- });
- return future;
- }
-
- @SuppressWarnings("WeakerAccess")
- LoadCallback<Value> getFutureAsCallback(
- final @NonNull ResolvableFuture<Result<Value>> future) {
- return new LoadCallback<Value>() {
- @Override
- public void onResult(@NonNull List<Value> data) {
- future.set(new Result<>(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Value>> loadBefore(
- final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadBefore(new LoadParams<>(params.key, params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Value>> loadAfter(
- final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadAfter(new LoadParams<>(params.key, params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
- * the callback via the three-parameter
- * {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
- * presenting data from this source to display placeholders to represent unloaded items.
- * <p>
- * {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
- * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
- * {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
- * initializing at the same location. If your DataSource never invalidates (for example,
- * loading from the network without the network ever signalling that old data must be reloaded),
- * it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
- * data set.
- *
- * @param params Parameters for initial load, including initial key and requested size.
- * @param callback Callback that receives initial load data.
- */
- public abstract void loadInitial(
- @NonNull LoadInitialParams<Key> params,
- @NonNull LoadInitialCallback<Value> callback);
-
- /**
- * Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * Data may be passed synchronously during the loadAfter method, or deferred and called at a
- * later time. Further loads going down will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load after, and requested size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadAfter(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Value> callback);
-
- /**
- * Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
- * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
- * <p>
- * Data may be passed synchronously during the loadBefore method, or deferred and called at a
- * later time. Further loads going up will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load before, and requested size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadBefore(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Value> callback);
-
- /**
- * Return a key associated with the given item.
- * <p>
- * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
- * integer ID, you would return {@code item.getID()} here. This key can then be passed to
- * {@link #loadBefore(LoadParams, LoadCallback)} or
- * {@link #loadAfter(LoadParams, LoadCallback)} to load additional items adjacent to the item
- * passed to this function.
- * <p>
- * If your key is more complex, such as when you're sorting by name, then resolving collisions
- * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
- * such as {@code Pair<String, Integer>} or, in Kotlin,
- * {@code data class Key(val name: String, val id: Int)}
- *
- * @param item Item to get the key from.
- * @return Key associated with given item.
- */
- @NonNull
- @Override
- public abstract Key getKey(@NonNull Value item);
-
- @NonNull
- @Override
- public final <ToValue> ItemKeyedDataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperItemKeyedDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <ToValue> ItemKeyedDataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListDataSource.java b/paging/common/src/main/java/androidx/paging/ListDataSource.java
deleted file mode 100644
index 3f0971f..0000000
--- a/paging/common/src/main/java/androidx/paging/ListDataSource.java
+++ /dev/null
@@ -1,50 +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<T> extends PositionalDataSource<T> {
- private final List<T> mList;
-
- public ListDataSource(List<T> list) {
- mList = new ArrayList<>(list);
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<T> 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<T> sublist = mList.subList(position, position + loadSize);
- callback.onResult(sublist, position, totalCount);
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback<T> callback) {
- callback.onResult(mList.subList(params.startPosition,
- Math.min(mList.size(), params.startPosition + params.loadSize)));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
deleted file mode 100644
index e81193d..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
+++ /dev/null
@@ -1,229 +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 com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for paging keyed content, where loaded content uses previously loaded
- * items as input to future loads.
- * <p>
- * Implement a DataSource using ListenableItemKeyedDataSource if you need to use data from item
- * {@code N - 1} to load item {@code N}. This is common, for example, in uniquely sorted database
- * queries where attributes of the item such just before the next query define how to execute it.
- *
- * @see ItemKeyedDataSource
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ListenableItemKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
- public ListenableItemKeyedDataSource() {
- super(KeyType.ITEM_KEYED);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<Value>> load(@NonNull Params<Key> params) {
- if (params.type == LoadType.INITIAL) {
- ItemKeyedDataSource.LoadInitialParams<Key> initParams =
- new ItemKeyedDataSource.LoadInitialParams<>(params.key,
- params.initialLoadSize, params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- //noinspection ConstantConditions (key is known to be non-null for non-initial queries)
- ItemKeyedDataSource.LoadParams<Key> loadParams =
- new ItemKeyedDataSource.LoadParams<>(params.key, params.pageSize);
-
- if (params.type == LoadType.START) {
- return loadBefore(loadParams);
- } else if (params.type == LoadType.END) {
- return loadAfter(loadParams);
- }
- }
- throw new IllegalArgumentException("Unsupported type " + params.type.toString());
- }
-
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- public static class LoadInitialParams<Key> {
- /**
- * Load items around this key, or at the beginning of the data set if {@code null} is
- * passed.
- * <p>
- * Note that this key is generally a hint, and may be ignored if you want to always load
- * from the beginning.
- */
- @Nullable
- public final Key requestedInitialKey;
-
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the loaded total count 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 {@code loadBefore()} and {@code loadAfter()}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
- }
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass {@code totalCount}
- * to the {@link InitialResult} constructor. This enables PagedLists presenting data from this
- * source to display placeholders to represent unloaded items.
- * <p>
- * {@link ItemKeyedDataSource.LoadInitialParams#requestedInitialKey} and
- * {@link ItemKeyedDataSource.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
- * DataSource 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.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<Value>> loadInitial(
- @NonNull LoadInitialParams<Key> params);
-
- /**
- * Load list data after the key specified in
- * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load after, and requested size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Value>> loadAfter(@NonNull LoadParams<Key> params);
-
-
- /**
- * Load list data after the key specified in
- * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
- * passed, so if you don't return a page of the requested size, ensure that the last item is
- * adjacent to the passed 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 preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load before, and requested size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Value>> loadBefore(@NonNull LoadParams<Key> params);
-
-
- @Nullable
- @Override
- public abstract Key getKey(@NonNull Value item);
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class InitialResult<V> extends BaseResult<V> {
- public InitialResult(@NonNull List<V> data, int position, int totalCount) {
- super(data, null, null, position, totalCount - data.size() - position, position, true);
- }
-
- public InitialResult(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-
- /**
- * Type produced by {@link #loadBefore(LoadParams)} and
- * {@link #loadAfter(LoadParams)} to represent a page of loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class Result<V> extends BaseResult<V> {
- public Result(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
deleted file mode 100644
index f15075c..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright 2019 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.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for page-keyed content, where requests return keys for next/previous
- * pages.
- * <p>
- * 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.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ListenablePageKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
- public ListenablePageKeyedDataSource() {
- super(KeyType.PAGE_KEYED);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<Value>> load(
- @NonNull Params<Key> params) {
- if (params.type == LoadType.INITIAL) {
- PageKeyedDataSource.LoadInitialParams<Key> initParams =
- new PageKeyedDataSource.LoadInitialParams<>(
- params.initialLoadSize, params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- if (params.key == null) {
- // null key, immediately return empty data
- ResolvableFuture<BaseResult<Value>> future = ResolvableFuture.create();
- future.set(BaseResult.<Value>empty());
- return future;
- }
-
- PageKeyedDataSource.LoadParams<Key> loadParams =
- new PageKeyedDataSource.LoadParams<>(params.key, params.pageSize);
-
- if (params.type == LoadType.START) {
- return loadBefore(loadParams);
- } else if (params.type == LoadType.END) {
- return loadAfter(loadParams);
- }
- }
- throw new IllegalArgumentException("Unsupported type " + params.type.toString());
- }
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- *
- * @param <Key> Type of data used to query pages.
- */
- @SuppressWarnings("unused")
- public static class LoadInitialParams<Key> {
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the loaded total count will be
- * ignored.
- */
- public final boolean placeholdersEnabled;
-
-
- public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
- this.requestedLoadSize = requestedLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
- }
- }
-
- /**
- * Holder object for inputs to {@code loadBefore()} and {@code loadAfter()}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
-
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
- }
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the position and
- * count to the
- * {@link InitialResult InitialResult constructor}. This
- * enables PagedLists presenting data from this source to display placeholders to represent
- * unloaded items.
- * <p>
- * {@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.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<Key, Value>> loadInitial(
- @NonNull LoadInitialParams<Key> params);
-
- /**
- * Prepend page with the key specified by
- * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Key, Value>> loadBefore(
- @NonNull LoadParams<Key> params);
- /**
- * Append page with the key specified by
- * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Key, Value>> loadAfter(
- @NonNull LoadParams<Key> params);
-
- @Nullable
- @Override
- Key getKey(@NonNull Value item) {
- 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;
- }
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <Key> Type of key used to identify pages.
- * @param <Value> Type of items being loaded by the DataSource.
- */
- public static class InitialResult<Key, Value> extends BaseResult<Value> {
- public InitialResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
- super(data, previousPageKey, nextPageKey,
- position, totalCount - data.size() - position, position, true);
- }
-
- public InitialResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey) {
- super(data, previousPageKey, nextPageKey, 0, 0, 0, false);
- }
- }
-
- /**
- * Type produced by {@link #loadBefore(LoadParams)} and {@link #loadAfter(LoadParams)} to
- * represent a page of loaded data.
- *
- * @param <Key> Type of key used to identify pages.
- * @param <Value> Type of items being loaded by the DataSource.
- */
- public static class Result<Key, Value> extends BaseResult<Value> {
- public Result(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
- super(data, adjacentPageKey, adjacentPageKey, 0, 0, 0, false);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
deleted file mode 100644
index a28e156..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
+++ /dev/null
@@ -1,336 +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 com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
- * arbitrary page positions.
- * <p>
- * Extend ListenablePositionalDataSource 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), either
- * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
- * the two parameter {@link InitialResult InitialResult constructor}.
- *
- * @see PositionalDataSource
- *
- * @param <T> Type of items being loaded by the PositionalDataSource.
- */
-public abstract class ListenablePositionalDataSource<T> extends DataSource<Integer, T> {
- public ListenablePositionalDataSource() {
- super(KeyType.POSITIONAL);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<T>> load(@NonNull Params<Integer> params) {
- if (params.type == LoadType.INITIAL) {
- int initialPosition = 0;
- int initialLoadSize = params.initialLoadSize;
- if (params.key != null) {
- initialPosition = params.key;
-
- if (params.placeholdersEnabled) {
- // snap load size to page multiple (minimum two)
- initialLoadSize = Math.max(initialLoadSize / params.pageSize, 2)
- * params.pageSize;
-
- // move start so the load is centered around the key, not starting at it
- final int idealStart = initialPosition - initialLoadSize / 2;
- initialPosition = Math.max(0, idealStart / params.pageSize * params.pageSize);
- } else {
- // not tiled, so don't try to snap or force multiple of a page size
- initialPosition = initialPosition - initialLoadSize / 2;
- }
-
- }
- PositionalDataSource.LoadInitialParams initParams =
- new PositionalDataSource.LoadInitialParams(
- initialPosition,
- initialLoadSize,
- params.pageSize,
- params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- int startIndex = params.key;
- int loadSize = params.pageSize;
- if (params.type == LoadType.START) {
- loadSize = Math.min(loadSize, startIndex + 1);
- startIndex = startIndex - loadSize + 1;
- }
- return loadRange(new PositionalDataSource.LoadRangeParams(startIndex, loadSize));
- }
- }
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- */
- public static class LoadInitialParams {
- /**
- * Initial load position requested.
- * <p>
- * 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.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines page size acceptable for return values.
- * <p>
- * 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 loaded total count 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 {@code loadRange()}.
- */
- public static class LoadRangeParams {
- /**
- * START position of data to load.
- * <p>
- * Returned data must start at this position.
- */
- public final int startPosition;
- /**
- * Number of items to load.
- * <p>
- * 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;
- }
- }
-
- /**
- * Load initial list data.
- * <p>
- * This method is called to load the initial page(s) from the DataSource.
- * <p>
- * 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.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<T>> loadInitial(
- @NonNull LoadInitialParams params);
-
- /**
- * Called to load a range of data from the DataSource.
- * <p>
- * This method is called to load additional pages from the DataSource after the
- * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
- * <p>
- * Unlike {@link #loadInitial(LoadInitialParams)}, this method must return
- * the number of items requested, at the position requested.
- *
- * @param params Parameters for load, including start position and load size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<RangeResult<T>> loadRange(@NonNull LoadRangeParams params);
-
- @Nullable
- @Override
- final Integer getKey(@NonNull T item) {
- return null;
- }
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class InitialResult<V> extends BaseResult<V> {
- public InitialResult(@NonNull List<V> data, int position, int totalCount) {
- super(data, null, null, position, totalCount - data.size() - position, 0, true);
- if (data.isEmpty() && position != 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- }
-
- public InitialResult(@NonNull List<V> data, int position) {
- super(data, null, null, 0, 0, position, false);
- if (data.isEmpty() && position != 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- }
- }
-
- /**
- * Type produced by {@link #loadRange(LoadRangeParams)} to represent a page
- * of loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class RangeResult<V> extends BaseResult<V> {
- public RangeResult(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-
- /**
- * Helper for computing an initial position in
- * {@link #loadInitial(LoadInitialParams)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * The value computed by this function will do bounds checking, page alignment, and positioning
- * based on initial load size requested.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * 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));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams)},
- * including page size, and requested start/loadSize.
- * @param totalCount Total size of the data set.
- * @return Position to start loading at.
- *
- *
- * @see #computeInitialLoadSize(ListenablePositionalDataSource.LoadInitialParams, int, int)
- */
- public static int computeInitialLoadPosition(
- @NonNull ListenablePositionalDataSource.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)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * This function takes the requested load size, and bounds checks it against the value returned
- * by
- * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * 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));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams)},
- * including page size, and requested start/loadSize.
- * @param initialLoadPosition Value returned by
- * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}
- * @param totalCount Total size of the data set.
- * @return Number of items to load.
- *
- * @see #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)
- */
- public static int computeInitialLoadSize(@NonNull
- ListenablePositionalDataSource.LoadInitialParams params,
- int initialLoadPosition, int totalCount) {
- return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
deleted file mode 100644
index 65f9c0d..0000000
--- a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
+++ /dev/null
@@ -1,360 +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 androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for page-keyed content, where requests return keys for next/previous
- * pages.
- * <p>
- * 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.
- * <p>
- * The {@code InMemoryByPageRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network PageKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class PageKeyedDataSource<Key, Value>
- extends ListenablePageKeyedDataSource<Key, Value> {
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadInitialParams<Key> extends
- ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
- super(requestedLoadSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadParams<Key> extends ListenablePageKeyedDataSource.LoadParams<Key> {
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- super(key, requestedLoadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data and, optionally, position/count information.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * If you can compute the number of items in the data set before and after the loaded range,
- * call the five parameter {@link #onResult(List, int, int, Key, Key)} to pass that
- * information. You can skip passing this information by calling the three parameter
- * {@link #onResult(List, Key, Key)}, either if it's difficult to compute, or if
- * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
- * information will be ignored.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Key> Type of data used to query pages.
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<Key, Value> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be loaded from this DataSource,
- * pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public abstract void onResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey);
-
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
- * initialize without counting available data, or supporting placeholders.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the 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<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- /**
- * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} to return data.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Key> Type of data used to query pages.
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadCallback<Key, Value> {
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this method from your PageKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- * <p>
- * 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<Value> data, @Nullable Key adjacentPageKey);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from your PageKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<Key, Value>> loadInitial(
- final @NonNull ListenablePageKeyedDataSource.LoadInitialParams<Key> params) {
- final ResolvableFuture<InitialResult<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadInitialCallback<Key, Value> callback = new LoadInitialCallback<Key, Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
- future.set(new InitialResult<>(data, position, totalCount, previousPageKey,
- nextPageKey));
- }
-
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey) {
- future.set(new InitialResult<>(data, previousPageKey, nextPageKey));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(new LoadInitialParams<Key>(
- params.requestedLoadSize,
- params.placeholdersEnabled),
- callback);
- }
- });
- return future;
- }
-
- @SuppressWarnings("WeakerAccess")
- LoadCallback<Key, Value> getFutureAsCallback(
- final @NonNull ResolvableFuture<Result<Key, Value>> future) {
- return new LoadCallback<Key, Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
- future.set(new Result<>(data, adjacentPageKey));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Key, Value>> loadBefore(
- final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadBefore(new LoadParams<>(
- params.key,
- params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Key, Value>> loadAfter(
- final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadAfter(new LoadParams<>(
- params.key,
- params.requestedLoadSize), getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
- * the callback via the three-parameter
- * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)}. This enables PagedLists
- * presenting data from this source to display placeholders to represent unloaded items.
- * <p>
- * {@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<Key> params,
- @NonNull LoadInitialCallback<Key, Value> callback);
-
- /**
- * Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * 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.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadBefore(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Key, Value> callback);
-
- /**
- * Append page with the key specified by {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * 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.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadAfter(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Key, Value> callback);
-
- @NonNull
- @Override
- public final <ToValue> PageKeyedDataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperPageKeyedDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <ToValue> PageKeyedDataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PagedList.java b/paging/common/src/main/java/androidx/paging/PagedList.java
deleted file mode 100644
index 2339f2b..0000000
--- a/paging/common/src/main/java/androidx/paging/PagedList.java
+++ /dev/null
@@ -1,1443 +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 androidx.arch.core.util.Function;
-import androidx.paging.futures.DirectExecutor;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-
-/**
- * Lazy loading list that pages in immutable content from a {@link DataSource}.
- * <p>
- * 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 androidx.paging.PagedListAdapter}, which enables the
- * binding of a PagedList to a {@link androidx.recyclerview.widget.RecyclerView}.
- * <h4>Loading Data</h4>
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * If you use {@link androidx.paging.LivePagedListBuilder} to get a
- * {@link androidx.lifecycle.LiveData}, it will initialize PagedLists on a
- * background thread for you.
- * <h4>Placeholders</h4>
- * <p>
- * There are two ways that PagedList can represent its not-yet-loaded data - with or without
- * {@code null} placeholders.
- * <p>
- * 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.
- * <p>
- * 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 <em>loaded</em> item. This is not necessarily the {@code N}th item in the
- * data set.
- * <p>
- * Placeholders have several benefits:
- * <ul>
- * <li>They express the full sized list to the presentation layer (often a
- * {@link androidx.paging.PagedListAdapter}), and so can support scrollbars (without jumping as pages are
- * loaded or dropped) and fast-scrolling to any position, loaded or not.
- * <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
- * is always full sized.
- * </ul>
- * <p>
- * They also have drawbacks:
- * <ul>
- * <li>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}.
- * <li>They don't work well if your item views are of different sizes, as this will prevent
- * loading items from cross-fading nicely.
- * <li>They require you to count your data set, which can be expensive or impossible, depending
- * on your DataSource.
- * </ul>
- * <p>
- * 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}.
- * <h4>Mutability and Snapshots</h4>
- * A PagedList is <em>mutable</em> 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 androidx.paging.PagedListAdapter} will listen
- * to these to signal RecyclerView about the updates/changes).
- * <p>
- * 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 androidx.paging.LivePagedListBuilder} for how new PagedLists are
- * created to represent changed data.
- * <p>
- * 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 <T> The type of the entries in the list.
- */
-public abstract class PagedList<T> extends AbstractList<T> {
-
- /**
- * Type of load a PagedList can perform.
- * <p>
- * You can use a {@link LoadStateListener} to observe {@link LoadState} of
- * any {@link LoadType}. For UI purposes (swipe refresh, loading spinner, retry button), this
- * is typically done by registering a Listener with the {@code PagedListAdapter} or
- * {@code AsyncPagedListDiffer}.
- *
- * @see LoadState
- */
- public enum LoadType {
- /**
- * PagedList content being reloaded, may contain content updates.
- */
- REFRESH,
-
- /**
- * Load at the start of the PagedList.
- */
- START,
-
- /**
- * Load at the end of the PagedList.
- */
- END
- }
-
- /**
- * State of a PagedList load - associated with a {@code LoadType}
- * <p>
- * You can use a {@link LoadStateListener} to observe {@link LoadState} of
- * any {@link LoadType}. For UI purposes (swipe refresh, loading spinner, retry button), this
- * is typically done by registering a Listener with the {@code PagedListAdapter} or
- * {@code AsyncPagedListDiffer}.
- */
- public enum LoadState {
- /**
- * Indicates the PagedList is not currently loading, and no error currently observed.
- */
- IDLE,
-
- /**
- * Loading is in progress.
- */
- LOADING,
-
- /**
- * Loading is complete.
- */
- DONE,
-
- /**
- * Loading hit a non-retryable error.
- */
- ERROR,
-
- /**
- * Loading hit a retryable error.
- *
- * @see #retry()
- */
- RETRYABLE_ERROR,
- }
-
- /**
- * Listener for changes to loading state - whether the refresh, prepend, or append is idle,
- * loading, or has an error.
- * <p>
- * Can be used to observe the {@link LoadState} of any {@link LoadType} (REFRESH/START/END).
- * For UI purposes (swipe refresh, loading spinner, retry button), this is typically done by
- * registering a Listener with the {@code PagedListAdapter} or {@code AsyncPagedListDiffer}.
- * <p>
- * These calls will be dispatched on the executor defined by
- * {@link Builder#setNotifyExecutor(Executor)}, which is generally the main/UI thread.
- *
- * @see LoadType
- * @see LoadState
- */
- public interface LoadStateListener {
- /**
- * Called when the LoadState has changed - whether the refresh, prepend, or append is
- * idle, loading, or has an error.
- * <p>
- * REFRESH events can be used to drive a {@code SwipeRefreshLayout}, or START/END events
- * can be used to drive loading spinner items in your {@code RecyclerView}.
- *
- * @param type Type of load - START, END, or REFRESH.
- * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
- * @param error Error, if in an error state, null otherwise.
- *
- * @see #retry()
- */
- void onLoadStateChanged(@NonNull LoadType type,
- @NonNull LoadState state, @Nullable Throwable error);
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static boolean equalsHelper(@Nullable Object a, @Nullable Object b) {
- // (Because Objects.equals() is API 19+)
- //noinspection EqualsReplaceableByObjectsCall
- return a == b || (a != null && a.equals(b));
- }
-
- abstract static class LoadStateManager {
- @NonNull
- private LoadState mRefresh = LoadState.IDLE;
- @Nullable
- private Throwable mRefreshError = null;
- @NonNull
- private LoadState mStart = LoadState.IDLE;
- @Nullable
- private Throwable mStartError = null;
- @NonNull
- private LoadState mEnd = LoadState.IDLE;
- @Nullable
- private Throwable mEndError = null;
-
- @NonNull
- public LoadState getRefresh() {
- return mRefresh;
- }
-
- @NonNull
- public LoadState getStart() {
- return mStart;
- }
-
- @NonNull
- public LoadState getEnd() {
- return mEnd;
- }
-
- void setState(@NonNull LoadType type, @NonNull LoadState state, @Nullable Throwable error) {
- boolean expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR;
- boolean hasError = error != null;
- if (expectError != hasError) {
- throw new IllegalArgumentException(
- "Error states must be accompanied by a throwable, other states must not");
- }
-
- // deduplicate signals
- switch (type) {
- case REFRESH:
- if (mRefresh.equals(state) && equalsHelper(mRefreshError, error)) return;
- mRefresh = state;
- mRefreshError = error;
- break;
- case START:
- if (mStart.equals(state) && equalsHelper(mStartError, error)) return;
- mStart = state;
- mStartError = error;
- break;
- case END:
- if (mEnd.equals(state) && equalsHelper(mEndError, error)) return;
- mEnd = state;
- mEndError = error;
- break;
- }
- onStateChanged(type, state, error);
- }
-
- protected abstract void onStateChanged(@NonNull LoadType type,
- @NonNull LoadState state, @Nullable Throwable error);
-
- void dispatchCurrentLoadState(LoadStateListener listener) {
- listener.onLoadStateChanged(PagedList.LoadType.REFRESH, mRefresh, mRefreshError);
- listener.onLoadStateChanged(PagedList.LoadType.START, mStart, mStartError);
- listener.onLoadStateChanged(PagedList.LoadType.END, mEnd, mEndError);
- }
- }
-
- void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {}
-
- /**
- * Retry any retryable errors associated with this PagedList.
- * <p>
- * If for example a network DataSource append timed out, calling this method will retry the
- * failed append load. Note that your DataSource will need to pass {@code true} to
- * {@code onError()} to signify the error as retryable.
- * <p>
- * You can observe loading state via {@link #addWeakLoadStateListener(LoadStateListener)},
- * though generally this is done through the {@link androidx.paging.PagedListAdapter} or
- * {@link androidx.paging.AsyncPagedListDiffer}.
- *
- * @see #addWeakLoadStateListener(LoadStateListener)
- * @see #removeWeakLoadStateListener(LoadStateListener)
- */
- public void retry() {}
-
- @NonNull
- final Executor mMainThreadExecutor;
- @NonNull
- final Executor mBackgroundThreadExecutor;
- @Nullable
- final BoundaryCallback<T> mBoundaryCallback;
- @NonNull
- final Config mConfig;
- @NonNull
- final PagedStorage<T> mStorage;
- @Nullable
- Runnable mRefreshRetryCallback = null;
-
- void setRetryCallback(@Nullable Runnable refreshRetryCallback) {
- mRefreshRetryCallback = refreshRetryCallback;
- }
-
- /**
- * Last access location, in total position space (including offset).
- * <p>
- * 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 ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ArrayList<WeakReference<LoadStateListener>> mListeners = new ArrayList<>();
-
- void dispatchStateChange(@NonNull LoadType type, @NonNull LoadState state,
- @Nullable Throwable error) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null) {
- mListeners.remove(i);
- } else {
- currentListener.onLoadStateChanged(type, state, error);
- }
- }
- }
-
- PagedList(@NonNull PagedStorage<T> storage,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @Nullable BoundaryCallback<T> 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 tat 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 <K> Key type that indicates to the DataSource what data to load.
- * @param <T> Type of items to be held and loaded by the PagedList.
- *
- * @return ListenableFuture for newly created PagedList, which will page in data from the
- * DataSource as needed.
- */
- @NonNull
- static <K, T> ListenableFuture<PagedList<T>> create(
- @NonNull final DataSource<K, T> dataSource,
- @NonNull final Executor notifyExecutor,
- @NonNull final Executor fetchExecutor,
- @NonNull final Executor initialLoadExecutor,
- @Nullable final BoundaryCallback<T> boundaryCallback,
- @NonNull final Config config,
- @Nullable K key) {
- dataSource.initExecutor(initialLoadExecutor);
-
- final int lastLoad = (dataSource.mType == DataSource.KeyType.POSITIONAL && key != null)
- ? (Integer) key : ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
-
- return Futures.transform(
- dataSource.load(
- new DataSource.Params<>(
- DataSource.LoadType.INITIAL,
- key,
- config.initialLoadSizeHint,
- config.enablePlaceholders,
- config.pageSize)),
- new Function<DataSource.BaseResult<T>, PagedList<T>>() {
- @Override
- public PagedList<T> apply(DataSource.BaseResult<T> initialResult) {
- dataSource.initExecutor(fetchExecutor);
- return new ContiguousPagedList<>(dataSource,
- notifyExecutor,
- fetchExecutor,
- boundaryCallback,
- config,
- initialResult,
- lastLoad);
- }
- },
- DirectExecutor.INSTANCE);
- }
-
- /**
- * Builder class for PagedList.
- * <p>
- * DataSource, Config, main thread and background executor must all be provided.
- * <p>
- * 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.
- * <p>
- * {@link androidx.paging.LivePagedListBuilder} does this creation on a background thread automatically, if you
- * want to receive a {@code LiveData<PagedList<...>>}.
- *
- * @param <Key> Type of key used to load data from the DataSource.
- * @param <Value> Type of items held and loaded by the PagedList.
- */
- @SuppressWarnings("WeakerAccess")
- public static final class Builder<Key, Value> {
- private final DataSource<Key, Value> mDataSource;
- private final Config mConfig;
- private Executor mNotifyExecutor;
- private Executor mFetchExecutor;
- private BoundaryCallback<Value> 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<Key, Value> 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.
- * <p>
- * This method is a convenience for:
- * <pre>
- * PagedList.Builder(dataSource,
- * new PagedList.Config.Builder().setPageSize(pageSize).build());
- * </pre>
- *
- * @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<Key, Value> 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<Key, Value> 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<Key, Value> setFetchExecutor(@NonNull Executor fetchExecutor) {
- mFetchExecutor = fetchExecutor;
- return this;
- }
-
- /**
- * The BoundaryCallback for out of data events.
- * <p>
- * 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<Key, Value> setBoundaryCallback(
- @Nullable BoundaryCallback<Value> 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<Key, Value> setInitialKey(@Nullable Key initialKey) {
- mInitialKey = initialKey;
- return this;
- }
-
- /**
- * Creates a {@link PagedList} with the given parameters.
- * <p>
- * This call will dispatch the {@link androidx.paging.DataSource}'s loadInitial method immediately on the
- * current thread, and block the current on the result. This method should always be called
- * on a worker thread to prevent blocking the main thread.
- * <p>
- * 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<PagedList>} can be safely constructed with {@link androidx.paging.LivePagedListBuilder}
- * on the main thread, since actual construction work is deferred, and done on a background
- * thread.
- * <p>
- * 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).
- *
- * @deprecated This method has no means of handling errors encountered during initial load,
- * and blocks on the initial load result. Use {@link #buildAsync()} instead.
- *
- * @return The newly constructed PagedList
- */
- @Deprecated
- @WorkerThread
- @NonNull
- public PagedList<Value> 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");
- }
-
- try {
- return create(DirectExecutor.INSTANCE).get();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates a {@link PagedList} asynchronously with the given parameters.
- * <p>
- * This call will dispatch the {@link DataSource}'s loadInitial method immediately, and
- * return a {@code ListenableFuture<PagedList<T>>} that will resolve (triggering listeners)
- * once the initial load is completed (success or failure).
- *
- * @return The newly constructed PagedList
- */
- @SuppressWarnings("unused")
- @NonNull
- public ListenableFuture<PagedList<Value>> buildAsync() {
- // 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");
- }
-
- return create(mFetchExecutor);
- }
-
- private ListenableFuture<PagedList<Value>> create(@NonNull Executor initialFetchExecutor) {
- return PagedList.create(
- mDataSource,
- mNotifyExecutor,
- mFetchExecutor,
- initialFetchExecutor,
- 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_PREFIX)
- 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.
- * <p>
- * 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.
- * <p>
- * 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<T> 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<?, T> getDataSource();
-
- /**
- * Return the key for the position passed most recently to {@link #loadAround(int)}.
- * <p>
- * 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 androidx.paging.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.
- * <p>
- * A detached list is {@link #isImmutable() immutable}.
- *
- * @return True if the data source is detached.
- */
- public abstract boolean isDetached();
-
- /**
- * Detach the PagedList from its DataSource, and attempt to load no more data.
- * <p>
- * This is called automatically when a DataSource is observed to be invalid, which is a
- * signal to stop loading. The PagedList will continue to present existing data, but will not
- * initiate new loads.
- */
- @SuppressWarnings("unused")
- public abstract void detach();
-
- /**
- * Position offset of the data in the list.
- * <p>
- * If data is supplied by a {@link PositionalDataSource}, the item returned from
- * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
- * <p>
- * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it
- * doesn't use positions, returns 0.
- */
- public int getPositionOffset() {
- return mStorage.getPositionOffset();
- }
-
- /**
- * Add a LoadStateListener to observe the loading state of the PagedList.
- *
- * @param listener Listener to receive updates.
- *
- * @see #removeWeakLoadStateListener(LoadStateListener)
- */
- public void addWeakLoadStateListener(@NonNull LoadStateListener listener) {
- // first, clean up any empty weak refs
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null) {
- mListeners.remove(i);
- }
- }
-
- // then add the new one
- mListeners.add(new WeakReference<>(listener));
- dispatchCurrentLoadState(listener);
- }
-
- /**
- * Remove a previously registered LoadStateListener.
- *
- * @param listener Previously registered listener.
- * @see #addWeakLoadStateListener(LoadStateListener)
- */
- public void removeWeakLoadStateListener(@NonNull LoadStateListener listener) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null || currentListener == listener) {
- // found Listener, or empty weak ref
- mListeners.remove(i);
- }
- }
- }
-
- abstract void dispatchCurrentLoadState(LoadStateListener listener);
-
- /**
- * Adds a callback, and issues updates since the previousSnapshot was created.
- * <p>
- * 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
- * <code>onChanged(14, 2)</code>.
- * <p>
- * 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.
- * <p>
- * The callback is internally held as weak reference, so PagedList doesn't hold a strong
- * reference to its observer, such as a {@link androidx.paging.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<T> 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<T> storageSnapshot = (PagedList<T>) 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<T> snapshot,
- @NonNull Callback callback);
-
- abstract void loadAroundInternal(int index);
-
- /**
- * Callback signaling when content is loaded into the list.
- * <p>
- * 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
- * <code>0</code> or <code>size - 1</code>.
- * @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
- * <code>0</code> or <code>size - 1</code>.
- * @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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * {@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}.
- * <p>
- * 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.
- * <p>
- * Should be several times the number of visible items onscreen.
- * <p>
- * 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).
- * <p>
- * 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.
- * <p>
- * Should be several times the number of visible items onscreen.
- * <p>
- * If not set, defaults to page size.
- * <p>
- * 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.
- * <p>
- * If not set, defaults to true.
- * <p>
- * A PagedList will present null placeholders for not-yet-loaded content if two
- * conditions are met:
- * <p>
- * 1) Its DataSource can count all unloaded items (so that the number of nulls to
- * present is known).
- * <p>
- * 2) placeholders are not disabled on the Config.
- * <p>
- * Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
- * (often a {@link androidx.paging.PagedListAdapter}) doesn't need to account for null items.
- * <p>
- * 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 androidx.paging.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.
- * <p>
- * 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.
- * <p>
- * When using a {@link PositionalDataSource}, the initial load size will be coerced to
- * an integer multiple of pageSize, to enable efficient tiling.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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:
- * <ul>
- * <li>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.
- * <li>Pages are never dropped if they are within a prefetch window (defined to be
- * {@code pageSize + (2 * prefetchDistance)}) of the most recent load.
- * </ul>
- * <p>
- * {@link PageKeyedDataSource} does not currently support dropping pages - when
- * loading from a {@code PageKeyedDataSource}, this value is ignored.
- * <p>
- * 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.
- * <p>
- * 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<PagedList>}, but it's still necessary to know when
- * to trigger network loads.
- * <p>
- * 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<PagedList>} will update automatically to
- * account for the new items.
- * <p>
- * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
- * {@link androidx.paging.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.
- * <p>
- * The database + network Repository in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network BoundaryCallback using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- * <h4>Requesting Network Data</h4>
- * 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.
- * <p>
- * 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:
- * <h5>Local storage Page key</h5>
- * 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).
- * <p>
- * 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.
- * <p>
- * Instead, for these positional cases, you can query the database for the number of items, and
- * pass that to the network.
- * <h5>In-Memory Page key</h5>
- * 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.
- * <p>
- * 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,
- * <a href="https://codelabs.developers.google.com/codelabs/android-paging/index.html#8">in the
- * Paging Codelab</a>, the GitHub network page index is stored in memory.
- *
- * @param <T> Type loaded by the PagedList.
- */
- @MainThread
- public abstract static class BoundaryCallback<T> {
- /**
- * 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.
- * <p>
- * 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.
- * <p>
- * 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/paging/common/src/main/java/androidx/paging/PagedStorage.java b/paging/common/src/main/java/androidx/paging/PagedStorage.java
deleted file mode 100644
index 232302f..0000000
--- a/paging/common/src/main/java/androidx/paging/PagedStorage.java
+++ /dev/null
@@ -1,663 +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.
- * <p>
- * 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<T> extends AbstractList<T> implements Pager.AdjacentProvider<T> {
- /**
- * 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();
-
- 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<List<T>> 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<T> page, int trailingNulls) {
- this();
- init(leadingNulls, page, trailingNulls, 0);
- }
-
- private PagedStorage(PagedStorage<T> 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<T> snapshot() {
- return new PagedStorage<>(this);
- }
-
- private void init(int leadingNulls, List<T> 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<T> 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<T> 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);
- }
-
- 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<T> 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 -------------------
-
- void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
- final int count = page.size();
- if (count == 0) {
- // Nothing returned from source, nothing to do
- 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<T> page, @NonNull Callback callback) {
- final int count = page.size();
- if (count == 0) {
- // Nothing returned from source, nothing to do
- 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);
- }
-
- // ------------- Adjacent Provider interface (contiguous-only) ------------------
-
- @Override
- public 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);
- }
-
- @Override
- public 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<T> page = mPages.get(mPages.size() - 1);
- return page.get(page.size() - 1);
- }
-
- @Override
- public int getFirstLoadedItemIndex() {
- return getLeadingNullCount() + getPositionOffset();
- }
-
- @Override
- public int getLastLoadedItemIndex() {
- return getLeadingNullCount() + getStorageCount() - 1 + getPositionOffset();
- }
-
- @Override
- public void onPageResultResolution(@NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<T> pageResult) {
- // ignored
- }
-
- // ------------------ 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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/paging/common/src/main/java/androidx/paging/Pager.java b/paging/common/src/main/java/androidx/paging/Pager.java
deleted file mode 100644
index 92ae848..0000000
--- a/paging/common/src/main/java/androidx/paging/Pager.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright 2019 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.paging.futures.FutureCallback;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-class Pager<K, V> {
- @SuppressWarnings("unchecked")
- Pager(@NonNull PagedList.Config config,
- @NonNull DataSource<K, V> source,
- @NonNull Executor notifyExecutor,
- @NonNull Executor fetchExecutor,
- @NonNull PageConsumer<V> pageConsumer,
- @Nullable AdjacentProvider<V> adjacentProvider,
- @NonNull DataSource.BaseResult<V> result) {
- mConfig = config;
- mSource = source;
- mNotifyExecutor = notifyExecutor;
- mFetchExecutor = fetchExecutor;
- mPageConsumer = pageConsumer;
- if (adjacentProvider == null) {
- adjacentProvider = new SimpleAdjacentProvider<>();
- }
- mAdjacentProvider = adjacentProvider;
- mPrevKey = (K) result.prevKey;
- mNextKey = (K) result.nextKey;
- adjacentProvider.onPageResultResolution(PagedList.LoadType.REFRESH, result);
- mTotalCount = result.totalCount();
-
- // TODO: move this validation to tiled paging impl, once that's added back
- if (mSource.mType == DataSource.KeyType.POSITIONAL && mConfig.enablePlaceholders) {
- result.validateForInitialTiling(mConfig.pageSize);
- }
- }
-
- private final int mTotalCount;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final PagedList.Config mConfig;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final DataSource<K, V> mSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final Executor mNotifyExecutor;
-
- @NonNull
- private final Executor mFetchExecutor;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final PageConsumer<V> mPageConsumer;
-
- @NonNull
- private final AdjacentProvider<V> mAdjacentProvider;
-
- @Nullable
- private K mPrevKey;
-
- @Nullable
- private K mNextKey;
-
- private final AtomicBoolean mDetached = new AtomicBoolean(false);
-
- PagedList.LoadStateManager mLoadStateManager = new PagedList.LoadStateManager() {
- @Override
- protected void onStateChanged(@NonNull PagedList.LoadType type,
- @NonNull PagedList.LoadState state, @Nullable Throwable error) {
- mPageConsumer.onStateChanged(type, state, error);
- }
- };
-
- private void listenTo(@NonNull final PagedList.LoadType type,
- @NonNull final ListenableFuture<? extends DataSource.BaseResult<V>> future) {
- // First listen on the BG thread if the DataSource is invalid, since it can be expensive
- future.addListener(new Runnable() {
- @Override
- public void run() {
- // if invalid, drop result on the floor
- if (mSource.isInvalid()) {
- detach();
- return;
- }
-
- // Source has been verified to be valid after producing data, so sent data to UI
- Futures.addCallback(future, new FutureCallback<DataSource.BaseResult<V>>() {
- @Override
- public void onSuccess(DataSource.BaseResult<V> value) {
- onLoadSuccess(type, value);
- }
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- onLoadError(type, throwable);
- }
- }, mNotifyExecutor);
- }
- }, mFetchExecutor);
- }
-
- interface PageConsumer<V> {
- // return true if we need to fetch more
- boolean onPageResult(
- @NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> pageResult);
-
- void onStateChanged(@NonNull PagedList.LoadType type,
- @NonNull PagedList.LoadState state, @Nullable Throwable error);
- }
-
- interface AdjacentProvider<V> {
- V getFirstLoadedItem();
-
- V getLastLoadedItem();
-
- int getFirstLoadedItemIndex();
-
- int getLastLoadedItemIndex();
-
- /**
- * Notify the AdjacentProvider of new loaded data, to update first/last item/index.
- *
- * NOTE: this data may not be committed (e.g. it may be dropped due to max size). Up to the
- * implementation of the AdjacentProvider to handle this (generally by ignoring this
- * call if dropping is supported).
- */
- void onPageResultResolution(
- @NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> result);
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- void onLoadSuccess(PagedList.LoadType type, DataSource.BaseResult<V> value) {
- if (isDetached()) {
- // abort!
- return;
- }
-
- mAdjacentProvider.onPageResultResolution(type, value);
-
- if (mPageConsumer.onPageResult(type, value)) {
- if (type.equals(PagedList.LoadType.START)) {
- //noinspection unchecked
- mPrevKey = (K) value.prevKey;
- schedulePrepend();
- } else if (type.equals(PagedList.LoadType.END)) {
- //noinspection unchecked
- mNextKey = (K) value.nextKey;
- scheduleAppend();
- } else {
- throw new IllegalStateException("Can only fetch more during append/prepend");
- }
- } else {
- PagedList.LoadState state =
- value.data.isEmpty() ? PagedList.LoadState.DONE : PagedList.LoadState.IDLE;
- mLoadStateManager.setState(type, state, null);
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- void onLoadError(PagedList.LoadType type, Throwable throwable) {
- if (isDetached()) {
- // abort!
- return;
- }
- // TODO: handle nesting
- PagedList.LoadState state = mSource.isRetryableError(throwable)
- ? PagedList.LoadState.RETRYABLE_ERROR : PagedList.LoadState.ERROR;
- mLoadStateManager.setState(type, state, throwable);
- }
-
- public void trySchedulePrepend() {
- if (mLoadStateManager.getStart().equals(PagedList.LoadState.IDLE)) {
- schedulePrepend();
- }
- }
-
- public void tryScheduleAppend() {
- if (mLoadStateManager.getEnd().equals(PagedList.LoadState.IDLE)) {
- scheduleAppend();
- }
- }
-
- private boolean canPrepend() {
- if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
- // don't know count / position from initial load, so be conservative, return true
- return true;
- }
-
- // position is known, do we have space left?
- return mAdjacentProvider.getFirstLoadedItemIndex() > 0;
- }
-
- private boolean canAppend() {
- if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
- // don't know count / position from initial load, so be conservative, return true
- return true;
- }
-
- // count is known, do we have space left?
- return mAdjacentProvider.getLastLoadedItemIndex() < mTotalCount - 1;
- }
-
- private void schedulePrepend() {
- if (!canPrepend()) {
- onLoadSuccess(PagedList.LoadType.START, DataSource.BaseResult.<V>empty());
- return;
- }
- K key;
- switch(mSource.mType) {
- case POSITIONAL:
- //noinspection unchecked
- key = (K) ((Integer) (mAdjacentProvider.getFirstLoadedItemIndex() - 1));
- break;
- case PAGE_KEYED:
- key = mPrevKey;
- break;
- case ITEM_KEYED:
- key = mSource.getKey(mAdjacentProvider.getFirstLoadedItem());
- break;
- default:
- throw new IllegalArgumentException("unknown source type");
- }
- mLoadStateManager.setState(
- PagedList.LoadType.START, PagedList.LoadState.LOADING, null);
- listenTo(PagedList.LoadType.START, mSource.load(new DataSource.Params<>(
- DataSource.LoadType.START,
- key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mConfig.pageSize)));
- }
-
- private void scheduleAppend() {
- if (!canAppend()) {
- onLoadSuccess(PagedList.LoadType.END, DataSource.BaseResult.<V>empty());
- return;
- }
-
- K key;
- switch(mSource.mType) {
- case POSITIONAL:
- //noinspection unchecked
- key = (K) ((Integer) (mAdjacentProvider.getLastLoadedItemIndex() + 1));
- break;
- case PAGE_KEYED:
- key = mNextKey;
- break;
- case ITEM_KEYED:
- key = mSource.getKey(mAdjacentProvider.getLastLoadedItem());
- break;
- default:
- throw new IllegalArgumentException("unknown source type");
- }
-
- mLoadStateManager.setState(PagedList.LoadType.END, PagedList.LoadState.LOADING, null);
- listenTo(PagedList.LoadType.END, mSource.load(new DataSource.Params<>(
- DataSource.LoadType.END,
- key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mConfig.pageSize)));
- }
-
- void retry() {
- if (mLoadStateManager.getStart().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
- schedulePrepend();
- }
- if (mLoadStateManager.getEnd().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
- scheduleAppend();
- }
- }
-
- boolean isDetached() {
- return mDetached.get();
- }
-
- void detach() {
- mDetached.set(true);
- }
-
- static class SimpleAdjacentProvider<V> implements AdjacentProvider<V> {
- private int mFirstIndex;
- private int mLastIndex;
-
- private V mFirstItem;
- private V mLastItem;
-
- boolean mCounted;
- int mLeadingUnloadedCount;
- int mTrailingUnloadedCount;
-
- @Override
- public V getFirstLoadedItem() {
- return mFirstItem;
- }
-
- @Override
- public V getLastLoadedItem() {
- return mLastItem;
- }
-
- @Override
- public int getFirstLoadedItemIndex() {
- return mFirstIndex;
- }
-
- @Override
- public int getLastLoadedItemIndex() {
- return mLastIndex;
- }
-
- @Override
- public void onPageResultResolution(@NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> result) {
- if (result.data.isEmpty()) {
- return;
- }
- if (type == PagedList.LoadType.START) {
- mFirstIndex -= result.data.size();
- mFirstItem = result.data.get(0);
- if (mCounted) {
- mLeadingUnloadedCount -= result.data.size();
- }
- } else if (type == PagedList.LoadType.END) {
- mLastIndex += result.data.size();
- mLastItem = result.data.get(result.data.size() - 1);
- if (mCounted) {
- mTrailingUnloadedCount -= result.data.size();
- }
- } else {
- mFirstIndex = result.leadingNulls + result.offset;
- mLastIndex = mFirstIndex + result.data.size() - 1;
- mFirstItem = result.data.get(0);
- mLastItem = result.data.get(result.data.size() - 1);
-
- if (result.counted) {
- mCounted = true;
- mLeadingUnloadedCount = result.leadingNulls;
- mTrailingUnloadedCount = result.trailingNulls;
- }
- }
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
deleted file mode 100644
index 3cb0217..0000000
--- a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
+++ /dev/null
@@ -1,418 +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.WorkerThread;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
- * arbitrary page positions.
- * <p>
- * 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), either
- * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
- * the two parameter {@link LoadInitialCallback#onResult(List, int)}.
- * <p>
- * Room can generate a Factory of PositionalDataSources for you:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
- * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
- * }</pre>
- *
- * @see ListenablePositionalDataSource
- *
- * @param <T> Type of items being loaded by the PositionalDataSource.
- */
-public abstract class PositionalDataSource<T> extends ListenablePositionalDataSource<T> {
-
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- */
- public static class LoadInitialParams extends ListenablePositionalDataSource.LoadInitialParams {
- public LoadInitialParams(
- int requestedStartPosition,
- int requestedLoadSize,
- int pageSize,
- boolean placeholdersEnabled) {
- super(requestedStartPosition, requestedLoadSize, pageSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- */
- public static class LoadRangeParams extends ListenablePositionalDataSource.LoadRangeParams {
- public LoadRangeParams(int startPosition, int loadSize) {
- super(startPosition, loadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data, position, and count.
- * <p>
- * A callback should be called only once, and may throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<T> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass 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<T> data, int position, int totalCount);
-
- /**
- * Called to pass initial load state from a DataSource without total count,
- * when placeholders aren't requested.
- * <p class="note"><strong>Note:</strong> This method can only be called when placeholders
- * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
- * <p>
- * 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<T> data, int position);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- /**
- * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)}
- * to return data.
- * <p>
- * A callback should be called only once, and may throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public abstract static class LoadRangeCallback<T> {
- /**
- * 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<T> data);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<T>> loadInitial(
- @NonNull final ListenablePositionalDataSource.LoadInitialParams params) {
- final ResolvableFuture<InitialResult<T>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- final LoadInitialParams newParams = new LoadInitialParams(
- params.requestedStartPosition,
- params.requestedLoadSize,
- params.pageSize,
- params.placeholdersEnabled);
- LoadInitialCallback<T> callback = new LoadInitialCallback<T>() {
- @Override
- public void onResult(@NonNull List<T> data, int position, int totalCount) {
- if (isInvalid()) {
- // NOTE: this isInvalid() check works around
- // https://issuetracker.google.com/issues/124511903
- future.set(new InitialResult<>(Collections.<T>emptyList(), 0, 0));
- } else {
- setFuture(newParams, new InitialResult<>(data, position, totalCount));
- }
- }
-
- @Override
- public void onResult(@NonNull List<T> data, int position) {
- if (isInvalid()) {
- // NOTE: this isInvalid() check works around
- // https://issuetracker.google.com/issues/124511903
- future.set(new InitialResult<>(Collections.<T>emptyList(), 0));
- } else {
- setFuture(newParams, new InitialResult<>(data, position));
- }
- }
-
- private void setFuture(
- @NonNull ListenablePositionalDataSource.LoadInitialParams params,
- @NonNull InitialResult<T> result) {
- if (params.placeholdersEnabled) {
- result.validateForInitialTiling(params.pageSize);
- }
- future.set(result);
- }
-
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(newParams,
- callback);
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<RangeResult<T>> loadRange(
- final @NonNull ListenablePositionalDataSource.LoadRangeParams params) {
- final ResolvableFuture<RangeResult<T>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadRangeCallback<T> callback = new LoadRangeCallback<T>() {
- @Override
- public void onResult(@NonNull List<T> data) {
- if (isInvalid()) {
- future.set(new RangeResult<>(Collections.<T>emptyList()));
- } else {
- future.set(new RangeResult<>(data));
- }
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadRange(new LoadRangeParams(
- params.startPosition,
- params.loadSize),
- callback);
- }
- });
- return future;
- }
-
- /**
- * Load initial list data.
- * <p>
- * This method is called to load the initial page(s) from the DataSource.
- * <p>
- * 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<T> callback);
-
- /**
- * Called to load a range of data from the DataSource.
- * <p>
- * This method is called to load additional pages from the DataSource after the
- * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
- * <p>
- * 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<T> callback);
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- /**
- * Helper for computing an initial position in
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * The value computed by this function will do bounds checking, page alignment, and positioning
- * based on initial load size requested.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * 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));
- * }
- * }</pre>
- *
- * @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) {
- return ListenablePositionalDataSource.computeInitialLoadPosition(params, totalCount);
- }
-
- /**
- * Helper for computing an initial load size in
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * This function takes the requested load size, and bounds checks it against the value returned
- * by
- * {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * 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));
- * }
- * }</pre>
- *
- * @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)
- */
- public static int computeInitialLoadSize(
- @NonNull LoadInitialParams params, int initialLoadPosition, int totalCount) {
- return ListenablePositionalDataSource.computeInitialLoadSize(params, initialLoadPosition,
- totalCount);
- }
-
- @NonNull
- @Override
- public final <V> PositionalDataSource<V> mapByPage(
- @NonNull Function<List<T>, List<V>> function) {
- return new WrapperPositionalDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <V> PositionalDataSource<V> map(@NonNull Function<T, V> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
deleted file mode 100644
index a3e5a12..0000000
--- a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.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.annotation.Nullable;
-
-class SnapshotPagedList<T> extends PagedList<T> {
- private final boolean mContiguous;
- private final Object mLastKey;
- private final DataSource<?, T> mDataSource;
-
- SnapshotPagedList(@NonNull PagedList<T> 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 void detach() {}
-
- @Override
- public boolean isDetached() {
- return true;
- }
-
- @Override
- boolean isContiguous() {
- return mContiguous;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mLastKey;
- }
-
- @NonNull
- @Override
- public DataSource<?, T> getDataSource() {
- return mDataSource;
- }
-
- @Override
- void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
- @NonNull Callback callback) {
- }
-
- @Override
- void dispatchCurrentLoadState(LoadStateListener listener) {
- }
-
- @Override
- void loadAroundInternal(int index) {
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/TiledDataSource.java b/paging/common/src/main/java/androidx/paging/TiledDataSource.java
deleted file mode 100644
index 7c45879..0000000
--- a/paging/common/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 <T> Type loaded by the TiledDataSource.
- *
- * @deprecated Use {@link PositionalDataSource}
- * @hide
- */
-@SuppressWarnings("DeprecatedIsStillUsed")
-@Deprecated
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
-
- @WorkerThread
- public abstract int countItems();
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- @Nullable
- @WorkerThread
- public abstract List<T> loadRange(int startPosition, int count);
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<T> callback) {
- int totalCount = countItems();
- if (totalCount == 0) {
- callback.onResult(Collections.<T>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<T> 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<T> callback) {
- List<T> list = loadRange(params.startPosition, params.loadSize);
- if (list != null) {
- callback.onResult(list);
- } else {
- invalidate();
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
deleted file mode 100644
index d8c4df4..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2019 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 androidx.paging.futures.DirectExecutor;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.IdentityHashMap;
-import java.util.List;
-
-/**
- * @param <Key> DataSource key type, same for original and wrapped.
- * @param <ValueFrom> Value type of original DataSource.
- * @param <ValueTo> Value type of new DataSource.
- */
-class WrapperDataSource<Key, ValueFrom, ValueTo> extends DataSource<Key, ValueTo> {
- private final DataSource<Key, ValueFrom> mSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<ValueFrom>, List<ValueTo>> mListFunction;
-
-
- private final IdentityHashMap<ValueTo, Key> mKeyMap;
-
- WrapperDataSource(@NonNull DataSource<Key, ValueFrom> source,
- @NonNull Function<List<ValueFrom>, List<ValueTo>> listFunction) {
- super(source.mType);
- mSource = source;
- mListFunction = listFunction;
- mKeyMap = source.mType == KeyType.ITEM_KEYED ? new IdentityHashMap<ValueTo, Key>() : null;
- }
-
- @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();
- }
-
- @Nullable
- @Override
- Key getKey(@NonNull ValueTo item) {
- if (mKeyMap != null) {
- synchronized (mKeyMap) {
- return mKeyMap.get(item);
- }
- }
- // positional / page-keyed
- return null;
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void stashKeysIfNeeded(@NonNull List<ValueFrom> source, @NonNull List<ValueTo> dest) {
- if (mKeyMap != null) {
- synchronized (mKeyMap) {
- for (int i = 0; i < dest.size(); i++) {
- mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
- }
- }
- }
- }
-
- @Override
- final ListenableFuture<? extends BaseResult> load(@NonNull Params params) {
- //noinspection unchecked
- return Futures.transform(
- mSource.load(params),
- new Function<BaseResult<ValueFrom>, BaseResult<ValueTo>>() {
- @Override
- public BaseResult<ValueTo> apply(BaseResult<ValueFrom> input) {
- BaseResult<ValueTo> result = new BaseResult<>(input, mListFunction);
- stashKeysIfNeeded(input.data, result.data);
- return result;
- }
- },
- DirectExecutor.INSTANCE);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
deleted file mode 100644
index 3a1b5c9..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
+++ /dev/null
@@ -1,131 +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<K, A, B> extends ItemKeyedDataSource<K, B> {
- private final ItemKeyedDataSource<K, A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- private final IdentityHashMap<B, K> mKeyMap = new IdentityHashMap<>();
-
- WrapperItemKeyedDataSource(ItemKeyedDataSource<K, A> source,
- Function<List<A>, List<B>> 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<B> convertWithStashedKeys(List<A> source) {
- List<B> 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<K> params,
- final @NonNull LoadInitialCallback<B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data, int position, int totalCount) {
- callback.onResult(convertWithStashedKeys(data), position, totalCount);
- }
-
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadAfter(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<B> callback) {
- mSource.loadAfter(params, new LoadCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadBefore(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<B> callback) {
- mSource.loadBefore(params, new LoadCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @NonNull
- @Override
- public K getKey(@NonNull B item) {
- synchronized (mKeyMap) {
- return mKeyMap.get(item);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
deleted file mode 100644
index 3ffc79d..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
+++ /dev/null
@@ -1,111 +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<K, A, B> extends PageKeyedDataSource<K, B> {
- private final PageKeyedDataSource<K, A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- WrapperPageKeyedDataSource(PageKeyedDataSource<K, A> source,
- Function<List<A>, List<B>> 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<K> params,
- final @NonNull LoadInitialCallback<K, B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> 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<A> data, @Nullable K previousPageKey,
- @Nullable K nextPageKey) {
- callback.onResult(convert(mListFunction, data), previousPageKey, nextPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadBefore(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<K, B> callback) {
- mSource.loadBefore(params, new LoadCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
- callback.onResult(convert(mListFunction, data), adjacentPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadAfter(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<K, B> callback) {
- mSource.loadAfter(params, new LoadCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
- callback.onResult(convert(mListFunction, data), adjacentPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
deleted file mode 100644
index fdc2b9a..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
+++ /dev/null
@@ -1,91 +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<A, B> extends PositionalDataSource<B> {
- private final PositionalDataSource<A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- WrapperPositionalDataSource(PositionalDataSource<A> source,
- Function<List<A>, List<B>> 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<B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data, int position, int totalCount) {
- callback.onResult(convert(mListFunction, data), position, totalCount);
- }
-
- @Override
- public void onResult(@NonNull List<A> data, int position) {
- callback.onResult(convert(mListFunction, data), position);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- final @NonNull LoadRangeCallback<B> callback) {
- mSource.loadRange(params, new LoadRangeCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convert(mListFunction, data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java b/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java
deleted file mode 100644
index 46eeb29..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2019 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.util.concurrent.Executor;
-
-/**
- * Executor that runs each task in the thread that invokes {@link Executor#execute execute}
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DirectExecutor implements Executor {
- /**
- * Returns an {@link Executor} that runs each task in the thread that invokes {@link
- * Executor#execute execute}.
- *
- * <p>This instance is equivalent to:
- *
- * <pre>{@code
- * final class DirectExecutor implements Executor {
- * public void execute(Runnable r) {
- * r.run();
- * }
- * }
- * }</pre>
- */
- @NonNull
- public static DirectExecutor INSTANCE = new DirectExecutor();
-
- private DirectExecutor() {}
- @Override
- public void execute(@NonNull Runnable runnable) {
- runnable.run();
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java b/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java
deleted file mode 100644
index 7d4aa0d..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2019 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-/**
- * A callback for accepting the results of a {@link Future} computation asynchronously.
- *
- * <p>To attach to a {@link ListenableFuture} use {@link Futures#addCallback}.
- * @param <V> Type of the Future result.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface FutureCallback<V> {
- /** Invoked with the result of the {@code Future} computation when it is successful. */
- @SuppressWarnings("UnknownNullness")
- void onSuccess(V value);
-
- /**
- * Invoked when a {@code Future} computation fails or is canceled.
- *
- * <p>If the future's {@link Future#get() get} method throws an {@link ExecutionException}, then
- * the cause is passed to this method. Any other thrown object is passed unaltered.
- */
- void onError(@NonNull Throwable throwable);
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/Futures.java b/paging/common/src/main/java/androidx/paging/futures/Futures.java
deleted file mode 100644
index 4ad52f8..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/Futures.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright 2019 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Futures {
- private Futures() {}
- /**
- * Registers separate success and failure callbacks to be run when the {@code Future}'s
- * computation is complete or, if the computation is already complete, immediately.
- *
- * <p>The callback is run on {@code executor}. There is no guaranteed ordering of execution of
- * callbacks, but any callback added through this method is guaranteed to be called once the
- * computation is complete.
- *
- * <p>Example:
- *
- * <pre>{@code
- * ListenableFuture<QueryResult> future = ...;
- * Executor e = ...
- * addCallback(future,
- * new FutureCallback<QueryResult>() {
- * public void onSuccess(QueryResult result) {
- * storeInCache(result);
- * }
- * public void onFailure(Throwable t) {
- * reportError(t);
- * }
- * }, e);
- * }</pre>
- *
- * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
- * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
- * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
- * callbacks passed to this method.
- *
- * <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
- * ListenableFuture#addListener addListener}.
- *
- * @param future The future attach the callback to.
- * @param callback The callback to invoke when {@code future} is completed.
- * @param executor The executor to run {@code callback} when the future completes.
- */
- public static <V> void addCallback(@NonNull final ListenableFuture<V> future,
- @NonNull final FutureCallback<? super V> callback, @NonNull Executor executor) {
- future.addListener(new Runnable() {
- @Override
- public void run() {
- final V value;
- try {
- value = future.get();
- } catch (ExecutionException e) {
- callback.onError(e.getCause());
- return;
- } catch (Throwable e) {
- callback.onError(e);
- return;
- }
- callback.onSuccess(value);
- }
- }, executor);
- }
-
- /**
- * Returns a new {@code Future} whose result is derived from the result of the given {@code
- * Future}. If {@code input} fails, the returned {@code Future} fails with the same exception
- * (and the function is not invoked). Example usage:
- *
- * <pre>{@code
- * ListenableFuture<QueryResult> queryFuture = ...;
- * ListenableFuture<List<Row>> rowsFuture =
- * transform(queryFuture, QueryResult::getRows, executor);
- * }</pre>
- *
- * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases.
- * See the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
- * documentation. All its warnings about heavyweight listeners are also applicable to
- * heavyweight functions passed to this method.
- *
- * <p>The returned {@code Future} attempts to keep its cancellation state in sync with that of
- * the input future. That is, if the returned {@code Future} is cancelled, it will attempt to
- * cancel the input, and if the input is cancelled, the returned {@code Future} will receive a
- * callback in which it will attempt to cancel itself.
- *
- * <p>An example use of this method is to convert a serializable object returned from an RPC
- * into a POJO.
- *
- * @param input The future to transform
- * @param function A Function to transform the results of the provided future to the results of
- * the returned future.
- * @param executor Executor to run the function in.
- * @return A future that holds result of the transformation.
- */
- @NonNull
- public static <I, O> ListenableFuture<O> transform(
- @NonNull final ListenableFuture<I> input,
- @NonNull final Function<? super I, ? extends O> function,
- @NonNull final Executor executor) {
- final ResolvableFuture<O> out = ResolvableFuture.create();
-
- // add success/error callback
- addCallback(input, new FutureCallback<I>() {
- @Override
- public void onSuccess(I value) {
- out.set(function.apply(value));
- }
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- out.setException(throwable);
- }
- }, executor);
-
- // propagate output future's cancellation to input future
- addCallback(out, new FutureCallback<O>() {
- @Override
- public void onSuccess(O value) {}
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- if (throwable instanceof CancellationException) {
- input.cancel(false);
- }
- }
- }, executor);
- return out;
- }
-}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
new file mode 100644
index 0000000..1574a84
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2019 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.MainThread
+import androidx.annotation.RestrictTo
+import java.util.concurrent.Executor
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+open class ContiguousPagedList<K : Any, V : Any>(
+ override val dataSource: DataSource<K, V>,
+ mainThreadExecutor: Executor,
+ backgroundThreadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<V>?,
+ config: Config,
+ initialResult: DataSource.BaseResult<V>,
+ lastLoad: Int
+) : PagedList<V>(
+ PagedStorage<V>(),
+ mainThreadExecutor,
+ backgroundThreadExecutor,
+ boundaryCallback,
+ config
+), PagedStorage.Callback, Pager.PageConsumer<V> {
+ internal companion object {
+ internal const val LAST_LOAD_UNSPECIFIED = -1
+
+ internal fun getPrependItemsRequested(
+ prefetchDistance: Int,
+ index: Int,
+ leadingNulls: Int
+ ) = prefetchDistance - (index - leadingNulls)
+
+ internal fun getAppendItemsRequested(
+ prefetchDistance: Int,
+ index: Int,
+ itemsBeforeTrailingNulls: Int
+ ) = index + prefetchDistance + 1 - itemsBeforeTrailingNulls
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var prependItemsRequested = 0
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var appendItemsRequested = 0
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var replacePagesWithNulls = false
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ val shouldTrim: Boolean
+
+ private val pager: Pager<K, V>
+
+ override val isDetached
+ get() = pager.isDetached
+
+ override val isContiguous = true
+
+ override val lastKey
+ get() = when (dataSource.type) {
+ DataSource.KeyType.POSITIONAL -> {
+ @Suppress("UNCHECKED_CAST")
+ lastLoad as K
+ }
+ else -> lastItem?.let { dataSource.getKeyInternal(it) }
+ }
+
+ /**
+ * Given a page result, apply or drop it, and return whether more loading is needed.
+ */
+ override fun onPageResult(type: LoadType, pageResult: DataSource.BaseResult<V>): Boolean {
+ var continueLoading = false
+ val page = pageResult.data
+
+ // if we end up trimming, we trim from side that's furthest from most recent access
+ val trimFromFront = lastLoad > storage.middleOfLoadedRange
+
+ // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
+ val skipNewPage = shouldTrim && storage.shouldPreTrimNewPage(
+ config.maxSize,
+ requiredRemainder,
+ page.size
+ )
+
+ if (type == LoadType.END) {
+ if (skipNewPage && !trimFromFront) {
+ // don't append this data, drop it
+ appendItemsRequested = 0
+ } else {
+ storage.appendPage(page, this@ContiguousPagedList)
+ appendItemsRequested -= page.size
+ if (appendItemsRequested > 0 && page.size != 0) {
+ continueLoading = true
+ }
+ }
+ } else if (type == LoadType.START) {
+ if (skipNewPage && trimFromFront) {
+ // don't append this data, drop it
+ prependItemsRequested = 0
+ } else {
+ storage.prependPage(page, this@ContiguousPagedList)
+ prependItemsRequested -= page.size
+ if (prependItemsRequested > 0 && page.size != 0) {
+ continueLoading = true
+ }
+ }
+ } else {
+ throw IllegalArgumentException("unexpected result type $type")
+ }
+
+ if (shouldTrim) {
+ // Try and trim, but only if the side being trimmed isn't actually fetching.
+ // For simplicity (both of impl here, and contract w/ DataSource) we don't
+ // allow fetches in same direction - this means reading the load state is safe.
+ if (trimFromFront) {
+ if (pager.loadStateManager.start != LoadState.LOADING) {
+ if (storage.trimFromFront(
+ replacePagesWithNulls,
+ config.maxSize,
+ requiredRemainder,
+ this@ContiguousPagedList
+ )
+ ) {
+ // trimmed from front, ensure we can fetch in that dir
+ pager.loadStateManager.setState(
+ LoadType.START,
+ LoadState.IDLE,
+ null
+ )
+ }
+ }
+ } else {
+ if (pager.loadStateManager.end != LoadState.LOADING) {
+ if (storage.trimFromEnd(
+ replacePagesWithNulls,
+ config.maxSize,
+ requiredRemainder,
+ this@ContiguousPagedList
+ )
+ ) {
+ pager.loadStateManager.setState(LoadType.END, LoadState.IDLE, null)
+ }
+ }
+ }
+ }
+
+ triggerBoundaryCallback(type, page)
+ return continueLoading
+ }
+
+ override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
+ dispatchStateChange(type, state, error)
+
+ private fun triggerBoundaryCallback(type: LoadType, page: List<V>) {
+ if (boundaryCallback != null) {
+ val deferEmpty = storage.size == 0
+ val deferBegin = (!deferEmpty && type == LoadType.START && page.isEmpty())
+ val deferEnd = (!deferEmpty && type == LoadType.END && page.isEmpty())
+ deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd)
+ }
+ }
+
+ override fun retry() {
+ super.retry()
+ pager.retry()
+
+ if (pager.loadStateManager.refresh == LoadState.RETRYABLE_ERROR) {
+ // Loading the next PagedList failed, signal the retry callback.
+ refreshRetryCallback?.run()
+ }
+ }
+
+ init {
+ this.lastLoad = lastLoad
+ pager = Pager(
+ config,
+ dataSource,
+ mainThreadExecutor,
+ backgroundThreadExecutor,
+ this,
+ storage,
+ initialResult
+ )
+
+ if (config.enablePlaceholders) {
+ // Placeholders enabled, pass raw data to storage init
+ storage.init(
+ initialResult.leadingNulls,
+ initialResult.data,
+ initialResult.trailingNulls,
+ initialResult.offset,
+ this
+ )
+ } else {
+ // If placeholder are disabled, avoid passing leading/trailing nulls,
+ // since DataSource may have passed them anyway
+ storage.init(
+ 0,
+ initialResult.data,
+ 0,
+ initialResult.offset + initialResult.leadingNulls,
+ this
+ )
+ }
+
+ shouldTrim =
+ dataSource.supportsPageDropping && config.maxSize != Config.MAX_SIZE_UNBOUNDED
+
+ if (this.lastLoad == LAST_LOAD_UNSPECIFIED) {
+ // Because the ContiguousPagedList wasn't initialized with a last load position,
+ // initialize it to the middle of the initial load
+ this.lastLoad = (initialResult.leadingNulls + initialResult.offset +
+ initialResult.data.size / 2)
+ }
+ triggerBoundaryCallback(LoadType.REFRESH, initialResult.data)
+ }
+
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) =
+ pager.loadStateManager.dispatchCurrentLoadState(listener)
+
+ override fun setInitialLoadState(loadState: LoadState, error: Throwable?) =
+ pager.loadStateManager.setState(LoadType.REFRESH, loadState, error)
+
+ @MainThread
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<V>, callback: Callback) {
+ val snapshotStorage = snapshot.storage
+
+ val newlyAppended = storage.numberAppended - snapshotStorage.numberAppended
+ val newlyPrepended = storage.numberPrepended - snapshotStorage.numberPrepended
+
+ val previousTrailing = snapshotStorage.trailingNullCount
+ val previousLeading = snapshotStorage.leadingNullCount
+
+ // 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 (snapshotStorage.isEmpty() ||
+ newlyAppended < 0 ||
+ newlyPrepended < 0 ||
+ storage.trailingNullCount != maxOf(previousTrailing - newlyAppended, 0) ||
+ storage.leadingNullCount != maxOf(previousLeading - newlyPrepended, 0) ||
+ storage.storageCount != snapshotStorage.storageCount + newlyAppended + newlyPrepended
+ ) {
+ throw IllegalArgumentException(
+ "Invalid snapshot provided - doesn't appear to be a snapshot of this PagedList"
+ )
+ }
+
+ if (newlyAppended != 0) {
+ val changedCount = minOf(previousTrailing, newlyAppended)
+ val addedCount = newlyAppended - changedCount
+
+ val endPosition = snapshotStorage.leadingNullCount + snapshotStorage.storageCount
+ if (changedCount != 0) {
+ callback.onChanged(endPosition, changedCount)
+ }
+ if (addedCount != 0) {
+ callback.onInserted(endPosition + changedCount, addedCount)
+ }
+ }
+ if (newlyPrepended != 0) {
+ val changedCount = minOf(previousLeading, newlyPrepended)
+ val addedCount = newlyPrepended - changedCount
+
+ if (changedCount != 0) {
+ callback.onChanged(previousLeading, changedCount)
+ }
+ if (addedCount != 0) {
+ callback.onInserted(0, addedCount)
+ }
+ }
+ }
+
+ @MainThread
+ override fun loadAroundInternal(index: Int) {
+ val prependItems =
+ getPrependItemsRequested(config.prefetchDistance, index, storage.leadingNullCount)
+ val appendItems = getAppendItemsRequested(
+ config.prefetchDistance,
+ index,
+ storage.leadingNullCount + storage.storageCount
+ )
+
+ prependItemsRequested = maxOf(prependItems, prependItemsRequested)
+ if (prependItemsRequested > 0) {
+ pager.trySchedulePrepend()
+ }
+
+ appendItemsRequested = maxOf(appendItems, appendItemsRequested)
+ if (appendItemsRequested > 0) {
+ pager.tryScheduleAppend()
+ }
+ }
+
+ override fun detach() = pager.detach()
+
+ @MainThread
+ override fun onInitialized(count: Int) {
+ notifyInserted(0, count)
+ // simple heuristic to decide if, when dropping pages, we should replace with placeholders
+ replacePagesWithNulls = storage.leadingNullCount > 0 || storage.trailingNullCount > 0
+ }
+
+ @MainThread
+ override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {
+ // finally dispatch callbacks, after prepend may have already been scheduled
+ notifyChanged(leadingNulls, changed)
+ notifyInserted(0, added)
+
+ offsetAccessIndices(added)
+ }
+
+ @MainThread
+ override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {
+ // finally dispatch callbacks, after append may have already been scheduled
+ notifyChanged(endPosition, changed)
+ notifyInserted(endPosition + changed, added)
+ }
+
+ @MainThread
+ override fun onPagePlaceholderInserted(pageIndex: Int) {
+ throw IllegalStateException("Tiled callback on ContiguousPagedList")
+ }
+
+ @MainThread
+ override fun onPageInserted(start: Int, count: Int) {
+ throw IllegalStateException("Tiled callback on ContiguousPagedList")
+ }
+
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
+
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
+ notifyChanged(startOfDrops, count)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
new file mode 100644
index 0000000..eec73ae
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import com.google.common.util.concurrent.ListenableFuture
+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 [PagedList].
+ *
+ * DataSource is queried to load pages of content into a [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.
+ *
+ * <h4>Loading Pages</h4>
+ *
+ * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
+ * calls [PagedList.loadAround] to load content as the user scrolls in a RecyclerView.
+ *
+ * To control how and when a PagedList queries data from its DataSource, see
+ * [PagedList.Config]. The Config object defines things like load sizes and prefetch distance.
+ *
+ * <h4>Updating Paged Data</h4>
+ *
+ * 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
+ * [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 [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 [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.
+ *
+ * <h4>Implementing a DataSource</h4>
+ *
+ * To implement, extend one of the subclasses: [PageKeyedDataSource], [ItemKeyedDataSource], or
+ * [PositionalDataSource].
+ *
+ * Use [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 [ItemKeyedDataSource] if you need to use data from item `N-1` to load item
+ * `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 [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 `null` item indicates a placeholder in [PagedList], DataSource may not
+ * return `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 Key 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 `<Value> Value type
+ * loaded by the DataSource.
+ */
+abstract class DataSource<Key : Any, Value : Any>
+// Since we currently rely on implementation details of two implementations, prevent external
+// subclassing, except through exposed subclasses.
+internal constructor(internal val type: KeyType) {
+ private val onInvalidatedCallbacks = CopyOnWriteArrayList<InvalidatedCallback>()
+
+ private val _invalid = AtomicBoolean(false)
+ /**
+ * @return `true` if the data source is invalid, and can no longer be queried for data.
+ */
+ open val isInvalid
+ @WorkerThread
+ get() = _invalid.get()
+
+ private var _executor: Executor? = null
+ /**
+ * `null` until `loadInitial` is called by [PagedList] construction.
+ *
+ * This backing variable is necessary for back-compatibility with paging-common:2.1.0 Java API,
+ * while still providing synthetic accessors for Kotlin API.
+ */
+ protected val executor: Executor
+ get() = _executor ?: throw IllegalStateException(
+ "This DataSource has not been passed to a PagedList, has no executor yet."
+ )
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun initExecutor(executor: Executor) {
+ _executor = executor
+ }
+
+ /**
+ * Factory for DataSources.
+ *
+ * Data-loading systems of an application or library can implement this interface to allow
+ * `LiveData<PagedList>`s to be created. For example, Room can provide a
+ * [DataSource.Factory] for a given SQL query:
+ *
+ * ```
+ * @Dao
+ * interface UserDao {
+ * @Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
+ * }
+ * ```
+ *
+ * In the above sample, `Integer` is used because it is the `Key` type of
+ * PositionalDataSource. Currently, Room uses the `LIMIT`/`OFFSET` SQL keywords to
+ * page a large query with a PositionalDataSource.
+ *
+ * @param Key Key identifying items in DataSource.
+ * @param Value Type of items in the list loaded by the DataSources.
+ */
+ abstract class Factory<Key : Any, Value : Any> {
+ /**
+ * 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.
+ *
+ * [androidx.paging.LivePagedListBuilder] for example will construct a new PagedList and
+ * DataSource when the current DataSource is invalidated, and pass the new PagedList through
+ * the `LiveData<PagedList>` to observers.
+ *
+ * @return the new DataSource.
+ */
+ abstract fun create(): DataSource<Key, Value>
+
+ /**
+ * Applies the given function to each value emitted by DataSources produced by this Factory.
+ *
+ * Same as [mapByPage], but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param ToValue 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
+ * @see DataSource.map
+ * @see DataSource.mapByPage
+ */
+ open fun <ToValue : Any> map(function: Function<Value, ToValue>): Factory<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+
+ /**
+ * Applies the given function to each value emitted by DataSources produced by this Factory.
+ *
+ * Same as [map], but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param ToValue 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
+ * @see DataSource.map
+ * @see DataSource.mapByPage
+ */
+ open fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): Factory<Key, ToValue> = object : Factory<Key, ToValue>() {
+ override fun create(): DataSource<Key, ToValue> =
+ [email protected]().mapByPage(function)
+ }
+ }
+
+ /**
+ * Applies the given function to each value emitted by the DataSource.
+ *
+ * Same as [map], but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param ToValue 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
+ * @see DataSource.Factory.map
+ * @see DataSource.Factory.mapByPage
+ */
+ open fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): DataSource<Key, ToValue> = WrapperDataSource(this, function)
+
+ /**
+ * Applies the given function to each value emitted by the DataSource.
+ *
+ * Same as [mapByPage], but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param ToValue 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
+ * @see DataSource.Factory.map
+ * @see DataSource.Factory.mapByPage
+ */
+ open fun <ToValue : Any> map(function: Function<Value, ToValue>): DataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+
+ /**
+ * Returns true if the data source guaranteed to produce a contiguous set of items, never
+ * producing gaps.
+ */
+ internal open val isContiguous = true
+
+ internal open val supportsPageDropping = true
+
+ /**
+ * 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.
+ */
+ 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 [invalidate]. It is valid for the
+ * data source to invalidate itself during its load methods, or for an outside source to
+ * invalidate it.
+ */
+ @AnyThread
+ fun onInvalidated()
+ }
+
+ /**
+ * 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 [invalidate] is called, on
+ * that thread.
+ *
+ * @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
+ * [DataSource].
+ */
+ @AnyThread
+ open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ onInvalidatedCallbacks.add(onInvalidatedCallback)
+ }
+
+ /**
+ * Remove a previously added invalidate callback.
+ *
+ * @param onInvalidatedCallback The previously added callback.
+ */
+ @AnyThread
+ open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ onInvalidatedCallbacks.remove(onInvalidatedCallback)
+ }
+
+ /**
+ * Signal the data source to stop loading, and notify its callback.
+ *
+ * If invalidate has already been called, this method does nothing.
+ */
+ @AnyThread
+ open fun invalidate() {
+ if (_invalid.compareAndSet(false, true)) {
+ onInvalidatedCallbacks.forEach { it.onInvalidated() }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ enum class LoadType {
+ INITIAL,
+ START,
+ END
+ }
+
+ /**
+ * @param K Type of the key used to query the [DataSource].
+ * @property key Can be `null` for init, otherwise non-null
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ class Params<K : Any> internal constructor(
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ val type: LoadType,
+ val key: K?,
+ val initialLoadSize: Int,
+ val placeholdersEnabled: Boolean,
+ val pageSize: Int
+ )
+
+ /**
+ * @param Value Type of the data produced by a [DataSource].
+ * @property counted Set to true if the result is an initial load that is passed totalCount
+ */
+ open class BaseResult<Value : Any> protected constructor(
+ @JvmField
+ val data: List<Value>,
+ val prevKey: Any?,
+ val nextKey: Any?,
+ val leadingNulls: Int,
+ val trailingNulls: Int,
+ val offset: Int,
+ val counted: Boolean
+ ) {
+ init {
+ validate()
+ }
+
+ // only one of leadingNulls / offset may be used
+ private fun position() = leadingNulls + offset
+
+ internal fun totalCount() = when {
+ // only one of leadingNulls / offset may be used
+ counted -> position() + data.size + trailingNulls
+ else -> TOTAL_COUNT_UNKNOWN
+ }
+
+ private fun validate() {
+ if (leadingNulls < 0 || offset < 0) {
+ throw IllegalArgumentException("Position must be non-negative")
+ }
+ if (data.isEmpty() && (leadingNulls != 0 || trailingNulls != 0)) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are" + " present in data set."
+ )
+ }
+ if (trailingNulls < 0) {
+ throw IllegalArgumentException(
+ "List size + position too large, last item in list beyond totalCount."
+ )
+ }
+ }
+
+ internal fun validateForInitialTiling(pageSize: Int) {
+ if (!counted) {
+ throw IllegalStateException(
+ "Placeholders requested, but totalCount not provided. Please call the" +
+ " three-parameter onResult method, or disable placeholders in the" +
+ " PagedList.Config"
+ )
+ }
+ if (trailingNulls != 0 && data.size % pageSize != 0) {
+ val totalCount = leadingNulls + data.size + trailingNulls
+ throw IllegalArgumentException(
+ "PositionalDataSource requires initial load size to be a multiple of page" +
+ " size to support internal tiling. loadSize ${data.size}, position" +
+ " $leadingNulls, totalCount $totalCount, pageSize $pageSize"
+ )
+ }
+ if (position() % pageSize != 0) {
+ throw IllegalArgumentException(
+ "Initial load must be pageSize aligned.Position = ${position()}, pageSize =" +
+ " $pageSize"
+ )
+ }
+ }
+
+ override fun equals(other: Any?) = when (other) {
+ is BaseResult<*> -> data == other.data &&
+ prevKey == other.prevKey &&
+ nextKey == other.nextKey &&
+ leadingNulls == other.leadingNulls &&
+ trailingNulls == other.trailingNulls &&
+ offset == other.offset &&
+ counted == other.counted
+ else -> false
+ }
+
+ internal companion object {
+ internal fun <T : Any> empty() = BaseResult(emptyList<T>(), null, null, 0, 0, 0, true)
+
+ internal const val TOTAL_COUNT_UNKNOWN = -1
+
+ internal fun <ToValue : Any, Value : Any> convert(
+ result: BaseResult<ToValue>,
+ function: Function<List<ToValue>, List<Value>>
+ ) = BaseResult(
+ data = convert(function, result.data),
+ prevKey = result.prevKey,
+ nextKey = result.nextKey,
+ leadingNulls = result.leadingNulls,
+ trailingNulls = result.trailingNulls,
+ offset = result.offset,
+ counted = result.counted
+ )
+ }
+ }
+
+ internal enum class KeyType {
+ POSITIONAL,
+ PAGE_KEYED,
+ ITEM_KEYED
+ }
+
+ internal abstract fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ internal abstract fun getKeyInternal(item: Value): Key
+
+ /**
+ * Determine whether an error passed to a loading method is retryable.
+ *
+ * @param error Throwable returned from an attempted load from this DataSource.
+ * @return `true` if the error is retryable, otherwise false.
+ */
+ open fun isRetryableError(error: Throwable) = false
+
+ internal companion object {
+ internal fun <A, B> convert(
+ function: Function<List<A>, List<B>>,
+ source: List<A>
+ ): List<B> {
+ val dest = function.apply(source)
+ if (dest.size != source.size) {
+ throw IllegalStateException(
+ "Invalid Function $function changed return size. This is not supported."
+ )
+ }
+ return dest
+ }
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
new file mode 100644
index 0000000..ef09669
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.paging.futures.DirectExecutor
+
+/**
+ * InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
+ *
+ * It's used solely for listening to [PagedList.LoadType.REFRESH] loading events, and retrying
+ * any errors that occur during initial load.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class InitialPagedList<K : Any, V : Any>(
+ dataSource: DataSource<K, V>,
+ config: Config,
+ initialKey: K?
+) :
+ ContiguousPagedList<K, V>(
+ dataSource,
+ DirectExecutor,
+ DirectExecutor,
+ null,
+ config,
+ DataSource.BaseResult.empty<V>(),
+ 0 // no previous load, so pass 0
+ ) {
+ override val lastKey = initialKey
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
new file mode 100644
index 0000000..1924dca
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ *
+ * Implement a DataSource using ItemKeyedDataSource if you need to use data from item `N - 1`
+ * to load item `N`. This is common, for example, in uniquely sorted database queries where
+ * attributes of the item such just before the next query define how to execute it.
+ *
+ * The `InMemoryByItemRepository` in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network ItemKeyedDataSource using
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network errors,
+ * and retry.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ *
+ * @see ListenableItemKeyedDataSource
+ */
+abstract class ItemKeyedDataSource<Key : Any, Value : Any> :
+ ListenableItemKeyedDataSource<Key, Value>() {
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ */
+ open class LoadInitialParams<Key : Any>(
+ requestedInitialKey: Key?,
+ requestedLoadSize: Int,
+ placeholdersEnabled: Boolean
+ ) : ListenableItemKeyedDataSource.LoadInitialParams<Key>(
+ requestedInitialKey,
+ requestedLoadSize,
+ placeholdersEnabled
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query Value types out of the [DataSource].
+ */
+ open class LoadParams<Key : Any>(key: Key, requestedLoadSize: Int) :
+ ListenableItemKeyedDataSource.LoadParams<Key>(key, requestedLoadSize)
+
+ /**
+ * Callback for [loadInitial]
+ * 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 [onResult] to pass that information. You can skip passing this
+ * information by calling the single parameter [onResult], either if it's difficult to compute,
+ * or if [LoadInitialParams.placeholdersEnabled] is `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 Value Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from your DataSource's `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 `N`
+ * items before the items in data that can be loaded from this DataSource,
+ * pass `N`.
+ * @param totalCount Total number of items that may be returned from this [DataSource].
+ * Includes the number in the initial `data` parameter as well as any
+ * items that can be loaded in front or behind of `data`.
+ */
+ abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
+ }
+
+ /**
+ * Callback for ItemKeyedDataSource [loadBefore] and [loadAfter] 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 Value Type of items being loaded.
+ */
+ abstract class LoadCallback<Value> {
+ /**
+ * Called to pass loaded data from a DataSource.
+ *
+ * Call this method from your ItemKeyedDataSource's [loadBefore] and [loadAfter] methods to
+ * return data.
+ *
+ * Call this from [loadInitial] 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].
+ */
+ abstract fun onResult(data: List<Value>)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial], [loadBefore], or [loadAfter]
+ * methods.
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ final override fun loadInitial(
+ params: ListenableItemKeyedDataSource.LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Value>> {
+ val future = ResolvableFuture.create<InitialResult<Value>>()
+ executor.execute {
+ val callback = object : LoadInitialCallback<Value>() {
+ override fun onResult(data: List<Value>, position: Int, totalCount: Int) {
+ future.set(InitialResult(data, position, totalCount))
+ }
+
+ override fun onResult(data: List<Value>) {
+ future.set(InitialResult(data))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(
+ LoadInitialParams(
+ params.requestedInitialKey,
+ params.requestedLoadSize,
+ params.placeholdersEnabled
+ ),
+ callback
+ )
+ }
+ return future
+ }
+
+ private fun getFutureAsCallback(future: ResolvableFuture<Result<Value>>): LoadCallback<Value> {
+ return object : LoadCallback<Value>() {
+ override fun onResult(data: List<Value>) {
+ future.set(Result(data))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ }
+
+ final override fun loadBefore(
+ params: ListenableItemKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Value>> {
+ val future = ResolvableFuture.create<Result<Value>>()
+ executor.execute {
+ val loadParams = LoadParams(params.key, params.requestedLoadSize)
+ loadBefore(loadParams, getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ final override fun loadAfter(
+ params: ListenableItemKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Value>> {
+ val future = ResolvableFuture.create<Result<Value>>()
+ executor.execute {
+ val loadParams = LoadParams(params.key, params.requestedLoadSize)
+ loadAfter(loadParams, getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ /**
+ * 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 [LoadInitialCallback.onResult]. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ *
+ * [LoadInitialParams.requestedInitialKey] and [LoadInitialParams.requestedLoadSize]
+ * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
+ * `requestedInitialKey` can prevent subsequent PagedList/DataSource pairs from
+ * initializing at the same location. If your DataSource never invalidates (for example,
+ * loading from the network without the network ever signalling that old data must be reloaded),
+ * it's fine to ignore the `initialLoadKey` and always start from the beginning of the
+ * data set.
+ *
+ * @param params Parameters for initial load, including initial key and requested size.
+ * @param callback Callback that receives initial load data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams<Key>, callback: LoadInitialCallback<Value>)
+
+ /**
+ * Load list data after the key specified in [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred 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 [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.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
+
+ /**
+ * Load list data before the key specified in [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred 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 [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.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
+
+ /**
+ * Return a key associated with the given item.
+ *
+ * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
+ * integer ID, you would return `item.getID()` here. This key can then be passed to
+ * [loadBefore] or [loadAfter] to load additional items adjacent to the item passed to this
+ * function.
+ *
+ * If your key is more complex, such as when you're sorting by name, then resolving collisions
+ * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+ * such as `Pair<String, Integer>` or, in Kotlin,
+ * `data class Key(val name: String, val id: Int)`
+ *
+ * @param item Item to get the key from.
+ * @return Key associated with given item.
+ */
+ abstract override fun getKey(item: Value): Key
+
+ final override fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): ItemKeyedDataSource<Key, ToValue> = WrapperItemKeyedDataSource(this, function)
+
+ final override fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): ItemKeyedDataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt
new file mode 100644
index 0000000..161a583
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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.VisibleForTesting
+import java.util.ArrayList
+
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+class ListDataSource<T : Any>(list: List<T>) : PositionalDataSource<T>() {
+ private val list: List<T> = ArrayList(list)
+
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
+ val totalCount = list.size
+ val position = computeInitialLoadPosition(params, totalCount)
+ val loadSize = computeInitialLoadSize(params, position, totalCount)
+
+ // for simplicity, we could return everything immediately,
+ // but we tile here since it's expected behavior
+ val sublist = list.subList(position, position + loadSize)
+ callback.onResult(sublist, position, totalCount)
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
+ val end = minOf(list.size, params.startPosition + params.loadSize)
+ callback.onResult(list.subList(params.startPosition, end))
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt
new file mode 100644
index 0000000..2289b23
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.RestrictTo
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ *
+ * Implement a DataSource using ListenableItemKeyedDataSource if you need to use data from item
+ * `N - 1` to load item `N`. This is common, for example, in uniquely sorted database
+ * queries where attributes of the item such just before the next query define how to execute it.
+ *
+ * @see ItemKeyedDataSource
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class ListenableItemKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(KeyType.ITEM_KEYED) {
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>> {
+ when (params.type) {
+ LoadType.INITIAL -> {
+ val initParams = ItemKeyedDataSource.LoadInitialParams(
+ params.key, params.initialLoadSize, params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ }
+ LoadType.START -> {
+ val loadParams = ItemKeyedDataSource.LoadParams(params.key!!, params.pageSize)
+ return loadBefore(loadParams)
+ }
+ LoadType.END -> {
+ val loadParams = ItemKeyedDataSource.LoadParams(params.key!!, params.pageSize)
+ return loadAfter(loadParams)
+ }
+ }
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @property requestedInitialKey Load items around this key, or at the beginning of the data set
+ * if `null` is passed.
+ *
+ * Note that this key is generally a hint, and may be ignored if
+ * you want to always load from the beginning.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+ * loaded total count will be ignored.
+ */
+ open class LoadInitialParams<Key : Any>(
+ @JvmField val requestedInitialKey: Key?,
+ @JvmField val requestedLoadSize: Int,
+ @JvmField val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @property key Load items before/after this key.
+ *
+ * Returned data must begin directly adjacent to this position.
+ * @property requestedLoadSize 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.
+ */
+ open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+
+ /**
+ * 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 `totalCount`
+ * to the [InitialResult] constructor. This enables PagedLists presenting data from this
+ * source to display placeholders to represent unloaded items.
+ *
+ * [ItemKeyedDataSource.LoadInitialParams.requestedInitialKey] and
+ * [ItemKeyedDataSource.LoadInitialParams.requestedLoadSize] are hints, not requirements,
+ * so they may be altered or ignored. Note that ignoring the `requestedInitialKey` can
+ * prevent subsequent PagedList/DataSource pairs from initializing at the same location. If your
+ * DataSource never invalidates (for example, loading from the network without the network ever
+ * signalling that old data must be reloaded), it's fine to ignore the `initialLoadKey`
+ * and always start from the beginning of the data set.
+ *
+ * @param params Parameters for initial load, including initial key and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams<Key>): ListenableFuture<InitialResult<Value>>
+
+ /**
+ * Load list data after the key specified in
+ * [LoadParams.key][ItemKeyedDataSource.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 preferred to increase the number loaded than
+ * reduce.
+ *
+ * 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 [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.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>): ListenableFuture<Result<Value>>
+
+ /**
+ * Load list data after the key specified in
+ * [LoadParams.key][ItemKeyedDataSource.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 preferred to increase the number loaded than
+ * reduce.
+ *
+ * **Note:** Data returned will be prepended just before the key
+ * passed, so if you don't return a page of the requested size, ensure that the last item is
+ * adjacent to the passed 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 preferred to increase the number loaded than
+ * reduce.
+ *
+ * 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 [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.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>): ListenableFuture<Result<Value>>
+
+ abstract fun getKey(item: Value): Key
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ final override fun getKeyInternal(item: Value): Key = getKey(item)
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class InitialResult<V : Any> : BaseResult<V> {
+ constructor(data: List<V>, position: Int, totalCount: Int) : super(
+ data,
+ null,
+ null,
+ position,
+ totalCount - data.size - position,
+ position,
+ true
+ )
+
+ constructor(data: List<V>) : super(data, null, null, 0, 0, 0, false)
+ }
+
+ /**
+ * Type produced by [loadBefore] and [loadAfter] to represent a page of loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class Result<V : Any>(data: List<V>) :
+ DataSource.BaseResult<V>(data, null, null, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt
new file mode 100644
index 0000000..7780b52
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * 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 `N - 1`
+ * to load page `N`. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class ListenablePageKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(KeyType.PAGE_KEYED) {
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>> {
+ if (params.type == LoadType.INITIAL) {
+ val initParams = PageKeyedDataSource.LoadInitialParams<Key>(
+ params.initialLoadSize,
+ params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ } else {
+ if (params.key == null) {
+ // null key, immediately return empty data
+ val future = ResolvableFuture.create<BaseResult<Value>>()
+ future.set(BaseResult.empty())
+ return future
+ }
+
+ val loadParams = PageKeyedDataSource.LoadParams(params.key, params.pageSize)
+
+ if (params.type == LoadType.START) {
+ return loadBefore(loadParams)
+ } else if (params.type == LoadType.END) {
+ return loadAfter(loadParams)
+ }
+ }
+ throw IllegalArgumentException("Unsupported type " + params.type.toString())
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query pages.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+ * loaded total count will be ignored.
+ */
+ open class LoadInitialParams<Key : Any>(
+ @JvmField val requestedLoadSize: Int,
+ @JvmField val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query pages.
+ * @property key Load items before/after this key.
+ *
+ * Returned data must begin directly adjacent to this position.
+ * @property requestedLoadSize 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.
+ */
+ open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+
+ /**
+ * 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 position and
+ * count to the [InitialResult constructor][InitialResult]. This enables PagedLists presenting
+ * data from this source to display placeholders to represent unloaded items.
+ *
+ * [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.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadInitial(
+ params: LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Key, Value>>
+
+ /**
+ * Prepend page with the key specified by [LoadParams.key][PageKeyedDataSource.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 preferred to increase the number loaded than
+ * reduce.
+ *
+ * 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 [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.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>): ListenableFuture<Result<Key, Value>>
+
+ /**
+ * Append page with the key specified by [LoadParams.key][PageKeyedDataSource.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 preferred to increase the number loaded than
+ * reduce.
+ *
+ * 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 [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.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>): ListenableFuture<Result<Key, Value>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ override fun getKeyInternal(item: Value): Key =
+ throw IllegalStateException("Cannot get key by item in pageKeyedDataSource")
+
+ /**
+ * 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.
+ */
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal override val supportsPageDropping = false
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param Key Type of key used to identify pages.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+ open class InitialResult<Key : Any, Value : Any> : BaseResult<Value> {
+ constructor(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ ) : super(
+ data,
+ previousPageKey,
+ nextPageKey,
+ position,
+ totalCount - data.size - position,
+ position,
+ true
+ )
+
+ constructor(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) :
+ super(data, previousPageKey, nextPageKey, 0, 0, 0, false)
+ }
+
+ /**
+ * Type produced by [loadBefore] and [loadAfter] to represent a page of loaded data.
+ *
+ * @param Key Type of key used to identify pages.
+ * @param Value Type of items being loaded by the [DataSource].
+ */
+ open class Result<Key : Any, Value : Any>(data: List<Value>, adjacentPageKey: Key?) :
+ DataSource.BaseResult<Value>(data, adjacentPageKey, adjacentPageKey, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt
new file mode 100644
index 0000000..7058c2f
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.paging.ListenablePositionalDataSource.InitialResult
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
+ *
+ * Extend [ListenablePositionalDataSource] 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), either
+ * use [PageKeyedDataSource] or [ItemKeyedDataSource], or pass the initial result with the two
+ * parameter [InitialResult constructor][InitialResult].
+ *
+ * @param T Type of items being loaded by the [PositionalDataSource].
+ *
+ * @see PositionalDataSource
+ */
+abstract class ListenablePositionalDataSource<T : Any> : DataSource<Int, T>(KeyType.POSITIONAL) {
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Int>): ListenableFuture<out BaseResult<T>> {
+ if (params.type == LoadType.INITIAL) {
+ var initialPosition = 0
+ var initialLoadSize = params.initialLoadSize
+ if (params.key != null) {
+ initialPosition = params.key
+
+ if (params.placeholdersEnabled) {
+ // snap load size to page multiple (minimum two)
+ initialLoadSize =
+ maxOf(initialLoadSize / params.pageSize, 2) * params.pageSize
+
+ // move start so the load is centered around the key, not starting at it
+ val idealStart = initialPosition - initialLoadSize / 2
+ initialPosition = maxOf(0, idealStart / params.pageSize * params.pageSize)
+ } else {
+ // not tiled, so don't try to snap or force multiple of a page size
+ initialPosition -= initialLoadSize / 2
+ }
+ }
+ val initParams = PositionalDataSource.LoadInitialParams(
+ initialPosition,
+ initialLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ } else {
+ var startIndex = params.key!!
+ var loadSize = params.pageSize
+ if (params.type == LoadType.START) {
+ loadSize = minOf(loadSize, startIndex + 1)
+ startIndex = startIndex - loadSize + 1
+ }
+ return loadRange(PositionalDataSource.LoadRangeParams(startIndex, loadSize))
+ }
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ */
+ open 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.
+ */
+ @JvmField
+ val requestedStartPosition: Int,
+ /**
+ * Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ */
+ @JvmField
+ val requestedLoadSize: Int,
+ /**
+ * Defines page size acceptable for return values.
+ *
+ * List of items passed to the callback must be an integer multiple of page size.
+ */
+ @JvmField
+ val pageSize: Int,
+ /**
+ * Defines whether placeholders are enabled, and whether the loaded total count will be
+ * ignored.
+ */
+ @JvmField
+ val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadRange].
+ */
+ open class LoadRangeParams(
+ /**
+ * START position of data to load.
+ *
+ * Returned data must start at this position.
+ */
+ @JvmField
+ val startPosition: Int,
+ /**
+ * Number of items to load.
+ *
+ * Returned data must be of this size, unless at end of the list.
+ */
+ @JvmField
+ val loadSize: Int
+ )
+
+ /**
+ * 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.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams): ListenableFuture<InitialResult<T>>
+
+ /**
+ * Called to load a range of data from the DataSource.
+ *
+ * This method is called to load additional pages from the DataSource after the
+ * [ItemKeyedDataSource.LoadInitialCallback] passed to dispatchLoadInitial has initialized a
+ * [PagedList].
+ *
+ * Unlike [ItemKeyedDataSource.loadInitial], this method must return the number of items
+ * requested, at the position requested.
+ *
+ * @param params Parameters for load, including start position and load size.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadRange(params: LoadRangeParams): ListenableFuture<RangeResult<T>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ final override fun getKeyInternal(item: T): Int =
+ throw IllegalStateException("Cannot get key by item in positionalDataSource")
+
+ internal companion object {
+ /**
+ * Helper for computing an initial position in [loadInitial] 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
+ * }
+ * }
+ * ```
+ *
+ * ```
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @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);
+ * }
+ * ```
+ *
+ * ```
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param totalCount Total size of the data set.
+ * @return Position to start loading at.
+ *
+ * @see [computeInitialLoadSize]
+ */
+ @JvmStatic
+ fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
+ val position = params.requestedStartPosition
+ val initialLoadSize = params.requestedLoadSize
+ val pageSize = params.pageSize
+
+ var pageStart = position / pageSize * pageSize
+
+ // maximum start pos is that which will encompass end of list
+ val maximumLoadPage =
+ (totalCount - initialLoadSize + pageSize - 1) / pageSize * pageSize
+ pageStart = minOf(maximumLoadPage, pageStart)
+
+ // minimum start position is 0
+ pageStart = maxOf(0, pageStart)
+
+ return pageStart
+ }
+
+ /**
+ * Helper for computing an initial load size in [loadInitial] 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 [computeInitialLoadPosition].
+ *
+ * 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
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @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);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested
+ * start / loadSize.
+ * @param initialLoadPosition Value returned by [computeInitialLoadPosition]
+ * @param totalCount Total size of the data set.
+ * @return Number of items to load.
+ *
+ * @see [computeInitialLoadPosition]
+ */
+ @JvmStatic
+ fun computeInitialLoadSize(
+ params: LoadInitialParams,
+ initialLoadPosition: Int,
+ totalCount: Int
+ ) = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
+ }
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class InitialResult<V : Any> : BaseResult<V> {
+ constructor(data: List<V>, position: Int, totalCount: Int) :
+ super(data, null, null, position, totalCount - data.size - position, 0, true) {
+ if (data.isEmpty() && position != 0) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set."
+ )
+ }
+ }
+
+ constructor(data: List<V>, position: Int) : super(data, null, null, 0, 0, position, false) {
+ if (data.isEmpty() && position != 0) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set."
+ )
+ }
+ }
+ }
+
+ /**
+ * Type produced by [loadRange] to represent a page of loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class RangeResult<V : Any>(data: List<V>) : BaseResult<V>(data, null, null, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
new file mode 100644
index 0000000..ad41ea0
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * 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 `N - 1` to
+ * load page `N`. This is common, for example, in network APIs that include a next/previous link or
+ * key with each page load.
+ *
+ * The `InMemoryByPageRepository` in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network PageKeyedDataSource using
+ * [Retrofit](https://square.github.io/retrofit/), while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class PageKeyedDataSource<Key : Any, Value : Any> :
+ ListenablePageKeyedDataSource<Key, Value>() {
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query pages.
+ */
+ open class LoadInitialParams<Key : Any>(requestedLoadSize: Int, placeholdersEnabled: Boolean) :
+ ListenablePageKeyedDataSource.LoadInitialParams<Key>(requestedLoadSize, placeholdersEnabled)
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query pages.
+ */
+ open class LoadParams<Key : Any>(key: Key, requestedLoadSize: Int) :
+ ListenablePageKeyedDataSource.LoadParams<Key>(key, requestedLoadSize)
+
+ /**
+ * Callback for [loadInitial] 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 [onResult] to pass that information. You can skip passing this
+ * information by calling the three parameter [onResult], either if it's difficult to compute,
+ * or if [LoadInitialParams.placeholdersEnabled] is `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 Key Type of data used to query pages.
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<Key, Value> {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from your DataSource's `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 `N`
+ * items before the items in data that can be loaded from this DataSource,
+ * pass `N`.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial `data` parameter as well as any
+ * items that can be loaded in front or behind of `data`.
+ */
+ abstract fun onResult(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ )
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ *
+ * Call this from [loadInitial] 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 `null` if no more
+ * data can be loaded before.
+ * @param nextPageKey Key for page after the initial load result, or `null` if no
+ * more data can be loaded after.
+ */
+ abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * Callback for [loadBefore] and [loadAfter] 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 Key Type of data used to query pages.
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadCallback<Key, Value> {
+ /**
+ * Called to pass loaded data from a [DataSource].
+ *
+ * Call this method from your PageKeyedDataSource's [loadBefore] and [loadAfter] 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 [loadBefore], pass the key for the previous page, or `null` if the
+ * loaded page is the first. If in [loadAfter], pass the key for the next page, or `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 [loadBefore] / next
+ * page in [loadAfter]), or `null` if there are no more pages to load
+ * in the current load direction.
+ */
+ abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from your PageKeyedDataSource's [loadBefore] and
+ * [loadAfter] methods.
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ final override fun loadInitial(
+ params: ListenablePageKeyedDataSource.LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Key, Value>> {
+ val future = ResolvableFuture.create<InitialResult<Key, Value>>()
+ executor.execute {
+ val callback = object : LoadInitialCallback<Key, Value>() {
+ override fun onResult(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ ) {
+ future.set(
+ InitialResult(data, position, totalCount, previousPageKey, nextPageKey)
+ )
+ }
+
+ override fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) {
+ future.set(InitialResult(data, previousPageKey, nextPageKey))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(
+ LoadInitialParams(params.requestedLoadSize, params.placeholdersEnabled),
+ callback
+ )
+ }
+ return future
+ }
+
+ private fun getFutureAsCallback(future: ResolvableFuture<Result<Key, Value>>) =
+ object : LoadCallback<Key, Value>() {
+ override fun onResult(data: List<Value>, adjacentPageKey: Key?) {
+ future.set(Result(data, adjacentPageKey))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+
+ final override fun loadBefore(
+ params: ListenablePageKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Key, Value>> {
+ val future = ResolvableFuture.create<Result<Key, Value>>()
+ executor.execute {
+ loadBefore(
+ LoadParams(params.key, params.requestedLoadSize),
+ getFutureAsCallback(future)
+ )
+ }
+ return future
+ }
+
+ final override fun loadAfter(
+ params: ListenablePageKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Key, Value>> {
+ val future = ResolvableFuture.create<Result<Key, Value>>()
+ executor.execute {
+ loadAfter(LoadParams(params.key, params.requestedLoadSize), getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ /**
+ * 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 [LoadInitialCallback.onResult]. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ *
+ * [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.
+ */
+ abstract fun loadInitial(
+ params: LoadInitialParams<Key>,
+ callback: LoadInitialCallback<Key, Value>
+ )
+
+ /**
+ * Prepend page with the key specified by [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 preferred 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 [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.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+
+ /**
+ * Append page with the key specified by [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 preferred 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 [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.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+
+ final override fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): PageKeyedDataSource<Key, ToValue> = WrapperPageKeyedDataSource(this, function)
+
+ final override fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): PageKeyedDataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
new file mode 100644
index 0000000..daf0c3b
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -0,0 +1,1370 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import androidx.paging.PagedList.Callback
+import androidx.paging.PagedList.Config
+import androidx.paging.PagedList.Config.Builder
+import androidx.paging.PagedList.Config.Companion.MAX_SIZE_UNBOUNDED
+import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.transform
+import com.google.common.util.concurrent.ListenableFuture
+import java.lang.ref.WeakReference
+import java.util.AbstractList
+import java.util.ArrayList
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+
+/**
+ * Lazy loading list that pages in immutable content from a [DataSource].
+ *
+ * A PagedList is a [List] which loads its data in chunks (pages) from a [DataSource]. Items can be
+ * accessed with [get], and further loading can be triggered with [loadAround]. To display a
+ * PagedList, see [androidx.paging.PagedListAdapter], which enables the binding of a PagedList to a
+ * [androidx.recyclerview.widget.RecyclerView].
+ *
+ * <h4>Loading Data</h4>
+ *
+ * All data in a PagedList is loaded from its [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 [loadAround] is called, items will be loaded in near the passed
+ * list index. If placeholder `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 [Config] to control how many items a [PagedList] loads, and when.
+ *
+ * If you use [androidx.paging.LivePagedListBuilder] to get a [androidx.lifecycle.LiveData], it will
+ * initialize PagedLists on a background thread for you.
+ *
+ * <h4>Placeholders</h4>
+ *
+ * There are two ways that [PagedList] can represent its not-yet-loaded data - with or without
+ * `null` placeholders.
+ *
+ * With placeholders, the [PagedList] is always the full size of the data set. `get(N)` returns
+ * the `N`th item in the data set, or `null` if its not yet loaded.
+ *
+ * Without `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 `get(N)`
+ * returns the `N`th *loaded* item. This is not necessarily the `N`th item in the
+ * data set.
+ *
+ * Placeholders have several benefits:
+ *
+ * * They express the full sized list to the presentation layer (often a
+ * [androidx.paging.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 `null` items. This often means providing default
+ * values in data you bind to a [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 `false` is passed to
+ * [Config.Builder.setEnablePlaceholders] when building a [Config].
+ *
+ * <h4>Mutability and Snapshots</h4>
+ *
+ * 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 [Callback]. (Note that [androidx.paging.PagedListAdapter] will
+ * listen to these to signal RecyclerView about the updates/changes).
+ *
+ * If a [PagedList] attempts to load from an invalid [DataSource], it will [detach] from the
+ * [DataSource], meaning that it will no longer attempt to load data. It will return true from
+ * [isImmutable], and a new [DataSource] / [PagedList] pair must be created to load further data.
+ *
+ * See [DataSource] and [androidx.paging.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 `List`. It will reference the same inner items, and contain the same `null`
+ * placeholders, if present.
+ *
+ * @param T The type of the entries in the list.
+ */
+abstract class PagedList<T : Any> : AbstractList<T> {
+ internal companion object {
+ /**
+ * 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 tat 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 K Key type that indicates to the DataSource what data to load.
+ * @param T Type of items to be held and loaded by the PagedList.
+ *
+ * @return [ListenableFuture] for newly created [PagedList], which will page in data from
+ * the [DataSource] as needed.
+ */
+ @JvmStatic
+ internal fun <K : Any, T : Any> create(
+ dataSource: DataSource<K, T>,
+ notifyExecutor: Executor,
+ fetchExecutor: Executor,
+ initialLoadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<T>?,
+ config: Config,
+ key: K?
+ ): ListenableFuture<PagedList<T>> {
+ dataSource.initExecutor(initialLoadExecutor)
+
+ val lastLoad = when {
+ dataSource.type == DataSource.KeyType.POSITIONAL && key != null -> key as Int
+ else -> ContiguousPagedList.LAST_LOAD_UNSPECIFIED
+ }
+
+ val params = DataSource.Params(
+ DataSource.LoadType.INITIAL,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ return dataSource.load(params).transform(
+ Function { initialResult ->
+ dataSource.initExecutor(fetchExecutor)
+ ContiguousPagedList(
+ dataSource,
+ notifyExecutor,
+ fetchExecutor,
+ boundaryCallback,
+ config,
+ initialResult,
+ lastLoad
+ )
+ },
+ DirectExecutor
+ )
+ }
+ }
+
+ /**
+ * Type of load a PagedList can perform.
+ *
+ * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the [androidx.paging.PagedListAdapter] or
+ * [androidx.paging.AsyncPagedListDiffer].
+ *
+ * @see LoadState
+ */
+ enum class LoadType {
+ /**
+ * PagedList content being reloaded, may contain content updates.
+ */
+ REFRESH,
+
+ /**
+ * Load at the start of the PagedList.
+ */
+ START,
+
+ /**
+ * Load at the end of the PagedList.
+ */
+ END
+ }
+
+ /**
+ * State of a PagedList load - associated with a `LoadType`
+ *
+ * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the `PagedListAdapter` or `AsyncPagedListDiffer`.
+ */
+ enum class LoadState {
+ /**
+ * Indicates the PagedList is not currently loading, and no error currently observed.
+ */
+ IDLE,
+
+ /**
+ * Loading is in progress.
+ */
+ LOADING,
+
+ /**
+ * Loading is complete.
+ */
+ DONE,
+
+ /**
+ * Loading hit a non-retryable error.
+ */
+ ERROR,
+
+ /**
+ * Loading hit a retryable error.
+ *
+ * @see .retry
+ */
+ RETRYABLE_ERROR
+ }
+
+ /**
+ * Listener for changes to loading state - whether the refresh, prepend, or append is idle,
+ * loading, or has an error.
+ *
+ * Can be used to observe the [LoadState] of any [LoadType] (REFRESH/START/END). For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the [PagedListAdapter] or [AsyncPagedListDiffer].
+ *
+ * These calls will be dispatched on the executor defined by [Builder.setNotifyExecutor], which
+ * is generally the main/UI thread.
+ *
+ * @see LoadType
+ *
+ * @see LoadState
+ */
+ interface LoadStateListener {
+ /**
+ * Called when the LoadState has changed - whether the refresh, prepend, or append is idle,
+ * loading, or has an error.
+ *
+ * REFRESH events can be used to drive a
+ * [androidx.swiperefreshlayout.widget.SwipeRefreshLayout], or START/END events can be used
+ * to drive loading spinner items in your `RecyclerView`.
+ *
+ * @param type Type of load - START, END, or REFRESH.
+ * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
+ * @param error Error, if in an error state, null otherwise.
+ *
+ * @see [retry]
+ */
+ fun onLoadStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ }
+
+ /**
+ * 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.
+ *
+ * [androidx.paging.LivePagedListBuilder] does this creation on a background thread
+ * automatically, if you want to receive a `LiveData<PagedList<...>>`.
+ *
+ * @param Key Type of key used to load data from the [DataSource].
+ * @param Value Type of items held and loaded by the [PagedList].
+ */
+ class Builder<Key : Any, Value : Any> {
+ private val dataSource: DataSource<Key, Value>
+ private val config: Config
+ private var notifyExecutor: Executor? = null
+ private var fetchExecutor: Executor? = null
+ private var boundaryCallback: BoundaryCallback<Value>? = null
+ private var initialKey: Key? = null
+
+ /**
+ * Create a PagedList.Builder with the provided [DataSource] and [Config].
+ *
+ * @param dataSource [DataSource] the [PagedList] will load from.
+ * @param config [Config] that defines how the [PagedList] loads data from its [DataSource].
+ */
+ constructor(dataSource: DataSource<Key, Value>, config: Config) {
+ this.dataSource = dataSource
+ this.config = config
+ }
+
+ /**
+ * Create a [PagedList.Builder] with the provided [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].
+ */
+ constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
+ dataSource,
+ PagedList.Config.Builder().setPageSize(pageSize).build()
+ )
+
+ /**
+ * The executor defining where page loading updates are dispatched.
+ *
+ * @param notifyExecutor Executor that receives [PagedList] updates, and where [Callback]
+ * calls are dispatched. Generally, this is the ui/main thread.
+ * @return this
+ */
+ fun setNotifyExecutor(notifyExecutor: Executor) = apply {
+ this.notifyExecutor = notifyExecutor
+ }
+
+ /**
+ * 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
+ */
+ fun setFetchExecutor(fetchExecutor: Executor) = apply {
+ this.fetchExecutor = fetchExecutor
+ }
+
+ /**
+ * 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
+ */
+ fun setBoundaryCallback(boundaryCallback: BoundaryCallback<Value>?) = apply {
+ this.boundaryCallback = boundaryCallback
+ }
+
+ /**
+ * 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
+ */
+ fun setInitialKey(initialKey: Key?) = apply {
+ this.initialKey = initialKey
+ }
+
+ /**
+ * Creates a [PagedList] with the given parameters.
+ *
+ * This call will dispatch the [androidx.paging.DataSource]'s loadInitial method immediately
+ * on the current thread, and block the current on the result. This method should always be
+ * called on a worker thread to prevent blocking the main thread.
+ *
+ * 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
+ * `LiveData<PagedList>` can be safely constructed with
+ * [androidx.paging.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 [detached][PagedList.isDetached], and you can retry
+ * construction (including setting a new [DataSource]).
+ *
+ * @return The newly constructed [PagedList]
+ */
+ @WorkerThread
+ @Deprecated(
+ "This method has no means of handling errors encountered during initial load, and" +
+ " blocks on the initial load result. Use {@link #buildAsync()} instead."
+ )
+ fun build(): PagedList<Value> {
+ // TODO: define defaults, once they can be used in module without android dependency
+ if (notifyExecutor == null) {
+ throw IllegalArgumentException("MainThreadExecutor required")
+ }
+ if (fetchExecutor == null) {
+ throw IllegalArgumentException("BackgroundThreadExecutor required")
+ }
+
+ try {
+ return create(DirectExecutor).get()
+ } catch (e: InterruptedException) {
+ throw RuntimeException(e)
+ } catch (e: ExecutionException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ /**
+ * Creates a [PagedList] asynchronously with the given parameters.
+ *
+ * This call will dispatch the [DataSource]'s loadInitial method immediately, and
+ * return a `ListenableFuture<PagedList<T>>` that will resolve (triggering listeners)
+ * once the initial load is completed (success or failure).
+ *
+ * @return The newly constructed PagedList
+ */
+ @Suppress("unused")
+ fun buildAsync(): ListenableFuture<PagedList<Value>> {
+ // TODO: define defaults, once they can be used in module without android dependency
+ if (notifyExecutor == null) {
+ throw IllegalArgumentException("MainThreadExecutor required")
+ }
+ if (fetchExecutor == null) {
+ throw IllegalArgumentException("BackgroundThreadExecutor required")
+ }
+
+ return create(fetchExecutor!!)
+ }
+
+ private fun create(initialFetchExecutor: Executor): ListenableFuture<PagedList<Value>> =
+ create(
+ dataSource,
+ notifyExecutor!!,
+ fetchExecutor!!,
+ initialFetchExecutor,
+ boundaryCallback,
+ config,
+ initialKey
+ )
+ }
+
+ /**
+ * 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 [Builder.setNotifyExecutor], which is generally the main/UI thread.
+ */
+ abstract 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.
+ */
+ abstract fun onChanged(position: Int, count: Int)
+
+ /**
+ * 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.
+ */
+ abstract fun onInserted(position: Int, count: Int)
+
+ /**
+ * 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.
+ */
+ abstract fun onRemoved(position: Int, count: Int)
+ }
+
+ /**
+ * Configures how a [PagedList] loads content from its [DataSource].
+ *
+ * Use [Config.Builder] to construct and define custom loading behavior, such as
+ * [Builder.setPageSize], which defines number of items loaded at a time}.
+ */
+ open class Config internal constructor(
+ /**
+ * Size of each page loaded by the PagedList.
+ */
+ @JvmField
+ val pageSize: Int,
+ /**
+ * 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
+ */
+ @JvmField
+ val prefetchDistance: Int,
+ /**
+ * Defines whether the PagedList may display null placeholders, if the DataSource provides
+ * them.
+ */
+ @JvmField
+ val enablePlaceholders: Boolean,
+ /**
+ * Size hint for initial load of PagedList, often larger than a regular page.
+ */
+ @JvmField
+ val initialLoadSizeHint: Int,
+ /**
+ * Defines the maximum number of items that may be loaded into this pagedList before pages
+ * should be dropped.
+ *
+ * [PageKeyedDataSource] does not currently support dropping pages - when loading from a
+ * [PageKeyedDataSource], this value is ignored.
+ *
+ * @see MAX_SIZE_UNBOUNDED
+ * @see Builder.setMaxSize
+ */
+ @JvmField
+ val maxSize: Int
+ ) {
+ /**
+ * Builder class for [Config].
+ *
+ * You must at minimum specify page size with [setPageSize].
+ */
+ class Builder {
+ private var pageSize = -1
+ private var prefetchDistance = -1
+ private var initialLoadSizeHint = -1
+ private var enablePlaceholders = true
+ private var maxSize = 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
+ */
+ fun setPageSize(@IntRange(from = 1) pageSize: Int) = apply {
+ if (pageSize < 1) {
+ throw IllegalArgumentException("Page size must be a positive number")
+ }
+ this.pageSize = pageSize
+ }
+
+ /**
+ * 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
+ */
+ fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int) = apply {
+ this.prefetchDistance = prefetchDistance
+ }
+
+ /**
+ * 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 `setEnablePlaceholders(false)` to ensure the receiver of the PagedList
+ * (often a [androidx.paging.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 [PagedList.Callback].
+ *
+ * [PagedList.Callback.onChanged] will not be issued as part of loading, though a
+ * [androidx.paging.PagedListAdapter] may still receive change events as a result of
+ * [PagedList] diffing.
+ *
+ * @param enablePlaceholders `false` if null placeholders should be disabled.
+ * @return this
+ */
+ fun setEnablePlaceholders(enablePlaceholders: Boolean) = apply {
+ this.enablePlaceholders = enablePlaceholders
+ }
+
+ /**
+ * 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 [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
+ */
+ fun setInitialLoadSizeHint(@IntRange(from = 1) initialLoadSizeHint: Int) = apply {
+ this.initialLoadSizeHint = initialLoadSizeHint
+ }
+
+ /**
+ * 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 [prefetchDistance][setPrefetchDistance]
+ * plus the [pageSize][setPageSize]). 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 [requested pageSize][Config.pageSize], so
+ * two pages may be larger than expected.
+ * * Pages are never dropped if they are within a prefetch window (defined to be
+ * `pageSize + (2 * prefetchDistance)`) of the most recent load.
+ *
+ * [PageKeyedDataSource] does not currently support dropping pages - when
+ * loading from a [PageKeyedDataSource], this value is ignored.
+ *
+ * If not set, defaults to [MAX_SIZE_UNBOUNDED], which disables page dropping.
+ *
+ * @param maxSize Maximum number of items to keep in memory, or [MAX_SIZE_UNBOUNDED] to
+ * disable page dropping.
+ * @return this
+ *
+ * @see Config.MAX_SIZE_UNBOUNDED
+ * @see Config.maxSize
+ */
+ fun setMaxSize(@IntRange(from = 2) maxSize: Int) = apply {
+ this.maxSize = maxSize
+ }
+
+ /**
+ * Creates a [Config] with the given parameters.
+ *
+ * @return A new [Config].
+ */
+ fun build(): Config {
+ if (prefetchDistance < 0) {
+ prefetchDistance = pageSize
+ }
+ if (initialLoadSizeHint < 0) {
+ initialLoadSizeHint = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
+ }
+ if (!enablePlaceholders && prefetchDistance == 0) {
+ throw 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 (maxSize != MAX_SIZE_UNBOUNDED && maxSize < pageSize + prefetchDistance * 2) {
+ throw IllegalArgumentException(
+ "Maximum size must be at least pageSize + 2*prefetchDist" +
+ ", pageSize=$pageSize, prefetchDist=$prefetchDistance" +
+ ", maxSize=$maxSize"
+ )
+ }
+
+ return Config(
+ pageSize,
+ prefetchDistance,
+ enablePlaceholders,
+ initialLoadSizeHint,
+ maxSize
+ )
+ }
+
+ internal companion object {
+ internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
+ }
+ }
+
+ internal companion object {
+ /**
+ * When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
+ * unbounded, and pages will never be dropped.
+ */
+ const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
+ }
+ }
+
+ /**
+ * 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 `LiveData<PagedList>`, 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, [onItemAtEndLoaded] 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 `LiveData<PagedList>` will update automatically to account for the new items.
+ *
+ * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
+ * [androidx.paging.LivePagedListBuilder.setBoundaryCallback], the callbacks may be issued
+ * multiple times. If for example [onItemAtEndLoaded] triggers a network load, it should avoid
+ * triggering it again while the load is ongoing.
+ *
+ * The database + network Repository in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network BoundaryCallback using
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh,
+ * network errors, and retry.
+ *
+ * <h4>Requesting Network Data</h4>
+ * [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:
+ *
+ * <h5>Local storage Page key</h5>
+ * 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 `listSize` as an input to the next load (or
+ * `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.
+ * <h5>In-Memory Page key</h5>
+ * 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 `LiveData`/`Observable` of
+ * `PagedList`, refresh data. For example,
+ * [in the Paging Codelab](https://codelabs.developers.google.com/codelabs/android-paging/index.html#8),
+ * the GitHub network page index is stored in memory.
+ *
+ * @param T Type loaded by the PagedList.
+ */
+ @MainThread
+ abstract class BoundaryCallback<T> {
+ /**
+ * Called when zero items are returned from an initial load of the PagedList's data source.
+ */
+ open fun onZeroItemsLoaded() {}
+
+ /**
+ * Called when the item at the front of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ * No more data will be prepended to the PagedList before this item.
+ *
+ * @param itemAtFront The first item of PagedList
+ */
+ open fun onItemAtFrontLoaded(itemAtFront: T) {}
+
+ /**
+ * Called when the item at the end of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ * No more data will be appended to the [PagedList] after this item.
+ *
+ * @param itemAtEnd The first item of [PagedList]
+ */
+ open fun onItemAtEndLoaded(itemAtEnd: T) {}
+ }
+
+ internal abstract class LoadStateManager {
+ var refresh = LoadState.IDLE
+ private set
+ private var mRefreshError: Throwable? = null
+ var start = LoadState.IDLE
+ private set
+ private var mStartError: Throwable? = null
+ var end = LoadState.IDLE
+ private set
+ private var mEndError: Throwable? = null
+
+ fun setState(type: LoadType, state: LoadState, error: Throwable?) {
+ val expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR
+ val hasError = error != null
+ if (expectError != hasError) {
+ throw IllegalArgumentException(
+ "Error states must be accompanied by a throwable, other states must not"
+ )
+ }
+
+ // deduplicate signals
+ when (type) {
+ LoadType.REFRESH -> {
+ if (refresh == state && mRefreshError == error) return
+ refresh = state
+ mRefreshError = error
+ }
+ LoadType.START -> {
+ if (start == state && mStartError == error) return
+ start = state
+ mStartError = error
+ }
+ LoadType.END -> {
+ if (end == state && mEndError == error) return
+ end = state
+ mEndError = error
+ }
+ }
+ onStateChanged(type, state, error)
+ }
+
+ protected abstract fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+
+ fun dispatchCurrentLoadState(listener: LoadStateListener) {
+ listener.onLoadStateChanged(LoadType.REFRESH, refresh, mRefreshError)
+ listener.onLoadStateChanged(LoadType.START, start, mStartError)
+ listener.onLoadStateChanged(LoadType.END, end, mEndError)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ constructor(
+ storage: PagedStorage<T>,
+ mainThreadExecutor: Executor,
+ backgroundThreadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<T>?,
+ config: Config
+ ) : super() {
+ this.storage = storage
+ this.mainThreadExecutor = mainThreadExecutor
+ this.backgroundThreadExecutor = backgroundThreadExecutor
+ this.boundaryCallback = boundaryCallback
+ this.config = config
+ this.callbacks = ArrayList()
+ this.listeners = ArrayList()
+ requiredRemainder = this.config.prefetchDistance * 2 + this.config.pageSize
+ }
+
+ internal val storage: PagedStorage<T>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun getStorage() = storage
+
+ internal val mainThreadExecutor: Executor
+ internal val backgroundThreadExecutor: Executor
+ internal val boundaryCallback: BoundaryCallback<T>?
+
+ internal var refreshRetryCallback: Runnable? = null
+
+ /**
+ * Last access location, in total position space (including offset).
+ *
+ * Used by positional data sources to initialize loading near viewport
+ */
+ internal var lastLoad = 0
+ internal var lastItem: T? = null
+
+ internal val requiredRemainder: Int
+
+ /**
+ * Return the Config used to construct this PagedList.
+ *
+ * @return the Config of this PagedList
+ */
+ open val config: Config
+
+ private val callbacks: MutableList<WeakReference<Callback>>
+
+ private val listeners: MutableList<WeakReference<LoadStateListener>>
+
+ // if set to true, boundaryCallback is non-null, and should
+ // be dispatched when nearby load has occurred
+ private var boundaryCallbackBeginDeferred = false
+
+ private var boundaryCallbackEndDeferred = false
+
+ // lowest and highest index accessed by loadAround. Used to
+ // decide when boundaryCallback should be dispatched
+ private var lowestIndexAccessed = Int.MAX_VALUE
+ private var highestIndexAccessed = Int.MIN_VALUE
+
+ /**
+ * Size of the list, including any placeholders (not-yet-loaded null padding).
+ *
+ * To get the number of loaded items, not counting placeholders, use [loadedCount].
+ *
+ * @see loadedCount
+ */
+ override val size
+ get() = storage.size
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ abstract val isContiguous: Boolean
+
+ /**
+ * The [DataSource] that provides data to this [PagedList].
+ */
+ abstract val dataSource: DataSource<*, T>
+
+ /**
+ * Return the key for the position passed most recently to [loadAround].
+ *
+ * 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 androidx.paging.LivePagedListBuilder, it
+ * will do this for you.
+ *
+ * @return Key of position most recently passed to [loadAround].
+ */
+ abstract val lastKey: Any?
+
+ /**
+ * True if the [PagedList] has detached the [DataSource] it was loading from, and will no longer
+ * load new data.
+ *
+ * A detached list is [immutable][isImmutable].
+ *
+ * @return True if the data source is detached.
+ */
+ abstract val isDetached: Boolean
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun dispatchCurrentLoadState(listener: LoadStateListener)
+
+ /**
+ * Dispatch updates since the non-empty snapshot was taken.
+ *
+ * @param snapshot Non-empty snapshot.
+ * @param callback [Callback] for updates that have occurred since snapshot.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<T>, callback: Callback)
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun loadAroundInternal(index: Int)
+
+ /**
+ * Detach the PagedList from its DataSource, and attempt to load no more data.
+ *
+ * This is called automatically when a DataSource is observed to be invalid, which is a
+ * signal to stop loading. The PagedList will continue to present existing data, but will not
+ * initiate new loads.
+ */
+ abstract fun detach()
+
+ /**
+ * Returns the number of items loaded in the [PagedList].
+ *
+ * Unlike [size] this counts only loaded items, not placeholders.
+ *
+ * If placeholders are [disabled][Config.enablePlaceholders], this method is equivalent to
+ * [size].
+ *
+ * @return Number of items currently loaded, not counting placeholders.
+ *
+ * @see size
+ */
+ open val loadedCount
+ get() = storage.loadedCount
+
+ /**
+ * 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 [isDetached].
+ *
+ * @return `true` if the [PagedList] is immutable.
+ */
+ open val isImmutable
+ get() = isDetached
+
+ /**
+ * Position offset of the data in the list.
+ *
+ * If data is supplied by a [PositionalDataSource], the item returned from `get(i)` has a
+ * position of `i + getPositionOffset()`.
+ *
+ * If the DataSource is a [ItemKeyedDataSource] or [PageKeyedDataSource], it doesn't use
+ * positions, returns 0.
+ */
+ open val positionOffset: Int
+ get() = storage.positionOffset
+
+ internal open fun setInitialLoadState(loadState: LoadState, error: Throwable?) {}
+
+ /**
+ * Retry any retryable errors associated with this [PagedList].
+ *
+ * If for example a network DataSource append timed out, calling this method will retry the
+ * failed append load. Note that your DataSource will need to pass `true` to `onError()` to
+ * signify the error as retryable.
+ *
+ * You can observe loading state via [addWeakLoadStateListener], though generally this is done
+ * through the [PagedListAdapter][androidx.paging.PagedListAdapter] or
+ * [AsyncPagedListDiffer][androidx.paging.AsyncPagedListDiffer].
+ *
+ * @see addWeakLoadStateListener
+ * @see removeWeakLoadStateListener
+ */
+ open fun retry() {}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun setRetryCallback(refreshRetryCallback: Runnable?) {
+ this.refreshRetryCallback = refreshRetryCallback
+ }
+
+ internal fun dispatchStateChange(type: LoadType, state: LoadState, error: Throwable?) {
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ currentListener?.onLoadStateChanged(type, state, error) ?: listeners.removeAt(i)
+ }
+ }
+
+ /**
+ * 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 < [size]
+ * @return The item at the passed index, or `null` if a `null` placeholder is at the specified
+ * position.
+ *
+ * @see size
+ */
+ override fun get(index: Int): T? {
+ val item = storage[index]
+ if (item != null) {
+ lastItem = item
+ }
+ return item
+ }
+
+ /**
+ * Load adjacent items to passed index.
+ *
+ * @param index Index at which to load.
+ */
+ open fun loadAround(index: Int) {
+ if (index < 0 || index >= size) {
+ throw IndexOutOfBoundsException("Index: $index, Size: $size")
+ }
+
+ lastLoad = index + positionOffset
+ loadAroundInternal(index)
+
+ lowestIndexAccessed = minOf(lowestIndexAccessed, index)
+ highestIndexAccessed = maxOf(highestIndexAccessed, index)
+
+ /*
+ * lowestIndexAccessed / highestIndexAccessed 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
+ internal fun deferBoundaryCallbacks(
+ deferEmpty: Boolean,
+ deferBegin: Boolean,
+ deferEnd: Boolean
+ ) {
+ if (boundaryCallback == null) {
+ throw 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 (lowestIndexAccessed == Int.MAX_VALUE) {
+ lowestIndexAccessed = storage.size
+ }
+ if (highestIndexAccessed == Int.MIN_VALUE) {
+ highestIndexAccessed = 0
+ }
+
+ if (deferEmpty || deferBegin || deferEnd) {
+ // Post to the main thread, since we may be on creation thread currently
+ mainThreadExecutor.execute {
+ // on is dispatched immediately, since items won't be accessed
+
+ if (deferEmpty) {
+ boundaryCallback.onZeroItemsLoaded()
+ }
+
+ // for other callbacks, mark deferred, and only dispatch if loadAround
+ // has been called near to the position
+ if (deferBegin) {
+ boundaryCallbackBeginDeferred = true
+ }
+ if (deferEnd) {
+ boundaryCallbackEndDeferred = true
+ }
+ tryDispatchBoundaryCallbacks(false)
+ }
+ }
+ }
+
+ /**
+ * Call this when lowest/HighestIndexAccessed are changed, or boundaryCallbackBegin/EndDeferred
+ * is set.
+ */
+ @Suppress("MemberVisibilityCanBePrivate") // synthetic access
+ internal fun tryDispatchBoundaryCallbacks(post: Boolean) {
+ val dispatchBegin =
+ boundaryCallbackBeginDeferred && lowestIndexAccessed <= config.prefetchDistance
+ val dispatchEnd = boundaryCallbackEndDeferred &&
+ highestIndexAccessed >= size - 1 - config.prefetchDistance
+
+ if (!dispatchBegin && !dispatchEnd) {
+ return
+ }
+
+ if (dispatchBegin) {
+ boundaryCallbackBeginDeferred = false
+ }
+ if (dispatchEnd) {
+ boundaryCallbackEndDeferred = false
+ }
+ if (post) {
+ mainThreadExecutor.execute { dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd) }
+ } else {
+ dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd)
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate") // synthetic access
+ internal fun dispatchBoundaryCallbacks(begin: Boolean, end: Boolean) {
+ // safe to deref boundaryCallback here, since we only defer if boundaryCallback present
+ if (begin) {
+ boundaryCallback!!.onItemAtFrontLoaded(storage.firstLoadedItem!!)
+ }
+ if (end) {
+ boundaryCallback!!.onItemAtEndLoaded(storage.lastLoadedItem!!)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ internal fun offsetAccessIndices(offset: Int) {
+ // update last loadAround index
+ lastLoad += offset
+
+ // update access range
+ lowestIndexAccessed += offset
+ highestIndexAccessed += offset
+ }
+
+ /**
+ * Returns an immutable snapshot of the [PagedList] in its current state.
+ *
+ * If this [PagedList] [is immutable][isImmutable] due to its DataSource being invalid, it will
+ * be returned.
+ *
+ * @return Immutable snapshot of PagedList data.
+ */
+ open fun snapshot(): List<T> = when {
+ isImmutable -> this
+ else -> SnapshotPagedList(this)
+ }
+
+ /**
+ * Add a [LoadStateListener] to observe the loading state of the [PagedList].
+ *
+ * @param listener Listener to receive updates.
+ *
+ * @see removeWeakLoadStateListener
+ */
+ open fun addWeakLoadStateListener(listener: LoadStateListener) {
+ // first, clean up any empty weak refs
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ if (currentListener == null) {
+ listeners.removeAt(i)
+ }
+ }
+
+ // then add the new one
+ listeners.add(WeakReference(listener))
+ dispatchCurrentLoadState(listener)
+ }
+
+ /**
+ * Remove a previously registered [LoadStateListener].
+ *
+ * @param listener Previously registered listener.
+ * @see addWeakLoadStateListener
+ */
+ open fun removeWeakLoadStateListener(listener: LoadStateListener) {
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ if (currentListener == null || currentListener === listener) {
+ // found Listener, or empty weak ref
+ listeners.removeAt(i)
+ }
+ }
+ }
+
+ /**
+ * 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 [androidx.paging.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
+ */
+ open fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
+ if (previousSnapshot != null && previousSnapshot !== this) {
+ if (previousSnapshot.isEmpty()) {
+ if (!storage.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, storage.size)
+ }
+ } else {
+ val storageSnapshot = previousSnapshot as PagedList<T>
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback)
+ }
+ }
+
+ // first, clean up any empty weak refs
+ for (i in callbacks.indices.reversed()) {
+ val currentCallback = callbacks[i].get()
+ if (currentCallback == null) {
+ callbacks.removeAt(i)
+ }
+ }
+
+ // then add the new one
+ callbacks.add(WeakReference(callback))
+ }
+
+ /**
+ * Removes a previously added callback.
+ *
+ * @param callback Callback, previously added.
+ * @see addWeakCallback
+ */
+ open fun removeWeakCallback(callback: Callback) {
+ for (i in callbacks.indices.reversed()) {
+ val currentCallback = callbacks[i].get()
+ if (currentCallback == null || currentCallback === callback) {
+ // found callback, or empty weak ref
+ callbacks.removeAt(i)
+ }
+ }
+ }
+
+ internal fun notifyInserted(position: Int, count: Int) {
+ if (count == 0) return
+
+ for (i in callbacks.indices.reversed()) {
+ val callback = callbacks[i].get()
+ callback?.onInserted(position, count)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun notifyChanged(position: Int, count: Int) {
+ if (count != 0) {
+ for (i in callbacks.indices.reversed()) {
+ callbacks[i].get()?.onChanged(position, count)
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun notifyRemoved(position: Int, count: Int) {
+ if (count != 0) {
+ for (i in callbacks.indices.reversed()) {
+ callbacks[i].get()?.onRemoved(position, count)
+ }
+ }
+ }
+}
diff --git a/paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
similarity index 76%
rename from paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt
rename to paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
index b1671a8..47676e2 100644
--- a/paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -31,15 +31,14 @@
pageSize: Int,
prefetchDistance: Int = pageSize,
enablePlaceholders: Boolean = true,
- initialLoadSizeHint: Int =
- pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER,
+ initialLoadSizeHint: Int = pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER,
maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED
): PagedList.Config {
return PagedList.Config.Builder()
- .setPageSize(pageSize)
- .setPrefetchDistance(prefetchDistance)
- .setEnablePlaceholders(enablePlaceholders)
- .setInitialLoadSizeHint(initialLoadSizeHint)
- .setMaxSize(maxSize)
- .build()
+ .setPageSize(pageSize)
+ .setPrefetchDistance(prefetchDistance)
+ .setEnablePlaceholders(enablePlaceholders)
+ .setInitialLoadSizeHint(initialLoadSizeHint)
+ .setMaxSize(maxSize)
+ .build()
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
new file mode 100644
index 0000000..e9df6d8
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
@@ -0,0 +1,651 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import java.util.AbstractList
+
+/**
+ * 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.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
+ /**
+ * List of pages in storage.
+ *
+ * Two storage modes:
+ *
+ * Contiguous - all content in pages is valid and loaded, but may return `false` from [isTiled].
+ * Safe to access any item in any page.
+ *
+ * Non-contiguous - pages may have nulls or a placeholder page, [isTiled] always returns `true`.
+ * pages may have nulls, or placeholder (empty) pages while content is loading.
+ */
+ private val pages: ArrayList<List<T>?>
+
+ var leadingNullCount: Int = 0
+ private set
+
+ var trailingNullCount: Int = 0
+ private set
+
+ var positionOffset: Int = 0
+ private set
+ /**
+ * Number of loaded items held by [pages]. When tiling, doesn't count unloaded pages in [pages].
+ * If tiling is disabled, same as [storageCount].
+ *
+ * This count is the one used for trimming.
+ */
+ var loadedCount: Int = 0
+ private set
+
+ /**
+ * Number of items represented by [pages]. If tiling is enabled, unloaded items in [pages] may
+ * be `null`, but this value still counts them.
+ */
+ var storageCount: Int = 0
+ private set
+
+ /**
+ *If pageSize > 0, tiling is enabled, 'pages' may have gaps, and leadingPages is set
+ */
+ private var pageSize: Int = 0
+
+ var numberPrepended: Int = 0
+ private set
+ var numberAppended: Int = 0
+ private set
+
+ /**
+ * `true` if all pages are the same size, except for the last, which may be smaller
+ */
+ val isTiled
+ get() = pageSize > 0
+
+ val pageCount
+ get() = pages.size
+
+ val middleOfLoadedRange
+ get() = leadingNullCount + positionOffset + storageCount / 2
+
+ // ------------- Adjacent Provider interface (contiguous-only) ------------------
+
+ override val firstLoadedItem
+ // Safe to access first page's first item here
+ // If contiguous, pages can't be empty, can't hold null Pages, and items can't be empty
+ get() = pages[0]?.first()
+
+ override val lastLoadedItem
+ // Safe to access last page's last item here:
+ // If contiguous, pages can't be empty, can't hold null Pages, and items can't be empty
+ get() = pages.last()?.last()
+
+ override val firstLoadedItemIndex
+ get() = leadingNullCount + positionOffset
+
+ override val lastLoadedItemIndex
+ get() = leadingNullCount + storageCount - 1 + positionOffset
+
+ constructor() {
+ leadingNullCount = 0
+ pages = ArrayList()
+ trailingNullCount = 0
+ positionOffset = 0
+ loadedCount = 0
+ storageCount = 0
+ pageSize = 1
+ numberPrepended = 0
+ numberAppended = 0
+ }
+
+ constructor(leadingNulls: Int, page: List<T>, trailingNulls: Int) : this() {
+ init(leadingNulls, page, trailingNulls, 0)
+ }
+
+ private constructor(other: PagedStorage<T>) {
+ leadingNullCount = other.leadingNullCount
+ pages = ArrayList(other.pages)
+ trailingNullCount = other.trailingNullCount
+ positionOffset = other.positionOffset
+ loadedCount = other.loadedCount
+ storageCount = other.storageCount
+ pageSize = other.pageSize
+ numberPrepended = other.numberPrepended
+ numberAppended = other.numberAppended
+ }
+
+ fun snapshot() = PagedStorage(this)
+
+ private fun init(leadingNulls: Int, page: List<T>, trailingNulls: Int, positionOffset: Int) {
+ leadingNullCount = leadingNulls
+ pages.clear()
+ pages.add(page)
+ trailingNullCount = trailingNulls
+
+ this.positionOffset = positionOffset
+ loadedCount = page.size
+ storageCount = loadedCount
+
+ // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+ // even if it will break if nulls convert.
+ pageSize = page.size
+
+ numberPrepended = 0
+ numberAppended = 0
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun init(
+ leadingNulls: Int,
+ page: List<T>,
+ trailingNulls: Int,
+ positionOffset: Int,
+ callback: Callback
+ ) {
+ init(leadingNulls, page, trailingNulls, positionOffset)
+ callback.onInitialized(size)
+ }
+
+ override fun get(index: Int): T? {
+ // is it definitely outside 'pages'?
+ val localIndex = index - leadingNullCount
+
+ when {
+ index < 0 || index >= size ->
+ throw IndexOutOfBoundsException("Index: $index, Size: $size")
+ localIndex < 0 || localIndex >= storageCount -> return null
+ }
+
+ var localPageIndex: Int
+ var pageInternalIndex: Int
+
+ if (isTiled) {
+ // it's inside pages, and we're tiled. Jump to correct tile.
+ localPageIndex = localIndex / pageSize
+ pageInternalIndex = localIndex % pageSize
+ } else {
+ // it's inside pages, 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
+ val localPageCount = pages.size
+ localPageIndex = 0
+ while (localPageIndex < localPageCount) {
+ val pageSize = pages[localPageIndex]!!.size
+ if (pageSize > pageInternalIndex) {
+ // stop, found the page
+ break
+ }
+ pageInternalIndex -= pageSize
+ localPageIndex++
+ }
+ }
+
+ val page = pages[localPageIndex]
+ return when {
+ // can only occur in tiled case, with untouched inner/placeholder pages
+ page == null || page.isEmpty() -> null
+ else -> page[pageInternalIndex]
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ interface Callback {
+ fun onInitialized(count: Int)
+ fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int)
+ fun onPageAppended(endPosition: Int, changed: Int, added: Int)
+ fun onPagePlaceholderInserted(pageIndex: Int)
+ fun onPageInserted(start: Int, count: Int)
+ fun onPagesRemoved(startOfDrops: Int, count: Int)
+ fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int)
+ }
+
+ override val size
+ get() = leadingNullCount + storageCount + trailingNullCount
+
+ fun computeLeadingNulls(): Int {
+ var total = leadingNullCount
+ val pageCount = pages.size
+ for (i in 0 until pageCount) {
+ val page = pages[i]
+ if (page != null && page !is PlaceholderList) {
+ break
+ }
+ total += pageSize
+ }
+ return total
+ }
+
+ fun computeTrailingNulls(): Int {
+ var total = trailingNullCount
+ for (i in pages.indices.reversed()) {
+ val page = pages[i]
+ if (page != null && page !is PlaceholderList) {
+ break
+ }
+ total += pageSize
+ }
+ 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 fun needsTrim(maxSize: Int, requiredRemaining: Int, localPageIndex: Int): Boolean {
+ val page = pages[localPageIndex]
+ return page == null || (loadedCount > maxSize &&
+ pages.size > 2 &&
+ page !is PlaceholderList &&
+ loadedCount - page.size >= requiredRemaining)
+ }
+
+ fun needsTrimFromFront(maxSize: Int, requiredRemaining: Int) =
+ needsTrim(maxSize, requiredRemaining, 0)
+
+ fun needsTrimFromEnd(maxSize: Int, requiredRemaining: Int) =
+ needsTrim(maxSize, requiredRemaining, pages.size - 1)
+
+ fun shouldPreTrimNewPage(maxSize: Int, requiredRemaining: Int, countToBeAdded: Int) =
+ loadedCount + countToBeAdded > maxSize &&
+ pages.size > 1 &&
+ loadedCount >= requiredRemaining
+
+ internal fun trimFromFront(
+ insertNulls: Boolean,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ): Boolean {
+ var totalRemoved = 0
+ while (needsTrimFromFront(maxSize, requiredRemaining)) {
+ val page = pages.removeAt(0)
+ val removed = page?.size ?: pageSize
+ totalRemoved += removed
+ storageCount -= removed
+ loadedCount -= page?.size ?: 0
+ }
+
+ if (totalRemoved > 0) {
+ if (insertNulls) {
+ // replace removed items with nulls
+ val previousLeadingNulls = leadingNullCount
+ leadingNullCount += totalRemoved
+ callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved)
+ } else {
+ // simply remove, and handle offset
+ positionOffset += totalRemoved
+ callback.onPagesRemoved(leadingNullCount, totalRemoved)
+ }
+ }
+ return totalRemoved > 0
+ }
+
+ internal fun trimFromEnd(
+ insertNulls: Boolean,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ): Boolean {
+ var totalRemoved = 0
+ while (needsTrimFromEnd(maxSize, requiredRemaining)) {
+ val page = pages.removeAt(pages.size - 1)
+ val removed = page?.size ?: pageSize
+ totalRemoved += removed
+ storageCount -= removed
+ loadedCount -= page?.size ?: 0
+ }
+
+ if (totalRemoved > 0) {
+ val newEndPosition = leadingNullCount + storageCount
+ if (insertNulls) {
+ // replace removed items with nulls
+ trailingNullCount += totalRemoved
+ callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved)
+ } else {
+ // items were just removed, signal
+ callback.onPagesRemoved(newEndPosition, totalRemoved)
+ }
+ }
+ return totalRemoved > 0
+ }
+
+ // ---------------- Contiguous API -------------------
+
+ internal fun prependPage(page: List<T>, callback: Callback) {
+ val count = page.size
+ if (count == 0) {
+ // Nothing returned from source, nothing to do
+ return
+ }
+ if (pageSize > 0 && count != pageSize) {
+ if (pages.size == 1 && count > pageSize) {
+ // prepending to a single item - update current page size to that of 'inner' page
+ pageSize = count
+ } else {
+ // no longer tiled
+ pageSize = -1
+ }
+ }
+
+ pages.add(0, page)
+ loadedCount += count
+ storageCount += count
+
+ val changedCount = minOf(leadingNullCount, count)
+ val addedCount = count - changedCount
+
+ if (changedCount != 0) {
+ leadingNullCount -= changedCount
+ }
+ positionOffset -= addedCount
+ numberPrepended += count
+
+ callback.onPagePrepended(leadingNullCount, changedCount, addedCount)
+ }
+
+ internal fun appendPage(page: List<T>, callback: Callback) {
+ val count = page.size
+ if (count == 0) {
+ // Nothing returned from source, nothing to do
+ return
+ }
+
+ if (pageSize > 0) {
+ // if the previous page was smaller than pageSize,
+ // or if this page is larger than the previous, disable tiling
+ if (pages[pages.size - 1]!!.size != pageSize || count > pageSize) {
+ pageSize = -1
+ }
+ }
+
+ pages.add(page)
+ loadedCount += count
+ storageCount += count
+
+ val changedCount = minOf(trailingNullCount, count)
+ val addedCount = count - changedCount
+
+ if (changedCount != 0) {
+ trailingNullCount -= changedCount
+ }
+ numberAppended += count
+ callback.onPageAppended(
+ leadingNullCount + storageCount - count,
+ changedCount, addedCount
+ )
+ }
+
+ override fun onPageResultResolution(
+ type: PagedList.LoadType,
+ result: DataSource.BaseResult<T>
+ ) {
+ // ignored
+ }
+
+ // ------------------ 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.
+ */
+ fun pageWouldBeBoundary(positionOfPage: Int, trimFromFront: Boolean): Boolean {
+ when {
+ pageSize < 1 || pages.size < 2 ->
+ throw IllegalStateException("Trimming attempt before sufficient load")
+ // position represent page in leading nulls
+ positionOfPage < leadingNullCount -> return trimFromFront
+ // position represent page in trailing nulls
+ positionOfPage >= leadingNullCount + storageCount -> return !trimFromFront
+ }
+
+ val localPageIndex = (positionOfPage - leadingNullCount) / pageSize
+
+ // walk outside in, return false if we find non-placeholder page before localPageIndex
+ if (trimFromFront) {
+ for (i in 0 until localPageIndex) {
+ if (pages[i] != null) {
+ return false
+ }
+ }
+ } else {
+ for (i in pages.size - 1 downTo localPageIndex + 1) {
+ if (pages[i] != null) {
+ return false
+ }
+ }
+ }
+
+ // didn't find another page, so this one would be a boundary
+ return true
+ }
+
+ internal fun initAndSplit(
+ leadingNulls: Int,
+ multiPageList: List<T>,
+ trailingNulls: Int,
+ positionOffset: Int,
+ pageSize: Int,
+ callback: Callback
+ ) {
+ val pageCount = (multiPageList.size + (pageSize - 1)) / pageSize
+ for (i in 0 until pageCount) {
+ val beginInclusive = i * pageSize
+ val endExclusive = minOf(multiPageList.size, (i + 1) * pageSize)
+
+ val sublist = multiPageList.subList(beginInclusive, endExclusive)
+
+ if (i == 0) {
+ // Trailing nulls for first page includes other pages in multiPageList
+ val initialTrailingNulls = trailingNulls + multiPageList.size - sublist.size
+ init(leadingNulls, sublist, initialTrailingNulls, positionOffset)
+ } else {
+ val insertPosition = leadingNulls + beginInclusive
+ insertPage(insertPosition, sublist, null)
+ }
+ }
+ callback.onInitialized(size)
+ }
+
+ internal fun tryInsertPageAndTrim(
+ position: Int,
+ page: List<T>,
+ lastLoad: Int,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ) {
+ val trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED
+ val trimFromFront = lastLoad > middleOfLoadedRange
+
+ val 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
+ val localPageIndex = (position - leadingNullCount) / pageSize
+ pages.set(localPageIndex, null)
+
+ // note: we also remove it, so we don't have to guess how large a 'null' page is later
+ storageCount -= page.size
+ if (trimFromFront) {
+ pages.removeAt(0)
+ leadingNullCount += page.size
+ } else {
+ pages.removeAt(pages.size - 1)
+ trailingNullCount += page.size
+ }
+ }
+
+ if (trim) {
+ if (trimFromFront) {
+ trimFromFront(true, maxSize, requiredRemaining, callback)
+ } else {
+ trimFromEnd(true, maxSize, requiredRemaining, callback)
+ }
+ }
+ }
+
+ internal fun insertPage(position: Int, page: List<T>, callback: Callback?) {
+ val newPageSize = page.size
+ if (newPageSize != pageSize) {
+ // 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)
+
+ val size = size
+ val addingLastPage = position == size - size % pageSize && newPageSize < pageSize
+ val onlyEndPagePresent = (trailingNullCount == 0 && pages.size == 1 &&
+ newPageSize > pageSize)
+
+ // OK only if existing single page, and it's the last one
+ if (!onlyEndPagePresent && !addingLastPage) {
+ throw IllegalArgumentException("page introduces incorrect tiling")
+ }
+ if (onlyEndPagePresent) {
+ pageSize = newPageSize
+ }
+ }
+
+ val pageIndex = position / pageSize
+
+ allocatePageRange(pageIndex, pageIndex)
+
+ val localPageIndex = pageIndex - leadingNullCount / pageSize
+
+ val oldPage = pages[localPageIndex]
+ if (oldPage != null && oldPage !is PlaceholderList) {
+ throw IllegalArgumentException(
+ "Invalid position $position: data already loaded"
+ )
+ }
+ pages[localPageIndex] = page
+ loadedCount += newPageSize
+ callback?.onPageInserted(position, newPageSize)
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ fun allocatePageRange(minimumPage: Int, maximumPage: Int) {
+ var leadingNullPages = leadingNullCount / pageSize
+
+ if (minimumPage < leadingNullPages) {
+ for (i in 0 until leadingNullPages - minimumPage) {
+ pages.add(0, null)
+ }
+ val newStorageAllocated = (leadingNullPages - minimumPage) * pageSize
+ storageCount += newStorageAllocated
+ leadingNullCount -= newStorageAllocated
+
+ leadingNullPages = minimumPage
+ }
+ if (maximumPage >= leadingNullPages + pages.size) {
+ val newStorageAllocated = minOf(
+ trailingNullCount,
+ (maximumPage + 1 - (leadingNullPages + pages.size)) * pageSize
+ )
+ for (i in pages.size..maximumPage - leadingNullPages) {
+ pages.add(pages.size, null)
+ }
+ storageCount += newStorageAllocated
+ trailingNullCount -= newStorageAllocated
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun allocatePlaceholders(index: Int, prefetchDistance: Int, pageSize: Int, callback: Callback) {
+ if (pageSize != this.pageSize) {
+ if (pageSize < this.pageSize) {
+ throw IllegalArgumentException("Page size cannot be reduced")
+ }
+ if (pages.size != 1 || trailingNullCount != 0) {
+ // not in single, last page allocated case - can't change page size
+ throw IllegalArgumentException(
+ "Page size can change only if last page is only one present"
+ )
+ }
+ this.pageSize = pageSize
+ }
+
+ val maxPageCount = (size + this.pageSize - 1) / this.pageSize
+ val minimumPage = maxOf((index - prefetchDistance) / this.pageSize, 0)
+ val maximumPage = minOf((index + prefetchDistance) / this.pageSize, maxPageCount - 1)
+
+ allocatePageRange(minimumPage, maximumPage)
+ val leadingNullPages = leadingNullCount / this.pageSize
+ for (pageIndex in minimumPage..maximumPage) {
+ val localPageIndex = pageIndex - leadingNullPages
+ if (pages[localPageIndex] == null) {
+ pages[localPageIndex] = placeholderList
+ callback.onPagePlaceholderInserted(pageIndex)
+ }
+ }
+ }
+
+ fun hasPage(pageSize: Int, index: Int): Boolean {
+ // NOTE: we pass pageSize here to avoid in case pageSize not fully initialized (when last
+ // page only one loaded).
+ val leadingNullPages = leadingNullCount / pageSize
+
+ if (index < leadingNullPages || index >= leadingNullPages + pages.size) {
+ return false
+ }
+
+ val page = pages[index - leadingNullPages]
+
+ return page != null && page !is PlaceholderList
+ }
+
+ override fun toString(): String {
+ var ret = "leading $leadingNullCount, storage $storageCount, trailing $trailingNullCount"
+ if (pages.isNotEmpty()) {
+ ret += " ${pages.joinToString(" ")}"
+ }
+ return ret
+ }
+
+ /**
+ * Lists instances are compared (with instance equality) to [placeholderList] 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.
+ */
+ private class PlaceholderList<T> : ArrayList<T>()
+
+ private val placeholderList = PlaceholderList<T>()
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
new file mode 100644
index 0000000..3387f9f
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2019 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.paging.DataSource.BaseResult
+import androidx.paging.PagedList.LoadState
+import androidx.paging.PagedList.LoadType
+import androidx.paging.futures.FutureCallback
+import androidx.paging.futures.addCallback
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+
+internal class Pager<K : Any, V : Any>(
+ val config: PagedList.Config,
+ val source: DataSource<K, V>,
+ val notifyExecutor: Executor,
+ private val fetchExecutor: Executor,
+ val pageConsumer: PageConsumer<V>,
+ adjacentProvider: AdjacentProvider<V>?,
+ result: BaseResult<V>
+) {
+ private val totalCount: Int
+ private val adjacentProvider: AdjacentProvider<V>
+ private var prevKey: K? = null
+ private var nextKey: K? = null
+ private val detached = AtomicBoolean(false)
+
+ var loadStateManager = object : PagedList.LoadStateManager() {
+ override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
+ pageConsumer.onStateChanged(type, state, error)
+ }
+
+ val isDetached
+ get() = detached.get()
+
+ init {
+ this.adjacentProvider = adjacentProvider ?: SimpleAdjacentProvider()
+ @Suppress("UNCHECKED_CAST")
+ prevKey = result.prevKey as K?
+ @Suppress("UNCHECKED_CAST")
+ nextKey = result.nextKey as K?
+ this.adjacentProvider.onPageResultResolution(LoadType.REFRESH, result)
+ totalCount = result.totalCount()
+
+ // TODO: move this validation to tiled paging impl, once that's added back
+ if (source.type === DataSource.KeyType.POSITIONAL && config.enablePlaceholders) {
+ result.validateForInitialTiling(config.pageSize)
+ }
+ }
+
+ private fun listenTo(type: LoadType, future: ListenableFuture<out BaseResult<V>>) {
+ // First listen on the BG thread if the DataSource is invalid, since it can be expensive
+ future.addListener(Runnable {
+ // if invalid, drop result on the floor
+ if (source.isInvalid) {
+ detach()
+ return@Runnable
+ }
+
+ // Source has been verified to be valid after producing data, so sent data to UI
+ future.addCallback(
+ object : FutureCallback<BaseResult<V>> {
+ override fun onSuccess(value: BaseResult<V>) = onLoadSuccess(type, value)
+ override fun onError(throwable: Throwable) = onLoadError(type, throwable)
+ },
+ notifyExecutor
+ )
+ }, fetchExecutor)
+ }
+
+ internal interface PageConsumer<V : Any> {
+ /**
+ * @return `true` if we need to fetch more
+ */
+ fun onPageResult(type: LoadType, pageResult: BaseResult<V>): Boolean
+
+ fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ }
+
+ internal interface AdjacentProvider<V : Any> {
+ val firstLoadedItem: V?
+ val lastLoadedItem: V?
+ val firstLoadedItemIndex: Int
+ val lastLoadedItemIndex: Int
+
+ /**
+ * Notify the [AdjacentProvider] of new loaded data, to update first/last item/index.
+ *
+ * NOTE: this data may not be committed (e.g. it may be dropped due to max size). Up to the
+ * implementation of the AdjacentProvider to handle this (generally by ignoring this call if
+ * dropping is supported).
+ */
+ fun onPageResultResolution(type: LoadType, result: BaseResult<V>)
+ }
+
+ fun onLoadSuccess(type: LoadType, value: BaseResult<V>) {
+ if (isDetached) return // abort!
+
+ adjacentProvider.onPageResultResolution(type, value)
+
+ if (pageConsumer.onPageResult(type, value)) {
+ when (type) {
+ LoadType.START -> {
+ @Suppress("UNCHECKED_CAST")
+ prevKey = value.prevKey as K?
+ schedulePrepend()
+ }
+ LoadType.END -> {
+ @Suppress("UNCHECKED_CAST")
+ nextKey = value.nextKey as K?
+ scheduleAppend()
+ }
+ else -> throw IllegalStateException("Can only fetch more during append/prepend")
+ }
+ } else {
+ val state = if (value.data.isEmpty()) LoadState.DONE else LoadState.IDLE
+ loadStateManager.setState(type, state, null)
+ }
+ }
+
+ fun onLoadError(type: LoadType, throwable: Throwable) {
+ if (isDetached) return // abort!
+
+ // TODO: handle nesting
+ val state = when {
+ source.isRetryableError(throwable) -> LoadState.RETRYABLE_ERROR
+ else -> LoadState.ERROR
+ }
+ loadStateManager.setState(type, state, throwable)
+ }
+
+ fun trySchedulePrepend() {
+ if (loadStateManager.start == LoadState.IDLE) schedulePrepend()
+ }
+
+ fun tryScheduleAppend() {
+ if (loadStateManager.end == LoadState.IDLE) scheduleAppend()
+ }
+
+ private fun canPrepend() = when (totalCount) {
+ // don't know count / position from initial load, so be conservative, return true
+ BaseResult.TOTAL_COUNT_UNKNOWN -> true
+ // position is known, do we have space left?
+ else -> adjacentProvider.firstLoadedItemIndex > 0
+ }
+
+ private fun canAppend() = when (totalCount) {
+ // don't know count / position from initial load, so be conservative, return true
+ BaseResult.TOTAL_COUNT_UNKNOWN -> true
+ // count is known, do we have space left?
+ else -> adjacentProvider.lastLoadedItemIndex < totalCount - 1
+ }
+
+ private fun schedulePrepend() {
+ if (!canPrepend()) {
+ onLoadSuccess(LoadType.START, BaseResult.empty())
+ return
+ }
+
+ val key = when (source.type) {
+ DataSource.KeyType.POSITIONAL ->
+ @Suppress("UNCHECKED_CAST")
+ (adjacentProvider.firstLoadedItemIndex - 1) as K
+ DataSource.KeyType.PAGE_KEYED -> prevKey
+ DataSource.KeyType.ITEM_KEYED -> (source as ListenableItemKeyedDataSource).getKey(
+ adjacentProvider.firstLoadedItem!!
+ )
+ }
+
+ loadStateManager.setState(LoadType.START, LoadState.LOADING, null)
+ listenTo(
+ LoadType.START,
+ source.load(
+ DataSource.Params(
+ DataSource.LoadType.START,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ )
+ )
+ }
+
+ private fun scheduleAppend() {
+ if (!canAppend()) {
+ onLoadSuccess(LoadType.END, BaseResult.empty())
+ return
+ }
+
+ val key = when (source.type) {
+ DataSource.KeyType.POSITIONAL ->
+ @Suppress("UNCHECKED_CAST")
+ (adjacentProvider.lastLoadedItemIndex + 1) as K
+ DataSource.KeyType.PAGE_KEYED -> nextKey
+ DataSource.KeyType.ITEM_KEYED -> (source as ListenableItemKeyedDataSource).getKey(
+ adjacentProvider.lastLoadedItem!!
+ )
+ }
+
+ loadStateManager.setState(LoadType.END, LoadState.LOADING, null)
+ listenTo(
+ LoadType.END,
+ source.load(
+ DataSource.Params(
+ DataSource.LoadType.END,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ )
+ )
+ }
+
+ fun retry() {
+ if (loadStateManager.start == LoadState.RETRYABLE_ERROR) schedulePrepend()
+ if (loadStateManager.end == LoadState.RETRYABLE_ERROR) scheduleAppend()
+ }
+
+ fun detach() = detached.set(true)
+
+ internal class SimpleAdjacentProvider<V : Any> : AdjacentProvider<V> {
+ override var firstLoadedItemIndex: Int = 0
+ private set
+ override var lastLoadedItemIndex: Int = 0
+ private set
+ override var firstLoadedItem: V? = null
+ private set
+ override var lastLoadedItem: V? = null
+ private set
+
+ private var counted: Boolean = false
+ private var leadingUnloadedCount: Int = 0
+ private var trailingUnloadedCount: Int = 0
+
+ override fun onPageResultResolution(type: LoadType, result: BaseResult<V>) {
+ if (result.data.isEmpty()) return
+
+ if (type == LoadType.START) {
+ firstLoadedItemIndex -= result.data.size
+ firstLoadedItem = result.data[0]
+ if (counted) {
+ leadingUnloadedCount -= result.data.size
+ }
+ } else if (type == LoadType.END) {
+ lastLoadedItemIndex += result.data.size
+ lastLoadedItem = result.data.last()
+ if (counted) {
+ trailingUnloadedCount -= result.data.size
+ }
+ } else {
+ firstLoadedItemIndex = result.leadingNulls + result.offset
+ lastLoadedItemIndex = firstLoadedItemIndex + result.data.size - 1
+ firstLoadedItem = result.data[0]
+ lastLoadedItem = result.data.last()
+
+ if (result.counted) {
+ counted = true
+ leadingUnloadedCount = result.leadingNulls
+ trailingUnloadedCount = result.trailingNulls
+ }
+ }
+ }
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
new file mode 100644
index 0000000..50bb351
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2019 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.RestrictTo
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.paging.PositionalDataSource.LoadInitialCallback
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * 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), either use
+ * [PageKeyedDataSource] or [ItemKeyedDataSource], or pass the initial result with the two parameter
+ * [LoadInitialCallback.onResult].
+ *
+ * Room can generate a Factory of PositionalDataSources for you:
+ * ```
+ * @Dao
+ * interface UserDao {
+ * @Query("SELECT * FROM user ORDER BY age DESC")
+ * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+ * }
+ * ```
+ *
+ * @param T Type of items being loaded by the [PositionalDataSource].
+ *
+ * @see [ListenablePositionalDataSource]
+ */
+abstract class PositionalDataSource<T : Any> : ListenablePositionalDataSource<T>() {
+ /**
+ * Holder object for inputs to [loadInitial].
+ */
+ open class LoadInitialParams(
+ requestedStartPosition: Int,
+ requestedLoadSize: Int,
+ pageSize: Int,
+ placeholdersEnabled: Boolean
+ ) : ListenablePositionalDataSource.LoadInitialParams(
+ requestedStartPosition,
+ requestedLoadSize,
+ pageSize,
+ placeholdersEnabled
+ )
+
+ /**
+ * Holder object for inputs to [loadRange].
+ */
+ open class LoadRangeParams(startPosition: Int, loadSize: Int) :
+ ListenablePositionalDataSource.LoadRangeParams(startPosition, loadSize)
+
+ /**
+ * Callback for [loadInitial] 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 T Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<T> {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from [loadInitial] function to return data, and inform how many
+ * placeholders should be shown before and after. If counting is cheap 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
+ * [LoadInitialParams.placeholdersEnabled] is false), you can instead call [onResult].
+ *
+ * @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 N items
+ * before the items in data that can be loaded from this DataSource, pass N.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial [data] parameter as well as any
+ * items that can be loaded in front or behind of [data].
+ */
+ abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
+
+ /**
+ * 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 (i.e.,
+ * [LoadInitialParams.placeholdersEnabled] is `false`).
+ *
+ * Call this method from [loadInitial] function to return data, if position is known but
+ * total size is not. If placeholders are requested, call the three parameter variant:
+ * [onResult].
+ *
+ * @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 N items
+ * before the items in data that can be provided by this [DataSource], pass
+ * N.
+ */
+ abstract fun onResult(data: List<T>, position: Int)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * Callback for PositionalDataSource [loadRange] 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 T Type of items being loaded.
+ */
+ abstract class LoadRangeCallback<T> {
+ /**
+ * Called to pass loaded data from [loadRange].
+ *
+ * @param data List of items loaded from the [DataSource]. Must be same size as requested,
+ * unless at end of list.
+ */
+ abstract fun onResult(data: List<T>)
+
+ /**
+ * Called to report an error from a [DataSource].
+ *
+ * Call this method to report an error from [loadRange].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ companion object {
+ /**
+ * Helper for computing an initial position in [loadInitial] 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
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @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);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param totalCount Total size of the data set.
+ * @return Position to start loading at.
+ *
+ * @see [computeInitialLoadSize]
+ */
+ @JvmStatic
+ fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int =
+ ListenablePositionalDataSource.computeInitialLoadPosition(params, totalCount)
+
+ /**
+ * Helper for computing an initial load size in [loadInitial] 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 [computeInitialLoadPosition].
+ *
+ * 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
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @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);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param initialLoadPosition Value returned by [computeInitialLoadPosition]
+ * @param totalCount Total size of the data set.
+ * @return Number of items to load.
+ *
+ * @see [computeInitialLoadPosition]
+ */
+ @JvmStatic
+ fun computeInitialLoadSize(
+ params: LoadInitialParams,
+ initialLoadPosition: Int,
+ totalCount: Int
+ ) = ListenablePositionalDataSource.computeInitialLoadSize(
+ params,
+ initialLoadPosition,
+ totalCount
+ )
+ }
+
+ final override fun loadInitial(
+ params: ListenablePositionalDataSource.LoadInitialParams
+ ): ListenableFuture<InitialResult<T>> {
+ val future = ResolvableFuture.create<InitialResult<T>>()
+ executor.execute {
+ val newParams = LoadInitialParams(
+ params.requestedStartPosition,
+ params.requestedLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled
+ )
+ val callback = object : LoadInitialCallback<T>() {
+ override fun onResult(data: List<T>, position: Int, totalCount: Int) {
+ if (isInvalid) {
+ // NOTE: this isInvalid check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(InitialResult(emptyList(), 0, 0))
+ } else {
+ setFuture(newParams, InitialResult(data, position, totalCount))
+ }
+ }
+
+ override fun onResult(data: List<T>, position: Int) {
+ if (isInvalid) {
+ // NOTE: this isInvalid check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(InitialResult(emptyList(), 0))
+ } else {
+ setFuture(newParams, InitialResult(data, position))
+ }
+ }
+
+ private fun setFuture(
+ params: ListenablePositionalDataSource.LoadInitialParams,
+ result: InitialResult<T>
+ ) {
+ if (params.placeholdersEnabled) {
+ result.validateForInitialTiling(params.pageSize)
+ }
+ future.set(result)
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(newParams, callback)
+ }
+ return future
+ }
+
+ final override fun loadRange(
+ params: ListenablePositionalDataSource.LoadRangeParams
+ ): ListenableFuture<RangeResult<T>> {
+ val future = ResolvableFuture.create<RangeResult<T>>()
+ executor.execute {
+ val callback = object : LoadRangeCallback<T>() {
+ override fun onResult(data: List<T>) {
+ when {
+ isInvalid -> future.set(RangeResult(emptyList()))
+ else -> future.set(RangeResult(data))
+ }
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadRange(LoadRangeParams(params.startPosition, params.loadSize), callback)
+ }
+ return future
+ }
+
+ /**
+ * 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
+ abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
+
+ /**
+ * 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 [loadInitial], 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
+ abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal override val isContiguous = false
+
+ final override fun <V : Any> mapByPage(
+ function: Function<List<T>, List<V>>
+ ): PositionalDataSource<V> = WrapperPositionalDataSource(this, function)
+
+ final override fun <V : Any> map(function: Function<T, V>): PositionalDataSource<V> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
new file mode 100644
index 0000000..7217d82
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 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
+
+internal class SnapshotPagedList<T : Any>(private val pagedList: PagedList<T>) : PagedList<T>(
+ pagedList.storage.snapshot(),
+ pagedList.mainThreadExecutor,
+ pagedList.backgroundThreadExecutor,
+ null,
+ pagedList.config
+) {
+ init {
+ lastLoad = pagedList.lastLoad
+ }
+
+ override val isContiguous
+ get() = pagedList.isContiguous
+
+ override val dataSource: DataSource<*, T> = pagedList.dataSource
+ override val isImmutable = true
+
+ override val lastKey
+ get() = pagedList.lastKey
+
+ override val isDetached = true
+
+ override fun detach() {}
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<T>, callback: Callback) {}
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) {}
+ override fun loadAroundInternal(index: Int) {}
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt
new file mode 100644
index 0000000..fa5b145
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.transform
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.IdentityHashMap
+
+/**
+ * @param Key DataSource key type, same for original and wrapped.
+ * @param ValueFrom Value type of original DataSource.
+ * @param ValueTo Value type of new DataSource.
+ */
+internal open class WrapperDataSource<Key : Any, ValueFrom : Any, ValueTo : Any>(
+ private val source: DataSource<Key, ValueFrom>,
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ val listFunction: Function<List<ValueFrom>, List<ValueTo>>
+) : DataSource<Key, ValueTo>(source.type) {
+ private val keyMap = when (source.type) {
+ KeyType.ITEM_KEYED -> IdentityHashMap<ValueTo, Key>()
+ else -> null
+ }
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun getKeyInternal(item: ValueTo): Key = when {
+ keyMap != null -> synchronized(keyMap) {
+ return keyMap[item]!!
+ }
+ // positional / page-keyed
+ else -> throw IllegalStateException("Cannot get key by item in non-item keyed DataSource")
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ fun stashKeysIfNeeded(source: List<ValueFrom>, dest: List<ValueTo>) {
+ if (keyMap != null) {
+ synchronized(keyMap) {
+ for (i in dest.indices) {
+ keyMap[dest[i]] =
+ (this.source as ListenableItemKeyedDataSource).getKey(source[i])
+ }
+ }
+ }
+ }
+
+ override fun load(params: Params<Key>): ListenableFuture<out BaseResult<ValueTo>> =
+ source.load(params).transform(
+ Function { input ->
+ val result = BaseResult.convert(input, listFunction)
+ stashKeysIfNeeded(input.data, result.data)
+ result
+ },
+ DirectExecutor
+ )
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
new file mode 100644
index 0000000..65047b9
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+import java.util.IdentityHashMap
+
+internal class WrapperItemKeyedDataSource<K : Any, A : Any, B : Any>(
+ private val source: ItemKeyedDataSource<K, A>,
+ private val listFunction: Function<List<A>, List<B>>
+) : ItemKeyedDataSource<K, B>() {
+
+ private val keyMap = IdentityHashMap<B, K>()
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.addInvalidatedCallback(onInvalidatedCallback)
+ }
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+ }
+
+ override fun invalidate() {
+ source.invalidate()
+ }
+
+ override val isInvalid
+ get() = source.isInvalid
+
+ fun convertWithStashedKeys(source: List<A>): List<B> {
+ val dest = convert(listFunction, source)
+ synchronized(keyMap) {
+ // synchronize on keyMap, since multiple loads may occur simultaneously.
+ // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap)
+ for (i in dest.indices) {
+ keyMap[dest[i]] = this.source.getKey(source[i])
+ }
+ }
+ return dest
+ }
+
+ override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
+ source.loadInitial(params, object : ItemKeyedDataSource.LoadInitialCallback<A>() {
+ override fun onResult(data: List<A>, position: Int, totalCount: Int) {
+ callback.onResult(convertWithStashedKeys(data), position, totalCount)
+ }
+
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
+ source.loadAfter(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
+ source.loadBefore(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun getKey(item: B): K = synchronized(keyMap) {
+ return keyMap[item]!!
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
new file mode 100644
index 0000000..c49b480
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+
+internal class WrapperPageKeyedDataSource<K : Any, A : Any, B : Any>(
+ private val source: PageKeyedDataSource<K, A>,
+ private val listFunction: Function<List<A>, List<B>>
+) : PageKeyedDataSource<K, B>() {
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<K, B>) {
+ source.loadInitial(params, object : PageKeyedDataSource.LoadInitialCallback<K, A>() {
+ override fun onResult(
+ data: List<A>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: K?,
+ nextPageKey: K?
+ ) {
+ val convertedData = convert(listFunction, data)
+ callback.onResult(convertedData, position, totalCount, previousPageKey, nextPageKey)
+ }
+
+ override fun onResult(data: List<A>, previousPageKey: K?, nextPageKey: K?) {
+ val convertedData = convert(listFunction, data)
+ callback.onResult(convertedData, previousPageKey, nextPageKey)
+ }
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+ source.loadBefore(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ override fun onResult(data: List<A>, adjacentPageKey: K?) =
+ callback.onResult(convert(listFunction, data), adjacentPageKey)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+ source.loadAfter(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ override fun onResult(data: List<A>, adjacentPageKey: K?) =
+ callback.onResult(convert(listFunction, data), adjacentPageKey)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
new file mode 100644
index 0000000..683d670
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 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.arch.core.util.Function
+
+internal class WrapperPositionalDataSource<A : Any, B : Any>(
+ private val source: PositionalDataSource<A>,
+ val listFunction: Function<List<A>, List<B>>
+) : PositionalDataSource<B>() {
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
+ source.loadInitial(params, object : LoadInitialCallback<A>() {
+ override fun onResult(data: List<A>, position: Int, totalCount: Int) =
+ callback.onResult(convert(listFunction, data), position, totalCount)
+
+ override fun onResult(data: List<A>, position: Int) =
+ callback.onResult(convert(listFunction, data), position)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<B>) {
+ source.loadRange(params, object : LoadRangeCallback<A>() {
+ override fun onResult(data: List<A>) = callback.onResult(convert(listFunction, data))
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt b/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt
new file mode 100644
index 0000000..2d1da12
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.futures
+
+import androidx.annotation.RestrictTo
+
+import java.util.concurrent.Executor
+
+/**
+ * TODO: Make this internal and remove @RestrictTo once common-ktx is merged in.
+ *
+ * An [Executor] that runs each task in the thread that invokes [execute][Executor.execute]
+ *
+ * This instance is equivalent to:
+ *```
+ * final class DirectExecutor implements Executor {
+ * public void execute(Runnable r) {
+ * r.run();
+ * }
+ * }
+ *```
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+object DirectExecutor : Executor {
+ override fun execute(runnable: Runnable) = runnable.run()
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt b/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt
new file mode 100644
index 0000000..899a5ec
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 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.futures
+
+import androidx.annotation.RestrictTo
+
+import com.google.common.util.concurrent.ListenableFuture
+
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+
+/**
+ * A callback for accepting the results of a [Future] computation asynchronously.
+ *
+ * To attach to a [ListenableFuture] use [addCallback].
+ * @param V Type of the Future result.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface FutureCallback<V> {
+ /**
+ * Invoked with the result of the `Future` computation when it is successful.
+ */
+ fun onSuccess(value: V)
+
+ /**
+ * Invoked when a `Future` computation fails or is canceled.
+ *
+ * If the future's [get][Future.get] method throws an [ExecutionException], then the cause is
+ * passed to this method. Any other thrown object is passed unaltered.
+ */
+ fun onError(throwable: Throwable)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt b/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt
new file mode 100644
index 0000000..a44dddd
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 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.
+ */
+
+// TODO: Remove once paging-runtime is converted to .kt.
+@file:JvmName("Futures")
+
+package androidx.paging.futures
+
+import androidx.annotation.RestrictTo
+import androidx.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+
+import com.google.common.util.concurrent.ListenableFuture
+
+import java.util.concurrent.CancellationException
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+
+/**
+ * Registers separate success and failure callbacks to be run when the `Future`'s computation is
+ * complete or, if the computation is already complete, immediately.
+ *
+ * The callback is run on `executor`. There is no guaranteed ordering of execution of callbacks,
+ * but any callback added through this method is guaranteed to be called once the computation is
+ * complete.
+ *
+ * Example:
+ * ```
+ * ListenableFuture<QueryResult> future = ...;
+ * Executor e = ...
+ * addCallback(future, new FutureCallback<QueryResult>() {
+ * public void onSuccess(QueryResult result) {
+ * storeInCache(result);
+ * }
+ * public void onFailure(Throwable t) {
+ * reportError(t);
+ * }
+ * }, e);
+ * ```
+ *
+ * When selecting an executor, note that `directExecutor` is dangerous in some cases. See the
+ * discussion in the [ListenableFuture.addListener] documentation. All its warnings about
+ * heavyweight listeners are also applicable to heavyweight callbacks passed to this method.
+ *
+ * For a more general interface to attach a completion listener to a `Future`, see
+ * [ListenableFuture.addListener].
+ *
+ * @param callback The callback to invoke when `future` is completed.
+ * @param executor The executor to run `callback` when the future completes.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <V> ListenableFuture<out V>.addCallback(callback: FutureCallback<in V>, executor: Executor) {
+ addListener(Runnable {
+ val value: V
+ try {
+ value = get()
+ } catch (e: ExecutionException) {
+ callback.onError(e.cause ?: e)
+ return@Runnable
+ } catch (e: Throwable) {
+ callback.onError(e)
+ return@Runnable
+ }
+
+ callback.onSuccess(value)
+ }, executor)
+}
+
+/**
+ * Returns a new `Future` whose result is derived from the result of the given `Future`. If
+ * `input` fails, the returned `Future` fails with the same exception (and the function is not
+ * invoked). Example usage:
+ *
+ * ```
+ * ListenableFuture<QueryResult> queryFuture = ...;
+ * ListenableFuture<List<Row>> rowsFuture =
+ * transform(queryFuture, QueryResult::getRows, executor);
+ * ```
+ *
+ * When selecting an executor, note that `directExecutor` is dangerous in some cases. See the
+ * discussion in the [ListenableFuture.addListener] documentation. All its warnings about
+ * heavyweight listeners are also applicable to heavyweight functions passed to this method.
+ *
+ * The returned `Future` attempts to keep its cancellation state in sync with that of the input
+ * future. That is, if the returned `Future` is cancelled, it will attempt to cancel the input,
+ * and if the input is cancelled, the returned `Future` will receive a callback in which it will
+ * attempt to cancel itself.
+ *
+ * An example use of this method is to convert a serializable object returned from an RPC into a
+ * POJO.
+ *
+ * @param function A Function to transform the results of the provided future to the results of
+ * the returned future.
+ * @param executor Executor to run the function in.
+ * @return A future that holds result of the transformation.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <I, O> ListenableFuture<out I>.transform(
+ function: Function<in I, out O>,
+ executor: Executor
+): ListenableFuture<O> {
+ val out = ResolvableFuture.create<O>()
+
+ // Add success/error callback.
+ addCallback(object : FutureCallback<I> {
+ override fun onSuccess(value: I) {
+ out.set(function.apply(value))
+ }
+
+ override fun onError(throwable: Throwable) {
+ out.setException(throwable)
+ }
+ }, executor)
+
+ // Propagate output future's cancellation to input future.
+ out.addCallback(object : FutureCallback<O> {
+ override fun onSuccess(value: O) {}
+
+ override fun onError(throwable: Throwable) {
+ if (throwable is CancellationException) {
+ cancel(false)
+ }
+ }
+ }, executor)
+ return out
+}
diff --git a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
deleted file mode 100644
index 2cae585..0000000
--- a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 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.paging.futures.DirectExecutor
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@Suppress("DEPRECATION")
-@RunWith(JUnit4::class)
-class TiledDataSourceTest {
-
- fun TiledDataSource<String>.loadInitial(
- startPosition: Int,
- count: Int,
- pageSize: Int
- ): List<String> {
- initExecutor(DirectExecutor.INSTANCE)
- return loadInitial(PositionalDataSource.LoadInitialParams(
- startPosition, count, pageSize, true)).get().data
- }
-
- @Test
- fun loadInitialEmpty() {
- class EmptyDataSource : TiledDataSource<String>() {
- override fun countItems(): Int {
- return 0
- }
-
- override fun loadRange(startPosition: Int, count: Int): List<String> {
- return emptyList()
- }
- }
-
- assertEquals(emptyList<String>(), EmptyDataSource().loadInitial(0, 1, 5))
- }
-
- @Test
- fun loadInitialTooLong() {
- val list = List(26) { "" + 'a' + it }
- class AlphabetDataSource : TiledDataSource<String>() {
- override fun countItems(): Int {
- return list.size
- }
-
- override fun loadRange(startPosition: Int, count: Int): List<String> {
- return list.subList(startPosition, startPosition + count)
- }
- }
- // baseline behavior
- assertEquals(list, AlphabetDataSource().loadInitial(0, 26, 10))
- assertEquals(list, AlphabetDataSource().loadInitial(50, 26, 10))
- }
-}
diff --git a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
similarity index 90%
rename from paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index c2a6f82..9ce5682 100644
--- a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -21,22 +21,22 @@
import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
import androidx.paging.futures.DirectExecutor
import androidx.testutils.TestExecutor
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.reset
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
+import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
@RunWith(Parameterized::class)
class ContiguousPagedListTest(private val placeholdersEnabled: Boolean) {
- private val mMainThread = TestExecutor()
- private val mBackgroundThread = TestExecutor()
+ private val mainThread = TestExecutor()
+ private val backgroundThread = TestExecutor()
private class Item(position: Int) {
val pos: Int = position
@@ -59,7 +59,7 @@
callback: LoadInitialCallback<Item>
) {
val initPos = params.requestedInitialKey ?: 0
- val start = Math.max(initPos - params.requestedLoadSize / 2, 0)
+ val start = maxOf(initPos - params.requestedLoadSize / 2, 0)
val result = getClampedRange(start, start + params.requestedLoadSize)
if (result == null) {
@@ -94,13 +94,13 @@
override fun getKey(item: Item): Int = item.pos
private fun getClampedRange(startInc: Int, endExc: Int): List<Item>? {
- val matching = errorIndices.filter { it in startInc..(endExc - 1) }
+ val matching = errorIndices.filter { it in startInc until endExc }
if (matching.isNotEmpty()) {
// found indices with errors enqueued - fail to load them
errorIndices.removeAll(matching)
return null
}
- return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
+ return listData.subList(maxOf(0, startInc), minOf(listData.size, endExc))
}
fun enqueueErrorForIndex(index: Int) {
@@ -124,14 +124,20 @@
return data
}
- private fun <E> PagedList<E>.addLoadStateCapture(desiredType: PagedList.LoadType):
+ private fun <E : Any> PagedList<E>.addLoadStateCapture(desiredType: PagedList.LoadType):
MutableList<PagedList.LoadState> {
val list = mutableListOf<PagedList.LoadState>()
- this.addWeakLoadStateListener { type, state, _ ->
- if (type == desiredType) {
- list.add(state)
+ this.addWeakLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == desiredType) {
+ list.add(state)
+ }
}
- }
+ })
return list
}
@@ -162,7 +168,7 @@
}
private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
- verifyRange(start, count, actual.mStorage)
+ verifyRange(start, count, actual.storage)
assertEquals(count, actual.loadedCount)
}
@@ -178,9 +184,9 @@
): ContiguousPagedList<Int, Item> {
val ret = PagedList.create(
dataSource,
- mMainThread,
- mBackgroundThread,
- DirectExecutor.INSTANCE,
+ mainThread,
+ backgroundThread,
+ DirectExecutor,
boundaryCallback,
PagedList.Config.Builder()
.setPageSize(pageSize)
@@ -260,7 +266,7 @@
@Test
fun append() {
val pagedList = createCountedPagedList(0)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 40, pagedList)
verifyZeroInteractions(callback)
@@ -276,7 +282,7 @@
@Test
fun prepend() {
val pagedList = createCountedPagedList(80)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(60, 40, pagedList)
verifyZeroInteractions(callback)
@@ -292,7 +298,7 @@
@Test
fun outwards() {
val pagedList = createCountedPagedList(40)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(20, 40, pagedList)
verifyZeroInteractions(callback)
@@ -379,7 +385,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 20, pagedList)
verifyZeroInteractions(callback)
@@ -416,7 +422,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(80, 20, pagedList)
verifyZeroInteractions(callback)
@@ -462,18 +468,18 @@
drain()
verifyRange(1, 3, pagedList)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the beginning...
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
// but before page received, access near end of list
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
verifyZeroInteractions(callback)
- mMainThread.executeAll()
+ mainThread.executeAll()
// and the load at the beginning is dropped without signaling callback
verifyNoMoreInteractions(callback)
verifyRange(1, 3, pagedList)
@@ -504,18 +510,18 @@
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
drain()
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the end...
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
// but before page received, access near front of list
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
verifyZeroInteractions(callback)
- mMainThread.executeAll()
+ mainThread.executeAll()
// and the load at the end is dropped without signaling callback
verifyNoMoreInteractions(callback)
verifyRange(1, 3, pagedList)
@@ -542,7 +548,7 @@
// trigger load
pagedList.loadAround(35)
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
verifyRange(0, 40, pagedList)
@@ -555,7 +561,7 @@
// trigger load which will error
pagedList.loadAround(55)
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
verifyRange(0, 60, pagedList)
@@ -566,7 +572,7 @@
// retry
pagedList.retry()
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
// load finishes
@@ -644,11 +650,17 @@
// have an error, move loading range, error goes away
val pagedList = createCountedPagedList(0)
val states = mutableListOf<PagedList.LoadState>()
- pagedList.addWeakLoadStateListener { type, state, _ ->
- if (type == PagedList.LoadType.END) {
- states.add(state)
+ pagedList.addWeakLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == PagedList.LoadType.END) {
+ states.add(state)
+ }
}
- }
+ })
pagedList.dataSource.enqueueErrorForIndex(45)
pagedList.loadAround(35)
@@ -661,9 +673,12 @@
fun distantPrefetch() {
val pagedList = createCountedPagedList(
0,
- initLoadSize = 10, pageSize = 10, prefetchDistance = 30
+ initLoadSize = 10,
+ pageSize = 10,
+ prefetchDistance = 30
)
- val callback = mock(PagedList.Callback::class.java)
+
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 10, pagedList)
verifyZeroInteractions(callback)
@@ -700,7 +715,7 @@
verifyRange(0, 60, snapshot)
// and verify the snapshot hasn't received them
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 60)
verifyNoMoreInteractions(callback)
@@ -724,7 +739,7 @@
verifyRange(20, 80, pagedList)
verifyRange(40, 60, snapshot)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 40, 0)
verifyNoMoreInteractions(callback)
@@ -739,7 +754,7 @@
dataSource = ListDataSource(ITEMS)
)
// With positional DataSource, last load is param passed
- assertEquals(4, pagedList.mLastLoad)
+ assertEquals(4, pagedList.lastLoad)
verifyRange(0, 20, pagedList)
}
@@ -750,14 +765,14 @@
initLoadSize = 20
)
// last load is middle of initial load
- assertEquals(10, pagedList.mLastLoad)
+ assertEquals(10, pagedList.lastLoad)
verifyRange(0, 20, pagedList)
}
@Test
fun addWeakCallbackEmpty() {
val pagedList = createCountedPagedList(0)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
verifyRange(0, 40, pagedList)
// capture empty snapshot
@@ -782,8 +797,7 @@
@Test
fun boundaryCallback_empty() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0,
listData = ArrayList(), boundaryCallback = boundaryCallback
@@ -803,8 +817,7 @@
fun boundaryCallback_singleInitialLoad() {
val shortList = ITEMS.subList(0, 4)
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0, listData = shortList,
initLoadSize = shortList.size, boundaryCallback = boundaryCallback
@@ -826,8 +839,7 @@
@Test
fun boundaryCallback_delayed() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
90,
initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback
@@ -872,8 +884,8 @@
private fun drain() {
var executed: Boolean
do {
- executed = mBackgroundThread.executeAll()
- executed = mMainThread.executeAll() || executed
+ executed = backgroundThread.executeAll()
+ executed = mainThread.executeAll() || executed
} while (executed)
}
diff --git a/paging/common/src/test/java/androidx/paging/FailExecutor.kt b/paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/FailExecutor.kt
rename to paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
diff --git a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
similarity index 86%
rename from paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index fb72c74..819d9db 100644
--- a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -16,7 +16,10 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
+import com.nhaarman.mockitokotlin2.capture
+import com.nhaarman.mockitokotlin2.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
@@ -24,7 +27,6 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -39,9 +41,10 @@
initialLoadSize: Int,
enablePlaceholders: Boolean
): DataSource.BaseResult<Item> {
- dataSource.initExecutor(DirectExecutor.INSTANCE)
+ dataSource.initExecutor(DirectExecutor)
return dataSource.loadInitial(
- ItemKeyedDataSource.LoadInitialParams(key, initialLoadSize, enablePlaceholders)).get()
+ ItemKeyedDataSource.LoadInitialParams(key, initialLoadSize, enablePlaceholders)
+ ).get()
}
@Test
@@ -178,15 +181,15 @@
fun loadBefore() {
val dataSource = ItemDataSource()
@Suppress("UNCHECKED_CAST")
- val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<Item>
+ val callback = mock<ItemKeyedDataSource.LoadCallback<Item>>()
dataSource.loadBefore(
- ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
+ ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback
+ )
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
- verify(callback).onResult(argument.capture())
+ verify(callback).onResult(capture(argument))
verifyNoMoreInteractions(callback)
val observed = argument.value
@@ -219,9 +222,9 @@
return
}
- val key = params.requestedInitialKey ?: Key("", Integer.MAX_VALUE)
- val start = Math.max(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
- val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+ val key = params.requestedInitialKey ?: Key("", Int.MAX_VALUE)
+ val start = maxOf(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
+ val endExclusive = minOf(start + params.requestedLoadSize, items.size)
if (params.placeholdersEnabled && counted) {
callback.onResult(items.subList(start, endExclusive), start, items.size)
@@ -238,7 +241,7 @@
}
val start = findFirstIndexAfter(params.key)
- val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+ val endExclusive = minOf(start + params.requestedLoadSize, items.size)
callback.onResult(items.subList(start, endExclusive))
}
@@ -251,8 +254,8 @@
}
val firstIndexBefore = findFirstIndexBefore(params.key)
- val endExclusive = Math.max(0, firstIndexBefore + 1)
- val start = Math.max(0, firstIndexBefore - params.requestedLoadSize + 1)
+ val endExclusive = maxOf(0, firstIndexBefore + 1)
+ val start = maxOf(0, firstIndexBefore - params.requestedLoadSize + 1)
callback.onResult(items.subList(start, endExclusive))
}
@@ -307,14 +310,16 @@
}
}
- PagedList.create(dataSource, FailExecutor(),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ PagedList.create(
+ dataSource, FailExecutor(),
+ DirectExecutor,
+ DirectExecutor,
null,
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").get()
+ ""
+ ).get()
}
@Test
@@ -354,8 +359,9 @@
it.onResult(emptyList(), 0, 2)
}
- private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
- : ItemKeyedDataSource<K, B>() {
+ private abstract class WrapperDataSource<K : Any, A : Any, B : Any>(
+ private val source: ItemKeyedDataSource<K, A>
+ ) : ItemKeyedDataSource<K, B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -368,9 +374,8 @@
source.invalidate()
}
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
source.loadInitial(params, object : LoadInitialCallback<A>() {
@@ -378,7 +383,7 @@
callback.onResult(convert(data), position, totalCount)
}
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -390,7 +395,7 @@
override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
source.loadAfter(params, object : LoadCallback<A>() {
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -402,7 +407,7 @@
override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
source.loadBefore(params, object : LoadCallback<A>() {
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -417,8 +422,8 @@
private data class DecoratedItem(val item: Item)
- private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>)
- : WrapperDataSource<Key, Item, DecoratedItem>(source) {
+ private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>) :
+ WrapperDataSource<Key, Item, DecoratedItem>(source) {
override fun convert(source: List<Item>): List<DecoratedItem> {
return source.map { DecoratedItem(it) }
}
@@ -438,14 +443,15 @@
// load initial - success
@Suppress("UNCHECKED_CAST")
- val loadInitialCallback = mock(ItemKeyedDataSource.LoadInitialCallback::class.java)
- as ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>
+ val loadInitialCallback = mock<ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>>()
val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
val initParams = ItemKeyedDataSource.LoadInitialParams(initKey, 10, false)
- wrapper.loadInitial(initParams,
- loadInitialCallback)
+ wrapper.loadInitial(
+ initParams,
+ loadInitialCallback
+ )
verify(loadInitialCallback).onResult(
- ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
+ ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
// error
orig.enqueueError()
wrapper.loadInitial(initParams, loadInitialCallback)
@@ -454,8 +460,8 @@
val key = orig.getKey(ITEMS_BY_NAME_ID[20])
@Suppress("UNCHECKED_CAST")
- var loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<DecoratedItem>
+ var loadCallback = mock<ItemKeyedDataSource.LoadCallback<DecoratedItem>>()
+
// load after
wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
@@ -467,8 +473,7 @@
// load before
@Suppress("UNCHECKED_CAST")
- loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<DecoratedItem>
+ loadCallback = mock()
wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
// load before - error
@@ -489,18 +494,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { DecoratedItem(it) } }
+ dataSource.mapByPage(Function { page -> page.map { DecoratedItem(it) } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { DecoratedItem(it) }
+ dataSource.map(Function { DecoratedItem(it) })
}
@Test
fun testInvalidateToWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { DecoratedItem(it) }
+ val wrapper = orig.map<DecoratedItem>(Function { DecoratedItem(it) })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -509,7 +514,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { DecoratedItem(it) }
+ val wrapper = orig.map<DecoratedItem>(Function { DecoratedItem(it) })
wrapper.invalidate()
assertTrue(orig.isInvalid)
@@ -521,10 +526,12 @@
private val ITEMS_BY_NAME_ID = List(100) {
val names = Array(10) { index -> "f" + ('a' + index) }
- Item(names[it % 10],
- it,
- Math.random() * 1000,
- (Math.random() * 200).toInt().toString() + " fake st.")
+ Item(
+ names[it % 10],
+ it,
+ Math.random() * 1000,
+ (Math.random() * 200).toInt().toString() + " fake st."
+ )
}.sortedWith(ITEM_COMPARATOR)
private val EXCEPTION = Exception()
diff --git a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
similarity index 85%
rename from paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index 6122172..63d3b28 100644
--- a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
import androidx.testutils.TestExecutor
import org.junit.Assert.assertEquals
@@ -32,15 +33,15 @@
@RunWith(JUnit4::class)
class PageKeyedDataSourceTest {
- private val mMainThread = TestExecutor()
- private val mBackgroundThread = TestExecutor()
+ private val mainThread = TestExecutor()
+ private val backgroundThread = TestExecutor()
internal data class Item(val name: String)
internal data class Page(val prev: String?, val data: List<Item>, val next: String?)
- internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP)
- : PageKeyedDataSource<String, Item>() {
+ internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP) :
+ PageKeyedDataSource<String, Item>() {
private var error = false
private fun getPage(key: String): Page = data[key]!!
@@ -91,14 +92,14 @@
// validate paging entire ItemDataSource results in full, correctly ordered data
val pagedListFuture = PagedList.create(
ItemDataSource(),
- mMainThread,
- mBackgroundThread,
- mBackgroundThread,
+ mainThread,
+ backgroundThread,
+ backgroundThread,
null,
PagedList.Config.Builder().setPageSize(100).build(),
null
)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
val pagedList = pagedListFuture.get()
// validate initial load
@@ -118,7 +119,7 @@
private fun performLoadInitial(
invalidateDataSource: Boolean = false,
callbackInvoker:
- (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
+ (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
) {
val dataSource = object : PageKeyedDataSource<String, String>() {
override fun loadInitial(
@@ -147,14 +148,17 @@
}
}
- PagedList.create(dataSource, FailExecutor(),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ PagedList.create(
+ dataSource,
+ FailExecutor(),
+ DirectExecutor,
+ DirectExecutor,
null,
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").get()
+ ""
+ ).get()
}
@Test
@@ -196,7 +200,7 @@
@Test
fun pageDroppingNotSupported() {
- assertFalse(ItemDataSource().supportsPageDropping())
+ assertFalse(ItemDataSource().supportsPageDropping)
}
@Test
@@ -228,10 +232,11 @@
@Suppress("UNCHECKED_CAST")
val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
+ mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = PagedList.create(dataSource,
+ val pagedList = PagedList.create(
+ dataSource,
executor,
executor,
executor,
@@ -239,7 +244,8 @@
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").apply { executor.executeAll() }.get()
+ ""
+ ).apply { executor.executeAll() }.get()
pagedList.loadAround(0)
@@ -281,10 +287,11 @@
@Suppress("UNCHECKED_CAST")
val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
+ mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = PagedList.create(dataSource,
+ val pagedList = PagedList.create(
+ dataSource,
executor,
executor,
executor,
@@ -292,7 +299,8 @@
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").apply { executor.executeAll() }.get()
+ ""
+ ).apply { executor.executeAll() }.get()
pagedList.loadAround(0)
@@ -306,8 +314,9 @@
verifyNoMoreInteractions(boundaryCallback)
}
- private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>)
- : PageKeyedDataSource<K, B>() {
+ private abstract class WrapperDataSource<K : Any, A : Any, B : Any>(
+ private val source: PageKeyedDataSource<K, A>
+ ) : PageKeyedDataSource<K, B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -320,9 +329,8 @@
source.invalidate()
}
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(
params: LoadInitialParams<K>,
@@ -336,11 +344,16 @@
previousPageKey: K?,
nextPageKey: K?
) {
- callback.onResult(convert(data), position, totalCount,
- previousPageKey, nextPageKey)
+ callback.onResult(
+ convert(data),
+ position,
+ totalCount,
+ previousPageKey,
+ nextPageKey
+ )
}
- override fun onResult(data: MutableList<A>, previousPageKey: K?, nextPageKey: K?) {
+ override fun onResult(data: List<A>, previousPageKey: K?, nextPageKey: K?) {
callback.onResult(convert(data), previousPageKey, nextPageKey)
}
@@ -377,8 +390,8 @@
protected abstract fun convert(source: List<A>): List<B>
}
- private class StringWrapperDataSource<K, V>(source: PageKeyedDataSource<K, V>)
- : WrapperDataSource<K, V, String>(source) {
+ private class StringWrapperDataSource<K : Any, V : Any>(source: PageKeyedDataSource<K, V>) :
+ WrapperDataSource<K, V, String>(source) {
override fun convert(source: List<V>): List<String> {
return source.map { it.toString() }
}
@@ -398,9 +411,11 @@
val initParams = PageKeyedDataSource.LoadInitialParams<String>(4, true)
wrapper.loadInitial(initParams, loadInitialCallback)
- val expectedInitial = PAGE_MAP[INIT_KEY]!!
- verify(loadInitialCallback).onResult(expectedInitial.data.map { it.toString() },
- expectedInitial.prev, expectedInitial.next)
+ val expectedInitial = PAGE_MAP.getValue(INIT_KEY)
+ verify(loadInitialCallback).onResult(
+ expectedInitial.data.map { it.toString() },
+ expectedInitial.prev, expectedInitial.next
+ )
verifyNoMoreInteractions(loadInitialCallback)
@Suppress("UNCHECKED_CAST")
@@ -421,8 +436,10 @@
loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
as PageKeyedDataSource.LoadCallback<String, String>
wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback)
- verify(loadCallback).onResult(expectedInitial.data.map { it.toString() },
- expectedInitial.prev)
+ verify(loadCallback).onResult(
+ expectedInitial.data.map { it.toString() },
+ expectedInitial.prev
+ )
verifyNoMoreInteractions(loadCallback)
// load before - error
orig.enqueueError()
@@ -442,18 +459,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { it.toString() } }
+ dataSource.mapByPage(Function { page -> page.map { it.toString() } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { it.toString() }
+ dataSource.map(Function { it.toString() })
}
@Test
fun testInvalidateToWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -462,7 +479,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
wrapper.invalidate()
assertTrue(orig.isInvalid)
@@ -470,7 +487,7 @@
companion object {
// first load is 2nd page to ensure we test prepend as well as append behavior
- private val INIT_KEY: String = "key 2"
+ private const val INIT_KEY: String = "key 2"
private val PAGE_MAP: Map<String, Page>
private val ITEM_LIST: List<Item>
private val EXCEPTION = Exception()
@@ -496,8 +513,8 @@
private fun drain() {
var executed: Boolean
do {
- executed = mBackgroundThread.executeAll()
- executed = mMainThread.executeAll() || executed
+ executed = backgroundThread.executeAll()
+ executed = mainThread.executeAll() || executed
} while (executed)
}
}
diff --git a/paging/common/src/test/java/androidx/paging/PagedListConfigBuilderTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/PagedListConfigBuilderTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
diff --git a/paging/common/ktx/src/test/java/PagedListConfigTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigTest.kt
similarity index 100%
rename from paging/common/ktx/src/test/java/PagedListConfigTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListConfigTest.kt
diff --git a/paging/common/src/test/java/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/PagedListTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
diff --git a/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
diff --git a/paging/common/src/test/java/androidx/paging/PagerTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
similarity index 91%
rename from paging/common/src/test/java/androidx/paging/PagerTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
index 4e6dc4f..f4c3a26 100644
--- a/paging/common/src/test/java/androidx/paging/PagerTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
@@ -62,8 +62,8 @@
executor.execute {
val position = params.startPosition
- val end = Math.min(position + params.loadSize, data.size)
- future.set(RangeResult(position, end))
+ val end = minOf(position + params.loadSize, data.size)
+ future.set(rangeResult(position, end))
}
return future
@@ -72,10 +72,8 @@
val data = List(9) { "$it" }
- private fun RangeResult(start: Int, end: Int):
- ListenablePositionalDataSource.RangeResult<String> {
- return ListenablePositionalDataSource.RangeResult(data.subList(start, end))
- }
+ private fun rangeResult(start: Int, end: Int) =
+ ListenablePositionalDataSource.RangeResult(data.subList(start, end))
private data class Result(
val type: PagedList.LoadType,
@@ -125,8 +123,8 @@
private fun createPager(consumer: MockConsumer, start: Int = 0, end: Int = 10) = Pager(
PagedList.Config(2, 2, true, 10, PagedList.Config.MAX_SIZE_UNBOUNDED),
ImmediateListDataSource(data),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ DirectExecutor,
+ DirectExecutor,
consumer,
null,
ListenablePositionalDataSource.InitialResult(data.subList(start, end), start, data.size)
@@ -152,7 +150,7 @@
testExecutor.executeAll()
assertEquals(
- listOf(Result(END, RangeResult(6, 8))),
+ listOf(Result(END, rangeResult(6, 8))),
consumer.takeResults()
)
assertEquals(
@@ -178,7 +176,7 @@
testExecutor.executeAll()
assertEquals(
- listOf(Result(START, RangeResult(2, 4))),
+ listOf(Result(START, rangeResult(2, 4))),
consumer.takeResults()
)
assertEquals(
@@ -199,8 +197,8 @@
assertEquals(
listOf(
- Result(END, RangeResult(6, 8)),
- Result(END, RangeResult(8, 9))
+ Result(END, rangeResult(6, 8)),
+ Result(END, rangeResult(8, 9))
), consumer.takeResults()
)
assertEquals(
@@ -225,8 +223,8 @@
assertEquals(
listOf(
- Result(START, RangeResult(2, 4)),
- Result(START, RangeResult(0, 2))
+ Result(START, rangeResult(2, 4)),
+ Result(START, rangeResult(0, 2))
), consumer.takeResults()
)
assertEquals(
diff --git a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
similarity index 80%
rename from paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
index 08cd3fe..6b7868a 100644
--- a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -26,6 +27,7 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import java.util.concurrent.Executor
@RunWith(JUnit4::class)
class PositionalDataSourceTest {
@@ -43,54 +45,69 @@
@Test
fun computeInitialLoadPositionZero() {
- assertEquals(0, computeInitialLoadPos(
+ assertEquals(
+ 0, computeInitialLoadPos(
requestedStartPosition = 0,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionRequestedPositionIncluded() {
- assertEquals(10, computeInitialLoadPos(
+ assertEquals(
+ 10, computeInitialLoadPos(
requestedStartPosition = 10,
requestedLoadSize = 10,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionRound() {
- assertEquals(10, computeInitialLoadPos(
+ assertEquals(
+ 10, computeInitialLoadPos(
requestedStartPosition = 13,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionEndAdjusted() {
- assertEquals(70, computeInitialLoadPos(
+ assertEquals(
+ 70, computeInitialLoadPos(
requestedStartPosition = 99,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionEndAdjustedAndAligned() {
- assertEquals(70, computeInitialLoadPos(
+ assertEquals(
+ 70, computeInitialLoadPos(
requestedStartPosition = 99,
requestedLoadSize = 35,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
private fun validatePositionOffset(enablePlaceholders: Boolean) {
val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
val success = mutableListOf(false)
val dataSource = object : PositionalDataSource<String>() {
override fun loadInitial(
@@ -116,10 +133,10 @@
@Suppress("DEPRECATION")
PagedList.Builder(dataSource, config)
- .setFetchExecutor { it.run() }
- .setNotifyExecutor { it.run() }
- .setInitialKey(36)
- .build()
+ .setFetchExecutor(Executor { it.run() })
+ .setNotifyExecutor(Executor { it.run() })
+ .setInitialKey(36)
+ .build()
assertTrue(success[0])
}
@@ -156,14 +173,19 @@
}
val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
- dataSource.initExecutor(DirectExecutor.INSTANCE)
+ dataSource.initExecutor(DirectExecutor)
- dataSource.loadInitial(PositionalDataSource.LoadInitialParams(
- 0, config.initialLoadSizeHint, config.pageSize, config.enablePlaceholders)).get()
+ val params = PositionalDataSource.LoadInitialParams(
+ 0,
+ config.initialLoadSizeHint,
+ config.pageSize,
+ config.enablePlaceholders
+ )
+ dataSource.loadInitial(params).get()
}
@Test
@@ -239,8 +261,9 @@
it.onResult(emptyList(), 0, 1)
}
- private abstract class WrapperDataSource<in A, B>(private val source: PositionalDataSource<A>)
- : PositionalDataSource<B>() {
+ private abstract class WrapperDataSource<in A : Any, B : Any>(
+ private val source: PositionalDataSource<A>
+ ) : PositionalDataSource<B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -249,13 +272,10 @@
source.removeInvalidatedCallback(onInvalidatedCallback)
}
- override fun invalidate() {
- source.invalidate()
- }
+ override fun invalidate() = source.invalidate()
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
source.loadInitial(params, object : LoadInitialCallback<A>() {
@@ -288,20 +308,17 @@
protected abstract fun convert(source: List<A>): List<B>
}
- private class StringWrapperDataSource<in A>(source: PositionalDataSource<A>)
- : WrapperDataSource<A, String>(source) {
+ private class StringWrapperDataSource<in A : Any>(source: PositionalDataSource<A>) :
+ WrapperDataSource<A, String>(source) {
override fun convert(source: List<A>): List<String> {
return source.map { it.toString() }
}
}
- class ListDataSource<T>(val list: List<T>) : PositionalDataSource<T>() {
+ class ListDataSource<T : Any>(val list: List<T>) : PositionalDataSource<T>() {
private var error = false
- override fun loadInitial(
- params: PositionalDataSource.LoadInitialParams,
- callback: PositionalDataSource.LoadInitialCallback<T>
- ) {
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
if (error) {
callback.onError(ERROR)
error = false
@@ -309,8 +326,8 @@
}
val totalCount = list.size
- val position = PositionalDataSource.computeInitialLoadPosition(params, totalCount)
- val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, totalCount)
+ val position = computeInitialLoadPosition(params, totalCount)
+ val loadSize = computeInitialLoadSize(params, position, totalCount)
// for simplicity, we could return everything immediately,
// but we tile here since it's expected behavior
@@ -318,20 +335,14 @@
callback.onResult(sublist, position, totalCount)
}
- override fun loadRange(
- params: PositionalDataSource.LoadRangeParams,
- callback: PositionalDataSource.LoadRangeCallback<T>
- ) {
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
if (error) {
callback.onError(ERROR)
error = false
return
}
callback.onResult(
- list.subList(
- params.startPosition,
- params.startPosition + params.loadSize
- )
+ list.subList(params.startPosition, params.startPosition + params.loadSize)
)
}
@@ -372,11 +383,11 @@
verifyNoMoreInteractions(loadRangeCallback)
// check invalidation behavior
- val invalCallback = mock(DataSource.InvalidatedCallback::class.java)
- wrapper.addInvalidatedCallback(invalCallback)
+ val invalidCallback = mock(DataSource.InvalidatedCallback::class.java)
+ wrapper.addInvalidatedCallback(invalidCallback)
orig.invalidate()
- verify(invalCallback).onInvalidated()
- verifyNoMoreInteractions(invalCallback)
+ verify(invalidCallback).onInvalidated()
+ verifyNoMoreInteractions(invalidCallback)
// verify invalidation
orig.invalidate()
@@ -390,26 +401,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { it.toString() } }
+ dataSource.mapByPage(Function { page -> page.map { it.toString() } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { it.toString() }
- }
-
- @Test
- fun testGetKey() {
- val source = ListDataSource(listOf("a", "b"))
- assertEquals(null, source.getKey("a"))
- assertEquals(1, source.getKey(1, "a"))
- assertEquals(1, source.getKey(1, null))
+ dataSource.map(Function { it.toString() })
}
@Test
fun testInvalidateToWrapper() {
val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -418,7 +421,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
wrapper.invalidate()
assertTrue(orig.isInvalid)
diff --git a/paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt b/paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
similarity index 89%
rename from paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
index 9301bd0..d740578 100644
--- a/paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
@@ -16,8 +16,8 @@
package androidx.paging.futures
+import androidx.arch.core.util.Function
import androidx.concurrent.futures.ResolvableFuture
-import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import org.junit.Assert.assertEquals
@@ -35,19 +35,19 @@
if (type == guava) {
val future = SettableFuture.create<String>()
- val wrapper = Futures.transform(
- future,
- com.google.common.base.Function<String, String> { it -> it },
- DirectExecutor.INSTANCE)
+ val wrapper = future.transform(
+ Function<String, String> { it },
+ DirectExecutor
+ )
tester(future, wrapper)
} else {
val future = ResolvableFuture.create<String>()
- val wrapper = androidx.paging.futures.Futures.transform(
- future,
- androidx.arch.core.util.Function<String, String> { it -> it },
- DirectExecutor.INSTANCE)
+ val wrapper = future.transform(
+ Function<String, String> { it },
+ DirectExecutor
+ )
tester(future, wrapper)
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index e10301a..cc831a3 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -60,8 +60,8 @@
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
- val position = PositionalDataSource.computeInitialLoadPosition(params, COUNT)
- val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, COUNT)
+ val position = computeInitialLoadPosition(params, COUNT)
+ val loadSize = computeInitialLoadSize(params, position, COUNT)
val data = loadRangeInternal(position, loadSize)
if (data == null) {
callback.onError(RetryableItemError())
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
index 474ac25..677e2f8 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
@@ -69,34 +69,40 @@
adapter.currentList?.retry()
}
- adapter.addLoadStateListener { type, state, _ ->
- val button = when (type) {
- PagedList.LoadType.REFRESH -> buttonRefresh
- PagedList.LoadType.START -> buttonStart
- PagedList.LoadType.END -> buttonEnd
- }
- when (state) {
- PagedList.LoadState.IDLE -> {
- button.text = "Idle"
- button.isEnabled = type == PagedList.LoadType.REFRESH
+ adapter.addLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ val button = when (type) {
+ PagedList.LoadType.REFRESH -> buttonRefresh
+ PagedList.LoadType.START -> buttonStart
+ PagedList.LoadType.END -> buttonEnd
}
- PagedList.LoadState.LOADING -> {
- button.text = "Loading"
- button.isEnabled = false
- }
- PagedList.LoadState.DONE -> {
- button.text = "Done"
- button.isEnabled = false
- }
- PagedList.LoadState.ERROR -> {
- button.text = "Error"
- button.isEnabled = false
- }
- PagedList.LoadState.RETRYABLE_ERROR -> {
- button.text = "Error"
- button.isEnabled = true
+ when (state) {
+ PagedList.LoadState.IDLE -> {
+ button.text = "Idle"
+ button.isEnabled = type == PagedList.LoadType.REFRESH
+ }
+ PagedList.LoadState.LOADING -> {
+ button.text = "Loading"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.DONE -> {
+ button.text = "Done"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.ERROR -> {
+ button.text = "Error"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.RETRYABLE_ERROR -> {
+ button.text = "Error"
+ button.isEnabled = true
+ }
}
}
- }
+ })
}
}
diff --git a/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
index 07e0bac..52343f4 100644
--- a/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
@@ -34,17 +34,17 @@
*
* @see LivePagedListBuilder
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toLiveData(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
return LivePagedListBuilder(this, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
- .setFetchExecutor(fetchExecutor)
- .build()
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
}
/**
@@ -61,15 +61,15 @@
*
* @see LivePagedListBuilder
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toLiveData(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
return LivePagedListBuilder(this, Config(pageSize))
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
- .setFetchExecutor(fetchExecutor)
- .build()
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 156334e..416934f 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -59,13 +59,13 @@
return differ
}
- private fun <V> createPagedListFromListAndPos(
+ private fun <V : Any> createPagedListFromListAndPos(
config: PagedList.Config,
data: List<V>,
initialKey: Int
): PagedList<V> {
@Suppress("DEPRECATION")
- return PagedList.Builder<Int, V>(ListDataSource(data), config)
+ return PagedList.Builder(ListDataSource(data), config)
.setInitialKey(initialKey)
.setNotifyExecutor(mMainThread)
.setFetchExecutor(mPageLoadingThread)
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 28aa2ba..56ec88c 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -181,9 +181,15 @@
assertNotNull(initPagedList!!)
assertTrue(initPagedList is InitialPagedList<*, *>)
- val loadStateListener = PagedList.LoadStateListener { type, state, error ->
- if (type == REFRESH) {
- loadStates.add(LoadState(type, state, error))
+ val loadStateListener = object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == REFRESH) {
+ loadStates.add(LoadState(type, state, error))
+ }
}
}
initPagedList.addWeakLoadStateListener(loadStateListener)
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index 2e85715..36e69c0 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -23,7 +23,7 @@
trailingNulls: Int,
vararg items: String
) : PagedList<String>(
- PagedStorage<String>(),
+ PagedStorage(),
TestExecutor(),
TestExecutor(),
null,
@@ -33,8 +33,7 @@
var detached = false
init {
- @Suppress("UNCHECKED_CAST")
- val keyedStorage = mStorage as PagedStorage<String>
+ val keyedStorage = getStorage()
keyedStorage.init(
leadingNulls,
list,
@@ -44,23 +43,20 @@
)
}
- internal override fun isContiguous(): Boolean = true
+ override val isContiguous = true
- override fun getLastKey(): Any? = null
+ override val lastKey: Any? = null
- override fun isDetached(): Boolean = detached
+ override val isDetached
+ get() = detached
override fun detach() {
detached = true
}
- override fun dispatchUpdatesSinceSnapshot(
- storageSnapshot: PagedList<String>,
- callback: Callback
- ) {
- }
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<String>, callback: Callback) {}
- override fun dispatchCurrentLoadState(listener: LoadStateListener?) {}
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) {}
override fun loadAroundInternal(index: Int) {}
@@ -74,15 +70,10 @@
override fun onPageInserted(start: Int, count: Int) {}
- override fun getDataSource(): DataSource<*, String> {
- return ListDataSource<String>(list)
- }
+ override val dataSource = ListDataSource(list)
- override fun onPagesRemoved(startOfDrops: Int, count: Int) {
- notifyRemoved(startOfDrops, count)
- }
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
- override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
notifyChanged(startOfDrops, count)
- }
}
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
index 0a02746..2c2e6f2 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
@@ -31,7 +31,7 @@
import java.util.concurrent.Executor;
/**
- * Helper object for mapping a {@link PagedList} into a
+ * Helper object for mapping a {@link androidx.paging.PagedList} into a
* {@link androidx.recyclerview.widget.RecyclerView.Adapter RecyclerView.Adapter}.
* <p>
* For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the
@@ -39,9 +39,9 @@
* base class to support paging isn't convenient.
* <p>
* 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.
+ * directly to {@link #submitList(androidx.paging.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.
* <p>
* It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an
* adapter to acquire and present data objects.
@@ -135,7 +135,7 @@
* 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.
+ * @param currentList The new current list, may be null.
*/
void onCurrentListChanged(
@Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList);
@@ -152,7 +152,9 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mMaxScheduledGeneration;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
+ // Intentional higher visibility for synthetic access.
+ // LoadStateManager is marked internal, but we really intend it to be package-private.
+ @SuppressWarnings({"WeakerAccess", "KotlinInternalInJava"})
final PagedList.LoadStateManager mLoadStateManager = new PagedList.LoadStateManager() {
@Override
protected void onStateChanged(@NonNull PagedList.LoadType type,
@@ -164,7 +166,7 @@
}
};
@SuppressWarnings("WeakerAccess") // synthetic access
- PagedList.LoadStateListener mLoadStateListener = new PagedList.LoadStateListener() {
+ PagedList.LoadStateListener mLoadStateListener = new PagedList.LoadStateListener() {
@Override
public void onLoadStateChanged(@NonNull PagedList.LoadType type,
@NonNull PagedList.LoadState state, @Nullable Throwable error) {
@@ -180,9 +182,9 @@
* Convenience for {@code AsyncPagedListDiffer(new AdapterListUpdateCallback(adapter),
* new AsyncDifferConfig.Builder<T>(diffCallback).build();}
*
- * @param adapter Adapter that will receive update signals.
+ * @param adapter Adapter that will receive update signals.
* @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
- * compare items in the list.
+ * compare items in the list.
*/
@SuppressWarnings("WeakerAccess")
public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
@@ -281,7 +283,7 @@
* 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 pagedList The new PagedList.
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
* it is committed.
*/
@@ -359,18 +361,19 @@
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
- final DiffUtil.DiffResult result;
- result = PagedStorageDiffHelper.computeDiff(
- oldSnapshot.mStorage,
- newSnapshot.mStorage,
+ //noinspection KotlinInternalInJava
+ final DiffUtil.DiffResult result = PagedStorageDiffHelper.computeDiff(
+ oldSnapshot.getStorage$paging_common(),
+ newSnapshot.getStorage$paging_common(),
mConfig.getDiffCallback());
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
+ //noinspection KotlinInternalInJava
latchPagedList(pagedList, newSnapshot, result,
- oldSnapshot.mLastLoad, commitCallback);
+ oldSnapshot.getLastLoad$paging_common(), commitCallback);
}
}
});
@@ -395,8 +398,10 @@
mSnapshot = null;
// dispatch update callback after updating mPagedList/mSnapshot
+ //noinspection KotlinInternalInJava
PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
- previousSnapshot.mStorage, newList.mStorage, diffResult);
+ previousSnapshot.getStorage$paging_common(), newList.getStorage$paging_common(),
+ diffResult);
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
@@ -407,8 +412,10 @@
// 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);
+ @SuppressWarnings("KotlinInternalInJava") int newPosition =
+ PagedStorageDiffHelper.transformAnchorIndex(
+ diffResult, previousSnapshot.getStorage$paging_common(),
+ diffSnapshot.getStorage$paging_common(), 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
@@ -436,7 +443,6 @@
* Add a PagedListListener to receive updates when the current PagedList changes.
*
* @param listener Listener to receive updates.
- *
* @see #getCurrentList()
* @see #removePagedListListener(PagedListListener)
*/
@@ -462,7 +468,6 @@
* current REFRESH, START, and END states.
*
* @param listener Listener to receive updates.
- *
* @see #removeLoadStateListListener(PagedList.LoadStateListener)
*/
public void addLoadStateListener(@NonNull PagedList.LoadStateListener listener) {
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.java b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
index a4b04ca..dfdfbef 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
@@ -98,9 +98,11 @@
mCurrentData.getDataSource().removeInvalidatedCallback(mCallback);
dataSource.addInvalidatedCallback(mCallback);
- mCurrentData.setInitialLoadState(PagedList.LoadState.LOADING, null);
+ //noinspection KotlinInternalInJava
+ mCurrentData.setInitialLoadState$paging_common(PagedList.LoadState.LOADING, null);
- return PagedList.create(
+ //noinspection KotlinInternalInJava
+ return PagedList.create$paging_common(
dataSource,
mNotifyExecutor,
mFetchExecutor,
@@ -116,7 +118,8 @@
.isRetryableError(throwable)
? PagedList.LoadState.RETRYABLE_ERROR
: PagedList.LoadState.ERROR;
- mCurrentData.setInitialLoadState(loadState, throwable);
+ //noinspection KotlinInternalInJava
+ mCurrentData.setInitialLoadState$paging_common(loadState, throwable);
}
@Override
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
index d156143..9fbcdb0 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
@@ -27,14 +27,14 @@
import java.util.concurrent.Executor;
/**
- * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
- * {@link PagedList.Config}.
+ * Builder for {@code LiveData<PagedList>}, given a {@link androidx.paging.DataSource.Factory} and a
+ * {@link androidx.paging.PagedList.Config}.
* <p>
* 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 <Key> Type of input valued used to load data from the DataSource. Must be integer if
- * you're using PositionalDataSource.
+ * you're using PositionalDataSource.
* @param <Value> Item type being presented.
*/
public final class LivePagedListBuilder<Key, Value> {
@@ -99,8 +99,8 @@
}
/**
- * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
- * additional data from network when paging from local storage.
+ * Sets a {@link androidx.paging.PagedList.BoundaryCallback} on each PagedList created,
+ * typically used to load additional data from network when paging from local storage.
* <p>
* 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
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
index 5b28d8a..9877ffb 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
+++ b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
@@ -27,7 +27,7 @@
/**
* {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from
- * {@link PagedList}s in a {@link RecyclerView}.
+ * {@link androidx.paging.PagedList}s in a {@link RecyclerView}.
* <p>
* This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common
* default behavior for item counting, and listening to PagedList update callbacks.
diff --git a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
index 2a07c7a..3fa1a22 100644
--- a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
+++ b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
@@ -70,7 +70,7 @@
params: LoadInitialParams,
callback: LoadInitialCallback<String>
) {
- callback.onResult(listOf<String>(), 0, 0)
+ callback.onResult(listOf(), 0, 0)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
diff --git a/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt b/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
index 29378f3..539fda5 100644
--- a/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
+++ b/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
@@ -21,7 +21,7 @@
import io.reactivex.Observable
import io.reactivex.Scheduler
-private fun <Key, Value> createRxPagedListBuilder(
+private fun <Key : Any, Value : Any> createRxPagedListBuilder(
dataSourceFactory: DataSource.Factory<Key, Value>,
config: PagedList.Config,
initialLoadKey: Key?,
@@ -30,8 +30,8 @@
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
val builder = RxPagedListBuilder(dataSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -56,7 +56,7 @@
* @see RxPagedListBuilder
* @see toFlowable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toObservable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -64,12 +64,13 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildObservable()
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildObservable()
}
/**
@@ -91,7 +92,7 @@
* @see RxPagedListBuilder
* @see toFlowable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toObservable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -99,12 +100,13 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildObservable()
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildObservable()
}
/**
@@ -127,7 +129,7 @@
* @see RxPagedListBuilder
* @see toObservable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toFlowable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -136,12 +138,13 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildFlowable(backpressureStrategy)
}
/**
@@ -164,7 +167,7 @@
* @see RxPagedListBuilder
* @see toObservable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toFlowable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -173,10 +176,11 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildFlowable(backpressureStrategy)
}
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
index a070466..271a56d 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
@@ -33,7 +33,7 @@
/**
* Builder for {@code Observable<PagedList>} or {@code Flowable<PagedList>}, given a
- * {@link DataSource.Factory} and a {@link PagedList.Config}.
+ * {@link androidx.paging.DataSource.Factory} and a {@link androidx.paging.PagedList.Config}.
* <p>
* 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).
@@ -90,8 +90,10 @@
* @param pageSize Size of pages to load.
*/
@SuppressWarnings("unused")
- public RxPagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
- int pageSize) {
+ public RxPagedListBuilder(
+ @NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ int pageSize
+ ) {
this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
}
@@ -112,8 +114,8 @@
}
/**
- * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
- * additional data from network when paging from local storage.
+ * Sets a {@link androidx.paging.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
@@ -149,8 +151,8 @@
* receiving PagedLists will also receive the internal updates to the PagedList.
*
* @param scheduler Scheduler that receives PagedList updates, and where
- * {@link PagedList.Callback} calls are dispatched. Generally, this is the
- * UI/main thread.
+ * {@link androidx.paging.PagedList.Callback} calls are dispatched. Generally,
+ * this is the UI/main thread.
* @return this
*/
@NonNull