Merge "Further split DataSource callbacks, so any method available is valid" into oc-mr1-support-27.0-dev
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
index 03f2e86..38b7cc0 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
@@ -19,21 +19,23 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
+import java.util.concurrent.Executor;
+
 abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
     @Override
     boolean isContiguous() {
         return true;
     }
 
-    public abstract void loadInitial(@Nullable Key key, int initialLoadSize,
-            boolean enablePlaceholders,
-            @NonNull InitialLoadCallback<Value> callback);
+    abstract void loadInitial(@Nullable Key key, int initialLoadSize,
+            int pageSize, boolean enablePlaceholders,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
 
     abstract void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
-            @NonNull LoadCallback<Value> callback);
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
 
     abstract void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
-            @NonNull LoadCallback<Value> callback);
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
 
     /**
      * Get the key from either the position, or item, or null if position/item invalid.
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
index b54cb84..a134e44 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
@@ -87,21 +87,12 @@
         if (mDataSource.isInvalid()) {
             detach();
         } else {
-            @DataSource.LoadCountType int type = mConfig.enablePlaceholders
-                    ? DataSource.LOAD_COUNT_ACCEPTED
-                    : DataSource.LOAD_COUNT_PREVENTED;
-
-            DataSource.InitialLoadCallback<V> callback = new DataSource.InitialLoadCallback<>(
-                    type, mConfig.pageSize, mDataSource, mReceiver);
             mDataSource.loadInitial(key,
                     mConfig.initialLoadSizeHint,
+                    mConfig.pageSize,
                     mConfig.enablePlaceholders,
-                    callback);
-
-            // If initialLoad's callback is not called within the body, we force any following calls
-            // to post to the UI thread. This constructor may be run on a background thread, but
-            // after constructor, mutation must happen on UI thread.
-            callback.setPostExecutor(mMainThreadExecutor);
+                    mMainThreadExecutor,
+                    mReceiver);
         }
     }
 
@@ -193,9 +184,8 @@
                 if (mDataSource.isInvalid()) {
                     detach();
                 } else {
-                    DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
-                            PageResult.PREPEND, mMainThreadExecutor, mDataSource, mReceiver);
-                    mDataSource.loadBefore(position, item, mConfig.pageSize, callback);
+                    mDataSource.loadBefore(position, item, mConfig.pageSize,
+                            mMainThreadExecutor, mReceiver);
                 }
 
             }
@@ -223,9 +213,8 @@
                 if (mDataSource.isInvalid()) {
                     detach();
                 } else {
-                    DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
-                            PageResult.APPEND, mMainThreadExecutor, mDataSource, mReceiver);
-                    mDataSource.loadAfter(position, item, mConfig.pageSize, callback);
+                    mDataSource.loadAfter(position, item, mConfig.pageSize,
+                            mMainThreadExecutor, mReceiver);
                 }
             }
         });
diff --git a/paging/common/src/main/java/android/arch/paging/DataSource.java b/paging/common/src/main/java/android/arch/paging/DataSource.java
index 2e41cf6..b82d4e6 100644
--- a/paging/common/src/main/java/android/arch/paging/DataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/DataSource.java
@@ -16,14 +16,11 @@
 
 package android.arch.paging;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.support.annotation.AnyThread;
-import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 
-import java.lang.annotation.Retention;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
@@ -137,173 +134,45 @@
      */
     abstract boolean isContiguous();
 
