aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2019-06-14 23:29:47 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-06-14 23:29:47 +0000
commit502d3944dd72628e71019619333fc94e38609dec (patch)
tree485ff7d5da086c19dad9753c7c2efb63c7ed397b
parentd4cc2951f98fc32b35bb527a1c95a5161b4011e0 (diff)
parentce047371fd8936cb6723fc06a077a569cb407f0b (diff)
downloadsupport-502d3944dd72628e71019619333fc94e38609dec.tar.gz
Merge "Removed tiled implementation from PagedStorage" into androidx-master-dev
-rw-r--r--paging/common/src/main/kotlin/androidx/paging/PagedList.kt6
-rw-r--r--paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt400
-rw-r--r--paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt7
-rw-r--r--paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt423
-rw-r--r--paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt42
-rw-r--r--paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.kt23
6 files changed, 65 insertions, 836 deletions
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 42fc73a53ca..830e27e1d22 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -1029,7 +1029,7 @@ abstract class PagedList<T : Any> : AbstractList<T> {
* @see size
*/
open val loadedCount
- get() = storage.loadedCount
+ get() = storage.storageCount
/**
* Returns whether the list is immutable.
@@ -1207,10 +1207,10 @@ abstract class PagedList<T : Any> : AbstractList<T> {
private fun dispatchBoundaryCallbacks(begin: Boolean, end: Boolean) {
// safe to deref boundaryCallback here, since we only defer if boundaryCallback present
if (begin) {
- boundaryCallback!!.onItemAtFrontLoaded(storage.firstLoadedItem!!)
+ boundaryCallback!!.onItemAtFrontLoaded(storage.firstLoadedItem)
}
if (end) {
- boundaryCallback!!.onItemAtEndLoaded(storage.lastLoadedItem!!)
+ boundaryCallback!!.onItemAtEndLoaded(storage.lastLoadedItem)
}
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
index e9df6d849c6..92f300fa5f1 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
@@ -22,25 +22,14 @@ 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.
+ * 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>?>
+ private val pages: ArrayList<List<T>>
var leadingNullCount: Int = 0
private set
@@ -51,54 +40,28 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
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.
+ * Number of loaded items held by [pages].
*/
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) ------------------
+ // ------------- Adjacent Provider interface ------------------
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()
+ // guaranteed to have pages, and every page is non-empty, so this is safe
+ get() = pages.first().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()
+ // guaranteed to have pages, and every page is non-empty, so this is safe
+ get() = pages.last().last()
override val firstLoadedItemIndex
get() = leadingNullCount + positionOffset
@@ -111,9 +74,7 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
pages = ArrayList()
trailingNullCount = 0
positionOffset = 0
- loadedCount = 0
storageCount = 0
- pageSize = 1
numberPrepended = 0
numberAppended = 0
}
@@ -127,9 +88,7 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
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
}
@@ -143,12 +102,7 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
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
+ storageCount = page.size
numberPrepended = 0
numberAppended = 0
@@ -179,36 +133,22 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
localIndex < 0 || localIndex >= storageCount -> return null
}
- var localPageIndex: Int
+ var localPageIndex = 0
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++
+ // Since we don't know if page sizes are regular, we walk to correct page.
+ pageInternalIndex = localIndex
+ val localPageCount = pages.size
+ 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]
- }
+ return pages[localPageIndex][pageInternalIndex]
}
/**
@@ -228,31 +168,6 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
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
@@ -265,10 +180,9 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
private fun needsTrim(maxSize: Int, requiredRemaining: Int, localPageIndex: Int): Boolean {
val page = pages[localPageIndex]
- return page == null || (loadedCount > maxSize &&
+ return storageCount > maxSize &&
pages.size > 2 &&
- page !is PlaceholderList &&
- loadedCount - page.size >= requiredRemaining)
+ storageCount - page.size >= requiredRemaining
}
fun needsTrimFromFront(maxSize: Int, requiredRemaining: Int) =
@@ -278,9 +192,9 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
needsTrim(maxSize, requiredRemaining, pages.size - 1)
fun shouldPreTrimNewPage(maxSize: Int, requiredRemaining: Int, countToBeAdded: Int) =
- loadedCount + countToBeAdded > maxSize &&
+ storageCount + countToBeAdded > maxSize &&
pages.size > 1 &&
- loadedCount >= requiredRemaining
+ storageCount >= requiredRemaining
internal fun trimFromFront(
insertNulls: Boolean,
@@ -291,10 +205,9 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
var totalRemoved = 0
while (needsTrimFromFront(maxSize, requiredRemaining)) {
val page = pages.removeAt(0)
- val removed = page?.size ?: pageSize
+ val removed = page.size
totalRemoved += removed
storageCount -= removed
- loadedCount -= page?.size ?: 0
}
if (totalRemoved > 0) {
@@ -321,10 +234,9 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
var totalRemoved = 0
while (needsTrimFromEnd(maxSize, requiredRemaining)) {
val page = pages.removeAt(pages.size - 1)
- val removed = page?.size ?: pageSize
+ val removed = page.size
totalRemoved += removed
storageCount -= removed
- loadedCount -= page?.size ?: 0
}
if (totalRemoved > 0) {
@@ -349,18 +261,8 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
// 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)
@@ -382,16 +284,7 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
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)
@@ -414,238 +307,7 @@ class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<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>()
+ override fun toString(): String =
+ "leading $leadingNullCount, storage $storageCount, trailing $trailingNullCount " +
+ pages.joinToString(" ")
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index 849f5070912..b85fd0af256 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -144,21 +144,16 @@ class ContiguousPagedListTest(private val placeholdersEnabled: Boolean) {
val expectedTrailing = ITEMS.size - start - count
assertEquals(ITEMS.size, actual.size)
- assertEquals(
- (ITEMS.size - start - expectedTrailing),
- actual.storageCount
- )
assertEquals(start, actual.leadingNullCount)
assertEquals(expectedTrailing, actual.trailingNullCount)
} else {
assertEquals(ITEMS.subList(start, start + count), actual)
assertEquals(count, actual.size)
- assertEquals(actual.size, actual.storageCount)
assertEquals(0, actual.leadingNullCount)
assertEquals(0, actual.trailingNullCount)
}
- assertEquals(count, actual.loadedCount)
+ assertEquals(count, actual.storageCount)
}
private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
index 44c5f48976d..75a53b35f96 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
@@ -24,7 +24,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -133,344 +132,15 @@ class PagedStorageTest {
}
@Test
- fun isTiled_addend_smallerPageIsNotLast() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage(0, createPage("a", "a"), 0)
- assertTrue(storage.isTiled)
-
- storage.appendPage(createPage("a", "a"), callback)
- assertTrue(storage.isTiled)
-
- storage.appendPage(createPage("a"), callback)
- assertTrue(storage.isTiled)
-
- // no matter what we append here, we're no longer tiled
- storage.appendPage(createPage("a", "a"), callback)
- assertFalse(storage.isTiled)
- }
-
- @Test
- fun isTiled_append_growingSizeDisable() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage(0, createPage("a", "a"), 0)
- assertTrue(storage.isTiled)
-
- // page size can't grow from append
- storage.appendPage(createPage("a", "a", "a"), callback)
- assertFalse(storage.isTiled)
- }
-
- @Test
- fun isTiled_prepend_smallerPage() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage(0, createPage("a"), 0)
- assertTrue(storage.isTiled)
-
- storage.prependPage(createPage("a", "a"), callback)
- assertTrue(storage.isTiled)
-
- storage.prependPage(createPage("a", "a"), callback)
- assertTrue(storage.isTiled)
-
- storage.prependPage(createPage("a"), callback)
- assertFalse(storage.isTiled)
- }
-
- @Test
- fun isTiled_prepend_smallerThanInitialPage() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage(0, createPage("a", "a"), 0)
- assertTrue(storage.isTiled)
-
- storage.prependPage(createPage("a"), callback)
- assertFalse(storage.isTiled)
- }
-
- @Test
- fun get_tiled() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage(1, createPage("a", "b"), 5)
- assertTrue(storage.isTiled)
-
- storage.appendPage(createPage("c", "d"), callback)
- storage.appendPage(createPage("e", "f"), callback)
-
- assertTrue(storage.isTiled)
- assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
- }
-
- @Test
- fun get_nonTiled() {
+ fun get() {
val callback = mock(PagedStorage.Callback::class.java)
val storage = PagedStorage(1, createPage("a"), 6)
- assertTrue(storage.isTiled)
-
storage.appendPage(createPage("b", "c"), callback)
storage.appendPage(createPage("d", "e", "f"), callback)
-
- assertFalse(storage.isTiled)
assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
}
@Test
- fun insertOne() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c", "d"), 3, 0, callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
- verify(callback).onInitialized(7)
- verifyNoMoreInteractions(callback)
-
- storage.insertPage(4, createPage("e", "f"), callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf(null, null, "c", "d", "e", "f", null), storage.toArray())
- verify(callback).onPageInserted(4, 2)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun insertThree() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c", "d"), 3, 0, callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
- verify(callback).onInitialized(7)
- verifyNoMoreInteractions(callback)
-
- // first, insert 1st page
- storage.insertPage(0, createPage("a", "b"), callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf("a", "b", "c", "d", null, null, null), storage.toArray())
- verify(callback).onPageInserted(0, 2)
- verifyNoMoreInteractions(callback)
-
- // then 3rd page
- storage.insertPage(4, createPage("e", "f"), callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", null), storage.toArray())
- verify(callback).onPageInserted(4, 2)
- verifyNoMoreInteractions(callback)
-
- // then last, small page
- storage.insertPage(6, createPage("g"), callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", "g"), storage.toArray())
- verify(callback).onPageInserted(6, 1)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun insertLastFirst() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(6, createPage("g"), 0, 0, callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf(null, null, null, null, null, null, "g"), storage.toArray())
- verify(callback).onInitialized(7)
- verifyNoMoreInteractions(callback)
-
- // insert 1st page
- storage.insertPage(0, createPage("a", "b"), callback)
-
- assertEquals(7, storage.size)
- assertArrayEquals(arrayOf("a", "b", null, null, null, null, "g"), storage.toArray())
- verify(callback).onPageInserted(0, 2)
- verifyNoMoreInteractions(callback)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun insertFailure_decreaseLast() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c", "d"), 0, 0, callback)
-
- // should throw, page too small
- storage.insertPage(0, createPage("a"), callback)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun insertFailure_increase() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(0, createPage("a", "b"), 3, 0, callback)
-
- // should throw, page too big
- storage.insertPage(2, createPage("c", "d", "e"), callback)
- }
-
- @Test
- fun allocatePlaceholders_simple() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c"), 2, 0, callback)
-
- verify(callback).onInitialized(5)
-
- storage.allocatePlaceholders(2, 1, 1, callback)
-
- verify(callback).onPagePlaceholderInserted(1)
- verify(callback).onPagePlaceholderInserted(3)
- verifyNoMoreInteractions(callback)
-
- assertArrayEquals(arrayOf(null, null, "c", null, null), storage.toArray())
- }
-
- @Test
- fun allocatePlaceholders_adoptPageSize() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(4, createPage("e"), 0, 0, callback)
-
- verify(callback).onInitialized(5)
-
- storage.allocatePlaceholders(0, 2, 2, callback)
-
- verify(callback).onPagePlaceholderInserted(0)
- verify(callback).onPagePlaceholderInserted(1)
- verifyNoMoreInteractions(callback)
-
- assertArrayEquals(arrayOf(null, null, null, null, "e"), storage.toArray())
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun allocatePlaceholders_cannotShrinkPageSize() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(4, createPage("e", "f"), 0, 0, callback)
-
- verify(callback).onInitialized(6)
-
- storage.allocatePlaceholders(0, 2, 1, callback)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun allocatePlaceholders_cannotAdoptPageSize() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c", "d"), 2, 0, callback)
-
- verify(callback).onInitialized(6)
-
- storage.allocatePlaceholders(0, 2, 3, callback)
- }
-
- @Test
- fun get_placeholdersMulti() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(2, createPage("c", "d"), 3, 0, callback)
-
- assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
-
- storage.allocatePlaceholders(0, 10, 2, callback)
-
- // allocating placeholders shouldn't affect result of get
- assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
- }
-
- @Test
- fun hasPage() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.init(4, createPage("e"), 0, 0, callback)
-
- assertFalse(storage.hasPage(1, 0))
- assertFalse(storage.hasPage(1, 1))
- assertFalse(storage.hasPage(1, 2))
- assertFalse(storage.hasPage(1, 3))
- assertTrue(storage.hasPage(1, 4))
-
- assertFalse(storage.hasPage(2, 0))
- assertFalse(storage.hasPage(2, 1))
- assertTrue(storage.hasPage(2, 2))
- }
-
- @Test
- fun pageWouldBeBoundary_unallocated() {
- val storage = PagedStorage<String>()
- storage.initAndSplit(2, listOf("c", "d", "e", "f"), 1, 0, 2, IGNORED_CALLBACK)
-
- assertTrue(storage.pageWouldBeBoundary(0, true))
- assertFalse(storage.pageWouldBeBoundary(0, false))
- assertTrue(storage.pageWouldBeBoundary(6, false))
- assertFalse(storage.pageWouldBeBoundary(6, true))
- }
-
- @Test
- fun pageWouldBeBoundary_front() {
- val storage = PagedStorage<String>()
- storage.initAndSplit(8, listOf("i", "j", "k", "l", "m", "n"), 0, 0, 2, IGNORED_CALLBACK)
-
- for (i in 0..6 step 2) {
- // any position in leading nulls would be front boundary
- assertTrue(storage.pageWouldBeBoundary(i, true))
- assertFalse(storage.pageWouldBeBoundary(i, false))
- }
-
- storage.allocatePlaceholders(8, 6, 2, IGNORED_CALLBACK)
-
- for (i in 0..6 step 2) {
- // 4 / 6 have a placeholder ahead, so they return false
- assertEquals(i < 4, storage.pageWouldBeBoundary(i, true))
- assertFalse(storage.pageWouldBeBoundary(i, false))
- }
- }
-
- @Test
- fun pageWouldBeBoundary_end() {
- val storage = PagedStorage<String>()
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e", "f"), 8, 0, 2, IGNORED_CALLBACK)
-
- for (i in 6..12 step 2) {
- // any position in leading nulls would be front boundary
- assertFalse(storage.pageWouldBeBoundary(i, true))
- assertTrue(storage.pageWouldBeBoundary(i, false))
- }
-
- storage.allocatePlaceholders(6, 6, 2, IGNORED_CALLBACK)
-
- for (i in 6..12 step 2) {
- // any position in leading nulls would be front boundary
- assertFalse(storage.pageWouldBeBoundary(i, true))
- assertEquals(i > 10, storage.pageWouldBeBoundary(i, false))
- }
- }
-
- @Test
- fun trim_noOp() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e"), 0, 0, 1, callback)
-
- verify(callback).onInitialized(5)
- storage.trimFromFront(true, 5, 5, callback)
- verifyNoMoreInteractions(callback)
- storage.trimFromEnd(true, 5, 5, callback)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
fun trim_twoPagesNoOp() {
val callback = mock(PagedStorage.Callback::class.java)
val storage = PagedStorage<String>()
@@ -487,9 +157,7 @@ class PagedStorageTest {
@Test
fun trim_remainderPreventsNoOp() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e", "f"), 0, 0, 2, callback)
+ val storage = PagedStorage(listOf(listOf("a", "b"), listOf("c", "d"), listOf("d", "e")))
// can't trim, since removing a page would mean fewer items than required
assertFalse(storage.needsTrimFromFront(5, 5))
@@ -503,11 +171,7 @@ class PagedStorageTest {
@Test
fun trimFromFront_simple() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e"), 0, 0, 1, callback)
- verify(callback).onInitialized(5)
- verifyNoMoreInteractions(callback)
+ val storage = PagedStorage(('a'..'e').map { listOf("$it") })
storage.trimFromFront(false, 4, 4, callback)
verify(callback).onPagesRemoved(0, 1)
@@ -520,11 +184,7 @@ class PagedStorageTest {
@Test
fun trimFromFront_simplePlaceholders() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e"), 0, 0, 1, callback)
- verify(callback).onInitialized(5)
- verifyNoMoreInteractions(callback)
+ val storage = PagedStorage(('a'..'e').map { listOf("$it") })
storage.trimFromFront(true, 4, 4, callback)
verify(callback).onPagesSwappedToPlaceholder(0, 1)
@@ -535,37 +195,9 @@ class PagedStorageTest {
}
@Test
- fun trimFromFront_complexWithGaps() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(4, listOf("e"), 3, 0, 1, callback)
- storage.insertPage(1, listOf("b"), callback)
- storage.insertPage(3, listOf("d"), callback)
- storage.insertPage(6, listOf("g"), callback)
- storage.insertPage(7, listOf("h"), callback)
- reset(callback)
- assertEquals(7, storage.pageCount) // page for everything but leading null
- assertEquals(listOf(null, "b", null, "d", "e", null, "g", "h"), storage)
-
- // going from: -b-de-gh
- // to: ----e-gh
- // we signal this as onPagesSwappedToPlaceholder(1, 3). We could theoretically separate
- // this into two single page drop signals, but it's too rare to be worth it.
- storage.trimFromFront(true, 3, 3, callback)
- verify(callback).onPagesSwappedToPlaceholder(1, 3)
- assertEquals(4, storage.pageCount)
- assertEquals(listOf(null, null, null, null, "e", null, "g", "h"), storage)
- }
-
- @Test
fun trimFromEnd_simple() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e"), 0, 0, 1, callback)
- verify(callback).onInitialized(5)
- verifyNoMoreInteractions(callback)
+ val storage = PagedStorage(('a'..'e').map { listOf("$it") })
storage.trimFromEnd(false, 4, 4, callback)
verify(callback).onPagesRemoved(4, 1)
@@ -575,42 +207,27 @@ class PagedStorageTest {
@Test
fun trimFromEnd_simplePlaceholders() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(0, listOf("a", "b", "c", "d", "e"), 0, 0, 1, callback)
- verify(callback).onInitialized(5)
- verifyNoMoreInteractions(callback)
+ val storage = PagedStorage(('a'..'e').map { listOf("$it") })
storage.trimFromEnd(true, 4, 4, callback)
verify(callback).onPagesSwappedToPlaceholder(4, 1)
verifyNoMoreInteractions(callback)
}
- @Test
- fun trimFromEnd_complexWithGaps() {
- val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<String>()
-
- storage.initAndSplit(3, listOf("d"), 4, 0, 1, callback)
- storage.insertPage(0, listOf("a"), callback)
- storage.insertPage(1, listOf("b"), callback)
- storage.insertPage(5, listOf("f"), callback)
- storage.insertPage(6, listOf("g"), callback)
- reset(callback)
- assertEquals(7, storage.pageCount) // page for everything but trailing null
- assertEquals(listOf("a", "b", null, "d", null, "f", "g", null), storage)
-
- // going from: ab-d-fg-
- // to: ab-d----
- // we signal this as onPagesSwappedToPlaceholder(4, 3). We could theoretically separate
- // this into two single page drop signals, but it's too rare to be worth it.
- storage.trimFromEnd(true, 3, 3, callback)
- verify(callback).onPagesSwappedToPlaceholder(4, 3)
- assertEquals(4, storage.pageCount)
- assertEquals(listOf("a", "b", null, "d", null, null, null, null), storage)
- }
-
companion object {
+ @Suppress("TestFunctionName")
+ private fun PagedStorage(pages: List<List<String>>): PagedStorage<String> {
+ val storage = PagedStorage<String>()
+ val totalPageCount = pages.map { it.size }.reduce { a, b -> a + b }
+ storage.init(0, pages[0], totalPageCount - pages[0].size, 0, IGNORED_CALLBACK)
+ pages.forEachIndexed { index, page ->
+ if (index != 0) {
+ storage.appendPage(page, IGNORED_CALLBACK)
+ }
+ }
+ return storage
+ }
+
private val IGNORED_CALLBACK = object : PagedStorage.Callback {
override fun onInitialized(count: Int) {}
override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {}
@@ -621,4 +238,4 @@ class PagedStorageTest {
override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {}
}
}
-}
+} \ No newline at end of file
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt
index 4c26b63da6e..57e931ec80c 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt
@@ -42,26 +42,6 @@ class PagedStorageDiffHelperTest {
}
@Test
- fun sameListNoUpdatesPlaceholder() {
- val storageNoPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
-
- val storageWithPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
- storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
- /* ignored */ mock(PagedStorage.Callback::class.java))
-
- // even though one has placeholders, and null counts are different...
- assertEquals(10, storageNoPlaceholder.trailingNullCount)
- assertEquals(7, storageWithPlaceholder.trailingNullCount)
-
- // ... should be no interactions, since content still same
- validateTwoListDiff(
- storageNoPlaceholder,
- storageWithPlaceholder) {
- verifyZeroInteractions(it)
- }
- }
-
- @Test
fun appendFill() {
validateTwoListDiff(
PagedStorage(5, listOf("a", "b"), 5),
@@ -205,28 +185,6 @@ class PagedStorageDiffHelperTest {
}
}
- @Test
- fun transformAnchorIndex_loadingSnapshot() {
- val oldList = PagedStorage(10, listOf("a"), 10)
- val newList = PagedStorage(10, listOf("a"), 10)
-
- oldList.allocatePlaceholders(10, 5, 1,
- /* ignored */ mock(PagedStorage.Callback::class.java))
-
- assertEquals(5, oldList.leadingNullCount)
- assertEquals(10, oldList.computeLeadingNulls())
-
- validateTwoListDiffTransform(
- oldList,
- newList) { transformAnchorIndex ->
- // previously, this would cause a crash where we tried to use storage space
- // (getLeadingNullCount..size-getTrailingNulls) incorrectly, instead of diff space
- // (computeLeadingNulls..size-computeTrailingNulls). Diff space greedily excludes the
- // nulls that represent partially loaded pages to minimize diff computation cost.
- assertEquals(15, transformAnchorIndex(15))
- }
- }
-
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.kt b/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.kt
index ef182ca5da4..8e6fe2b3a8e 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.kt
@@ -30,19 +30,16 @@ import androidx.recyclerview.widget.ListUpdateCallback
*
* To only inform DiffUtil about single loaded page in this case, by pruning all other nulls from
* consideration.
- *
- * @see PagedStorage.computeLeadingNulls
- * @see PagedStorage.computeTrailingNulls
*/
internal fun <T : Any> PagedStorage<T>.computeDiff(
newList: PagedStorage<T>,
diffCallback: DiffUtil.ItemCallback<T>
): DiffUtil.DiffResult {
- val oldOffset = computeLeadingNulls()
- val newOffset = newList.computeLeadingNulls()
+ val oldOffset = leadingNullCount
+ val newOffset = newList.leadingNullCount
- val oldSize = size - oldOffset - computeTrailingNulls()
- val newSize = newList.size - newOffset - newList.computeTrailingNulls()
+ val oldSize = size - oldOffset - trailingNullCount
+ val newSize = newList.size - newOffset - newList.trailingNullCount
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
@@ -121,10 +118,10 @@ internal fun <T : Any> PagedStorage<T>.dispatchDiff(
newList: PagedStorage<T>,
diffResult: DiffUtil.DiffResult
) {
- val trailingOld = computeTrailingNulls()
- val trailingNew = newList.computeTrailingNulls()
- val leadingOld = computeLeadingNulls()
- val leadingNew = newList.computeLeadingNulls()
+ val trailingOld = trailingNullCount
+ val trailingNew = newList.trailingNullCount
+ val leadingOld = leadingNullCount
+ val leadingNew = newList.leadingNullCount
if (trailingOld == 0 &&
trailingNew == 0 &&
@@ -168,13 +165,13 @@ internal fun PagedStorage<*>.transformAnchorIndex(
newList: PagedStorage<*>,
oldPosition: Int
): Int {
- val oldOffset = computeLeadingNulls()
+ val oldOffset = leadingNullCount
// diffResult's indices starting after nulls, need to transform to diffutil indices
// (see also dispatchDiff(), which adds this offset when dispatching)
val diffIndex = oldPosition - oldOffset
- val oldSize = size - oldOffset - computeTrailingNulls()
+ val oldSize = size - oldOffset - trailingNullCount
// if our anchor is non-null, use it or close item's position in new list
if (diffIndex in 0 until oldSize) {