From 131ebd3c0e7a85e847e11d3559dd6229dc997396 Mon Sep 17 00:00:00 2001 From: mindwalkr Date: Tue, 23 Mar 2021 13:46:47 -0400 Subject: [PATCH] Fix crash while scrolling during call to updateBricks (#256) * Fix crash while scrolling during call to updateBricks --- .../SafeAdapterListUpdateCallbackTest.kt | 53 +++++++++++++++++++ .../com/wayfair/brickkit/BrickDataManager.kt | 6 ++- .../brickkit/SafeAdapterListUpdateCallback.kt | 29 ++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/SafeAdapterListUpdateCallbackTest.kt create mode 100644 BrickKit/bricks/src/main/java/com/wayfair/brickkit/SafeAdapterListUpdateCallback.kt diff --git a/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/SafeAdapterListUpdateCallbackTest.kt b/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/SafeAdapterListUpdateCallbackTest.kt new file mode 100644 index 00000000..c8d5cc4d --- /dev/null +++ b/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/SafeAdapterListUpdateCallbackTest.kt @@ -0,0 +1,53 @@ +package com.wayfair.brickkit + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Before +import org.junit.runner.RunWith +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import org.junit.Test + +@RunWith(AndroidJUnit4::class) +class SafeAdapterListUpdateCallbackTest { + + private val brickRecyclerAdapter: BrickRecyclerAdapter = mock() + private lateinit var adapter: SafeAdapterListUpdateCallback + + @Before + fun setup() { + adapter = SafeAdapterListUpdateCallback(brickRecyclerAdapter) + } + + @Test + fun testOnInserted() { + val position = 3 + val count = 1 + adapter.onInserted(position, count) + verify(brickRecyclerAdapter).safeNotifyItemRangeInserted(position, count) + } + + @Test + fun testOnRemoved() { + val position = 4 + val count = 1 + adapter.onRemoved(position, count) + verify(brickRecyclerAdapter).safeNotifyItemRangeRemoved(position, count) + } + + @Test + fun testOnMoved() { + val position = 5 + val count = 9 + adapter.onMoved(position, count) + verify(brickRecyclerAdapter).safeNotifyItemMoved(position, count) + } + + @Test + fun testOnChanged() { + val position = 2 + val count = 6 + val any: Any = mock() + adapter.onChanged(position, count, any) + verify(brickRecyclerAdapter).safeNotifyItemRangeChanged(position, count, any) + } +} diff --git a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickDataManager.kt b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickDataManager.kt index 1a2c0952..56edc47c 100644 --- a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickDataManager.kt +++ b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickDataManager.kt @@ -122,7 +122,11 @@ open class BrickDataManager : Serializable { currentItems.addAll(bricks) dataHasChanged() - brickRecyclerAdapter?.let { DiffUtil.calculateDiff(diffUtilCallback).dispatchUpdatesTo(it) } + brickRecyclerAdapter?.let { + DiffUtil.calculateDiff(diffUtilCallback).dispatchUpdatesTo( + SafeAdapterListUpdateCallback(it) + ) + } } /** diff --git a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/SafeAdapterListUpdateCallback.kt b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/SafeAdapterListUpdateCallback.kt new file mode 100644 index 00000000..6ad324f2 --- /dev/null +++ b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/SafeAdapterListUpdateCallback.kt @@ -0,0 +1,29 @@ +package com.wayfair.brickkit + +import androidx.recyclerview.widget.ListUpdateCallback + +/** + * A safe implementation of [androidx.recyclerview.widget.ListUpdateCallback], + * which allows updates to the adapter during scrolling by mapping the calls + * to safe versions. + */ +internal class SafeAdapterListUpdateCallback( + private val adapter: BrickRecyclerAdapter +) : ListUpdateCallback { + + override fun onInserted(position: Int, count: Int) { + adapter.safeNotifyItemRangeInserted(position, count) + } + + override fun onRemoved(position: Int, count: Int) { + adapter.safeNotifyItemRangeRemoved(position, count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + adapter.safeNotifyItemMoved(fromPosition, toPosition) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + adapter.safeNotifyItemRangeChanged(position, count, payload) + } +}