-    /**
-     * Callback for DataSource initial loading methods to return data and position/count
-     * information.
-     * <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 <T> Type of items being loaded.
-     */
-    public static class InitialLoadCallback<T> extends LoadCallback<T> {
-        private final int mPageSize;
-
-        InitialLoadCallback(@LoadCountType int countType, int pageSize,
-                DataSource dataSource, PageResult.Receiver<T> receiver) {
-            super(PageResult.INIT, countType, dataSource, receiver);
-            mPageSize = pageSize;
-            if (mPageSize < 1) {
-                throw new IllegalArgumentException("Page size must be non-negative");
-            }
-        }
-
-        /**
-         * 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.
-         *
-         * @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, relative to the total
-         *                 count. 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 void onResult(@NonNull List<T> data, int position, int totalCount) {
+    static class BaseLoadCallback<T> {
+        static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
             if (position < 0) {
                 throw new IllegalArgumentException("Position must be non-negative");
             }
             if (data.size() + position > totalCount) {
                 throw new IllegalArgumentException(
-                        "List size + position too large; last item in list beyond totalCount");
+                        "List size + position too large, last item in list beyond totalCount.");
             }
             if (data.size() == 0 && totalCount > 0) {
                 throw new IllegalArgumentException(
                         "Initial result cannot be empty if items are present in data set.");
             }
-            if (mCountType == LOAD_COUNT_REQUIRED_TILED
-                    && position + data.size() != totalCount
-                    && data.size() % mPageSize != 0) {
-                throw new IllegalArgumentException("PositionalDataSource requires initial load size"
-                        + " to be a multiple of page size to support internal tiling.");
-            }
-
-            int trailingUnloadedCount = totalCount - position - data.size();
-            if (mCountType == LOAD_COUNT_REQUIRED_TILED || mCountType == LOAD_COUNT_ACCEPTED) {
-                dispatchResultToReceiver(new PageResult<>(
-                        data, position, trailingUnloadedCount, 0));
-            } else {
-                dispatchResultToReceiver(new PageResult<>(data, position));
-            }
         }
 
-        /**
-         * Called to pass initial load state from a DataSource without supporting placeholders.
-         * <p>
-         * Call this method from your DataSource's {@code loadInitial} function to return data,
-         * if position is known but total size is not. If counting is not expensive, consider
-         * calling 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}.
-         */
-        void onResult(@NonNull List<T> data, int position) {
-            // not counting, don't need to check mAcceptCount
-            dispatchResultToReceiver(new PageResult<>(
-                    data, 0, 0, position));
-        }
-    }
-
-    @Retention(SOURCE)
-    @IntDef({LOAD_COUNT_PREVENTED, LOAD_COUNT_ACCEPTED, LOAD_COUNT_REQUIRED_TILED})
-    @interface LoadCountType {}
-    static final int LOAD_COUNT_PREVENTED = 0;
-    static final int LOAD_COUNT_ACCEPTED = 1;
-    static final int LOAD_COUNT_REQUIRED_TILED = 2;
-
-    /**
-     * Callback for DataSource loading methods 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 <T> Type of items being loaded.
-     */
-    public static class LoadCallback<T> {
         @PageResult.ResultType
         final int mResultType;
-        @LoadCountType
-        final int mCountType;
         private final DataSource mDataSource;
         private final PageResult.Receiver<T> mReceiver;
 
-        private int mPositionOffset = 0;
-
         // mSignalLock protects mPostExecutor, and mHasSignalled
         private final Object mSignalLock = new Object();
         private Executor mPostExecutor = null;
         private boolean mHasSignalled = false;
 
-        private LoadCallback(@PageResult.ResultType int resultType, @LoadCountType int countType,
-                DataSource dataSource, PageResult.Receiver<T> receiver) {
+        BaseLoadCallback(@PageResult.ResultType int resultType, @NonNull DataSource dataSource,
+                @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
             mResultType = resultType;
-            mCountType = countType;
-            mDataSource = dataSource;
-            mReceiver = receiver;
-        }
-
-        LoadCallback(int type, Executor mainThreadExecutor,
-                DataSource dataSource, PageResult.Receiver<T> receiver) {
-            mResultType = type;
-            mCountType = LOAD_COUNT_PREVENTED;
             mPostExecutor = mainThreadExecutor;
             mDataSource = dataSource;
             mReceiver = receiver;
         }
 
-        void setPositionOffset(int positionOffset) {
-            mPositionOffset = positionOffset;
-        }
-
         void setPostExecutor(Executor postExecutor) {
             synchronized (mSignalLock) {
                 mPostExecutor = postExecutor;
             }
         }
 
-        /**
-         * Called to pass loaded data from a DataSource.
-         * <p>
-         * Call this method from your DataSource's {@code load} methods to return data.
-         *
-         * @param data List of items loaded from the DataSource.
-         */
-        public void onResult(@NonNull List<T> data) {
-            if (mCountType == LOAD_COUNT_REQUIRED_TILED && !data.isEmpty()) {
-                throw new IllegalArgumentException(
-                        "PositionalDataSource requires calling the three argument version of"
-                                + " InitialLoadCallback.onResult() to pass position information");
-            }
-            dispatchResultToReceiver(new PageResult<>(
-                    data, 0, 0, mPositionOffset));
-        }
-
         void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
             Executor executor;
             synchronized (mSignalLock) {
diff --git a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
index b6656f3..4f62692 100644
--- a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
@@ -20,6 +20,8 @@
 import android.support.annotation.Nullable;
 
 import java.util.List;
+import java.util.concurrent.Executor;
+
 /**
  * Incremental data loader for paging keyed content, where loaded content uses previously loaded
  * items as input to future loads.
@@ -32,16 +34,97 @@
  * @param <Value> Type of items being loaded by the DataSource.
  */
 public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
-    @Override
-    final void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
-            @NonNull LoadCallback<Value> callback) {
-        loadAfter(getKey(currentEndItem), pageSize, callback);
+
+    /**
+     * Callback for KeyedDataSource initial loading methods to return data and (optionally)
+     * position/count information.
+     * <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 <T> Type of items being loaded.
+     */
+    public static class InitialLoadCallback<T> extends LoadCallback<T> {
+        private final boolean mCountingEnabled;
+        InitialLoadCallback(@NonNull KeyedDataSource dataSource, boolean countingEnabled,
+                @NonNull PageResult.Receiver<T> receiver) {
+            super(dataSource, PageResult.INIT, null, receiver);
+            mCountingEnabled = countingEnabled;
+        }
+
+        /**
+         * 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 void onResult(@NonNull List<T> data, int position, int totalCount) {
+            validateInitialLoadParams(data, position, totalCount);
+
+            int trailingUnloadedCount = totalCount - position - data.size();
+            if (mCountingEnabled) {
+                dispatchResultToReceiver(new PageResult<>(
+                        data, position, trailingUnloadedCount, 0));
+            } else {
+                dispatchResultToReceiver(new PageResult<>(data, position));
+            }
+        }
     }
 
-    @Override
-    final void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
-            @NonNull LoadCallback<Value> callback) {
-        loadBefore(getKey(currentBeginItem), pageSize, callback);
+    /**
+     * Callback for KeyedDataSource {@link #loadBefore(Object, int, LoadCallback)}
+     * and {@link #loadAfter(Object, int, LoadCallback)} methods 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 <T> Type of items being loaded.
+     */
+    public static class LoadCallback<T> extends BaseLoadCallback<T> {
+        LoadCallback(@NonNull KeyedDataSource dataSource, @PageResult.ResultType int type,
+                @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+            super(type, dataSource, mainThreadExecutor, receiver);
+        }
+
+        /**
+         * Called to pass loaded data from a DataSource.
+         * <p>
+         * Call this method from your KeyedDataSource's
+         * {@link #loadBefore(Object, int, LoadCallback)} and
+         * {@link #loadAfter(Object, int, LoadCallback)} methods to return data.
+         * <p>
+         * Call this from {@link #loadInitial(Object, int, boolean, InitialLoadCallback)} 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 KeyedDataSource.
+         */
+        public void onResult(@NonNull List<T> data) {
+            dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+        }
     }
 
     @Nullable
@@ -54,6 +137,33 @@
         return getKey(item);
     }
 
+    @Override
+    public void loadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        InitialLoadCallback<Value> callback =
+                new InitialLoadCallback<>(this, enablePlaceholders, receiver);
+        loadInitial(key, initialLoadSize, enablePlaceholders, callback);
+
+        // If initialLoad's callback is not called within the body, we force any following calls
+        // to post to the UI thread. This constructor may be run on a background thread, but
+        // after constructor, mutation must happen on UI thread.
+        callback.setPostExecutor(mainThreadExecutor);
+    }
+
+    @Override
+    void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+        loadAfter(getKey(currentEndItem), pageSize,
+                new LoadCallback<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+    }
+
+    @Override
+    void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+        loadBefore(getKey(currentBeginItem), pageSize,
+                new LoadCallback<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+    }
 
     /**
      * Load initial data.
@@ -61,7 +171,7 @@
      * 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 DataSource.InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
+     * {@link InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
      * presenting data from this source to display placeholders to represent unloaded items.
      * <p>
      * {@code initialLoadKey} and {@code requestedLoadSize} are hints, not requirements, so if it is
@@ -76,11 +186,10 @@
      * @param requestedLoadSize Suggested number of items to load.
      * @param enablePlaceholders Signals whether counting is requested. If false, you can
      *                           potentially save work by calling the single-parameter variant of
-     *                           {@link DataSource.LoadCallback#onResult(List)} and not counting the
+     *                           {@link LoadCallback#onResult(List)} and not counting the
      *                           number of items in the data set.
      * @param callback DataSource.LoadCallback that receives initial load data.
      */
-    @Override
     public abstract void loadInitial(@Nullable Key initialLoadKey, int requestedLoadSize,
             boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback);
 
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
index 5dd3a83..d394637 100644
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
@@ -21,6 +21,8 @@
 import android.support.annotation.WorkerThread;
 
 import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary
@@ -44,6 +46,144 @@
  * @param <T> Type of items being loaded by the PositionalDataSource.
  */
 public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
+    /**
+     * Callback for PositionalDataSource initial loading methods to return data, position, and
+     * (optionally) count information.
+     * <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 <T> Type of items being loaded.
+     */
+    public static class InitialLoadCallback<T> extends BaseLoadCallback<T> {
+        private final boolean mCountingEnabled;
+        private final int mPageSize;
+
+        InitialLoadCallback(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
+                int pageSize, PageResult.Receiver<T> receiver) {
+            super(PageResult.INIT, dataSource, null, receiver);
+            mCountingEnabled = countingEnabled;
+            mPageSize = pageSize;
+            if (mPageSize < 1) {
+                throw new IllegalArgumentException("Page size must be non-negative");
+            }
+        }
+
+        /**
+         * 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.
+         *
+         * @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 void onResult(@NonNull List<T> data, int position, int totalCount) {
+            validateInitialLoadParams(data, position, totalCount);
+            if (position + data.size() != totalCount
+                    && data.size() % mPageSize != 0) {
+                throw new IllegalArgumentException("PositionalDataSource requires initial load size"
+                        + " to be a multiple of page size to support internal tiling.");
+            }
+
+            if (mCountingEnabled) {
+                int trailingUnloadedCount = totalCount - position - data.size();
+                dispatchResultToReceiver(
+                        new PageResult<>(data, position, trailingUnloadedCount, 0));
+            } else {
+                // Only occurs when wrapped as contiguous
+                dispatchResultToReceiver(new PageResult<>(data, position));
+            }
+        }
+
+        /**
+         * Called to pass initial load state from a DataSource without supporting placeholders.
+         * <p>
+         * Call this method from your DataSource's {@code loadInitial} function to return data,
+         * if position is known but total size is not. If counting is not expensive, consider
+         * calling 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}.
+         */
+        void onResult(@NonNull List<T> data, int position) {
+            // not counting, don't need to check mAcceptCount
+            dispatchResultToReceiver(new PageResult<>(
+                    data, 0, 0, position));
+        }
+    }
+
+    /**
+     * Callback for PositionalDataSource {@link #loadRange(int, int, LoadCallback)} methods
+     * 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 <T> Type of items being loaded.
+     */
+    public static class LoadCallback<T> extends BaseLoadCallback<T> {
+        private final int mPositionOffset;
+        LoadCallback(@NonNull PositionalDataSource dataSource, int positionOffset,
+                Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
+            super(PageResult.TILE, dataSource, mainThreadExecutor, receiver);
+            mPositionOffset = positionOffset;
+        }
+
+        /**
+         * Called to pass loaded data from a DataSource.
+         * <p>
+         * Call this method from your DataSource's {@code load} methods to return data.
+         *
+         * @param data List of items loaded from the DataSource.
+         */
+        public void onResult(@NonNull List<T> data) {
+            dispatchResultToReceiver(new PageResult<>(
+                    data, 0, 0, mPositionOffset));
+        }
+    }
+
+    void loadInitial(boolean acceptCount,
+            int requestedStartPosition, int requestedLoadSize, int pageSize,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+        InitialLoadCallback<T> callback =
+                new InitialLoadCallback<>(this, acceptCount, pageSize, receiver);
+        loadInitial(requestedStartPosition, requestedLoadSize, pageSize, callback);
+
+        // If initialLoad's callback is not called within the body, we force any following calls
+        // to post to the UI thread. This constructor may be run on a background thread, but
+        // after constructor, mutation must happen on UI thread.
+        callback.setPostExecutor(mainThreadExecutor);
+    }
+
+    void loadRange(int startPosition, int count,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+        LoadCallback<T> callback =
+                new LoadCallback<>(this, startPosition, mainThreadExecutor, receiver);
+        if (count == 0) {
+            callback.onResult(Collections.<T>emptyList());
+        } else {
+            loadRange(startPosition, count, callback);
+        }
+    }
 
     /**
      * Load initial list data.
@@ -80,14 +220,14 @@
      * @param callback DataSource.LoadCallback that receives loaded data.
      */
     @WorkerThread
-    public abstract void loadRange(int startPosition, int count,
-            @NonNull LoadCallback<T> callback);
+    public abstract void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback);
 
     @Override
     boolean isContiguous() {
         return false;
     }
 
+
     @NonNull
     ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
         return new ContiguousWithoutPlaceholdersWrapper<>(this);
@@ -119,34 +259,39 @@
         }
 
         @Override
