Merge "Fix AdapterHelper update/notify order" into oc-mr1-support-27.0-dev
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
index 3518540..963d047 100644
--- a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
+++ b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.java
@@ -21,6 +21,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -289,6 +290,64 @@
         assertFalse(helper.getCurrentList().isImmutable());
     }
 
+    @Test
+    public void itemCountUpdatedBeforeListUpdateCallbacks() {
+        // verify that itemCount is updated in the helper before dispatching ListUpdateCallbacks
+
+        final int[] expectedCount = new int[] { 0 };
+        // provides access to helper, which must be constructed after callback
+        final PagedListAdapterHelper[] helperAccessor = new PagedListAdapterHelper[] { null };
+
+        ListUpdateCallback callback = new ListUpdateCallback() {
+            @Override
+            public void onInserted(int position, int count) {
+                assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+            }
+
+            @Override
+            public void onRemoved(int position, int count) {
+                assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+            }
+
+            @Override
+            public void onMoved(int fromPosition, int toPosition) {
+                fail("not expected");
+            }
+
+            @Override
+            public void onChanged(int position, int count, Object payload) {
+                fail("not expected");
+            }
+        };
+
+        PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
+        helperAccessor[0] = helper;
+
+        PagedList.Config config = new PagedList.Config.Builder()
+                .setPageSize(20)
+                .build();
+
+
+        // in the fast-add case...
+        expectedCount[0] = 5;
+        assertEquals(0, helper.getItemCount());
+        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0));
+        assertEquals(5, helper.getItemCount());
+
+        // in the slow, diff on BG thread case...
+        expectedCount[0] = 10;
+        assertEquals(5, helper.getItemCount());
+        helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0));
+        drain();
+        assertEquals(10, helper.getItemCount());
+
+        // and in the fast-remove case
+        expectedCount[0] = 0;
+        assertEquals(10, helper.getItemCount());
+        helper.setList(null);
+        assertEquals(0, helper.getItemCount());
+    }
+
     private void drainExceptDiffThread() {
         boolean executed;
         do {
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
index 0007a2e..abcff41 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
@@ -118,6 +118,8 @@
  * @param <T> Type of the PagedLists this helper will receive.
  */
 public class PagedListAdapterHelper<T> {
+    // updateCallback notifications must only be notified *after* new data and item count are stored
+    // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
     private final ListUpdateCallback mUpdateCallback;
     private final ListAdapterConfig<T> mConfig;
 
@@ -236,21 +238,25 @@
         final int runGeneration = ++mMaxScheduledGeneration;
 
         if (pagedList == null) {
-            mUpdateCallback.onRemoved(0, getItemCount());
+            int removedCount = getItemCount();
             if (mPagedList != null) {
                 mPagedList.removeWeakCallback(mPagedListCallback);
                 mPagedList = null;
             } else if (mSnapshot != null) {
                 mSnapshot = null;
             }
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onRemoved(0, removedCount);
             return;
         }
 
         if (mPagedList == null && mSnapshot == null) {
             // fast simple first insert
-            mUpdateCallback.onInserted(0, pagedList.size());
             mPagedList = pagedList;
             pagedList.addWeakCallback(null, mPagedListCallback);
+
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onInserted(0, pagedList.size());
             return;
         }
 
@@ -296,10 +302,14 @@
             throw new IllegalStateException("must be in snapshot state to apply diff");
         }
 
-        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
-                mSnapshot.mStorage, newList.mStorage, diffResult);
+        PagedList<T> previousSnapshot = mSnapshot;
         mPagedList = newList;
         mSnapshot = null;
+
+        // dispatch update callback after updating mPagedList/mSnapshot
+        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+                previousSnapshot.mStorage, newList.mStorage, diffResult);
+
         newList.addWeakCallback(diffSnapshot, mPagedListCallback);
     }