blob: e0e120c8d13dda2895c3571e354f5a9dba8ea9e7 [file] [log] [blame]
/*
* Copyright 2021 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 androidx.room.paging
import android.database.Cursor
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.paging.LoadType
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingSource.LoadResult
import androidx.paging.PagingState
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.room.util.CursorUtil
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
private val tableName: String = "TestItem"
@RunWith(AndroidJUnit4::class)
@SmallTest
class LimitOffsetPagingSourceTest {
@JvmField
@Rule
val countingTaskExecutorRule = CountingTaskExecutorRule()
private lateinit var database: LimitOffsetTestDb
private lateinit var dao: TestItemDao
private val itemsList = createItemsForDb(0, 100)
@Before
fun init() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
LimitOffsetTestDb::class.java,
).build()
dao = database.dao
}
@After
fun tearDown() {
database.close()
// At the end of all tests, query executor should be idle (transaction thread released).
countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
assertThat(countingTaskExecutorRule.isIdle).isTrue()
}
@Test
fun test_itemCount() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
// count query is executed on first load
pagingSource.refresh()
assertThat(pagingSource.itemCount.get()).isEqualTo(100)
}
}
@Test
fun test_itemCountWithSuppliedLimitOffset() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString = "SELECT * FROM $tableName ORDER BY id ASC LIMIT 60 OFFSET 30",
)
runBlocking {
// count query is executed on first load
pagingSource.refresh()
// should be 60 instead of 100
assertThat(pagingSource.itemCount.get()).isEqualTo(60)
}
}
@Test
fun dbInsert_pagingSourceInvalidates() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
// load once to register db observers
pagingSource.refresh()
assertThat(pagingSource.invalid).isFalse()
// paging source should be invalidated when insert into db
val result = dao.addTestItem(TestItem(101))
countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
assertThat(result).isEqualTo(101)
assertTrue(pagingSource.invalid)
}
}
@Test
fun dbDelete_pagingSourceInvalidates() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
// load once to register db observers
pagingSource.refresh()
assertThat(pagingSource.invalid).isFalse()
// paging source should be invalidated when delete from db
dao.deleteTestItem(TestItem(50))
countingTaskExecutorRule.drainTasks(5, TimeUnit.SECONDS)
assertTrue(pagingSource.invalid)
}
}
@Test
fun invalidDbQuery_pagingSourceDoesNotInvalidate() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
// load once to register db observers
pagingSource.refresh()
assertThat(pagingSource.invalid).isFalse()
val result = dao.deleteTestItem(TestItem(1000))
// invalid delete. Should have 0 items deleted and paging source remains valid
assertThat(result).isEqualTo(0)
assertFalse(pagingSource.invalid)
}
}
@Test
fun load_initialLoad() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(0, 15)
)
}
}
@Test
fun load_initialEmptyLoad() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
assertTrue(result.data.isEmpty())
// now add items
dao.addAllItems(itemsList)
// the db write should cause pagingSource to realize it is invalid
assertThat(pagingSource.refresh()).isInstanceOf(
LoadResult.Invalid::class.java
)
assertTrue(pagingSource.invalid)
}
}
@Test
fun load_initialLoadWithInitialKey() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(database)
// refresh with initial key = 20
runBlocking {
val result = pagingSource.refresh(key = 20) as LoadResult.Page
// item in pos 21-35 (TestItemId 20-34) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(20, 35)
)
}
}
@Test
fun load_initialLoadWithSuppliedLimitOffset() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString = "SELECT * FROM $tableName ORDER BY id ASC LIMIT 10 OFFSET 30",
)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
// default initial loadSize = 15 starting from index 0.
// user supplied limit offset should cause initial loadSize = 10, starting from index 30
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(30, 40)
)
// check that no append/prepend can be triggered after this terminal load
assertThat(result.nextKey).isNull()
assertThat(result.prevKey).isNull()
assertThat(result.itemsBefore).isEqualTo(0)
assertThat(result.itemsAfter).isEqualTo(0)
}
}
@Test
fun load_oneAdditionalQueryArguments() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString =
"SELECT * FROM $tableName WHERE id < 50 ORDER BY id ASC",
)
// refresh with initial key = 40
runBlocking {
val result = pagingSource.refresh(key = 40) as LoadResult.Page
// initial loadSize = 15, but limited by id < 50, should only load items 40 - 50
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(40, 50)
)
// should have 50 items fulfilling condition of id < 50 (TestItem id 0 - 49)
assertThat(pagingSource.itemCount.get()).isEqualTo(50)
}
}
@Test
fun load_multipleQueryArguments() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString =
"SELECT * " +
"FROM $tableName " +
"WHERE id > 50 AND value LIKE 'item 90'" +
"ORDER BY id ASC",
)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
assertThat(result.data).containsExactly(itemsList[90])
assertThat(pagingSource.itemCount.get()).isEqualTo(1)
}
}
@Test
fun load_InvalidUserSuppliedOffset_returnEmpty() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString = "SELECT * FROM $tableName ORDER BY id ASC LIMIT 10 OFFSET 500",
)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
// invalid OFFSET = 500 should return empty data
assertThat(result.data).isEmpty()
// check that no append/prepend can be triggered
assertThat(pagingSource.itemCount.get()).isEqualTo(0)
assertThat(result.nextKey).isNull()
assertThat(result.prevKey).isNull()
assertThat(result.itemsBefore).isEqualTo(0)
assertThat(result.itemsAfter).isEqualTo(0)
}
}
@Test
fun load_UserSuppliedNegativeLimit() {
dao.addAllItems(itemsList)
val pagingSource = LimitOffsetPagingSourceImpl(
db = database,
queryString = "SELECT * FROM $tableName ORDER BY id ASC LIMIT -1",
)
runBlocking {
val result = pagingSource.refresh() as LoadResult.Page
// ensure that it respects SQLite's default behavior for negative LIMIT
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(0, 15)
)
// should behave as if no LIMIT were set
assertThat(pagingSource.itemCount.get()).isEqualTo(100)
assertThat(result.nextKey).isEqualTo(15)
assertThat(result.prevKey).isNull()
assertThat(result.itemsBefore).isEqualTo(0)
assertThat(result.itemsAfter).isEqualTo(85)
}
}
@Test
fun invalidInitialKey_dbEmpty_returnsEmpty() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
runBlocking {
val result = pagingSource.refresh(key = 101) as LoadResult.Page
assertThat(result.data).isEmpty()
}
}
@Test
fun invalidInitialKey_keyTooLarge_returnsLastPage() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
val result = pagingSource.refresh(key = 101) as LoadResult.Page
// should load the last page
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(85, 100)
)
}
}
@Test
fun invalidInitialKey_negativeKey() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
// should throw error when initial key is negative
val expectedException = assertFailsWith<IllegalArgumentException> {
pagingSource.refresh(key = -1)
}
// default message from Paging 3 for negative initial key
assertThat(expectedException.message).isEqualTo(
"itemsBefore cannot be negative"
)
}
}
@Test
fun append_middleOfList() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
val result = pagingSource.append(key = 20) as LoadResult.Page
// item in pos 21-25 (TestItemId 20-24) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(20, 25)
)
assertThat(result.nextKey).isEqualTo(25)
assertThat(result.prevKey).isEqualTo(20)
}
}
@Test
fun append_availableItemsLessThanLoadSize() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
val result = pagingSource.append(key = 97) as LoadResult.Page
// item in pos 98-100 (TestItemId 97-99) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(97, 100)
)
assertThat(result.nextKey).isEqualTo(null)
assertThat(result.prevKey).isEqualTo(97)
}
}
@Test
fun load_consecutiveAppend() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
// first append
val result = pagingSource.append(key = 30) as LoadResult.Page
// TestItemId 30-34 loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(30, 35)
)
// second append using nextKey from previous load
val result2 = pagingSource.append(key = result.nextKey) as LoadResult.Page
// TestItemId 35 - 39 loaded
assertThat(result2.data).containsExactlyElementsIn(
itemsList.subList(35, 40)
)
}
}
@Test
fun append_invalidResult() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
// first append
val result = pagingSource.append(key = 30) as LoadResult.Page
// TestItemId 30-34 loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(30, 35)
)
// make changes to database
dao.deleteTestItem(itemsList[42])
// this append should check invalidation tables, realize it has been updated,
// and return a LoadResult.Invalid
val result2 = pagingSource.append(key = result.nextKey)
assertThat(result2).isInstanceOf(LoadResult.Invalid::class.java)
}
}
@Test
fun prepend_middleOfList() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
val result = pagingSource.prepend(key = 30) as LoadResult.Page
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(25, 30)
)
assertThat(result.nextKey).isEqualTo(30)
assertThat(result.prevKey).isEqualTo(25)
}
}
@Test
fun prepend_availableItemsLessThanLoadSize() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
val result = pagingSource.prepend(key = 3) as LoadResult.Page
// items in pos 0 - 2 (TestItemId 0 - 2) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(0, 3)
)
assertThat(result.nextKey).isEqualTo(3)
assertThat(result.prevKey).isEqualTo(null)
}
}
@Test
fun load_consecutivePrepend() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
// first prepend
val result = pagingSource.prepend(key = 20) as LoadResult.Page
// items pos 16-20 (TestItemId 15-19) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(15, 20)
)
// second prepend using prevKey from previous load
val result2 = pagingSource.prepend(key = result.prevKey) as LoadResult.Page
// items pos 11-15 (TestItemId 10 - 14) loaded
assertThat(result2.data).containsExactlyElementsIn(
itemsList.subList(10, 15)
)
}
}
@FlakyTest(bugId = 193653151)
@Test
fun prepend_invalidResult() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
// to bypass check for initial load and run as non-initial load
pagingSource.itemCount.set(100)
runBlocking {
// first prepend
val result = pagingSource.prepend(key = 20) as LoadResult.Page
// items pos 16-20 (TestItemId 15-19) loaded
assertThat(result.data).containsExactlyElementsIn(
itemsList.subList(15, 20)
)
// now write into database
dao.deleteTestItem(itemsList[30])
// second prepend using prevKey from previous load
val result2 = pagingSource.prepend(key = result.prevKey)
// this prepend should check invalidation tables, realize it has been updated,
// and return a LoadResult.Invalid
assertThat(result2).isInstanceOf(LoadResult.Invalid::class.java)
}
}
@Test
fun test_itemsBefore() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
// for initial load
val result = pagingSource.refresh(key = 50) as LoadResult.Page
// initial loads items in pos 51 - 65, should have 50 items before
assertThat(result.itemsBefore).isEqualTo(50)
// prepend from initial load
val result2 = pagingSource.prepend(key = result.prevKey) as LoadResult.Page
// prepend loads items in pos 46 - 50, should have 45 item before
assertThat(result2.itemsBefore).isEqualTo(45)
// append from initial load
val result3 = pagingSource.append(key = result.nextKey) as LoadResult.Page
// append loads items in position 66 - 70 , should have 65 item before
assertThat(result3.itemsBefore).isEqualTo(65)
}
}
@Test
fun test_itemsAfter() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
// for initial load
val result = pagingSource.refresh(key = 30) as LoadResult.Page
// initial loads items in position 31 - 45, should have 55 items after
assertThat(result.itemsAfter).isEqualTo(55)
// prepend from initial load
val result2 = pagingSource.prepend(key = result.prevKey) as LoadResult.Page
// prepend loads items in position 26 - 30, should have 70 item after
assertThat(result2.itemsAfter).isEqualTo(70)
// append from initial load
val result3 = pagingSource.append(result.nextKey) as LoadResult.Page
// append loads items in position 46 - 50 , should have 50 item after
assertThat(result3.itemsAfter).isEqualTo(50)
}
}
@Test
fun test_getRefreshKey() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
// initial load
val result = pagingSource.refresh() as LoadResult.Page
// 15 items loaded, assuming anchorPosition = 14 as the last item loaded
var refreshKey = pagingSource.getRefreshKey(
PagingState(
pages = listOf(result),
anchorPosition = 14,
config = CONFIG,
leadingPlaceholderCount = 0
)
)
// should load around anchor position
// Initial load size = 15, refresh key should be (15/2 = 7) items
// before anchorPosition (14 - 7 = 7)
assertThat(refreshKey).isEqualTo(7)
// append after refresh
val result2 = pagingSource.append(key = result.nextKey) as LoadResult.Page
assertThat(result2.data).isEqualTo(
itemsList.subList(15, 20)
)
refreshKey = pagingSource.getRefreshKey(
PagingState(
pages = listOf(result, result2),
// 20 items loaded, assume anchorPosition = 19 as the last item loaded
anchorPosition = 19,
config = CONFIG,
leadingPlaceholderCount = 0
)
)
// initial load size 15. Refresh key should be (15/2 = 7) items before anchorPosition
// (19 - 7 = 12)
assertThat(refreshKey).isEqualTo(12)
}
}
@Test
fun load_refreshKeyGreaterThanItemCount_lastPage() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
pagingSource.refresh(key = 70)
dao.deleteTestItems(40, 100)
// assume user was viewing last item of the refresh load with anchorPosition = 85,
// initialLoadSize = 15. This mimics how getRefreshKey() calculates refresh key.
val refreshKey = 85 - (15 / 2)
assertThat(refreshKey).isEqualTo(78)
val pagingSource2 = LimitOffsetPagingSourceImpl(database)
val result2 = pagingSource2.refresh(key = refreshKey) as LoadResult.Page
// database should only have 40 items left. Refresh key is invalid at this point
// (greater than item count after deletion)
assertThat(pagingSource2.itemCount.get()).isEqualTo(40)
// ensure that paging source can handle invalid refresh key properly
// should load last page with items 25 - 40
assertThat(result2.data).containsExactlyElementsIn(
itemsList.subList(25, 40)
)
// should account for updated item count to return correct itemsBefore, itemsAfter,
// prevKey, nextKey
assertThat(result2.itemsBefore).isEqualTo(25)
assertThat(result2.itemsAfter).isEqualTo(0)
// no append can be triggered
assertThat(result2.prevKey).isEqualTo(25)
assertThat(result2.nextKey).isEqualTo(null)
}
}
/**
* Tests the behavior if user was viewing items in the top of the database and those items
* were deleted.
*
* Currently, if anchorPosition is small enough (within bounds of 0 to loadSize/2), then on
* invalidation from dropped items at the top, refresh will load with offset = 0. If
* anchorPosition is larger than loadsize/2, then the refresh load's offset will
* be 0 to (anchorPosition - loadSize/2).
*
* Ideally, in the future Paging will be able to handle this case better.
*/
@Test
fun load_refreshKeyGreaterThanItemCount_firstPage() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
pagingSource.refresh()
assertThat(pagingSource.itemCount.get()).isEqualTo(100)
// items id 0 - 29 deleted (30 items removed)
dao.deleteTestItems(0, 29)
val pagingSource2 = LimitOffsetPagingSourceImpl(database)
// assume user was viewing first few items with anchorPosition = 0 and refresh key
// clips to 0
val refreshKey = 0
val result2 = pagingSource2.refresh(key = refreshKey) as LoadResult.Page
// database should only have 70 items left
assertThat(pagingSource2.itemCount.get()).isEqualTo(70)
// first 30 items deleted, refresh should load starting from pos 31 (item id 30 - 45)
assertThat(result2.data).containsExactlyElementsIn(
itemsList.subList(30, 45)
)
// should account for updated item count to return correct itemsBefore, itemsAfter,
// prevKey, nextKey
assertThat(result2.itemsBefore).isEqualTo(0)
assertThat(result2.itemsAfter).isEqualTo(55)
// no prepend can be triggered
assertThat(result2.prevKey).isEqualTo(null)
assertThat(result2.nextKey).isEqualTo(15)
}
}
@Test
fun load_loadSizeAndRefreshKeyGreaterThanItemCount() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
dao.addAllItems(itemsList)
runBlocking {
pagingSource.refresh(key = 30)
assertThat(pagingSource.itemCount.get()).isEqualTo(100)
// items id 0 - 94 deleted (95 items removed)
dao.deleteTestItems(0, 94)
val pagingSource2 = LimitOffsetPagingSourceImpl(database)
// assume user was viewing first few items with anchorPosition = 0 and refresh key
// clips to 0
val refreshKey = 0
val result2 = pagingSource2.refresh(key = refreshKey) as LoadResult.Page
// database should only have 5 items left
assertThat(pagingSource2.itemCount.get()).isEqualTo(5)
// only 5 items should be loaded with offset = 0
assertThat(result2.data).containsExactlyElementsIn(
itemsList.subList(95, 100)
)
// should recognize that this is a terminal load
assertThat(result2.itemsBefore).isEqualTo(0)
assertThat(result2.itemsAfter).isEqualTo(0)
assertThat(result2.prevKey).isEqualTo(null)
assertThat(result2.nextKey).isEqualTo(null)
}
}
@Test
fun test_jumpSupport() {
val pagingSource = LimitOffsetPagingSourceImpl(database)
assertTrue(pagingSource.jumpingSupported)
}
private fun createLoadParam(
loadType: LoadType,
key: Int? = null,
initialLoadSize: Int = CONFIG.initialLoadSize,
pageSize: Int = CONFIG.pageSize,
placeholdersEnabled: Boolean = CONFIG.enablePlaceholders
): PagingSource.LoadParams<Int> {
return when (loadType) {
LoadType.REFRESH -> {
PagingSource.LoadParams.Refresh(
key = key,
loadSize = initialLoadSize,
placeholdersEnabled = placeholdersEnabled
)
}
LoadType.APPEND -> {
PagingSource.LoadParams.Append(
key = key ?: -1,
loadSize = pageSize,
placeholdersEnabled = placeholdersEnabled
)
}
LoadType.PREPEND -> {
PagingSource.LoadParams.Prepend(
key = key ?: -1,
loadSize = pageSize,
placeholdersEnabled = placeholdersEnabled
)
}
}
}
private fun createItemsForDb(startId: Int, count: Int): List<TestItem> {
return List(count) {
TestItem(
id = it + startId,
)
}
}
private suspend fun PagingSource<Int, TestItem>.refresh(
key: Int? = null,
): LoadResult<Int, TestItem> {
return this.load(
createLoadParam(
loadType = LoadType.REFRESH,
key = key,
)
)
}
private suspend fun PagingSource<Int, TestItem>.append(
key: Int? = -1,
): LoadResult<Int, TestItem> {
return this.load(
createLoadParam(
loadType = LoadType.APPEND,
key = key,
)
)
}
private suspend fun PagingSource<Int, TestItem>.prepend(
key: Int? = -1,
): LoadResult<Int, TestItem> {
return this.load(
createLoadParam(
loadType = LoadType.PREPEND,
key = key,
)
)
}
companion object {
val CONFIG = PagingConfig(
pageSize = 5,
enablePlaceholders = true,
initialLoadSize = 15
)
}
}
class LimitOffsetPagingSourceImpl(
db: RoomDatabase,
queryString: String = "SELECT * FROM $tableName ORDER BY id ASC",
) : LimitOffsetPagingSource<TestItem>(
sourceQuery = RoomSQLiteQuery.acquire(
queryString,
0
),
db = db,
tables = arrayOf("$tableName")
) {
override fun convertRows(cursor: Cursor): List<TestItem> {
val cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(cursor, "id")
val data = mutableListOf<TestItem>()
while (cursor.moveToNext()) {
val tmpId = cursor.getInt(cursorIndexOfId)
data.add(TestItem(tmpId))
}
return data
}
}