-        public void loadInitial(@Nullable Integer position, int initialLoadSize,
-                boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback) {
+        void loadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
+                boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
             final int convertPosition = position == null ? 0 : position;
 
             // Note enablePlaceholders will be false here, but we don't have a way to communicate
             // this to PositionalDataSource. This is fine, because only the list and its position
             // offset will be consumed by the InitialLoadCallback.
-            mPositionalDataSource.loadInitial(
-                    convertPosition, initialLoadSize, initialLoadSize, callback);
+            mPositionalDataSource.loadInitial(false, convertPosition, initialLoadSize,
+                    pageSize, mainThreadExecutor, receiver);
         }
 
         @Override
         void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
-                @NonNull LoadCallback<Value> callback) {
+                @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
             int startIndex = currentEndIndex + 1;
-            mPositionalDataSource.loadRange(startIndex, pageSize, callback);
+            mPositionalDataSource.loadRange(startIndex, pageSize, mainThreadExecutor, receiver);
         }
 
         @Override
         void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
-                @NonNull LoadCallback<Value> callback) {
+                @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+
             int startIndex = currentBeginIndex - 1;
             if (startIndex < 0) {
-                callback.onResult(Collections.<Value>emptyList());
+                // trigger empty list load
+                mPositionalDataSource.loadRange(startIndex, 0, mainThreadExecutor, receiver);
             } else {
                 int loadSize = Math.min(pageSize, startIndex + 1);
                 startIndex = startIndex - loadSize + 1;
-                mPositionalDataSource.loadRange(startIndex, loadSize, callback);
+                mPositionalDataSource.loadRange(startIndex, loadSize, mainThreadExecutor, receiver);
             }
         }
 
