diff options
Diffstat (limited to 'android/arch/paging/PagedStorageDiffHelper.java')
-rw-r--r-- | android/arch/paging/PagedStorageDiffHelper.java | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/android/arch/paging/PagedStorageDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java new file mode 100644 index 00000000..6fc70390 --- /dev/null +++ b/android/arch/paging/PagedStorageDiffHelper.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.paging; + +import android.support.annotation.Nullable; +import android.support.v7.recyclerview.extensions.DiffCallback; +import android.support.v7.util.DiffUtil; +import android.support.v7.util.ListUpdateCallback; + +class PagedStorageDiffHelper { + private PagedStorageDiffHelper() { + } + + static <T> DiffUtil.DiffResult computeDiff( + final PagedStorage<?, T> oldList, + final PagedStorage<?, T> newList, + final DiffCallback<T> diffCallback) { + final int oldOffset = oldList.computeLeadingNulls(); + final int newOffset = newList.computeLeadingNulls(); + + final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls(); + final int newSize = newList.size() - newOffset - newList.computeTrailingNulls(); + + return DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Nullable + @Override + public Object getChangePayload(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition + oldOffset); + T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + if (oldItem == null || newItem == null) { + return null; + } + return diffCallback.getChangePayload(oldItem, newItem); + } + + @Override + public int getOldListSize() { + return oldSize; + } + + @Override + public int getNewListSize() { + return newSize; + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition + oldOffset); + T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + if (oldItem == newItem) { + return true; + } + //noinspection SimplifiableIfStatement + if (oldItem == null || newItem == null) { + return false; + } + return diffCallback.areItemsTheSame(oldItem, newItem); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition + oldOffset); + T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + if (oldItem == newItem) { + return true; + } + //noinspection SimplifiableIfStatement + if (oldItem == null || newItem == null) { + return false; + } + + return diffCallback.areContentsTheSame(oldItem, newItem); + } + }, true); + } + + private static class OffsettingListUpdateCallback implements ListUpdateCallback { + private final int mOffset; + private final ListUpdateCallback mCallback; + + private OffsettingListUpdateCallback(int offset, ListUpdateCallback callback) { + mOffset = offset; + mCallback = callback; + } + + @Override + public void onInserted(int position, int count) { + mCallback.onInserted(position + mOffset, count); + } + + @Override + public void onRemoved(int position, int count) { + mCallback.onRemoved(position + mOffset, count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + mCallback.onRemoved(fromPosition + mOffset, toPosition + mOffset); + } + + @Override + public void onChanged(int position, int count, Object payload) { + mCallback.onChanged(position + mOffset, count, payload); + } + } + + /** + * TODO: improve diffing logic + * + * This function currently does a naive diff, assuming null does not become an item, and vice + * versa (so it won't dispatch onChange events for these). It's similar to passing a list with + * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert + * for changed nulls at the beginning / end of the list. + * + * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we + * handle this by passing the snapshot to the callback, and dispatching those changes + * immediately after dispatching this diff. + */ + static <T> void dispatchDiff(ListUpdateCallback callback, + final PagedStorage<?, T> oldList, + final PagedStorage<?, T> newList, + final DiffUtil.DiffResult diffResult) { + + final int trailingOld = oldList.computeTrailingNulls(); + final int trailingNew = newList.computeTrailingNulls(); + final int leadingOld = oldList.computeLeadingNulls(); + final int leadingNew = newList.computeLeadingNulls(); + + if (trailingOld == 0 + && trailingNew == 0 + && leadingOld == 0 + && leadingNew == 0) { + // Simple case, dispatch & return + diffResult.dispatchUpdatesTo(callback); + return; + } + + // First, remove or insert trailing nulls + if (trailingOld > trailingNew) { + int count = trailingOld - trailingNew; + callback.onRemoved(oldList.size() - count, count); + } else if (trailingOld < trailingNew) { + callback.onInserted(oldList.size(), trailingNew - trailingOld); + } + + // Second, remove or insert leading nulls + if (leadingOld > leadingNew) { + callback.onRemoved(0, leadingOld - leadingNew); + } else if (leadingOld < leadingNew) { + callback.onInserted(0, leadingNew - leadingOld); + } + + // apply the diff, with an offset if needed + if (leadingNew != 0) { + diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback)); + } else { + diffResult.dispatchUpdatesTo(callback); + } + } +} |