diff --git a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
index 27aeda0..34d0091 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
@@ -47,10 +47,10 @@
 
     @Override
     public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
-            @NonNull InitialLoadCallback<T> callback) {
+            @NonNull InitialLoadCallback callback) {
         int totalCount = countItems();
         if (totalCount == 0) {
-            callback.onResult(Collections.<T>emptyList());
+            callback.onResult(Collections.<T>emptyList(), 0, 0);
             return;
         }
 
@@ -69,7 +69,7 @@
     }
 
     @Override
-    public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) {
+    public void loadRange(int startPosition, int count, @NonNull LoadCallback callback) {
         List<T> list = loadRange(startPosition, count);
         if (list != null) {
             callback.onResult(list);
diff --git a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
index 652b489..6c189cd 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
@@ -92,15 +92,8 @@
             final int idealStart = position - firstLoadSize / 2;
             final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
 
-            DataSource.InitialLoadCallback<T> callback = new DataSource.InitialLoadCallback<>(
-                            DataSource.LOAD_COUNT_REQUIRED_TILED,
-                    mConfig.pageSize, mDataSource, mReceiver);
-            mDataSource.loadInitial(roundedPageStart, firstLoadSize, pageSize, callback);
-
-            // If initialLoad's callback is not called within the body, we force any following calls
-            // to post to the UI thread. This constructor may be run on a background thread, but
-            // after constructor, mutation must happen on UI thread.
-            callback.setPostExecutor(mMainThreadExecutor);
+            mDataSource.loadInitial(true, roundedPageStart, firstLoadSize,
+                    pageSize, mMainThreadExecutor, mReceiver);
         }
     }
 
@@ -185,10 +178,7 @@
                 } else {
                     int startPosition = pageIndex * pageSize;
                     int count = Math.min(pageSize, mStorage.size() - startPosition);
-                    DataSource.LoadCallback<T> callback = new DataSource.LoadCallback<>(
-                            PageResult.TILE, mMainThreadExecutor, mDataSource, mReceiver);
-                    callback.setPositionOffset(startPosition);
-                    mDataSource.loadRange(startPosition, count, callback);
+                    mDataSource.loadRange(startPosition, count, mMainThreadExecutor, mReceiver);
                 }
             }
         });
diff --git a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
index 70636af..547c8ec 100644
--- a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
+++ b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
@@ -21,7 +21,10 @@
     val workItems: MutableList<() -> Unit> = ArrayList()
     private val listDataSource = ListDataSource(list)
 
-    override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int, pageSize: Int,
+    override fun loadInitial(
+            requestedStartPosition: Int,
+            requestedLoadSize: Int,
+            pageSize: Int,
             callback: InitialLoadCallback<T>) {
         workItems.add {
             listDataSource.loadInitial(
diff --git a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
index 4b03104..8642b04 100644
--- a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
@@ -27,6 +27,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.verifyZeroInteractions
+import java.util.concurrent.Executor
 
 @RunWith(Parameterized::class)
 class ContiguousPagedListTest(private val mCounted: Boolean) {
@@ -43,32 +44,56 @@
 
     private inner class TestSource(val listData: List<Item> = ITEMS)
             : ContiguousDataSource<Int, Item>() {
-        override fun loadInitial(key: Int?, initialLoadSize: Int,
-                enablePlaceholders: Boolean, callback: InitialLoadCallback<Item>) {
+        override fun loadInitial(
+                key: Int?,
+                initialLoadSize: Int,
+                pageSize: Int,
+                enablePlaceholders: Boolean,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
 
             val convertPosition = key ?: 0
-            val loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2))
-
-            val data = getClampedRange(loadPosition, loadPosition + initialLoadSize)
+            val position = Math.max(0, (convertPosition - initialLoadSize / 2))
+            val data = getClampedRange(position, position + initialLoadSize)
+            val trailingUnloadedCount = listData.size - position - data.size
 
             if (enablePlaceholders && mCounted) {
-                callback.onResult(data, loadPosition, listData.size)
+                receiver.onPageResult(PageResult.INIT,
+                        PageResult(data, position, trailingUnloadedCount, 0))
             } else {
                 // still must pass offset, even if not counted
-                callback.onResult(data, loadPosition)
+                receiver.onPageResult(PageResult.INIT,
+                        PageResult(data, position))
             }
         }
 
-        override fun loadAfter(currentEndIndex: Int, currentEndItem: Item, pageSize: Int,
-                callback: LoadCallback<Item>) {
+        override fun loadAfter(
+                currentEndIndex: Int,
+                currentEndItem: Item,
+                pageSize: Int,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
             val startIndex = currentEndIndex + 1
-            callback.onResult(getClampedRange(startIndex, startIndex + pageSize))
+            val data = getClampedRange(startIndex, startIndex + pageSize)
+
+            mainThreadExecutor.execute {
+                receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
+            }
         }
 
-        override fun loadBefore(currentBeginIndex: Int, currentBeginItem: Item, pageSize: Int,
-                callback: LoadCallback<Item>) {
+        override fun loadBefore(
+                currentBeginIndex: Int,
+                currentBeginItem: Item,
+                pageSize: Int,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
+
             val startIndex = currentBeginIndex - 1
-            callback.onResult(getClampedRange(startIndex - pageSize + 1, startIndex + 1))
+            val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
+
+            mainThreadExecutor.execute {
+                receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
+            }
         }
 
         override fun getKey(position: Int, item: Item?): Int {
diff --git a/paging/common/src/test/java/android/arch/paging/TestExecutor.kt b/paging/common/src/test/java/android/arch/paging/Executors.kt
similarity index 85%
rename from paging/common/src/test/java/android/arch/paging/TestExecutor.kt
rename to paging/common/src/test/java/android/arch/paging/Executors.kt
index 7034aa0..b472eed 100644
--- a/paging/common/src/test/java/android/arch/paging/TestExecutor.kt
+++ b/paging/common/src/test/java/android/arch/paging/Executors.kt
@@ -16,6 +16,7 @@
 
 package android.arch.paging
 
+import org.junit.Assert.fail
 import java.util.LinkedList
 import java.util.concurrent.Executor
 
@@ -37,3 +38,9 @@
         return consumed
     }
 }
+
+class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
+    override fun execute(p0: Runnable?) {
+        fail(string)
+    }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
index c9dbec4..b2e739a 100644
--- a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
@@ -19,6 +19,7 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -41,10 +42,8 @@
         val captor = ArgumentCaptor.forClass(PageResult::class.java)
                 as ArgumentCaptor<PageResult<Item>>
 
-        val callback = DataSource.InitialLoadCallback(
-                DataSource.LOAD_COUNT_ACCEPTED, /* ignored page size */ 10, dataSource, receiver)
-
-        dataSource.loadInitial(key, initialLoadSize, enablePlaceholders, callback)
+        dataSource.loadInitial(key, initialLoadSize,
+                /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
 
         verify(receiver).onPageResult(anyInt(), captor.capture())
         verifyNoMoreInteractions(receiver)
@@ -186,9 +185,10 @@
     fun loadBefore() {
         val dataSource = ItemDataSource()
         @Suppress("UNCHECKED_CAST")
-        val callback = mock(DataSource.LoadCallback::class.java) as DataSource.LoadCallback<Item>
+        val callback = mock(KeyedDataSource.LoadCallback::class.java)
+                as KeyedDataSource.LoadCallback<Item>
 
-        dataSource.loadBefore(5, ITEMS_BY_NAME_ID[5], 5, callback)
+        dataSource.loadBefore(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5, callback)
 
         @Suppress("UNCHECKED_CAST")
         val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
@@ -253,6 +253,81 @@
         }
     }
 
+    private fun performInitialLoad(
+            callbackInvoker: (callback: KeyedDataSource.InitialLoadCallback<String>) -> Unit) {
+        val dataSource = object : KeyedDataSource<String, String>() {
+            override fun getKey(item: String): String {
+                return ""
+            }
+
+            override fun loadInitial(
+                    initialLoadKey: String?,
+                    requestedLoadSize: Int,
+                    enablePlaceholders: Boolean,
+                    callback: InitialLoadCallback<String>) {
+                callbackInvoker(callback)
+            }
+
+            override fun loadAfter(
+                    currentEndKey: String,
+                    pageSize: Int,
+                    callback: LoadCallback<String>) {
+                fail("loadRange not expected")
+            }
+
+            override fun loadBefore(
+                    currentBeginKey: String,
+                    pageSize: Int,
+                    callback: LoadCallback<String>) {
+                fail("loadRange not expected")
+            }
+        }
+
+        ContiguousPagedList<String, String>(
+                dataSource, FailExecutor(), FailExecutor(), null,
+                PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .build(),
+                "")
+    }
+
+    @Test
+    fun initialLoadCallbackSuccess() = performInitialLoad {
+        // InitialLoadCallback correct usage
+        it.onResult(listOf("a", "b"), 0, 2)
+    }
+
+    @Test
+    fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+        // Keyed InitialLoadCallback *can* accept result that's not a multiple of page size
+        val elevenLetterList = List(11) { "" + 'a' + it }
+        it.onResult(elevenLetterList, 0, 12)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackListTooBig() = performInitialLoad {
+        // InitialLoadCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b", "c"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+        // InitialLoadCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b"), 1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionNegative() = performInitialLoad {
+        // InitialLoadCallback can't accept negative position
+        it.onResult(listOf("a", "b", "c"), -1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+        // InitialLoadCallback can't accept empty result unless data set is empty
+        it.onResult(emptyList(), 0, 2)
+    }
+
     companion object {
         private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
         private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
index 34e0a57..12db934 100644
--- a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
@@ -17,6 +17,7 @@
 package android.arch.paging
 
 import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -47,4 +48,61 @@
     fun computeFirstLoadPositionEndAdjustedAndAligned() {
         assertEquals(70, PositionalDataSource.computeFirstLoadPosition(99, 35, 10, 100))
     }
+
+    private fun performInitialLoad(
+            callbackInvoker: (callback: PositionalDataSource.InitialLoadCallback<String>) -> Unit) {
+        val dataSource = object : PositionalDataSource<String>() {
+            override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int,
+                                     pageSize: Int, callback: InitialLoadCallback<String>) {
+                callbackInvoker(callback)
+            }
+            override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<String>) {
+                fail("loadRange not expected")
+            }
+        }
+
+        TiledPagedList(
+                dataSource, FailExecutor(), FailExecutor(), null,
+                PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .build(),
+                0)
+    }
+
+    @Test
+    fun initialLoadCallbackSuccess() = performInitialLoad {
+        // InitialLoadCallback correct usage
+        it.onResult(listOf("a", "b"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+        // Positional InitialLoadCallback can't accept result that's not a multiple of page size
+        val elevenLetterList = List(11) { "" + 'a' + it }
+        it.onResult(elevenLetterList, 0, 12)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackListTooBig() = performInitialLoad {
+        // InitialLoadCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b", "c"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+        // InitialLoadCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b"), 1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionNegative() = performInitialLoad {
+        // InitialLoadCallback can't accept negative position
+        it.onResult(listOf("a", "b", "c"), -1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+        // InitialLoadCallback can't accept empty result unless data set is empty
+        it.onResult(emptyList(), 0, 2)
+    }
 }
diff --git a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
index 03a9a7b..15b787a 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
@@ -36,10 +36,7 @@
         @Suppress("UNCHECKED_CAST")
         val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
 
-        val callback = DataSource.InitialLoadCallback(
-                DataSource.LOAD_COUNT_REQUIRED_TILED, pageSize, this, receiver)
-
-        this.loadInitial(startPosition, count, pageSize, callback)
+        this.loadInitial(true, startPosition, count, pageSize, FailExecutor(), receiver)
 
         @Suppress("UNCHECKED_CAST")
         val argument = ArgumentCaptor.forClass(PageResult::class.java)
diff --git a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
index 66049a4..9069a1a 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
@@ -21,7 +21,6 @@
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertSame
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -399,74 +398,6 @@
         verifyNoMoreInteractions(boundaryCallback)
     }
 
-    private fun performInitialLoad(
-            callbackInvoker: (callback: DataSource.InitialLoadCallback<String>) -> Unit) {
-        val dataSource = object : PositionalDataSource<String>() {
-            override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int,
-                    pageSize: Int, callback: InitialLoadCallback<String>) {
-                callbackInvoker(callback)
-            }
-            override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<String>) {
-                fail("loadRange not expected")
-            }
-        }
-        TiledPagedList(
-                dataSource, mMainThread, mBackgroundThread, null,
-                PagedList.Config.Builder()
-                        .setPageSize(PAGE_SIZE)
-                        .build(),
-                0)
-    }
-
-    @Test
-    fun initialLoadCallbackSuccess() = performInitialLoad {
-        // InitialLoadCallback correct usage
-        it.onResult(listOf("a", "b"), 0, 2)
-    }
-
-    @Test
-    fun initialLoadCallbackEmptySuccess() = performInitialLoad {
-        // InitialLoadCallback correct usage - empty special case
-        it.onResult(emptyList())
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
-        // Positional InitialLoadCallback can't accept result that's not a multiple of page size
-        val elevenLetterList = List(11) { "" + 'a' + it }
-        it.onResult(elevenLetterList, 0, 12)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackMissingPlaceholders() = performInitialLoad {
-        // Positional InitialLoadCallback can't accept list-only call
-        it.onResult(listOf("a", "b"))
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackListTooBig() = performInitialLoad {
-        // InitialLoadCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b", "c"), 0, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
-        // InitialLoadCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b"), 1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackPositionNegative() = performInitialLoad {
-        // InitialLoadCallback can't accept negative position
-        it.onResult(listOf("a", "b", "c"), -1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
-        // Positional InitialLoadCallback can't accept empty result unless data set is empty
-        it.onResult(emptyList(), 0, 2)
-    }
-
     private fun drain() {
         var executed: Boolean
         do {