Migrate paging to use ktfmt
See go/why-ktfmt
BUG: 319663977
Change-Id: I240b51a54f9dd56c9e8fc94de6d058e8980e8bb1
diff --git a/gradle.properties b/gradle.properties
index 47b6866..1af19a4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -48,7 +48,7 @@
androidx.unpinComposeCompiler=false
# Prefix of projects that are opted-in to use ktfmt
-androidx.ktfmt.optin=:a,:b,:camera,:car,:collection,:concurrent,:constraintlayout,:core,:d,:e,:f,:g,:h,:i,:j,:k,:l,:n
+androidx.ktfmt.optin=:a,:b,:camera,:car,:collection,:concurrent,:constraintlayout,:core,:d,:e,:f,:g,:h,:i,:j,:k,:l,:n,:paging
# Disable features we do not use
android.defaults.buildfeatures.aidl=false
android.defaults.buildfeatures.buildconfig=false
diff --git a/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt b/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt
index 68a89b0..df0b938 100644
--- a/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt
+++ b/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt
@@ -46,8 +46,7 @@
@LargeTest
@RunWith(AndroidJUnit4::class)
class OnPagesUpdatedTest {
- @get:Rule
- val scenarioRule = ActivityScenarioRule(V3Activity::class.java)
+ @get:Rule val scenarioRule = ActivityScenarioRule(V3Activity::class.java)
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@@ -57,9 +56,7 @@
lateinit var job: Job
lateinit var adapter: V3Adapter
- scenario.onActivity { activity ->
- adapter = activity.pagingAdapter
- }
+ scenario.onActivity { activity -> adapter = activity.pagingAdapter }
// Wait for initial load to complete.
adapter.onPagesUpdatedFlow.first { adapter.itemCount > 0 }
@@ -70,12 +67,13 @@
// Items are loaded before we start observing.
assertThat(activity.pagingAdapter.itemCount).isGreaterThan(0)
- job = activity.lifecycleScope.launch {
- activity.pagingAdapter.onPagesUpdatedFlow.collect {
- onPagesUpdatedEventsCh.send(it)
- processNextPageUpdateCh.receive()
+ job =
+ activity.lifecycleScope.launch {
+ activity.pagingAdapter.onPagesUpdatedFlow.collect {
+ onPagesUpdatedEventsCh.send(it)
+ processNextPageUpdateCh.receive()
+ }
}
- }
// Page update from before we started listening should not be buffered.
assertTrue { onPagesUpdatedEventsCh.isEmpty }
@@ -96,9 +94,7 @@
processNextPageUpdateCh.send(Unit)
// Trigger a bunch of updates without unblocking page update collector.
- repeat(66) {
- adapter.refreshAndAwaitIdle()
- }
+ repeat(66) { adapter.refreshAndAwaitIdle() }
// Fully unblock collector.
var pageUpdates = 0
@@ -128,30 +124,31 @@
private suspend fun V3Adapter.refreshAndAwaitIdle() {
val scenario = scenarioRule.scenario
val loadStateCollectorStarted = CompletableDeferred<Unit>()
- val result = CoroutineScope(EmptyCoroutineContext).async {
- loadStateFlow
- .onStart { loadStateCollectorStarted.complete(Unit) }
- .scan(RefreshState.INITIAL) { acc, next ->
- when (acc) {
- RefreshState.INITIAL -> {
- if (next.source.refresh is LoadState.Loading) {
- RefreshState.LOADING
- } else {
- RefreshState.INITIAL
+ val result =
+ CoroutineScope(EmptyCoroutineContext).async {
+ loadStateFlow
+ .onStart { loadStateCollectorStarted.complete(Unit) }
+ .scan(RefreshState.INITIAL) { acc, next ->
+ when (acc) {
+ RefreshState.INITIAL -> {
+ if (next.source.refresh is LoadState.Loading) {
+ RefreshState.LOADING
+ } else {
+ RefreshState.INITIAL
+ }
}
- }
- RefreshState.LOADING -> {
- if (next.source.refresh !is LoadState.Loading) {
- RefreshState.DONE
- } else {
- RefreshState.LOADING
+ RefreshState.LOADING -> {
+ if (next.source.refresh !is LoadState.Loading) {
+ RefreshState.DONE
+ } else {
+ RefreshState.LOADING
+ }
}
+ else -> acc
}
- else -> acc
}
- }
- .first { it == RefreshState.DONE }
- }
+ .first { it == RefreshState.DONE }
+ }
loadStateCollectorStarted.await()
scenario.onActivity { refresh() }
@@ -159,6 +156,8 @@
}
private enum class RefreshState {
- INITIAL, LOADING, DONE
+ INITIAL,
+ LOADING,
+ DONE
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index cbefb66..9725eb3 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -25,9 +25,7 @@
val dataSourceError = AtomicBoolean(false)
-/**
- * Sample position-based PagingSource with artificial data.
- */
+/** Sample position-based PagingSource with artificial data. */
internal class ItemDataSource : PagingSource<Int, Item>() {
class RetryableItemError : Exception()
@@ -44,31 +42,19 @@
)
is LoadParams.Prepend -> {
val loadSize = minOf(params.key, params.loadSize)
- loadInternal(
- position = params.key - loadSize,
- loadSize = loadSize
- )
+ loadInternal(position = params.key - loadSize, loadSize = loadSize)
}
- is LoadParams.Append ->
- loadInternal(
- position = params.key,
- loadSize = params.loadSize
- )
+ is LoadParams.Append -> loadInternal(position = params.key, loadSize = params.loadSize)
}
- private suspend fun loadInternal(
- position: Int,
- loadSize: Int
- ): LoadResult<Int, Item> {
+ private suspend fun loadInternal(position: Int, loadSize: Int): LoadResult<Int, Item> {
delay(1000)
if (dataSourceError.compareAndSet(true, false)) {
return LoadResult.Error(RetryableItemError())
} else {
val bgColor = COLORS[generationId % COLORS.size]
val endExclusive = (position + loadSize).coerceAtMost(COUNT)
- val data = (position until endExclusive).map {
- Item(it, "item $it", bgColor)
- }
+ val data = (position until endExclusive).map { Item(it, "item $it", bgColor) }
return LoadResult.Page(
data = data,
@@ -83,8 +69,7 @@
companion object {
private const val COUNT = 60
- @ColorInt
- private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
+ @ColorInt private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
private var sGenerationId: Int = 0
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
index 775f65b..716d226 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
@@ -29,9 +29,7 @@
import androidx.paging.integration.testapp.v3.StateItemAdapter
import androidx.recyclerview.widget.RecyclerView
-/**
- * Sample PagedList activity with artificial data source.
- */
+/** Sample PagedList activity with artificial data source. */
class PagedListSampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -40,21 +38,18 @@
val pagingAdapter = PagedListItemAdapter()
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
- recyclerView.adapter = pagingAdapter.withLoadStateHeaderAndFooter(
- header = StateItemAdapter { pagingAdapter.currentList?.retry() },
- footer = StateItemAdapter { pagingAdapter.currentList?.retry() }
- )
+ recyclerView.adapter =
+ pagingAdapter.withLoadStateHeaderAndFooter(
+ header = StateItemAdapter { pagingAdapter.currentList?.retry() },
+ footer = StateItemAdapter { pagingAdapter.currentList?.retry() }
+ )
@Suppress("DEPRECATION")
- viewModel.livePagedList.observe(this) { pagedList ->
- pagingAdapter.submitList(pagedList)
- }
+ viewModel.livePagedList.observe(this) { pagedList -> pagingAdapter.submitList(pagedList) }
setupLoadStateButtons(viewModel, pagingAdapter)
- findViewById<Button>(R.id.button_error).setOnClickListener {
- dataSourceError.set(true)
- }
+ findViewById<Button>(R.id.button_error).setOnClickListener { dataSourceError.set(true) }
}
private fun setupLoadStateButtons(
@@ -64,9 +59,7 @@
) {
val button = findViewById<Button>(R.id.button_refresh)
- button.setOnClickListener {
- viewModel.invalidateList()
- }
+ button.setOnClickListener { viewModel.invalidateList() }
adapter.addLoadStateListener { type: LoadType, state: LoadState ->
if (type != LoadType.REFRESH) return@addLoadStateListener
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
index e593fab..a21d318 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKey.kt
@@ -18,11 +18,8 @@
import androidx.room.Entity
import androidx.room.PrimaryKey
-/**
- * Sample entity to persist remote key for use in RemoteMediator.
- */
+/** Sample entity to persist remote key for use in RemoteMediator. */
@Entity(tableName = "remote_key")
class RemoteKey(val prevKey: Int, val nextKey: Int) {
- @PrimaryKey
- var id = 0
+ @PrimaryKey var id = 0
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt
index 1b46925..b7aad82 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RemoteKeyDao.kt
@@ -20,9 +20,7 @@
import androidx.room.OnConflictStrategy.Companion.REPLACE
import androidx.room.Query
-/**
- * Simple Customer DAO for Room Customer list sample.
- */
+/** Simple Customer DAO for Room Customer list sample. */
@Dao
interface RemoteKeyDao {
/**
@@ -30,18 +28,11 @@
*
* @param remoteKey
*/
- @Insert(onConflict = REPLACE)
- suspend fun insert(remoteKey: RemoteKey)
+ @Insert(onConflict = REPLACE) suspend fun insert(remoteKey: RemoteKey)
- /**
- * Clears the RemoteKey
- */
- @Query("DELETE FROM remote_key")
- fun delete()
+ /** Clears the RemoteKey */
+ @Query("DELETE FROM remote_key") fun delete()
- /**
- * @return Latest persisted RemoteKey
- */
- @Query("SELECT * FROM remote_key LIMIT 1")
- suspend fun queryRemoteKey(): RemoteKey?
+ /** @return Latest persisted RemoteKey */
+ @Query("SELECT * FROM remote_key LIMIT 1") suspend fun queryRemoteKey(): RemoteKey?
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/Item.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/Item.kt
index 925e550..4e8fb7e 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/Item.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/Item.kt
@@ -20,14 +20,15 @@
data class Item(val id: Int, val text: String, val bgColor: Int) {
companion object {
- val DIFF_CALLBACK: DiffUtil.ItemCallback<Item> = object : DiffUtil.ItemCallback<Item>() {
- override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
- return oldItem == newItem
- }
+ val DIFF_CALLBACK: DiffUtil.ItemCallback<Item> =
+ object : DiffUtil.ItemCallback<Item>() {
+ override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
- return oldItem.id == newItem.id
+ override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
+ return oldItem.id == newItem.id
+ }
}
- }
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
index fd4b1bf..36e905f 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
@@ -25,9 +25,7 @@
val dataSourceError = AtomicBoolean(false)
-/**
- * Sample position-based PagingSource with artificial data.
- */
+/** Sample position-based PagingSource with artificial data. */
internal class ItemPagingSource : PagingSource<Int, Item>() {
class RetryableItemError : Exception()
@@ -44,31 +42,19 @@
)
is LoadParams.Prepend -> {
val loadSize = minOf(params.key, params.loadSize)
- loadInternal(
- position = params.key - loadSize,
- loadSize = loadSize
- )
+ loadInternal(position = params.key - loadSize, loadSize = loadSize)
}
- is LoadParams.Append ->
- loadInternal(
- position = params.key,
- loadSize = params.loadSize
- )
+ is LoadParams.Append -> loadInternal(position = params.key, loadSize = params.loadSize)
}
- private suspend fun loadInternal(
- position: Int,
- loadSize: Int
- ): LoadResult<Int, Item> {
+ private suspend fun loadInternal(position: Int, loadSize: Int): LoadResult<Int, Item> {
delay(1000)
if (dataSourceError.compareAndSet(true, false)) {
return LoadResult.Error(RetryableItemError())
} else {
val bgColor = COLORS[generationId % COLORS.size]
val endExclusive = (position + loadSize).coerceAtMost(COUNT)
- val data = (position until endExclusive).map {
- Item(it, "item $it", bgColor)
- }
+ val data = (position until endExclusive).map { Item(it, "item $it", bgColor) }
return LoadResult.Page(
data = data,
@@ -89,8 +75,7 @@
private const val COUNT = 60
- @ColorInt
- private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
+ @ColorInt private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
private var sGenerationId: Int = 0
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/StateItemAdapter.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/StateItemAdapter.kt
index 3b921a8..81ce633 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/StateItemAdapter.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/StateItemAdapter.kt
@@ -27,14 +27,14 @@
import androidx.paging.integration.testapp.R
import androidx.recyclerview.widget.RecyclerView
-class LoadStateViewHolder(
- parent: ViewGroup,
- retry: () -> Unit
-) : RecyclerView.ViewHolder(inflate(parent)) {
+class LoadStateViewHolder(parent: ViewGroup, retry: () -> Unit) :
+ RecyclerView.ViewHolder(inflate(parent)) {
private val progressBar: ProgressBar = itemView.findViewById(R.id.progress_bar)
private val errorMsg: TextView = itemView.findViewById(R.id.error_msg)
- private val retry: Button = itemView.findViewById<Button>(R.id.retry_button)
- .also { it.setOnClickListener { retry.invoke() } }
+ private val retry: Button =
+ itemView.findViewById<Button>(R.id.retry_button).also {
+ it.setOnClickListener { retry.invoke() }
+ }
fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
@@ -45,22 +45,20 @@
errorMsg.visibility = toVisibility(loadState !is LoadState.Loading)
}
- private fun toVisibility(constraint: Boolean): Int = if (constraint) {
- View.VISIBLE
- } else {
- View.GONE
- }
+ private fun toVisibility(constraint: Boolean): Int =
+ if (constraint) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
companion object {
fun inflate(parent: ViewGroup): View =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.load_state_item, parent, false)
+ LayoutInflater.from(parent.context).inflate(R.layout.load_state_item, parent, false)
}
}
-class StateItemAdapter(
- private val retry: () -> Unit
-) : LoadStateAdapter<LoadStateViewHolder>() {
+class StateItemAdapter(private val retry: () -> Unit) : LoadStateAdapter<LoadStateViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState) =
LoadStateViewHolder(parent, retry)
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
index c85f3f3..051fa2b 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
@@ -43,11 +43,12 @@
setContentView(R.layout.activity_recycler_view)
val viewModel by viewModels<V3ViewModel>()
- val orientationText = when (resources.configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> "land"
- Configuration.ORIENTATION_PORTRAIT -> "port"
- else -> "unknown"
- }
+ val orientationText =
+ when (resources.configuration.orientation) {
+ Configuration.ORIENTATION_LANDSCAPE -> "land"
+ Configuration.ORIENTATION_PORTRAIT -> "port"
+ else -> "unknown"
+ }
// NOTE: lifecycleScope means we don't respect paused state here
lifecycleScope.launch {
viewModel.flow
@@ -58,26 +59,26 @@
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
- recyclerView.adapter = pagingAdapter.withLoadStateHeaderAndFooter(
- header = StateItemAdapter { pagingAdapter.retry() },
- footer = StateItemAdapter { pagingAdapter.retry() }
- )
+ recyclerView.adapter =
+ pagingAdapter.withLoadStateHeaderAndFooter(
+ header = StateItemAdapter { pagingAdapter.retry() },
+ footer = StateItemAdapter { pagingAdapter.retry() }
+ )
setupLoadStateButtons(pagingAdapter)
- findViewById<Button>(R.id.button_error).setOnClickListener {
- dataSourceError.set(true)
- }
+ findViewById<Button>(R.id.button_error).setOnClickListener { dataSourceError.set(true) }
}
private fun setupLoadStateButtons(adapter: PagingDataAdapter<Item, RecyclerView.ViewHolder>) {
val button = findViewById<Button>(R.id.button_refresh)
adapter.addLoadStateListener { loadStates: CombinedLoadStates ->
- button.text = when (loadStates.refresh) {
- is NotLoading -> "Refresh"
- is Loading -> "Loading"
- is Error -> "Error"
- }
+ button.text =
+ when (loadStates.refresh) {
+ is NotLoading -> "Refresh"
+ is Loading -> "Loading"
+ is Error -> "Error"
+ }
if (loadStates.refresh is NotLoading) {
button.setOnClickListener { adapter.refresh() }
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Adapter.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Adapter.kt
index 4eff329..71568b7 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Adapter.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Adapter.kt
@@ -23,16 +23,16 @@
import androidx.paging.integration.testapp.R
import androidx.recyclerview.widget.RecyclerView
-class V3Adapter : PagingDataAdapter<Item, RecyclerView.ViewHolder>(
- diffCallback = Item.DIFF_CALLBACK
-) {
+class V3Adapter :
+ PagingDataAdapter<Item, RecyclerView.ViewHolder>(diffCallback = Item.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val holder = object : RecyclerView.ViewHolder(TextView(parent.context)) {}
holder.itemView.minimumHeight = 150
- holder.itemView.layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- )
+ holder.itemView.layoutParams =
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
return holder
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3ViewModel.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3ViewModel.kt
index 943e2d0..4a439b6 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3ViewModel.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3ViewModel.kt
@@ -28,33 +28,26 @@
import kotlinx.coroutines.flow.map
class V3ViewModel : ViewModel() {
- val flow = Pager(PagingConfig(10), pagingSourceFactory = ItemPagingSource.Factory)
- .flow
- .map { pagingData ->
- pagingData
- .insertSeparators { before: Item?, after: Item? ->
- if (after == null || (after.id / 3) == (before?.id ?: 0) / 3) {
- // no separator, because at bottom or not needed yet
- null
- } else {
- Item(
- id = -1,
- text = "DIVIDER" + after.id / 3,
- bgColor = Color.DKGRAY
- )
+ val flow =
+ Pager(PagingConfig(10), pagingSourceFactory = ItemPagingSource.Factory)
+ .flow
+ .map { pagingData ->
+ pagingData
+ .insertSeparators { before: Item?, after: Item? ->
+ if (after == null || (after.id / 3) == (before?.id ?: 0) / 3) {
+ // no separator, because at bottom or not needed yet
+ null
+ } else {
+ Item(id = -1, text = "DIVIDER" + after.id / 3, bgColor = Color.DKGRAY)
+ }
}
- }
- .insertSeparators { before: Item?, _: Item? ->
- if (before != null && before.id == -1) {
- Item(
- id = -2,
- text = "RIGHT BELOW DIVIDER",
- bgColor = Color.BLACK
- )
- } else null
- }
- .insertHeaderItem(item = Item(Int.MIN_VALUE, "HEADER", Color.MAGENTA))
- .insertFooterItem(item = Item(Int.MAX_VALUE, "FOOTER", Color.MAGENTA))
- }
- .cachedIn(viewModelScope)
+ .insertSeparators { before: Item?, _: Item? ->
+ if (before != null && before.id == -1) {
+ Item(id = -2, text = "RIGHT BELOW DIVIDER", bgColor = Color.BLACK)
+ } else null
+ }
+ .insertHeaderItem(item = Item(Int.MIN_VALUE, "HEADER", Color.MAGENTA))
+ .insertFooterItem(item = Item(Int.MAX_VALUE, "FOOTER", Color.MAGENTA))
+ }
+ .cachedIn(viewModelScope)
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
index 13709de..828b63e 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
@@ -21,9 +21,7 @@
import androidx.paging.integration.testapp.room.Customer
import java.util.UUID
-/**
- * Sample position-based PagingSource with artificial data.
- */
+/** Sample position-based PagingSource with artificial data. */
internal class NetworkCustomerPagingSource : PagingSource<Int, Customer>() {
private fun createCustomer(i: Int): Customer {
val customer = Customer()
@@ -32,19 +30,17 @@
return customer
}
- override fun getRefreshKey(
- state: PagingState<Int, Customer>
- ): Int? = state.anchorPosition?.let {
- maxOf(0, it - 5)
- }
+ override fun getRefreshKey(state: PagingState<Int, Customer>): Int? =
+ state.anchorPosition?.let { maxOf(0, it - 5) }
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Customer> {
val key = params.key ?: 0
- val data = if (params is LoadParams.Prepend) {
- List(params.loadSize) { createCustomer(it + key - params.loadSize) }
- } else {
- List(params.loadSize) { createCustomer(it + key) }
- }
+ val data =
+ if (params is LoadParams.Prepend) {
+ List(params.loadSize) { createCustomer(it + key - params.loadSize) }
+ } else {
+ List(params.loadSize) { createCustomer(it + key) }
+ }
return LoadResult.Page(
data = data,
prevKey = if (key > 0) key else null,
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
index 1f3dac2..34e2dbd 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
@@ -53,25 +53,29 @@
// Fetch latest remote key from db. We cannot rely on PagingState because the
// invalidate + load loop in paging may race with the actual load + insert happening in
// RemoteMediator.
- val remoteKey = withContext(Dispatchers.IO) {
- database.remoteKeyDao.queryRemoteKey() ?: RemoteKey(-1, 0)
- }
+ val remoteKey =
+ withContext(Dispatchers.IO) {
+ database.remoteKeyDao.queryRemoteKey() ?: RemoteKey(-1, 0)
+ }
// TODO: Move this to be a more fully featured sample which demonstrated key translation
// between two types of PagingSources where the keys do not map 1:1.
- val loadParams = when (loadType) {
- LoadType.REFRESH -> PagingSource.LoadParams.Refresh(
- key = 0,
- loadSize = 10,
- placeholdersEnabled = false
- )
- LoadType.PREPEND -> throw IllegalStateException()
- LoadType.APPEND -> PagingSource.LoadParams.Append(
- key = remoteKey.nextKey,
- loadSize = 10,
- placeholdersEnabled = false
- )
- }
+ val loadParams =
+ when (loadType) {
+ LoadType.REFRESH ->
+ PagingSource.LoadParams.Refresh(
+ key = 0,
+ loadSize = 10,
+ placeholdersEnabled = false
+ )
+ LoadType.PREPEND -> throw IllegalStateException()
+ LoadType.APPEND ->
+ PagingSource.LoadParams.Append(
+ key = remoteKey.nextKey,
+ loadSize = 10,
+ placeholdersEnabled = false
+ )
+ }
return when (val result = networkSource.load(loadParams)) {
is PagingSource.LoadResult.Page -> {
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomActivity.kt
index d248ea5..18d2385 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomActivity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomActivity.kt
@@ -37,11 +37,7 @@
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.adapter = adapter
- lifecycleScope.launch {
- viewModel.flow.collectLatest {
- adapter.submitData(it)
- }
- }
+ lifecycleScope.launch { viewModel.flow.collectLatest { adapter.submitData(it) } }
val addButton = findViewById<Button>(R.id.addButton)
addButton.setOnClickListener { viewModel.insertCustomer() }
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
index 56ef134..5c96dd8 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
@@ -26,15 +26,15 @@
import androidx.paging.integration.testapp.room.Customer
import androidx.recyclerview.widget.RecyclerView
-class V3RoomAdapter : PagingDataAdapter<Customer, RecyclerView.ViewHolder>(
- diffCallback = Customer.DIFF_CALLBACK
-) {
+class V3RoomAdapter :
+ PagingDataAdapter<Customer, RecyclerView.ViewHolder>(diffCallback = Customer.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return object : RecyclerView.ViewHolder(TextView(parent.context)) {}
- .apply<RecyclerView.ViewHolder> {
- itemView.minimumHeight = 150
- itemView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- }
+ return object : RecyclerView.ViewHolder(TextView(parent.context)) {}.apply<
+ RecyclerView.ViewHolder
+ > {
+ itemView.minimumHeight = 150
+ itemView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ }
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
index 32ffe30..5de62da 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
@@ -34,11 +34,9 @@
import kotlinx.coroutines.flow.map
class V3RoomViewModel(application: Application) : AndroidViewModel(application) {
- val database = Room.databaseBuilder(
- getApplication(),
- SampleDatabase::class.java,
- "customerDatabaseV3"
- ).build()
+ val database =
+ Room.databaseBuilder(getApplication(), SampleDatabase::class.java, "customerDatabaseV3")
+ .build()
private fun createCustomer(): Customer {
val customer = Customer()
@@ -48,8 +46,9 @@
}
internal fun insertCustomer() {
- ArchTaskExecutor.getInstance()
- .executeOnDiskIO { database.customerDao.insert(createCustomer()) }
+ ArchTaskExecutor.getInstance().executeOnDiskIO {
+ database.customerDao.insert(createCustomer())
+ }
}
internal fun clearAllCustomers() {
@@ -62,52 +61,53 @@
}
@OptIn(ExperimentalPagingApi::class)
- val flow = Pager(
- PagingConfig(10),
- remoteMediator = V3RemoteMediator(
- database,
- NetworkCustomerPagingSource.FACTORY
- )
- ) {
- database.customerDao.loadPagedAgeOrderPagingSource()
- }.flow
- .map { pagingData ->
- pagingData
- .insertSeparators { before: Customer?, after: Customer? ->
- if (after == null || (after.id / 3) == (before?.id ?: 0) / 3) {
- // no separator, because at bottom or not needed yet
- null
- } else {
- Customer().apply {
- id = -1
- name = "RIGHT ABOVE DIVIDER"
- lastName = "RIGHT ABOVE DIVIDER"
+ val flow =
+ Pager(
+ PagingConfig(10),
+ remoteMediator = V3RemoteMediator(database, NetworkCustomerPagingSource.FACTORY)
+ ) {
+ database.customerDao.loadPagedAgeOrderPagingSource()
+ }
+ .flow
+ .map { pagingData ->
+ pagingData
+ .insertSeparators { before: Customer?, after: Customer? ->
+ if (after == null || (after.id / 3) == (before?.id ?: 0) / 3) {
+ // no separator, because at bottom or not needed yet
+ null
+ } else {
+ Customer().apply {
+ id = -1
+ name = "RIGHT ABOVE DIVIDER"
+ lastName = "RIGHT ABOVE DIVIDER"
+ }
}
}
- }
- .insertSeparators { before: Customer?, _: Customer? ->
- if (before != null && before.id == -1) {
- Customer().apply {
- id = -2
- name = "RIGHT BELOW DIVIDER"
- lastName = "RIGHT BELOW DIVIDER"
- }
- } else null
- }
- .insertHeaderItem(
- item = Customer().apply {
- id = Int.MIN_VALUE
- name = "HEADER"
- lastName = "HEADER"
+ .insertSeparators { before: Customer?, _: Customer? ->
+ if (before != null && before.id == -1) {
+ Customer().apply {
+ id = -2
+ name = "RIGHT BELOW DIVIDER"
+ lastName = "RIGHT BELOW DIVIDER"
+ }
+ } else null
}
- )
- .insertFooterItem(
- item = Customer().apply {
- id = Int.MAX_VALUE
- name = "FOOTER"
- lastName = "FOOTER"
- }
- )
- }
- .cachedIn(viewModelScope)
+ .insertHeaderItem(
+ item =
+ Customer().apply {
+ id = Int.MIN_VALUE
+ name = "HEADER"
+ lastName = "HEADER"
+ }
+ )
+ .insertFooterItem(
+ item =
+ Customer().apply {
+ id = Int.MAX_VALUE
+ name = "FOOTER"
+ lastName = "FOOTER"
+ }
+ )
+ }
+ .cachedIn(viewModelScope)
}
diff --git a/paging/paging-common/src/androidMain/kotlin/androidx/paging/PagingLogger.android.kt b/paging/paging-common/src/androidMain/kotlin/androidx/paging/PagingLogger.android.kt
index 1d5c6fc..13f6bae 100644
--- a/paging/paging-common/src/androidMain/kotlin/androidx/paging/PagingLogger.android.kt
+++ b/paging/paging-common/src/androidMain/kotlin/androidx/paging/PagingLogger.android.kt
@@ -22,8 +22,8 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public actual object PagingLogger {
/**
- * isLoggable returns true if Logging is enabled via adb shell command i.e.
- * "adb shell setprop log.tag.Paging VERBOSE"
+ * isLoggable returns true if Logging is enabled via adb shell command i.e. "adb shell setprop
+ * log.tag.Paging VERBOSE"
*/
public actual fun isLoggable(level: Int): Boolean {
return Log.isLoggable(LOG_TAG, level)
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ContiguousPagedList.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ContiguousPagedList.jvm.kt
index 02b8230..3ee6114 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ContiguousPagedList.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ContiguousPagedList.jvm.kt
@@ -40,13 +40,14 @@
config: Config,
initialPage: PagingSource.LoadResult.Page<K, V>,
private val initialLastKey: K?
-) : PagedList<V>(
- pagingSource,
- coroutineScope,
- notifyDispatcher,
- PagedStorage<V>(),
- config,
-),
+) :
+ PagedList<V>(
+ pagingSource,
+ coroutineScope,
+ notifyDispatcher,
+ PagedStorage<V>(),
+ config,
+ ),
PagedStorage.Callback,
LegacyPageFetcher.PageConsumer<V> {
@@ -82,34 +83,30 @@
private val shouldTrim = config.maxSize != Config.MAX_SIZE_UNBOUNDED
@Suppress("UNCHECKED_CAST")
- private val pager = LegacyPageFetcher(
- coroutineScope,
- config,
- pagingSource,
- notifyDispatcher,
- backgroundDispatcher,
- this,
- storage as LegacyPageFetcher.KeyProvider<K>
- )
+ private val pager =
+ LegacyPageFetcher(
+ coroutineScope,
+ config,
+ pagingSource,
+ notifyDispatcher,
+ backgroundDispatcher,
+ this,
+ storage as LegacyPageFetcher.KeyProvider<K>
+ )
@Suppress("UNCHECKED_CAST")
override val lastKey: K?
get() {
- return (storage.getRefreshKeyInfo(config) as PagingState<K, V>?)
- ?.let { pagingSource.getRefreshKey(it) }
- ?: initialLastKey
+ return (storage.getRefreshKeyInfo(config) as PagingState<K, V>?)?.let {
+ pagingSource.getRefreshKey(it)
+ } ?: initialLastKey
}
override val isDetached: Boolean
get() = pager.isDetached
- /**
- * Given a page result, apply or drop it, and return whether more loading is needed.
- */
- override fun onPageResult(
- type: LoadType,
- page: PagingSource.LoadResult.Page<*, V>
- ): Boolean {
+ /** Given a page result, apply or drop it, and return whether more loading is needed. */
+ override fun onPageResult(type: LoadType, page: PagingSource.LoadResult.Page<*, V>): Boolean {
var continueLoading = false
val list = page.data
@@ -117,11 +114,8 @@
val trimFromFront = lastLoad() > storage.middleOfLoadedRange
// is the new page big enough to warrant pre-trimming (i.e. dropping) it?
- val skipNewPage = shouldTrim && storage.shouldPreTrimNewPage(
- config.maxSize,
- requiredRemainder,
- list.size
- )
+ val skipNewPage =
+ shouldTrim && storage.shouldPreTrimNewPage(config.maxSize, requiredRemainder, list.size)
if (type == APPEND) {
if (skipNewPage && !trimFromFront) {
@@ -155,7 +149,8 @@
// allow fetches in same direction - this means reading the load state is safe.
if (trimFromFront) {
if (pager.loadStateManager.startState !is Loading) {
- if (storage.trimFromFront(
+ if (
+ storage.trimFromFront(
replacePagesWithNulls,
config.maxSize,
requiredRemainder,
@@ -168,7 +163,8 @@
}
} else {
if (pager.loadStateManager.endState !is Loading) {
- if (storage.trimFromEnd(
+ if (
+ storage.trimFromEnd(
replacePagesWithNulls,
config.maxSize,
requiredRemainder,
@@ -252,10 +248,11 @@
* is set.
*/
private fun tryDispatchBoundaryCallbacks(post: Boolean) {
- val dispatchBegin = boundaryCallbackBeginDeferred &&
- lowestIndexAccessed <= config.prefetchDistance
- val dispatchEnd = boundaryCallbackEndDeferred &&
- highestIndexAccessed >= size - 1 - config.prefetchDistance
+ val dispatchBegin =
+ boundaryCallbackBeginDeferred && lowestIndexAccessed <= config.prefetchDistance
+ val dispatchEnd =
+ boundaryCallbackEndDeferred &&
+ highestIndexAccessed >= size - 1 - config.prefetchDistance
if (!dispatchBegin && !dispatchEnd) return
@@ -336,11 +333,12 @@
override fun loadAroundInternal(index: Int) {
val prependItems =
getPrependItemsRequested(config.prefetchDistance, index, storage.placeholdersBefore)
- val appendItems = getAppendItemsRequested(
- config.prefetchDistance,
- index,
- storage.placeholdersBefore + storage.dataCount
- )
+ val appendItems =
+ getAppendItemsRequested(
+ config.prefetchDistance,
+ index,
+ storage.placeholdersBefore + storage.dataCount
+ )
prependItemsRequested = maxOf(prependItems, prependItemsRequested)
if (prependItemsRequested > 0) {
@@ -375,8 +373,7 @@
// If we're not presenting placeholders at initialization time, we won't add them when
// we drop a page. Note that we don't use config.enablePlaceholders, since the
// PagingSource may have opted not to load any.
- replacePagesWithNulls = storage.placeholdersBefore > 0 ||
- storage.placeholdersAfter > 0
+ replacePagesWithNulls = storage.placeholdersBefore > 0 || storage.placeholdersAfter > 0
}
@MainThread
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/DataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/DataSource.jvm.kt
index 35bbc92..52250d2 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/DataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/DataSource.jvm.kt
@@ -27,26 +27,26 @@
/**
* Base class for loading pages of snapshot data into a [PagedList].
*
- * DataSource is queried to load pages of content into a [PagedList]. A PagedList can grow as
- * it loads more data, but the data loaded cannot be updated. If the underlying data set is
- * modified, a new PagedList / DataSource pair must be created to represent the new data.
+ * DataSource is queried to load pages of content into a [PagedList]. A PagedList can grow as it
+ * loads more data, but the data loaded cannot be updated. If the underlying data set is modified, a
+ * new PagedList / DataSource pair must be created to represent the new data.
*
* ### Loading Pages
*
- * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
- * calls [PagedList.loadAround] to load content as the user scrolls in a RecyclerView.
+ * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter calls
+ * [PagedList.loadAround] to load content as the user scrolls in a RecyclerView.
*
- * To control how and when a PagedList queries data from its DataSource, see
- * [PagedList.Config]. The Config object defines things like load sizes and prefetch distance.
+ * To control how and when a PagedList queries data from its DataSource, see [PagedList.Config]. The
+ * Config object defines things like load sizes and prefetch distance.
*
* ### Updating Paged Data
*
- * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
- * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
- * content update occurs. A DataSource must detect that it cannot continue loading its
- * snapshot (for instance, when Database query notices a table being invalidated), and call
- * [invalidate]. Then a new PagedList / DataSource pair would be created to load data from the new
- * state of the Database query.
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of PagedList /
+ * DataSource must be created if an update occurs, such as a reorder, insert, delete, or content
+ * update occurs. A DataSource must detect that it cannot continue loading its snapshot (for
+ * instance, when Database query notices a table being invalidated), and call [invalidate]. Then a
+ * new PagedList / DataSource pair would be created to load data from the new state of the Database
+ * query.
*
* To page in data that doesn't update, you can create a single DataSource, and pass it to a single
* PagedList. For example, loading from network when the network's paging API doesn't provide
@@ -61,10 +61,10 @@
* signal to call [invalidate] on the current [DataSource].
*
* If you have more granular update signals, such as a network API signaling an update to a single
- * item in the list, it's recommended to load data from network into memory. Then present that
- * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
- * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
- * snapshot can be created.
+ * item in the list, it's recommended to load data from network into memory. Then present that data
+ * to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy
+ * changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot
+ * can be created.
*
* ### Implementing a DataSource
*
@@ -74,54 +74,49 @@
* Use [PageKeyedDataSource] if pages you load embed keys for loading adjacent pages. For example a
* network response that returns some items, and a next/previous page links.
*
- * Use [ItemKeyedDataSource] if you need to use data from item `N-1` to load item
- * `N`. For example, if requesting the backend for the next comments in the list
- * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
- * from a name-sorted database query requires the name and unique ID of the previous.
+ * Use [ItemKeyedDataSource] if you need to use data from item `N-1` to load item `N`. For example,
+ * if requesting the backend for the next comments in the list requires the ID or timestamp of the
+ * most recent loaded comment, or if querying the next users from a name-sorted database query
+ * requires the name and unique ID of the previous.
*
- * Use [PositionalDataSource] if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
- * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
- * PositionalDataSource is required to respect page size for efficient tiling. If you want to
- * override page size (e.g. when network page size constraints are only known at runtime), use one
- * of the other DataSource classes.
+ * Use [PositionalDataSource] if you can load pages of a requested size at arbitrary positions, and
+ * provide a fixed item count. PositionalDataSource supports querying pages at arbitrary positions,
+ * so can provide data to PagedLists in arbitrary order. Note that PositionalDataSource is required
+ * to respect page size for efficient tiling. If you want to override page size (e.g. when network
+ * page size constraints are only known at runtime), use one of the other DataSource classes.
*
- * Because a `null` item indicates a placeholder in [PagedList], DataSource may not
- * return `null` items in lists that it loads. This is so that users of the PagedList
- * can differentiate unloaded placeholder items from content that has been paged in.
+ * Because a `null` item indicates a placeholder in [PagedList], DataSource may not return `null`
+ * items in lists that it loads. This is so that users of the PagedList can differentiate unloaded
+ * placeholder items from content that has been paged in.
*
* @param Key Unique identifier for item loaded from DataSource. Often an integer to represent
- * position in data set. Note - this is distinct from e.g. Room's `<Value>` Value type
- * loaded by the DataSource.
+ * position in data set. Note - this is distinct from e.g. Room's `<Value>` Value type loaded by
+ * the DataSource.
*/
public abstract class DataSource<Key : Any, Value : Any>
// Since we currently rely on implementation details of two implementations, prevent external
// subclassing, except through exposed subclasses.
internal constructor(internal val type: KeyType) {
- private val invalidateCallbackTracker = InvalidateCallbackTracker<InvalidatedCallback>(
- callbackInvoker = { it.onInvalidated() },
- invalidGetter = { isInvalid },
- )
+ private val invalidateCallbackTracker =
+ InvalidateCallbackTracker<InvalidatedCallback>(
+ callbackInvoker = { it.onInvalidated() },
+ invalidGetter = { isInvalid },
+ )
internal val invalidateCallbackCount: Int
- @VisibleForTesting
- get() = invalidateCallbackTracker.callbackCount()
+ @VisibleForTesting get() = invalidateCallbackTracker.callbackCount()
- /**
- * @return `true` if the data source is invalid, and can no longer be queried for data.
- */
+ /** @return `true` if the data source is invalid, and can no longer be queried for data. */
public open val isInvalid: Boolean
- @WorkerThread
- get() = invalidateCallbackTracker.invalid
+ @WorkerThread get() = invalidateCallbackTracker.invalid
/**
* Factory for DataSources.
*
* Data-loading systems of an application or library can implement this interface to allow
- * `LiveData<PagedList>`s to be created. For example, Room can provide a
- * [DataSource.Factory] for a given SQL query:
- *
+ * `LiveData<PagedList>`s to be created. For example, Room can provide a [DataSource.Factory]
+ * for a given SQL query:
* ```
* @Dao
* interface UserDao {
@@ -130,9 +125,9 @@
* }
* ```
*
- * In the above sample, `Integer` is used because it is the `Key` type of
- * PositionalDataSource. Currently, Room uses the `LIMIT`/`OFFSET` SQL keywords to
- * page a large query with a PositionalDataSource.
+ * In the above sample, `Integer` is used because it is the `Key` type of PositionalDataSource.
+ * Currently, Room uses the `LIMIT`/`OFFSET` SQL keywords to page a large query with a
+ * PositionalDataSource.
*
* @param Key Key identifying items in DataSource.
* @param Value Type of items in the list loaded by the DataSources.
@@ -159,10 +154,9 @@
* Same as [mapByPage], but operates on individual items.
*
* @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
+ * new type.
* @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
- *
* @see mapByPage
* @see DataSource.map
* @see DataSource.mapByPage
@@ -181,10 +175,9 @@
* Same as [mapByPage], but operates on individual items.
*
* @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
+ * new type.
* @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
- *
* @see mapByPage
* @see DataSource.map
* @see DataSource.mapByPage
@@ -200,20 +193,20 @@
* Same as [map], but allows for batch conversions.
*
* @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
+ * new type.
* @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
- *
* @see map
* @see DataSource.map
* @see DataSource.mapByPage
*/
public open fun <ToValue : Any> mapByPage(
function: Function<List<Value>, List<ToValue>>
- ): Factory<Key, ToValue> = object : Factory<Key, ToValue>() {
- override fun create(): DataSource<Key, ToValue> =
- [email protected]().mapByPage(function)
- }
+ ): Factory<Key, ToValue> =
+ object : Factory<Key, ToValue>() {
+ override fun create(): DataSource<Key, ToValue> =
+ [email protected]().mapByPage(function)
+ }
/**
* Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -223,10 +216,9 @@
* Same as [map], but allows for batch conversions.
*
* @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
+ * new type.
* @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
- *
* @see map
* @see DataSource.map
* @see DataSource.mapByPage
@@ -239,12 +231,11 @@
@JvmOverloads
public fun asPagingSourceFactory(
fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
- ): () -> PagingSource<Key, Value> = SuspendingPagingSourceFactory(
- delegate = {
- LegacyPagingSource(fetchDispatcher, create())
- },
- dispatcher = fetchDispatcher
- )
+ ): () -> PagingSource<Key, Value> =
+ SuspendingPagingSourceFactory(
+ delegate = { LegacyPagingSource(fetchDispatcher, create()) },
+ dispatcher = fetchDispatcher
+ )
}
/**
@@ -252,11 +243,10 @@
*
* Same as [map], but allows for batch conversions.
*
- * @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
+ * @param function Function that runs on each loaded page, returning items of a potentially new
+ * type.
* @param ToValue Type of items produced by the new DataSource, from the passed function.
* @return A new DataSource, which transforms items using the given function.
- *
* @see map
* @see DataSource.Factory.map
* @see DataSource.Factory.mapByPage
@@ -272,11 +262,10 @@
*
* Same as [map], but allows for batch conversions.
*
- * @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
+ * @param function Function that runs on each loaded page, returning items of a potentially new
+ * type.
* @param ToValue Type of items produced by the new DataSource, from the passed function.
* @return A new [DataSource], which transforms items using the given function.
- *
* @see map
* @see DataSource.Factory.map
* @see DataSource.Factory.mapByPage
@@ -291,11 +280,10 @@
*
* Same as [mapByPage], but operates on individual items.
*
- * @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
+ * @param function Function that runs on each loaded item, returning items of a potentially new
+ * type.
* @param ToValue Type of items produced by the new DataSource, from the passed function.
* @return A new DataSource, which transforms items using the given function.
- *
* @see mapByPage
* @see DataSource.Factory.map
* @see DataSource.Factory.mapByPage
@@ -313,19 +301,16 @@
*
* Same as [mapByPage], but operates on individual items.
*
- * @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
+ * @param function Function that runs on each loaded item, returning items of a potentially new
+ * type.
* @param ToValue Type of items produced by the new DataSource, from the passed function.
* @return A new DataSource, which transforms items using the given function.
- *
* @see mapByPage
* @see DataSource.Factory.map
- *
*/
@JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
- public open fun <ToValue : Any> map(
- function: (Value) -> ToValue
- ): DataSource<Key, ToValue> = map(Function { function(it) })
+ public open fun <ToValue : Any> map(function: (Value) -> ToValue): DataSource<Key, ToValue> =
+ map(Function { function(it) })
/**
* Returns true if the data source guaranteed to produce a contiguous set of items, never
@@ -350,8 +335,7 @@
* data source to invalidate itself during its load methods, or for an outside source to
* invalidate it.
*/
- @AnyThread
- public fun onInvalidated()
+ @AnyThread public fun onInvalidated()
}
/**
@@ -366,7 +350,7 @@
* triggered immediately.
*
* @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
- * [DataSource].
+ * [DataSource].
*/
@AnyThread
@Suppress("RegistrationName")
@@ -399,7 +383,8 @@
* @param K Type of the key used to query the [DataSource].
* @property key Can be `null` for init, otherwise non-null
*/
- internal class Params<K : Any> internal constructor(
+ internal class Params<K : Any>
+ internal constructor(
internal val type: LoadType,
val key: K?,
val initialLoadSize: Int,
@@ -413,12 +398,10 @@
}
}
- /**
- * @param Value Type of the data produced by a [DataSource].
- */
- internal class BaseResult<Value : Any> internal constructor(
- @JvmField
- val data: List<Value>,
+ /** @param Value Type of the data produced by a [DataSource]. */
+ internal class BaseResult<Value : Any>
+ internal constructor(
+ @JvmField val data: List<Value>,
val prevKey: Any?,
val nextKey: Any?,
val itemsBefore: Int = COUNT_UNDEFINED,
@@ -442,9 +425,8 @@
}
/**
- * While it may seem unnecessary to do this validation now that tiling is gone, we do
- * this to ensure consistency with 2.1, and to ensure all loadRanges have the same page
- * size.
+ * While it may seem unnecessary to do this validation now that tiling is gone, we do this
+ * to ensure consistency with 2.1, and to ensure all loadRanges have the same page size.
*/
internal fun validateForInitialTiling(pageSize: Int) {
if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
@@ -471,15 +453,16 @@
}
}
- override fun equals(other: Any?) = when (other) {
- is BaseResult<*> ->
- data == other.data &&
- prevKey == other.prevKey &&
- nextKey == other.nextKey &&
- itemsBefore == other.itemsBefore &&
- itemsAfter == other.itemsAfter
- else -> false
- }
+ override fun equals(other: Any?) =
+ when (other) {
+ is BaseResult<*> ->
+ data == other.data &&
+ prevKey == other.prevKey &&
+ nextKey == other.nextKey &&
+ itemsBefore == other.itemsBefore &&
+ itemsAfter == other.itemsAfter
+ else -> false
+ }
internal companion object {
internal fun <T : Any> empty() = BaseResult(emptyList<T>(), null, null, 0, 0)
@@ -487,13 +470,14 @@
internal fun <ToValue : Any, Value : Any> convert(
result: BaseResult<ToValue>,
function: Function<List<ToValue>, List<Value>>
- ) = BaseResult(
- data = convert(function, result.data),
- prevKey = result.prevKey,
- nextKey = result.nextKey,
- itemsBefore = result.itemsBefore,
- itemsAfter = result.itemsAfter
- )
+ ) =
+ BaseResult(
+ data = convert(function, result.data),
+ prevKey = result.prevKey,
+ nextKey = result.nextKey,
+ itemsBefore = result.itemsBefore,
+ itemsAfter = result.itemsAfter
+ )
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialDataSource.jvm.kt
index 624db53..3103fb6 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialDataSource.jvm.kt
@@ -17,8 +17,8 @@
package androidx.paging
/**
- * [InitialDataSource] is a placeholder [DataSource] implementation that only returns empty
- * pages and `null` keys.
+ * [InitialDataSource] is a placeholder [DataSource] implementation that only returns empty pages
+ * and `null` keys.
*
* It should be used exclusively in [InitialPagedList] since it is required to be supplied
* synchronously, but [DataSource.Factory] should run on background thread.
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialPagedList.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialPagedList.jvm.kt
index fc00fa4..f6b6c6b 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialPagedList.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/InitialPagedList.jvm.kt
@@ -23,9 +23,8 @@
/**
* [InitialPagedList] is an empty placeholder that's sent at the front of a stream of [PagedList].
*
- * It's used solely for listening to [LoadType.REFRESH] loading events, and retrying
- * any errors that occur during initial load.
- *
+ * It's used solely for listening to [LoadType.REFRESH] loading events, and retrying any errors that
+ * occur during initial load.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class InitialPagedList<K : Any, V : Any>(
@@ -34,13 +33,14 @@
backgroundDispatcher: CoroutineDispatcher,
config: Config,
initialLastKey: K?
-) : ContiguousPagedList<K, V>(
- pagingSource = LegacyPagingSource(notifyDispatcher, InitialDataSource()),
- coroutineScope = coroutineScope,
- notifyDispatcher = notifyDispatcher,
- backgroundDispatcher = backgroundDispatcher,
- boundaryCallback = null,
- config = config,
- initialPage = PagingSource.LoadResult.Page.empty(),
- initialLastKey = initialLastKey
-)
+) :
+ ContiguousPagedList<K, V>(
+ pagingSource = LegacyPagingSource(notifyDispatcher, InitialDataSource()),
+ coroutineScope = coroutineScope,
+ notifyDispatcher = notifyDispatcher,
+ backgroundDispatcher = backgroundDispatcher,
+ boundaryCallback = null,
+ config = config,
+ initialPage = PagingSource.LoadResult.Page.empty(),
+ initialLastKey = initialLastKey
+ )
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
index abc5785..d2f1944 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/ItemKeyedDataSource.jvm.kt
@@ -27,9 +27,9 @@
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
*
- * Implement a DataSource using ItemKeyedDataSource if you need to use data from item `N - 1`
- * to load item `N`. This is common, for example, in uniquely sorted database queries where
- * attributes of the item such just before the next query define how to execute it.
+ * Implement a DataSource using ItemKeyedDataSource if you need to use data from item `N - 1` to
+ * load item `N`. This is common, for example, in uniquely sorted database queries where attributes
+ * of the item such just before the next query define how to execute it.
*
* The `InMemoryByItemRepository` in the
* [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
@@ -42,37 +42,32 @@
*/
@Deprecated(
message = "ItemKeyedDataSource is deprecated and has been replaced by PagingSource",
- replaceWith = ReplaceWith(
- "PagingSource<Key, Value>",
- "androidx.paging.PagingSource"
- )
+ replaceWith = ReplaceWith("PagingSource<Key, Value>", "androidx.paging.PagingSource")
)
-public abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
- ITEM_KEYED
-) {
+public abstract class ItemKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(ITEM_KEYED) {
/**
* Holder object for inputs to [loadInitial].
*
* @param Key Type of data used to query [Value] types out of the [DataSource].
- * @param requestedInitialKey Load items around this key, or at the beginning of the data set
- * if `null` is passed.
+ * @param requestedInitialKey Load items around this key, or at the beginning of the data set if
+ * `null` is passed.
*
* Note that this key is generally a hint, and may be ignored if you want to always load from
* the beginning.
+ *
* @param requestedLoadSize Requested number of items to load.
*
* Note that this may be larger than available data.
- * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the
- * loaded total count will be ignored.
+ *
+ * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the loaded
+ * total count will be ignored.
*/
public open class LoadInitialParams<Key : Any>(
- @JvmField
- public val requestedInitialKey: Key?,
- @JvmField
- public val requestedLoadSize: Int,
- @JvmField
- public val placeholdersEnabled: Boolean
+ @JvmField public val requestedInitialKey: Key?,
+ @JvmField public val requestedLoadSize: Int,
+ @JvmField public val placeholdersEnabled: Boolean
)
/**
@@ -82,21 +77,19 @@
* @param key Load items before/after this key.
*
* Returned data must begin directly adjacent to this position.
+ *
* @param requestedLoadSize Requested number of items to load.
*
* Returned page can be of this size, but it may be altered if that is easier, e.g. a network
* data source where the backend defines page size.
*/
public open class LoadParams<Key : Any>(
- @JvmField
- public val key: Key,
- @JvmField
- public val requestedLoadSize: Int
+ @JvmField public val key: Key,
+ @JvmField public val requestedLoadSize: Int
)
/**
- * Callback for [loadInitial]
- * to return data and, optionally, position/count information.
+ * Callback for [loadInitial] to return data and, optionally, position/count information.
*
* A callback can be called only once, and will throw if called again.
*
@@ -116,21 +109,21 @@
/**
* Called to pass initial load state from a DataSource.
*
- * Call this method from your DataSource's `loadInitial` function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
+ * Call this method from your DataSource's `loadInitial` function to return data, and inform
+ * how many placeholders should be shown before and after. If counting is cheap to compute
+ * (for example, if a network load returns the information regardless), it's recommended to
+ * pass data back through this method.
*
* It is always valid to pass a different amount of data than what is requested. Pass an
* empty list if there is no more data to load.
*
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are `N`
- * items before the items in data that can be loaded from this DataSource, pass `N`.
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource is
+ * treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are `N` items
+ * before the items in data that can be loaded from this DataSource, pass `N`.
* @param totalCount Total number of items that may be returned from this [DataSource].
- * Includes the number in the initial `data` parameter as well as any items that can be
- * loaded in front or behind of `data`.
+ * Includes the number in the initial `data` parameter as well as any items that can be
+ * loaded in front or behind of `data`.
*/
public abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
}
@@ -167,19 +160,21 @@
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal final override suspend fun load(params: Params<Key>): BaseResult<Value> {
return when (params.type) {
- LoadType.REFRESH -> loadInitial(
- LoadInitialParams(
- params.key,
- params.initialLoadSize,
- params.placeholdersEnabled
+ LoadType.REFRESH ->
+ loadInitial(
+ LoadInitialParams(
+ params.key,
+ params.initialLoadSize,
+ params.placeholdersEnabled
+ )
)
- )
LoadType.PREPEND -> loadBefore(LoadParams(params.key!!, params.pageSize))
LoadType.APPEND -> loadAfter(LoadParams(params.key!!, params.pageSize))
}
}
internal fun List<Value>.getPrevKey() = firstOrNull()?.let { getKey(it) }
+
internal fun List<Value>.getNextKey() = lastOrNull()?.let { getKey(it) }
@VisibleForTesting
@@ -217,13 +212,7 @@
private fun CancellableContinuation<BaseResult<Value>>.asCallback() =
object : ItemKeyedDataSource.LoadCallback<Value>() {
override fun onResult(data: List<Value>) {
- resume(
- BaseResult(
- data,
- data.getPrevKey(),
- data.getNextKey()
- )
- )
+ resume(BaseResult(data, data.getPrevKey(), data.getNextKey()))
}
}
@@ -235,9 +224,7 @@
@VisibleForTesting
internal suspend fun loadAfter(params: LoadParams<Key>): BaseResult<Value> {
- return suspendCancellableCoroutine { cont ->
- loadAfter(params, cont.asCallback())
- }
+ return suspendCancellableCoroutine { cont -> loadAfter(params, cont.asCallback()) }
}
/**
@@ -248,13 +235,12 @@
* the callback via the three-parameter [LoadInitialCallback.onResult]. This enables PagedLists
* presenting data from this source to display placeholders to represent unloaded items.
*
- * [LoadInitialParams.requestedInitialKey] and [LoadInitialParams.requestedLoadSize]
- * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
- * `requestedInitialKey` can prevent subsequent PagedList/DataSource pairs from
- * initializing at the same location. If your DataSource never invalidates (for example,
- * loading from the network without the network ever signalling that old data must be reloaded),
- * it's fine to ignore the `initialLoadKey` and always start from the beginning of the
- * data set.
+ * [LoadInitialParams.requestedInitialKey] and [LoadInitialParams.requestedLoadSize] are hints,
+ * not requirements, so they may be altered or ignored. Note that ignoring the
+ * `requestedInitialKey` can prevent subsequent PagedList/DataSource pairs from initializing at
+ * the same location. If your DataSource never invalidates (for example, loading from the
+ * network without the network ever signalling that old data must be reloaded), it's fine to
+ * ignore the `initialLoadKey` and always start from the beginning of the data set.
*
* @param params Parameters for initial load, including initial key and requested size.
* @param callback Callback that receives initial load data.
@@ -309,14 +295,13 @@
* Return a key associated with the given item.
*
* If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
- * integer ID, you would return `item.getID()` here. This key can then be passed to
- * [loadBefore] or [loadAfter] to load additional items adjacent to the item passed to this
- * function.
+ * integer ID, you would return `item.getID()` here. This key can then be passed to [loadBefore]
+ * or [loadAfter] to load additional items adjacent to the item passed to this function.
*
* If your key is more complex, such as when you're sorting by name, then resolving collisions
* with integer ID, you'll need to return both. In such a case you would use a wrapper class,
- * such as `Pair<String, Integer>` or, in Kotlin,
- * `data class Key(val name: String, val id: Int)`
+ * such as `Pair<String, Integer>` or, in Kotlin, `data class Key(val name: String, val id:
+ * Int)`
*
* @param item Item to get the key from.
* @return Key associated with given item.
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPageFetcher.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPageFetcher.jvm.kt
index cc919f4..50e3ea5 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPageFetcher.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPageFetcher.jvm.kt
@@ -26,8 +26,7 @@
internal class LegacyPageFetcher<K : Any, V : Any>(
private val pagedListScope: CoroutineScope,
- @Suppress("DEPRECATION")
- val config: PagedList.Config,
+ @Suppress("DEPRECATION") val config: PagedList.Config,
val source: PagingSource<K, V>,
private val notifyDispatcher: CoroutineDispatcher,
private val fetchDispatcher: CoroutineDispatcher,
@@ -37,12 +36,13 @@
private val detached = AtomicBoolean(false)
@Suppress("DEPRECATION")
- var loadStateManager = object : PagedList.LoadStateManager() {
- override fun onStateChanged(type: LoadType, state: LoadState) {
- // Don't need to post - PagedList will already have done that
- pageConsumer.onStateChanged(type, state)
+ var loadStateManager =
+ object : PagedList.LoadStateManager() {
+ override fun onStateChanged(type: LoadType, state: LoadState) {
+ // Don't need to post - PagedList will already have done that
+ pageConsumer.onStateChanged(type, state)
+ }
}
- }
val isDetached
get() = detached.get()
@@ -121,11 +121,12 @@
loadStateManager.setState(LoadType.PREPEND, Loading)
- val loadParams = LoadParams.Prepend(
- key,
- config.pageSize,
- config.enablePlaceholders,
- )
+ val loadParams =
+ LoadParams.Prepend(
+ key,
+ config.pageSize,
+ config.enablePlaceholders,
+ )
scheduleLoad(LoadType.PREPEND, loadParams)
}
@@ -137,21 +138,18 @@
}
loadStateManager.setState(LoadType.APPEND, Loading)
- val loadParams = LoadParams.Append(
- key,
- config.pageSize,
- config.enablePlaceholders,
- )
+ val loadParams =
+ LoadParams.Append(
+ key,
+ config.pageSize,
+ config.enablePlaceholders,
+ )
scheduleLoad(LoadType.APPEND, loadParams)
}
fun retry() {
- loadStateManager.startState.run {
- if (this is LoadState.Error) schedulePrepend()
- }
- loadStateManager.endState.run {
- if (this is LoadState.Error) scheduleAppend()
- }
+ loadStateManager.startState.run { if (this is LoadState.Error) schedulePrepend() }
+ loadStateManager.endState.run { if (this is LoadState.Error) scheduleAppend() }
}
fun detach() {
@@ -159,9 +157,7 @@
}
internal interface PageConsumer<V : Any> {
- /**
- * @return `true` if we need to fetch more
- */
+ /** @return `true` if we need to fetch more */
fun onPageResult(type: LoadType, page: PagingSource.LoadResult.Page<*, V>): Boolean
fun onStateChanged(type: LoadType, state: LoadState)
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPagingSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPagingSource.jvm.kt
index a3d9b27..2a6c4ce 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPagingSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/LegacyPagingSource.jvm.kt
@@ -29,10 +29,7 @@
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.withContext
-/**
- * A wrapper around [DataSource] which adapts it to the [PagingSource] API.
- *
- */
+/** A wrapper around [DataSource] which adapts it to the [PagingSource] API. */
@OptIn(DelicateCoroutinesApi::class)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class LegacyPagingSource<Key : Any, Value : Any>(
@@ -51,8 +48,7 @@
}
}
- /**
- */
+ /** */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public override fun setPageSize(pageSize: Int) {
check(this.pageSize == PAGE_SIZE_NOT_SET || pageSize == this.pageSize) {
@@ -75,11 +71,12 @@
}
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
- val type = when (params) {
- is LoadParams.Refresh -> REFRESH
- is LoadParams.Append -> APPEND
- is LoadParams.Prepend -> PREPEND
- }
+ val type =
+ when (params) {
+ is LoadParams.Refresh -> REFRESH
+ is LoadParams.Append -> APPEND
+ is LoadParams.Prepend -> PREPEND
+ }
if (pageSize == PAGE_SIZE_NOT_SET) {
// println because we don't have android logger here
println(
@@ -94,17 +91,13 @@
If you are seeing this message despite using a Pager, please file a bug:
$BUGANIZER_URL
- """.trimIndent()
+ """
+ .trimIndent()
)
pageSize = guessPageSize(params)
}
- val dataSourceParams = Params(
- type,
- params.key,
- params.loadSize,
- params.placeholdersEnabled,
- pageSize
- )
+ val dataSourceParams =
+ Params(type, params.key, params.loadSize, params.placeholdersEnabled, pageSize)
return withContext(fetchContext) {
dataSource.load(dataSourceParams).run {
@@ -124,12 +117,13 @@
@Suppress("UNCHECKED_CAST")
override fun getRefreshKey(state: PagingState<Key, Value>): Key? {
return when (dataSource.type) {
- POSITIONAL -> state.anchorPosition?.let { anchorPosition ->
- state.anchorPositionToPagedIndices(anchorPosition) { _, indexInPage ->
- val offset = state.closestPageToPosition(anchorPosition)?.prevKey ?: 0
- (offset as Int).plus(indexInPage) as Key?
+ POSITIONAL ->
+ state.anchorPosition?.let { anchorPosition ->
+ state.anchorPositionToPagedIndices(anchorPosition) { _, indexInPage ->
+ val offset = state.closestPageToPosition(anchorPosition)?.prevKey ?: 0
+ (offset as Int).plus(indexInPage) as Key?
+ }
}
- }
PAGE_KEYED -> null
ITEM_KEYED ->
state.anchorPosition
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/Logger.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/Logger.kt
index 0e8f6b6..46147a8 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/Logger.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/Logger.kt
@@ -22,5 +22,6 @@
@Deprecated("Logger interface is no longer supported.")
public interface Logger {
public fun isLoggable(level: Int): Boolean
+
public fun log(level: Int, message: String, tr: Throwable? = null)
}
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
index 7cf9aad..2eda80c 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PageKeyedDataSource.jvm.kt
@@ -33,22 +33,18 @@
* The `InMemoryByPageRepository` in the
* [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
* shows how to implement a network PageKeyedDataSource using
- * [Retrofit](https://square.github.io/retrofit/), while
- * handling swipe-to-refresh, network errors, and retry.
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network errors,
+ * and retry.
*
* @param Key Type of data used to query Value types out of the [DataSource].
* @param Value Type of items being loaded by the [DataSource].
*/
@Deprecated(
message = "PageKeyedDataSource is deprecated and has been replaced by PagingSource",
- replaceWith = ReplaceWith(
- "PagingSource<Key, Value>",
- "androidx.paging.PagingSource"
- )
+ replaceWith = ReplaceWith("PagingSource<Key, Value>", "androidx.paging.PagingSource")
)
-public abstract class PageKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
- PAGE_KEYED
-) {
+public abstract class PageKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(PAGE_KEYED) {
/**
* Holder object for inputs to [loadInitial].
@@ -57,8 +53,9 @@
* @param requestedLoadSize Requested number of items to load.
*
* Note that this may be larger than available data.
- * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the
- * loaded total count will be ignored.
+ *
+ * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the loaded
+ * total count will be ignored.
*/
public open class LoadInitialParams<Key : Any>(
@JvmField public val requestedLoadSize: Int,
@@ -72,6 +69,7 @@
* @param key Load items before/after this key.
*
* Returned data must begin directly adjacent to this position.
+ *
* @param requestedLoadSize Requested number of items to load.
*
* Returned page can be of this size, but it may be altered if that is easier, e.g. a network
@@ -104,25 +102,25 @@
/**
* Called to pass initial load state from a DataSource.
*
- * Call this method from your DataSource's `loadInitial` function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
+ * Call this method from your DataSource's `loadInitial` function to return data, and inform
+ * how many placeholders should be shown before and after. If counting is cheap to compute
+ * (for example, if a network load returns the information regardless), it's recommended to
+ * pass data back through this method.
*
* It is always valid to pass a different amount of data than what is requested. Pass an
* empty list if there is no more data to load.
*
* @param data List of items loaded from the [DataSource]. If this is empty, the
- * [DataSource] is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are `N`
- * items before the items in data that can be loaded from this DataSource, pass `N`.
+ * [DataSource] is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are `N` items
+ * before the items in data that can be loaded from this DataSource, pass `N`.
* @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial `data` parameter as well as any items that can be
- * loaded in front or behind of `data`.
+ * Includes the number in the initial `data` parameter as well as any items that can be
+ * loaded in front or behind of `data`.
* @param previousPageKey Key for page before the initial load result, or `null` if no more
- * data can be loaded before.
+ * data can be loaded before.
* @param nextPageKey Key for page after the initial load result, or `null` if no more data
- * can be loaded after.
+ * can be loaded after.
*/
public abstract fun onResult(
data: List<Value>,
@@ -143,9 +141,9 @@
*
* @param data List of items loaded from the [PageKeyedDataSource].
* @param previousPageKey Key for page before the initial load result, or `null` if no more
- * data can be loaded before.
+ * data can be loaded before.
* @param nextPageKey Key for page after the initial load result, or `null` if no more data
- * can be loaded after.
+ * can be loaded after.
*/
public abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
}
@@ -174,33 +172,28 @@
*
* Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
* loaded a page in [loadBefore], pass the key for the previous page, or `null` if the
- * loaded page is the first. If in [loadAfter], pass the key for the next page, or `null`
- * if the loaded page is the last.
+ * loaded page is the first. If in [loadAfter], pass the key for the next page, or `null` if
+ * the loaded page is the last.
*
* @param data List of items loaded from the PageKeyedDataSource.
* @param adjacentPageKey Key for subsequent page load (previous page in [loadBefore] / next
- * page in [loadAfter]), or `null` if there are no more pages to load in the current load
- * direction.
+ * page in [loadAfter]), or `null` if there are no more pages to load in the current load
+ * direction.
*/
public abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
}
- /**
- * @throws [IllegalArgumentException] when passed an unsupported load type.
- */
+ /** @throws [IllegalArgumentException] when passed an unsupported load type. */
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
- internal final override suspend fun load(params: Params<Key>): BaseResult<Value> = when {
- params.type == LoadType.REFRESH -> loadInitial(
- LoadInitialParams(
- params.initialLoadSize,
- params.placeholdersEnabled
- )
- )
- params.key == null -> BaseResult.empty()
- params.type == LoadType.PREPEND -> loadBefore(LoadParams(params.key, params.pageSize))
- params.type == LoadType.APPEND -> loadAfter(LoadParams(params.key, params.pageSize))
- else -> throw IllegalArgumentException("Unsupported type " + params.type.toString())
- }
+ internal final override suspend fun load(params: Params<Key>): BaseResult<Value> =
+ when {
+ params.type == LoadType.REFRESH ->
+ loadInitial(LoadInitialParams(params.initialLoadSize, params.placeholdersEnabled))
+ params.key == null -> BaseResult.empty()
+ params.type == LoadType.PREPEND -> loadBefore(LoadParams(params.key, params.pageSize))
+ params.type == LoadType.APPEND -> loadAfter(LoadParams(params.key, params.pageSize))
+ else -> throw IllegalArgumentException("Unsupported type " + params.type.toString())
+ }
private suspend fun loadInitial(params: LoadInitialParams<Key>) =
suspendCancellableCoroutine<BaseResult<Value>> { cont ->
@@ -299,7 +292,7 @@
* prevent further loading.
*
* @param params Parameters for the load, including the key for the new page, and requested load
- * size.
+ * size.
* @param callback Callback that receives loaded data.
*/
public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
@@ -319,7 +312,7 @@
* prevent further loading.
*
* @param params Parameters for the load, including the key for the new page, and requested load
- * size.
+ * size.
* @param callback Callback that receives loaded data.
*/
public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedList.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedList.kt
index a9b5655..ab554bf 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedList.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedList.kt
@@ -48,10 +48,9 @@
* generally not be presented to the user.
*
* A [PagedList] initially presents this first partial load as its content, and expands over time as
- * content is loaded in. When [loadAround] is called, items will be loaded in near the passed
- * list index. If placeholder `null`s are present in the list, they will be replaced as
- * content is loaded. If not, newly loaded items will be inserted at the beginning or end of the
- * list.
+ * content is loaded in. When [loadAround] is called, items will be loaded in near the passed list
+ * index. If placeholder `null`s are present in the list, they will be replaced as content is
+ * loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
*
* [PagedList] can present data for an unbounded, infinite scrolling list, or a very large but
* countable list. Use [PagedList.Config] to control how many items a [PagedList] loads, and when.
@@ -64,41 +63,37 @@
* There are two ways that [PagedList] can represent its not-yet-loaded data - with or without
* `null` placeholders.
*
- * With placeholders, the [PagedList] is always the full size of the data set. `get(N)` returns
- * the `N`th item in the data set, or `null` if its not yet loaded.
+ * With placeholders, the [PagedList] is always the full size of the data set. `get(N)` returns the
+ * `N`th item in the data set, or `null` if it's not yet loaded.
*
- * Without `null` placeholders, the [PagedList] is the sublist of data that has already been
- * loaded. The size of the [PagedList] is the number of currently loaded items, and `get(N)`
- * returns the `N`th *loaded* item. This is not necessarily the `N`th item in the
- * data set.
+ * Without `null` placeholders, the [PagedList] is the sublist of data that has already been loaded.
+ * The size of the [PagedList] is the number of currently loaded items, and `get(N)` returns the
+ * `N`th *loaded* item. This is not necessarily the `N`th item in the data set.
*
* Placeholders have several benefits:
- *
- * * They express the full sized list to the presentation layer (often a
- * [androidx.paging.PagedListAdapter]), and so can support scrollbars (without jumping as pages are
- * loaded or dropped) and fast-scrolling to any position, loaded or not.
- * * They avoid the need for a loading spinner at the end of the loaded list, since the list
- * is always full sized.
+ * * They express the full sized list to the presentation layer (often a
+ * [androidx.paging.PagedListAdapter]), and so can support scrollbars (without jumping as pages
+ * are loaded or dropped) and fast-scrolling to any position, loaded or not.
+ * * They avoid the need for a loading spinner at the end of the loaded list, since the list is
+ * always full sized.
*
* They also have drawbacks:
- *
- * * Your Adapter needs to account for `null` items. This often means providing default
- * values in data you bind to a [androidx.recyclerview.widget.RecyclerView.ViewHolder].
- * * They don't work well if your item views are of different sizes, as this will prevent
- * loading items from cross-fading nicely.
- * * They require you to count your data set, which can be expensive or impossible, depending
- * on your [PagingSource].
+ * * Your Adapter needs to account for `null` items. This often means providing default values in
+ * data you bind to a [androidx.recyclerview.widget.RecyclerView.ViewHolder].
+ * * They don't work well if your item views are of different sizes, as this will prevent loading
+ * items from cross-fading nicely.
+ * * They require you to count your data set, which can be expensive or impossible, depending on
+ * your [PagingSource].
*
* Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
- * [PagingSource] does not count its data set in its initial load, or if `false` is passed to
+ * [PagingSource] does not count its data set in its initial load, or if `false` is passed to
* [PagedList.Config.Builder.setEnablePlaceholders] when building a [PagedList.Config].
*
* ### Mutability and Snapshots
*
- * A [PagedList] is *mutable* while loading, or ready to load from its [PagingSource].
- * As loads succeed, a mutable [PagedList] will be updated via Runnables on the main thread. You can
- * listen to these updates with a [PagedList.Callback]. (Note that [androidx.paging
- * .PagedListAdapter] will
+ * A [PagedList] is *mutable* while loading, or ready to load from its [PagingSource]. As loads
+ * succeed, a mutable [PagedList] will be updated via Runnables on the main thread. You can listen
+ * to these updates with a [PagedList.Callback]. (Note that [androidx.paging .PagedListAdapter] will
* listen to these to signal RecyclerView about the updates/changes).
*
* If a [PagedList] attempts to load from an invalid [PagingSource], it will [detach] from the
@@ -116,11 +111,9 @@
*/
@Suppress("DEPRECATION")
@Deprecated("PagedList is deprecated and has been replaced by PagingData")
-public abstract class PagedList<T : Any> internal constructor(
- /**
- * The [PagingSource] that provides data to this [PagedList].
- *
- */
+public abstract class PagedList<T : Any>
+internal constructor(
+ /** The [PagingSource] that provides data to this [PagedList]. */
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open val pagingSource: PagingSource<*, T>,
internal val coroutineScope: CoroutineScope,
@@ -141,17 +134,15 @@
*
* @param pagingSource [PagingSource] providing data to the [PagedList]
* @param notifyDispatcher [CoroutineDispatcher] that will use and consume data from the
- * [PagedList]. Generally, this is the UI/main thread.
+ * [PagedList]. Generally, this is the UI/main thread.
* @param fetchDispatcher Data loading jobs will be dispatched to this
- * [CoroutineDispatcher] - should be a background thread.
+ * [CoroutineDispatcher] - should be a background thread.
* @param boundaryCallback Optional boundary callback to attach to the list.
* @param config [PagedList.Config], which defines how the [PagedList] will load data.
* @param K Key type that indicates to the [PagingSource] what data to load.
* @param T Type of items to be held and loaded by the [PagedList].
- *
* @return The newly created [PagedList], which will page in data from the [PagingSource] as
- * needed.
- *
+ * needed.
*/
@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -165,34 +156,38 @@
config: Config,
key: K?
): PagedList<T> {
- val resolvedInitialPage = when (initialPage) {
- null -> {
- // Compatibility codepath - perform the initial load immediately, since caller
- // hasn't done it. We block in this case, but it's only used in the legacy path.
- val params = PagingSource.LoadParams.Refresh(
- key,
- config.initialLoadSizeHint,
- config.enablePlaceholders,
- )
- runBlocking {
- val initialResult = pagingSource.load(params)
- when (initialResult) {
- is PagingSource.LoadResult.Page -> initialResult
- is PagingSource.LoadResult.Error -> throw initialResult.throwable
- is PagingSource.LoadResult.Invalid ->
- throw IllegalStateException(
- "Failed to create PagedList. The provided PagingSource " +
- "returned LoadResult.Invalid, but a LoadResult.Page was " +
- "expected. To use a PagingSource which supports " +
- "invalidation, use a PagedList builder that accepts a " +
- "factory method for PagingSource or DataSource.Factory, " +
- "such as LivePagedList."
- )
+ val resolvedInitialPage =
+ when (initialPage) {
+ null -> {
+ // Compatibility codepath - perform the initial load immediately, since
+ // caller
+ // hasn't done it. We block in this case, but it's only used in the legacy
+ // path.
+ val params =
+ PagingSource.LoadParams.Refresh(
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ )
+ runBlocking {
+ val initialResult = pagingSource.load(params)
+ when (initialResult) {
+ is PagingSource.LoadResult.Page -> initialResult
+ is PagingSource.LoadResult.Error -> throw initialResult.throwable
+ is PagingSource.LoadResult.Invalid ->
+ throw IllegalStateException(
+ "Failed to create PagedList. The provided PagingSource " +
+ "returned LoadResult.Invalid, but a LoadResult.Page was " +
+ "expected. To use a PagingSource which supports " +
+ "invalidation, use a PagedList builder that accepts a " +
+ "factory method for PagingSource or DataSource.Factory, " +
+ "such as LivePagedList."
+ )
+ }
}
}
+ else -> initialPage
}
- else -> initialPage
- }
return ContiguousPagedList(
pagingSource,
coroutineScope,
@@ -242,9 +237,9 @@
* [pagingSource], [config], [notifyDispatcher] and [fetchDispatcher] must all be provided.
*
* A [PagedList] queries initial data from its [PagingSource] during construction, to avoid
- * empty [PagedList]s being presented to the UI when possible. It's preferred to present
- * initial data, so that the UI doesn't show an empty list, or placeholders for a few frames,
- * just before showing initial content.
+ * empty [PagedList]s being presented to the UI when possible. It's preferred to present initial
+ * data, so that the UI doesn't show an empty list, or placeholders for a few frames, just
+ * before showing initial content.
*
* [LivePagedListBuilder][androidx.paging.LivePagedListBuilder] does this creation on a
* background thread automatically, if you want to receive a `LiveData<PagedList<...>>`.
@@ -253,8 +248,9 @@
* @param Value Type of items held and loaded by the [PagedList].
*/
@Deprecated(
- message = "PagedList is deprecated and has been replaced by PagingData, which no " +
- "longer supports constructing snapshots of loaded data manually.",
+ message =
+ "PagedList is deprecated and has been replaced by PagingData, which no " +
+ "longer supports constructing snapshots of loaded data manually.",
replaceWith = ReplaceWith("Pager.flow", "androidx.paging.Pager")
)
public class Builder<Key : Any, Value : Any> {
@@ -275,7 +271,7 @@
*
* @param dataSource [DataSource] the [PagedList] will load from.
* @param config [PagedList.Config] that defines how the [PagedList] loads data from its
- * [DataSource].
+ * [DataSource].
*/
public constructor(dataSource: DataSource<Key, Value>, config: Config) {
this.pagingSource = null
@@ -295,12 +291,12 @@
*
* @param dataSource [DataSource] the [PagedList] will load from.
* @param pageSize Size of loaded pages when the [PagedList] loads data from its
- * [DataSource].
+ * [DataSource].
*/
- public constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
- dataSource = dataSource,
- config = Config(pageSize)
- )
+ public constructor(
+ dataSource: DataSource<Key, Value>,
+ pageSize: Int
+ ) : this(dataSource = dataSource, config = Config(pageSize))
/**
* Create a [PagedList.Builder] with the provided [PagingSource], initial
@@ -309,7 +305,7 @@
* @param pagingSource [PagingSource] the [PagedList] will load from.
* @param initialPage Initial page loaded from the [PagingSource].
* @param config [PagedList.Config] that defines how the [PagedList] loads data from its
- * [PagingSource].
+ * [PagingSource].
*/
public constructor(
pagingSource: PagingSource<Key, Value>,
@@ -338,17 +334,13 @@
* @param pagingSource [PagingSource] the [PagedList] will load from.
* @param initialPage Initial page loaded from the [PagingSource].
* @param pageSize Size of loaded pages when the [PagedList] loads data from its
- * [PagingSource].
+ * [PagingSource].
*/
public constructor(
pagingSource: PagingSource<Key, Value>,
initialPage: PagingSource.LoadResult.Page<Key, Value>,
pageSize: Int
- ) : this(
- pagingSource = pagingSource,
- initialPage = initialPage,
- config = Config(pageSize)
- )
+ ) : this(pagingSource = pagingSource, initialPage = initialPage, config = Config(pageSize))
/**
* Set the [CoroutineScope] that page loads should be launched within.
@@ -362,9 +354,7 @@
* @param coroutineScope
* @return this
*/
- public fun setCoroutineScope(
- coroutineScope: CoroutineScope
- ): Builder<Key, Value> = apply {
+ public fun setCoroutineScope(coroutineScope: CoroutineScope): Builder<Key, Value> = apply {
this.coroutineScope = coroutineScope
}
@@ -372,20 +362,20 @@
* The [Executor] defining where page loading updates are dispatched.
*
* @param notifyExecutor [Executor] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
* @return this
*/
@Deprecated(
- message = "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
- "consider passing a CoroutineDispatcher directly",
- replaceWith = ReplaceWith(
- "setNotifyDispatcher(fetchExecutor.asCoroutineDispatcher())",
- "kotlinx.coroutines.asCoroutineDispatcher"
- )
+ message =
+ "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
+ "consider passing a CoroutineDispatcher directly",
+ replaceWith =
+ ReplaceWith(
+ "setNotifyDispatcher(fetchExecutor.asCoroutineDispatcher())",
+ "kotlinx.coroutines.asCoroutineDispatcher"
+ )
)
- public fun setNotifyExecutor(
- notifyExecutor: Executor
- ): Builder<Key, Value> = apply {
+ public fun setNotifyExecutor(notifyExecutor: Executor): Builder<Key, Value> = apply {
this.notifyDispatcher = notifyExecutor.asCoroutineDispatcher()
}
@@ -393,14 +383,13 @@
* The [CoroutineDispatcher] defining where page loading updates are dispatched.
*
* @param notifyDispatcher [CoroutineDispatcher] that receives [PagedList] updates, and
- * where [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
+ * where [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
* @return this
*/
- public fun setNotifyDispatcher(
- notifyDispatcher: CoroutineDispatcher
- ): Builder<Key, Value> = apply {
- this.notifyDispatcher = notifyDispatcher
- }
+ public fun setNotifyDispatcher(notifyDispatcher: CoroutineDispatcher): Builder<Key, Value> =
+ apply {
+ this.notifyDispatcher = notifyDispatcher
+ }
/**
* The [Executor] used to fetch additional pages from the [PagingSource].
@@ -409,20 +398,20 @@
* [PagedList] is created on.
*
* @param fetchExecutor [Executor] used to fetch from [PagingSource]s, generally a
- * background thread pool for e.g. I/O or network loading.
+ * background thread pool for e.g. I/O or network loading.
* @return this
*/
@Deprecated(
- message = "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
- "consider passing a CoroutineDispatcher directly",
- replaceWith = ReplaceWith(
- "setFetchDispatcher(fetchExecutor.asCoroutineDispatcher())",
- "kotlinx.coroutines.asCoroutineDispatcher"
- )
+ message =
+ "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
+ "consider passing a CoroutineDispatcher directly",
+ replaceWith =
+ ReplaceWith(
+ "setFetchDispatcher(fetchExecutor.asCoroutineDispatcher())",
+ "kotlinx.coroutines.asCoroutineDispatcher"
+ )
)
- public fun setFetchExecutor(
- fetchExecutor: Executor
- ): Builder<Key, Value> = apply {
+ public fun setFetchExecutor(fetchExecutor: Executor): Builder<Key, Value> = apply {
this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
}
@@ -433,14 +422,13 @@
* [PagedList] is created on.
*
* @param fetchDispatcher [CoroutineDispatcher] used to fetch from [PagingSource]s,
- * generally a background thread pool for e.g. I/O or network loading.
+ * generally a background thread pool for e.g. I/O or network loading.
* @return this
*/
- public fun setFetchDispatcher(
- fetchDispatcher: CoroutineDispatcher
- ): Builder<Key, Value> = apply {
- this.fetchDispatcher = fetchDispatcher
- }
+ public fun setFetchDispatcher(fetchDispatcher: CoroutineDispatcher): Builder<Key, Value> =
+ apply {
+ this.fetchDispatcher = fetchDispatcher
+ }
/**
* The [BoundaryCallback] for out of data events.
@@ -452,9 +440,7 @@
*/
public fun setBoundaryCallback(
boundaryCallback: BoundaryCallback<Value>?
- ): Builder<Key, Value> = apply {
- this.boundaryCallback = boundaryCallback
- }
+ ): Builder<Key, Value> = apply { this.boundaryCallback = boundaryCallback }
/**
* Sets the initial key the [PagingSource] should load around as part of initialization.
@@ -462,9 +448,7 @@
* @param initialKey Key the [PagingSource] should load around as part of initialization.
* @return this
*/
- public fun setInitialKey(
- initialKey: Key?
- ): Builder<Key, Value> = apply {
+ public fun setInitialKey(initialKey: Key?): Builder<Key, Value> = apply {
this.initialKey = initialKey
}
@@ -488,18 +472,16 @@
* happens, the [PagedList] will be immediately [detached][PagedList.isDetached], and you
* can retry construction (including setting a new [PagingSource]).
*
- * @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
- *
* @return The newly constructed [PagedList]
+ * @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
*/
public fun build(): PagedList<Value> {
val fetchDispatcher = fetchDispatcher ?: Dispatchers.IO
- val pagingSource = pagingSource ?: dataSource?.let { dataSource ->
- LegacyPagingSource(
- fetchContext = fetchDispatcher,
- dataSource = dataSource
- )
- }
+ val pagingSource =
+ pagingSource
+ ?: dataSource?.let { dataSource ->
+ LegacyPagingSource(fetchContext = fetchDispatcher, dataSource = dataSource)
+ }
if (pagingSource is LegacyPagingSource) {
pagingSource.setPageSize(config.pageSize)
@@ -525,9 +507,9 @@
/**
* Callback signaling when content is loaded into the list.
*
- * Can be used to listen to items being paged in and out. These calls will be dispatched on
- * the dispatcher defined by [PagedList.Builder.setNotifyDispatcher], which is generally the
- * main/UI thread.
+ * Can be used to listen to items being paged in and out. These calls will be dispatched on the
+ * dispatcher defined by [PagedList.Builder.setNotifyDispatcher], which is generally the main/UI
+ * thread.
*/
public abstract class Callback {
/**
@@ -535,7 +517,7 @@
* data that hasn't been used in a while has been dropped, and swapped back to null.
*
* @param position Position of first newly loaded items, out of total number of items
- * (including padded nulls).
+ * (including padded nulls).
* @param count Number of items loaded.
*/
public abstract fun onChanged(position: Int, count: Int)
@@ -544,7 +526,7 @@
* Called when new items have been loaded at the end or beginning of the list.
*
* @param position Position of the first newly loaded item (in practice, either `0` or
- * `size - 1`.
+ * `size - 1`.
* @param count Number of items loaded.
*/
public abstract fun onInserted(position: Int, count: Int)
@@ -554,7 +536,7 @@
* been replaced by padded nulls.
*
* @param position Position of the first newly loaded item (in practice, either `0` or
- * `size - 1`.
+ * `size - 1`.
* @param count Number of items loaded.
*/
public abstract fun onRemoved(position: Int, count: Int)
@@ -567,12 +549,10 @@
* [setPageSize][PagedList.Config.Builder.setPageSize], which defines number of items loaded at
* a time.
*/
- public class Config internal constructor(
- /**
- * Size of each page loaded by the PagedList.
- */
- @JvmField
- public val pageSize: Int,
+ public class Config
+ internal constructor(
+ /** Size of each page loaded by the PagedList. */
+ @JvmField public val pageSize: Int,
/**
* Prefetch distance which defines how far ahead to load.
*
@@ -581,19 +561,14 @@
*
* @see PagedList.loadAround
*/
- @JvmField
- public val prefetchDistance: Int,
+ @JvmField public val prefetchDistance: Int,
/**
* Defines whether the [PagedList] may display null placeholders, if the [PagingSource]
* provides them.
*/
- @JvmField
- public val enablePlaceholders: Boolean,
- /**
- * Size hint for initial load of PagedList, often larger than a regular page.
- */
- @JvmField
- public val initialLoadSizeHint: Int,
+ @JvmField public val enablePlaceholders: Boolean,
+ /** Size hint for initial load of PagedList, often larger than a regular page. */
+ @JvmField public val initialLoadSizeHint: Int,
/**
* Defines the maximum number of items that may be loaded into this pagedList before pages
* should be dropped.
@@ -603,8 +578,7 @@
* @see PagedList.Config.Companion.MAX_SIZE_UNBOUNDED
* @see PagedList.Config.Builder.setMaxSize
*/
- @JvmField
- public val maxSize: Int
+ @JvmField public val maxSize: Int
) {
/**
* Builder class for [PagedList.Config].
@@ -635,12 +609,9 @@
*
* @param pageSize Number of items loaded at once from the [PagingSource].
* @return this
- *
* @throws IllegalArgumentException if pageSize is < `1`.
*/
- public fun setPageSize(
- @IntRange(from = 1) pageSize: Int
- ): Builder = apply {
+ public fun setPageSize(@IntRange(from = 1) pageSize: Int): Builder = apply {
if (pageSize < 1) {
throw IllegalArgumentException("Page size must be a positive number")
}
@@ -662,31 +633,29 @@
* @param prefetchDistance Distance the [PagedList] should prefetch.
* @return this
*/
- public fun setPrefetchDistance(
- @IntRange(from = 0) prefetchDistance: Int
- ): Builder = apply {
- this.prefetchDistance = prefetchDistance
- }
+ public fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int): Builder =
+ apply {
+ this.prefetchDistance = prefetchDistance
+ }
/**
- * Pass false to disable null placeholders in [PagedList]s using this [PagedList.Config].
+ * Pass false to disable null placeholders in [PagedList]s using this
+ * [PagedList.Config].
*
* If not set, defaults to true.
*
* A [PagedList] will present null placeholders for not-yet-loaded content if two
* conditions are met:
- *
* 1) Its [PagingSource] can count all unloaded items (so that the number of nulls to
- * present is known).
- *
+ * present is known).
* 2) placeholders are not disabled on the [PagedList.Config].
*
- * Call `setEnablePlaceholders(false)` to ensure the receiver of the PagedList
- * (often a [androidx.paging.PagedListAdapter]) doesn't need to account for null items.
+ * Call `setEnablePlaceholders(false)` to ensure the receiver of the PagedList (often a
+ * [androidx.paging.PagedListAdapter]) doesn't need to account for null items.
*
* If placeholders are disabled, not-yet-loaded content will not be present in the list.
- * Paging will still occur, but as items are loaded or removed, they will be signaled
- * as inserts to the [PagedList.Callback].
+ * Paging will still occur, but as items are loaded or removed, they will be signaled as
+ * inserts to the [PagedList.Callback].
*
* [PagedList.Callback.onChanged] will not be issued as part of loading, though a
* [androidx.paging.PagedListAdapter] may still receive change events as a result of
@@ -695,9 +664,7 @@
* @param enablePlaceholders `false` if null placeholders should be disabled.
* @return this
*/
- public fun setEnablePlaceholders(
- enablePlaceholders: Boolean
- ): Builder = apply {
+ public fun setEnablePlaceholders(enablePlaceholders: Boolean): Builder = apply {
this.enablePlaceholders = enablePlaceholders
}
@@ -714,9 +681,7 @@
*/
public fun setInitialLoadSizeHint(
@IntRange(from = 1) initialLoadSizeHint: Int
- ): Builder = apply {
- this.initialLoadSizeHint = initialLoadSizeHint
- }
+ ): Builder = apply { this.initialLoadSizeHint = initialLoadSizeHint }
/**
* Defines how many items to keep loaded at once.
@@ -731,20 +696,19 @@
* The max size specified here best effort, not a guarantee. In practice, if [maxSize]
* is many times the page size, the number of items held by the [PagedList] will not
* grow above this number. Exceptions are made as necessary to guarantee:
- * * Pages are never dropped until there are more than two pages loaded. Note that
- * a [PagingSource] may not be held strictly to
- * [requested pageSize][PagedList.Config.pageSize], so two pages may be larger than
- * expected.
- * * Pages are never dropped if they are within a prefetch window (defined to be
- * `pageSize + (2 * prefetchDistance)`) of the most recent load.
+ * * Pages are never dropped until there are more than two pages loaded. Note that a
+ * [PagingSource] may not be held strictly to
+ * [requested pageSize][PagedList.Config.pageSize], so two pages may be larger than
+ * expected.
+ * * Pages are never dropped if they are within a prefetch window (defined to be
+ * `pageSize + (2 * prefetchDistance)`) of the most recent load.
*
* If not set, defaults to [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED], which
* disables page dropping.
*
* @param maxSize Maximum number of items to keep in memory, or
- * [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED] to disable page dropping.
+ * [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED] to disable page dropping.
* @return this
- *
* @see Config.MAX_SIZE_UNBOUNDED
* @see Config.maxSize
*/
@@ -756,11 +720,10 @@
* Creates a [PagedList.Config] with the given parameters.
*
* @return A new [PagedList.Config].
- *
- * @throws IllegalArgumentException if placeholders are disabled and prefetchDistance
- * is set to 0
+ * @throws IllegalArgumentException if placeholders are disabled and prefetchDistance is
+ * set to 0
* @throws IllegalArgumentException if maximum size is less than pageSize +
- * 2*prefetchDistance
+ * 2*prefetchDistance
*/
public fun build(): Config {
if (prefetchDistance < 0) {
@@ -803,8 +766,7 @@
* When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
* unbounded, and pages will never be dropped.
*/
- @Suppress("MinMaxConstant")
- const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
+ @Suppress("MinMaxConstant") const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
}
}
@@ -829,8 +791,8 @@
* The database + network Repository in the
* [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
* shows how to implement a network BoundaryCallback using
- * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh,
- * network errors, and retry.
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network
+ * errors, and retry.
*
* ### Requesting Network Data
* [BoundaryCallback] only passes the item at front or end of the list when out of data. This
@@ -842,26 +804,24 @@
* If this is the case, the paging library doesn't know about the page key or index used in the
* [BoundaryCallback], so you need to track it yourself. You can do this in one of two ways:
*
- * <h5>Local storage Page key</h5>
- * If you want to perfectly resume your query, even if the app is killed and resumed, you can
- * store the key on disk. Note that with a positional/page index network API, there's a simple
- * way to do this, by using the `listSize` as an input to the next load (or
- * `listSize / NETWORK_PAGE_SIZE`, for page indexing).
+ * <h5>Local storage Page key</h5> If you want to perfectly resume your query, even if the app
+ * is killed and resumed, you can store the key on disk. Note that with a positional/page index
+ * network API, there's a simple way to do this, by using the `listSize` as an input to the next
+ * load (or `listSize / NETWORK_PAGE_SIZE`, for page indexing).
*
* The current list size isn't passed to the BoundaryCallback though. This is because the
* PagedList doesn't necessarily know the number of items in local storage. Placeholders may be
* disabled, or the [PagingSource] may not count total number of items.
*
* Instead, for these positional cases, you can query the database for the number of items, and
- * pass that to the network.
- * <h5>In-Memory Page key</h5>
- * Often it doesn't make sense to query the next page from network if the last page you fetched
- * was loaded many hours or days before. If you keep the key in memory, you can refresh any time
- * you start paging from a network source.
+ * pass that to the network. <h5>In-Memory Page key</h5> Often it doesn't make sense to query
+ * the next page from network if the last page you fetched was loaded many hours or days before.
+ * If you keep the key in memory, you can refresh any time you start paging from a network
+ * source.
*
* Store the next key in memory, inside your BoundaryCallback. When you create a new
- * BoundaryCallback when creating a new `LiveData`/`Observable` of
- * `PagedList`, refresh data. For example,
+ * BoundaryCallback when creating a new `LiveData`/`Observable` of `PagedList`, refresh data.
+ * For example,
* [in the Paging Codelab](https://codelabs.developers.google.com/codelabs/android-paging/index.html#8),
* the GitHub network page index is stored in memory.
*
@@ -885,8 +845,8 @@
public open fun onItemAtFrontLoaded(itemAtFront: T) {}
/**
- * Called when the item at the end of the PagedList has been loaded, and access has
- * occurred within [PagedList.Config.prefetchDistance] of it.
+ * Called when the item at the end of the PagedList has been loaded, and access has occurred
+ * within [PagedList.Config.prefetchDistance] of it.
*
* No more data will be appended to the [PagedList] after this item.
*
@@ -940,7 +900,6 @@
* Last access location in list.
*
* Used by list diffing to re-initialize loading near viewport.
- *
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun lastLoad(): Int = storage.lastLoadAroundIndex
@@ -962,13 +921,14 @@
get() = storage.size
/**
- * @throws IllegalStateException if this [PagedList] was instantiated without a
- * wrapping a backing [DataSource]
+ * @throws IllegalStateException if this [PagedList] was instantiated without a wrapping a
+ * backing [DataSource]
*/
@Deprecated(
- message = "DataSource is deprecated and has been replaced by PagingSource. PagedList " +
- "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
- "you should not need to access the DataSource/PagingSource."
+ message =
+ "DataSource is deprecated and has been replaced by PagingSource. PagedList " +
+ "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
+ "you should not need to access the DataSource/PagingSource."
)
public val dataSource: DataSource<*, T>
@Suppress("DocumentExceptions")
@@ -1009,8 +969,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
public abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- public abstract fun loadAroundInternal(index: Int)
+ @RestrictTo(RestrictTo.Scope.LIBRARY) public abstract fun loadAroundInternal(index: Int)
/**
* Detach the [PagedList] from its [PagingSource], and attempt to load no more data.
@@ -1030,7 +989,6 @@
* equivalent to [size].
*
* @return Number of items currently loaded, not counting placeholders.
- *
* @see size
*/
public val loadedCount: Int
@@ -1052,18 +1010,17 @@
/**
* Position offset of the data in the list.
*
- * If the PagingSource backing this PagedList is counted, the item returned from `get(i)` has
- * a position in the original data set of `i + getPositionOffset()`.
+ * If the PagingSource backing this PagedList is counted, the item returned from `get(i)` has a
+ * position in the original data set of `i + getPositionOffset()`.
*
- * If placeholders are enabled, this value is always `0`, since `get(i)` will return either
- * the data in its original index, or null if it is not loaded.
+ * If placeholders are enabled, this value is always `0`, since `get(i)` will return either the
+ * data in its original index, or null if it is not loaded.
*/
public val positionOffset: Int
get() = storage.positionOffset
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {
- }
+ public open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {}
/**
* Retry any errors associated with this [PagedList].
@@ -1097,8 +1054,7 @@
*
* @param index Index in the loaded item list. Must be >= 0, and < [size]
* @return The item at the passed index, or `null` if a `null` placeholder is at the specified
- * position.
- *
+ * position.
* @see size
*/
public override fun get(index: Int): T? = storage[index]
@@ -1107,7 +1063,6 @@
* Load adjacent items to passed index.
*
* @param index Index at which to load.
- *
* @throws IndexOutOfBoundsException if index is not within bounds.
*/
public fun loadAround(index: Int) {
@@ -1126,16 +1081,16 @@
*
* @return Immutable snapshot of [PagedList] data.
*/
- public fun snapshot(): List<T> = when {
- isImmutable -> this
- else -> SnapshotPagedList(this)
- }
+ public fun snapshot(): List<T> =
+ when {
+ isImmutable -> this
+ else -> SnapshotPagedList(this)
+ }
/**
* Add a listener to observe the loading state of the [PagedList].
*
* @param listener Listener to receive updates.
- *
* @see removeWeakLoadStateListener
*/
public fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
@@ -1151,7 +1106,6 @@
* Remove a previously registered load state listener.
*
* @param listener Previously registered listener.
- *
* @see addWeakLoadStateListener
*/
public fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
@@ -1163,20 +1117,19 @@
*
* If [previousSnapshot] is passed, the [callback] will also immediately be dispatched any
* differences between the previous snapshot, and the current state. For example, if the
- * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls,
- * 12 items, 3 nulls, the callback would immediately receive a call of`onChanged(14, 2)`.
+ * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls, 12
+ * items, 3 nulls, the callback would immediately receive a call of`onChanged(14, 2)`.
*
* This allows an observer that's currently presenting a snapshot to catch up to the most recent
* version, including any changes that may have been made.
*
* The callback is internally held as weak reference, so [PagedList] doesn't hold a strong
- * reference to its observer, such as a [PagedListAdapter][androidx.paging.PagedListAdapter].
- * If an adapter were held with a strong reference, it would be necessary to clear its
- * [PagedList] observer before it could be GC'd.
+ * reference to its observer, such as a [PagedListAdapter][androidx.paging.PagedListAdapter]. If
+ * an adapter were held with a strong reference, it would be necessary to clear its [PagedList]
+ * observer before it could be GC'd.
*
* @param previousSnapshot Snapshot previously captured from this List, or `null`.
* @param callback [PagedList.Callback] to dispatch to.
- *
* @see removeWeakCallback
*/
@Deprecated(
@@ -1200,7 +1153,6 @@
* it could be GC'd.
*
* @param callback Callback to dispatch to.
- *
* @see removeWeakCallback
*/
@Suppress("RegistrationName")
@@ -1216,7 +1168,6 @@
* Removes a previously added callback.
*
* @param callback Callback, previously added.
- *
* @see addWeakCallback
*/
@Suppress("RegistrationName")
@@ -1250,16 +1201,13 @@
* @param dataSource [DataSource] the [PagedList] will load from.
* @param config Config that defines how the [PagedList] loads data from its [DataSource].
* @param notifyExecutor [Executor] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI/main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI/main thread.
* @param fetchExecutor [Executor] used to fetch from [DataSource]s, generally a background thread
- * pool for e.g. I/O or network loading.
+ * pool for e.g. I/O or network loading.
* @param boundaryCallback [PagedList.BoundaryCallback] for listening to out-of-data events.
* @param initialKey [Key] the [DataSource] should load around as part of initialization.
*/
-@Suppress(
- "FunctionName",
- "DEPRECATION"
-)
+@Suppress("FunctionName", "DEPRECATION")
@JvmSynthetic
@Deprecated("DataSource is deprecated and has been replaced by PagingSource")
public fun <Key : Any, Value : Any> PagedList(
@@ -1282,8 +1230,9 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun <Key : Any> PagedList.Config.toRefreshLoadParams(
key: Key?
-): PagingSource.LoadParams<Key> = PagingSource.LoadParams.Refresh(
- key,
- initialLoadSizeHint,
- enablePlaceholders,
-)
+): PagingSource.LoadParams<Key> =
+ PagingSource.LoadParams.Refresh(
+ key,
+ initialLoadSizeHint,
+ enablePlaceholders,
+ )
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
index a1e4299..c35fd13 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
@@ -24,13 +24,9 @@
* @param enablePlaceholders False if null placeholders should be disabled.
* @param initialLoadSizeHint Number of items to load while initializing the PagedList.
* @param maxSize Maximum number of items to keep in memory, or
- * [PagedList.Config.MAX_SIZE_UNBOUNDED] to disable page dropping.
+ * [PagedList.Config.MAX_SIZE_UNBOUNDED] to disable page dropping.
*/
-@Suppress(
- "FunctionName",
- "DEPRECATION",
- "ReferencesDeprecated"
-)
+@Suppress("FunctionName", "DEPRECATION", "ReferencesDeprecated")
@JvmSynthetic
public fun Config(
pageSize: Int,
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedStorage.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedStorage.jvm.kt
index c6d537b..c624460 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedStorage.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedStorage.jvm.kt
@@ -27,13 +27,12 @@
* prefetching.
*/
internal class PagedStorage<T : Any> :
- AbstractList<T>,
- LegacyPageFetcher.KeyProvider<Any>,
- PlaceholderPaddedList<T> {
+ AbstractList<T>, LegacyPageFetcher.KeyProvider<Any>, PlaceholderPaddedList<T> {
private val pages = mutableListOf<Page<*, T>>()
internal val firstLoadedItem: T
get() = pages.first().data.first()
+
internal val lastLoadedItem: T
get() = pages.last().data.last()
@@ -48,15 +47,11 @@
private var counted = true
- /**
- * Number of loaded items held by [pages].
- */
+ /** Number of loaded items held by [pages]. */
override var dataCount: Int = 0
private set
- /**
- * Last accessed index for loadAround in storage space
- */
+ /** Last accessed index for loadAround in storage space */
private var lastLoadAroundLocalIndex: Int = 0
var lastLoadAroundIndex: Int
get() = placeholdersBefore + lastLoadAroundLocalIndex
@@ -69,11 +64,7 @@
constructor()
- constructor(
- leadingNulls: Int,
- page: Page<*, T>,
- trailingNulls: Int
- ) : this() {
+ constructor(leadingNulls: Int, page: Page<*, T>, trailingNulls: Int) : this() {
init(leadingNulls, page, trailingNulls, 0, true)
}
@@ -124,23 +115,25 @@
// ------------- Adjacent Provider interface ------------------
override val prevKey: Any?
- get() = if (!counted || placeholdersBefore + positionOffset > 0) {
- pages.first().prevKey
- } else {
- null
- }
+ get() =
+ if (!counted || placeholdersBefore + positionOffset > 0) {
+ pages.first().prevKey
+ } else {
+ null
+ }
override val nextKey: Any?
- get() = if (!counted || placeholdersAfter > 0) {
- pages.last().nextKey
- } else {
- null
- }
+ get() =
+ if (!counted || placeholdersAfter > 0) {
+ pages.last().nextKey
+ } else {
+ null
+ }
/**
* Traverse to the page and pageInternalIndex of localIndex.
*
- * Bounds check (between 0 and storageCount) must be performed before calling this function.
+ * Bounds check (between 0 and storageCount) must be performed before calling this function.
*/
private inline fun <V> traversePages(
localIndex: Int,
@@ -163,13 +156,9 @@
return onLastPage(pages[localPageIndex], pageInternalIndex)
}
- /**
- * Walk through the list of pages to find the data at local index
- */
+ /** Walk through the list of pages to find the data at local index */
override fun getItem(index: Int): T =
- traversePages(index) { page, pageInternalIndex ->
- page.data[pageInternalIndex]
- }
+ traversePages(index) { page, pageInternalIndex -> page.data[pageInternalIndex] }
fun getRefreshKeyInfo(@Suppress("DEPRECATION") config: PagedList.Config): PagingState<*, T>? {
if (pages.isEmpty()) {
@@ -180,13 +169,14 @@
return PagingState(
pages = pages.toList() as List<Page<Any, T>>,
anchorPosition = lastLoadAroundIndex,
- config = PagingConfig(
- config.pageSize,
- config.prefetchDistance,
- config.enablePlaceholders,
- config.initialLoadSizeHint,
- config.maxSize
- ),
+ config =
+ PagingConfig(
+ config.pageSize,
+ config.prefetchDistance,
+ config.enablePlaceholders,
+ config.initialLoadSizeHint,
+ config.maxSize
+ ),
leadingPlaceholderCount = placeholdersBefore
)
}
@@ -206,9 +196,13 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
interface Callback {
fun onInitialized(count: Int)
+
fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int)
+
fun onPageAppended(endPosition: Int, changed: Int, added: Int)
+
fun onPagesRemoved(startOfDrops: Int, count: Int)
+
fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int)
}
@@ -239,9 +233,7 @@
needsTrim(maxSize, requiredRemaining, pages.size - 1)
fun shouldPreTrimNewPage(maxSize: Int, requiredRemaining: Int, countToBeAdded: Int) =
- dataCount + countToBeAdded > maxSize &&
- pages.size > 1 &&
- dataCount >= requiredRemaining
+ dataCount + countToBeAdded > maxSize && pages.size > 1 && dataCount >= requiredRemaining
internal fun trimFromFront(
insertNulls: Boolean,
@@ -341,10 +333,7 @@
placeholdersAfter -= changedCount
}
- callback?.onPageAppended(
- placeholdersBefore + dataCount - count,
- changedCount, addedCount
- )
+ callback?.onPageAppended(placeholdersBefore + dataCount - count, changedCount, addedCount)
}
override fun toString(): String =
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagingDataTransforms.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagingDataTransforms.jvm.kt
index 5184f3f..eed8965 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagingDataTransforms.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagingDataTransforms.jvm.kt
@@ -25,8 +25,8 @@
import kotlinx.coroutines.withContext
/**
- * Returns a [PagingData] containing the result of applying the given [transform] to each
- * element, as it is loaded.
+ * Returns a [PagingData] containing the result of applying the given [transform] to each element,
+ * as it is loaded.
*
* @see PagingData.map
*/
@@ -35,14 +35,12 @@
executor: Executor,
transform: (T) -> R,
): PagingData<R> = transform { event ->
- withContext(executor.asCoroutineDispatcher()) {
- event.map { transform(it) }
- }
+ withContext(executor.asCoroutineDispatcher()) { event.map { transform(it) } }
}
/**
- * Returns a [PagingData] of all elements returned from applying the given [transform]
- * to each element, as it is loaded.
+ * Returns a [PagingData] of all elements returned from applying the given [transform] to each
+ * element, as it is loaded.
*
* @see flatMap
*/
@@ -51,9 +49,7 @@
executor: Executor,
transform: (T) -> Iterable<R>
): PagingData<R> = transform { event ->
- withContext(executor.asCoroutineDispatcher()) {
- event.flatMap { transform(it) }
- }
+ withContext(executor.asCoroutineDispatcher()) { event.flatMap { transform(it) } }
}
/**
@@ -67,22 +63,18 @@
executor: Executor,
predicate: (T) -> Boolean
): PagingData<T> = transform { event ->
- withContext(executor.asCoroutineDispatcher()) {
- event.filter { predicate(it) }
- }
+ withContext(executor.asCoroutineDispatcher()) { event.filter { predicate(it) } }
}
// NOTE: samples in the doc below are manually imported from Java code in the samples
// project, since Java cannot be linked with @sample.
// DO NOT CHANGE THE BELOW COMMENT WITHOUT MAKING THE CORRESPONDING CHANGE IN `samples/`
/**
+ * Returns a [PagingData] containing each original element, with an optional separator generated by
+ * [generator], given the elements before and after (or null, in boundary conditions).
*
- * Returns a [PagingData] containing each original element, with an optional separator
- * generated by [generator], given the elements before and after (or null, in boundary
- * conditions).
- *
- * Note that this transform is applied asynchronously, as pages are loaded. Potential
- * separators between pages are only computed once both pages are loaded.
+ * Note that this transform is applied asynchronously, as pages are loaded. Potential separators
+ * between pages are only computed once both pages are loaded.
*
* **Kotlin callers should instead use the suspending extension function variant of
* insertSeparators**
@@ -164,16 +156,14 @@
* }
* ```
*
- * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and
- * footer are added.
- *
+ * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and footer
+ * are added.
* @param executor [Executor] to run the [generator] function in.
- *
- * @param generator Generator function used to construct a separator item given the item before
- * and the item after. For terminal separators (header and footer), the arguments passed to the
- * generator, `before` and `after`, will be `null` respectively. In cases where the fully paginated
- * list is empty, a single separator will be added where both `before` and `after` items are `null`.
- *
+ * @param generator Generator function used to construct a separator item given the item before and
+ * the item after. For terminal separators (header and footer), the arguments passed to the
+ * generator, `before` and `after`, will be `null` respectively. In cases where the fully
+ * paginated list is empty, a single separator will be added where both `before` and `after` items
+ * are `null`.
*/
@CheckResult
@JvmOverloads
@@ -183,8 +173,6 @@
generator: (T?, T?) -> R?,
): PagingData<R> {
return insertSeparators(terminalSeparatorType) { before, after ->
- withContext(executor.asCoroutineDispatcher()) {
- generator(before, after)
- }
+ withContext(executor.asCoroutineDispatcher()) { generator(before, after) }
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PositionalDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PositionalDataSource.jvm.kt
index 3ef7545..74e7e2b 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PositionalDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PositionalDataSource.jvm.kt
@@ -28,9 +28,9 @@
* Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
* arbitrary page positions.
*
- * Extend PositionalDataSource if you can load pages of a requested size at arbitrary positions,
- * and provide a fixed item count. If your data source can't support loading arbitrary requested
- * page sizes (e.g. when network page size constraints are only known at runtime), either use
+ * Extend PositionalDataSource if you can load pages of a requested size at arbitrary positions, and
+ * provide a fixed item count. If your data source can't support loading arbitrary requested page
+ * sizes (e.g. when network page size constraints are only known at runtime), either use
* [PageKeyedDataSource] or [ItemKeyedDataSource], or pass the initial result with the two parameter
* [LoadInitialCallback.onResult].
*
@@ -47,16 +47,11 @@
*/
@Deprecated(
message = "PositionalDataSource is deprecated and has been replaced by PagingSource",
- replaceWith = ReplaceWith(
- "PagingSource<Int, T>",
- "androidx.paging.PagingSource"
- )
+ replaceWith = ReplaceWith("PagingSource<Int, T>", "androidx.paging.PagingSource")
)
public abstract class PositionalDataSource<T : Any> : DataSource<Int, T>(POSITIONAL) {
- /**
- * Holder object for inputs to [loadInitial].
- */
+ /** Holder object for inputs to [loadInitial]. */
public open class LoadInitialParams(
/**
* Initial load position requested.
@@ -64,60 +59,46 @@
* Note that this may not be within the bounds of your data set, it may need to be adjusted
* before you execute your load.
*/
- @JvmField
- public val requestedStartPosition: Int,
+ @JvmField public val requestedStartPosition: Int,
/**
* Requested number of items to load.
*
* Note that this may be larger than available data.
*/
- @JvmField
- public val requestedLoadSize: Int,
+ @JvmField public val requestedLoadSize: Int,
/**
* Defines page size acceptable for return values.
*
* List of items passed to the callback must be an integer multiple of page size.
*/
- @JvmField
- public val pageSize: Int,
+ @JvmField public val pageSize: Int,
/**
* Defines whether placeholders are enabled, and whether the loaded total count will be
* ignored.
*/
- @JvmField
- public val placeholdersEnabled: Boolean
+ @JvmField public val placeholdersEnabled: Boolean
) {
init {
- check(requestedStartPosition >= 0) {
- "invalid start position: $requestedStartPosition"
- }
- check(requestedLoadSize >= 0) {
- "invalid load size: $requestedLoadSize"
- }
- check(pageSize >= 0) {
- "invalid page size: $pageSize"
- }
+ check(requestedStartPosition >= 0) { "invalid start position: $requestedStartPosition" }
+ check(requestedLoadSize >= 0) { "invalid load size: $requestedLoadSize" }
+ check(pageSize >= 0) { "invalid page size: $pageSize" }
}
}
- /**
- * Holder object for inputs to [loadRange].
- */
+ /** Holder object for inputs to [loadRange]. */
public open class LoadRangeParams(
/**
* START position of data to load.
*
* Returned data must start at this position.
*/
- @JvmField
- public val startPosition: Int,
+ @JvmField public val startPosition: Int,
/**
* Number of items to load.
*
* Returned data must be of this size, unless at end of the list.
*/
- @JvmField
- public val loadSize: Int
+ @JvmField public val loadSize: Int
)
/**
@@ -142,12 +123,12 @@
* [LoadInitialParams.placeholdersEnabled] is false), you can instead call [onResult].
*
* @param data List of items loaded from the [DataSource]. If this is empty, the
- * [DataSource] is treated as empty, and no further loads will occur.
+ * [DataSource] is treated as empty, and no further loads will occur.
* @param position Position of the item at the front of the list. If there are N items
- * before the items in data that can be loaded from this DataSource, pass N.
+ * before the items in data that can be loaded from this DataSource, pass N.
* @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial [data] parameter as well as any items that can be
- * loaded in front or behind of [data].
+ * Includes the number in the initial [data] parameter as well as any items that can be
+ * loaded in front or behind of [data].
*/
public abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
@@ -163,9 +144,9 @@
* [onResult].
*
* @param data List of items loaded from the [DataSource]. If this is empty, the
- * [DataSource] is treated as empty, and no further loads will occur.
+ * [DataSource] is treated as empty, and no further loads will occur.
* @param position Position of the item at the front of the list. If there are N items
- * before the items in data that can be provided by this [DataSource], pass N.
+ * before the items in data that can be provided by this [DataSource], pass N.
*/
public abstract fun onResult(data: List<T>, position: Int)
}
@@ -186,7 +167,7 @@
* Called to pass loaded data from [loadRange].
*
* @param data List of items loaded from the [DataSource]. Must be same size as requested,
- * unless at end of list.
+ * unless at end of list.
*/
public abstract fun onResult(data: List<T>)
}
@@ -228,10 +209,9 @@
* ```
*
* @param params Params passed to [loadInitial], including page size, and requested start /
- * loadSize.
+ * loadSize.
* @param totalCount Total size of the data set.
* @return Position to start loading at.
- *
* @see [computeInitialLoadSize]
*/
@JvmStatic
@@ -289,11 +269,10 @@
* ```
*
* @param params Params passed to [loadInitial], including page size, and requested start /
- * loadSize.
+ * loadSize.
* @param initialLoadPosition Value returned by [computeInitialLoadPosition]
* @param totalCount Total size of the data set.
* @return Number of items to load.
- *
* @see [computeInitialLoadPosition]
*/
@JvmStatic
@@ -313,8 +292,7 @@
if (params.placeholdersEnabled) {
// snap load size to page multiple (minimum two)
- initialLoadSize =
- maxOf(initialLoadSize / params.pageSize, 2) * params.pageSize
+ initialLoadSize = maxOf(initialLoadSize / params.pageSize, 2) * params.pageSize
// move start so the load is centered around the key, not starting at it
val idealStart = initialPosition - initialLoadSize / 2
@@ -324,12 +302,13 @@
initialPosition = maxOf(0, initialPosition - initialLoadSize / 2)
}
}
- val initParams = LoadInitialParams(
- initialPosition,
- initialLoadSize,
- params.pageSize,
- params.placeholdersEnabled
- )
+ val initParams =
+ LoadInitialParams(
+ initialPosition,
+ initialLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled
+ )
return loadInitial(initParams)
} else {
var startIndex = params.key!!
@@ -390,7 +369,8 @@
data = data,
// skip passing prevKey if nothing else to load
prevKey = if (position == 0) null else position,
- // can't do same for nextKey, since we don't know if load is terminal
+ // can't do same for nextKey, since we don't know if load is
+ // terminal
nextKey = position + data.size,
itemsBefore = position,
itemsAfter = COUNT_UNDEFINED
@@ -426,17 +406,19 @@
object : LoadRangeCallback<T>() {
override fun onResult(data: List<T>) {
// skip passing prevKey if nothing else to load. We only do this for prepend
- // direction, since 0 as first index is well defined, but max index may not be
+ // direction, since 0 as first index is well defined, but max index may not
+ // be
val prevKey = if (params.startPosition == 0) null else params.startPosition
when {
isInvalid -> cont.resume(BaseResult.empty())
- else -> cont.resume(
- BaseResult(
- data = data,
- prevKey = prevKey,
- nextKey = params.startPosition + data.size
+ else ->
+ cont.resume(
+ BaseResult(
+ data = data,
+ prevKey = prevKey,
+ nextKey = params.startPosition + data.size
+ )
)
- )
}
}
}
@@ -451,9 +433,9 @@
* LoadResult list must be a multiple of pageSize to enable efficient tiling.
*
* @param params Parameters for initial load, including requested start position, load size, and
- * page size.
+ * page size.
* @param callback Callback that receives initial load data, including position and total data
- * set size.
+ * set size.
*/
@WorkerThread
public abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/SnapshotPagedList.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/SnapshotPagedList.jvm.kt
index b70ff11..0a98586 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/SnapshotPagedList.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/SnapshotPagedList.jvm.kt
@@ -17,13 +17,14 @@
package androidx.paging
@Suppress("DEPRECATION")
-internal class SnapshotPagedList<T : Any>(private val pagedList: PagedList<T>) : PagedList<T>(
- pagedList.pagingSource,
- pagedList.coroutineScope,
- pagedList.notifyDispatcher,
- pagedList.storage.snapshot(),
- pagedList.config
-) {
+internal class SnapshotPagedList<T : Any>(private val pagedList: PagedList<T>) :
+ PagedList<T>(
+ pagedList.pagingSource,
+ pagedList.coroutineScope,
+ pagedList.notifyDispatcher,
+ pagedList.storage.snapshot(),
+ pagedList.config
+ ) {
override val isImmutable = true
override val lastKey
@@ -32,6 +33,8 @@
override val isDetached = true
override fun detach() {}
+
override fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {}
+
override fun loadAroundInternal(index: Int) {}
}
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperDataSource.jvm.kt
index 5889c63..f0d29a0 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperDataSource.jvm.kt
@@ -28,10 +28,11 @@
private val source: DataSource<Key, ValueFrom>,
private val listFunction: Function<List<ValueFrom>, List<ValueTo>>
) : DataSource<Key, ValueTo>(source.type) {
- private val keyMap = when (source.type) {
- KeyType.ITEM_KEYED -> IdentityHashMap<ValueTo, Key>()
- else -> null
- }
+ private val keyMap =
+ when (source.type) {
+ KeyType.ITEM_KEYED -> IdentityHashMap<ValueTo, Key>()
+ else -> null
+ }
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
source.addInvalidatedCallback(onInvalidatedCallback)
@@ -44,13 +45,16 @@
override val isInvalid
get() = source.isInvalid
- override fun getKeyInternal(item: ValueTo): Key = when {
- keyMap != null -> synchronized(keyMap) {
- return keyMap[item]!!
+ override fun getKeyInternal(item: ValueTo): Key =
+ when {
+ keyMap != null ->
+ synchronized(keyMap) {
+ return keyMap[item]!!
+ }
+ // positional / page-keyed
+ else ->
+ throw IllegalStateException("Cannot get key by item in non-item keyed DataSource")
}
- // positional / page-keyed
- else -> throw IllegalStateException("Cannot get key by item in non-item keyed DataSource")
- }
@SuppressWarnings("WeakerAccess") /* synthetic access */
fun stashKeysIfNeeded(source: List<ValueFrom>, dest: List<ValueTo>) {
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperItemKeyedDataSource.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperItemKeyedDataSource.jvm.kt
index a4eb776..e4c9d3e 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperItemKeyedDataSource.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/WrapperItemKeyedDataSource.jvm.kt
@@ -91,7 +91,8 @@
)
}
- override fun getKey(item: B): K = synchronized(keyMap) {
- return keyMap[item]!!
- }
+ override fun getKey(item: B): K =
+ synchronized(keyMap) {
+ return keyMap[item]!!
+ }
}
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/internal/Atomics.jvm.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/internal/Atomics.jvm.kt
index 58b53e9..62d7602 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/internal/Atomics.jvm.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/internal/Atomics.jvm.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
@file:Suppress("ACTUAL_WITHOUT_EXPECT") // https://youtrack.jetbrains.com/issue/KT-37316
+
package androidx.paging.internal
internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
index b9c6182..1f22026 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/CachedPageEventFlowLeakTest.kt
@@ -29,57 +29,47 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
-/**
- * reproduces b/203594733
- */
+/** reproduces b/203594733 */
public class CachedPageEventFlowLeakTest {
private val gcHelper = GarbageCollectionTestHelper()
- private data class Item(
- val generation: Int,
- val pagePos: Int
- )
+ private data class Item(val generation: Int, val pagePos: Int)
private var sourceGeneration = 0
- private val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- initialLoadSize = 20
- ),
- pagingSourceFactory = {
- val generation = sourceGeneration++
- object : PagingSource<Int, Item>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
- return LoadResult.Page(
- data = (0 until params.loadSize).map {
- Item(
- generation = generation,
- pagePos = it
- )
- },
- prevKey = (params.key ?: 0) - 1,
- nextKey = (params.key ?: 0) + 1
- )
- }
+ private val pager =
+ Pager(
+ config = PagingConfig(pageSize = 10, initialLoadSize = 20),
+ pagingSourceFactory = {
+ val generation = sourceGeneration++
+ object : PagingSource<Int, Item>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
+ return LoadResult.Page(
+ data =
+ (0 until params.loadSize).map {
+ Item(generation = generation, pagePos = it)
+ },
+ prevKey = (params.key ?: 0) - 1,
+ nextKey = (params.key ?: 0) + 1
+ )
+ }
- override fun getRefreshKey(state: PagingState<Int, Item>): Int? {
- return null
+ override fun getRefreshKey(state: PagingState<Int, Item>): Int? {
+ return null
+ }
}
}
- }
- )
+ )
- private val tracker = object : ActiveFlowTracker {
- override fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>) {
- gcHelper.track(cachedPageEventFlow)
- }
+ private val tracker =
+ object : ActiveFlowTracker {
+ override fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>) {
+ gcHelper.track(cachedPageEventFlow)
+ }
- override suspend fun onStart(flowType: ActiveFlowTracker.FlowType) {
- }
+ override suspend fun onStart(flowType: ActiveFlowTracker.FlowType) {}
- override suspend fun onComplete(flowType: ActiveFlowTracker.FlowType) {
+ override suspend fun onComplete(flowType: ActiveFlowTracker.FlowType) {}
}
- }
private suspend fun <T : Any> collectPages(
flow: Flow<PagingData<T>>,
@@ -97,9 +87,7 @@
// collect expected generations to generate garbage
var remaining = generationCount
flow
- .takeWhile {
- !finishCollecting || remaining > 0
- }
+ .takeWhile { !finishCollecting || remaining > 0 }
.collectLatest { pagingData ->
val willInvalidate = remaining-- > 0
if (willInvalidate) {
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
index 4713d4f..294c0ae 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
@@ -35,9 +35,7 @@
size++
}
- fun assertLiveObjects(
- vararg expected: Pair<KClass<*>, Int>
- ) {
+ fun assertLiveObjects(vararg expected: Pair<KClass<*>, Int>) {
val continueTriggeringGc = AtomicBoolean(true)
thread {
val leak: ArrayList<ByteArray> = ArrayList()
@@ -49,41 +47,38 @@
}
var collectedItemCount = 0
val expectedItemCount = size - expected.sumOf { it.second }
- while (collectedItemCount < expectedItemCount &&
- queue.remove(5.seconds.inWholeMilliseconds) != null
+ while (
+ collectedItemCount < expectedItemCount &&
+ queue.remove(5.seconds.inWholeMilliseconds) != null
) {
collectedItemCount++
}
continueTriggeringGc.set(false)
val leakedObjects = countLiveObjects()
- val leakedObjectToStrings = references.mapNotNull {
- it.get()
- }.joinToString("\n")
+ val leakedObjectToStrings = references.mapNotNull { it.get() }.joinToString("\n")
+ assertWithMessage("expected to collect $expectedItemCount, collected $collectedItemCount")
+ .that(collectedItemCount)
+ .isEqualTo(expectedItemCount)
assertWithMessage(
- "expected to collect $expectedItemCount, collected $collectedItemCount"
- ).that(collectedItemCount).isEqualTo(expectedItemCount)
- assertWithMessage(
- """
+ """
expected to collect $expectedItemCount, collected $collectedItemCount.
live objects: $leakedObjectToStrings
- """.trimIndent()
- ).that(leakedObjects).containsExactlyElementsIn(expected)
+ """
+ .trimIndent()
+ )
+ .that(leakedObjects)
+ .containsExactlyElementsIn(expected)
}
- /**
- * Tries to trigger garbage collection until an element is available in the given queue.
- */
+ /** Tries to trigger garbage collection until an element is available in the given queue. */
fun assertEverythingIsCollected() {
assertLiveObjects()
}
private fun countLiveObjects(): List<Pair<KClass<*>, Int>> {
- return references.mapNotNull {
- it.get()
- }.groupBy {
- it::class
- }.map { entry ->
- entry.key to entry.value.size
- }
+ return references
+ .mapNotNull { it.get() }
+ .groupBy { it::class }
+ .map { entry -> entry.key to entry.value.size }
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPageFetcherTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPageFetcherTest.kt
index cfed11e..f7a4151 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPageFetcherTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPageFetcherTest.kt
@@ -49,13 +49,12 @@
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
val key = params.key ?: 0
- val (start, end) = when (params) {
- is Refresh -> key to key + params.loadSize
- is LoadParams.Prepend -> key - params.loadSize to key
- is LoadParams.Append -> key to key + params.loadSize
- }.let { (start, end) ->
- start.coerceAtLeast(0) to end.coerceAtMost(data.size)
- }
+ val (start, end) =
+ when (params) {
+ is Refresh -> key to key + params.loadSize
+ is LoadParams.Prepend -> key - params.loadSize to key
+ is LoadParams.Append -> key to key + params.loadSize
+ }.let { (start, end) -> start.coerceAtLeast(0) to end.coerceAtMost(data.size) }
if (invalidData) {
invalidData = false
@@ -73,18 +72,16 @@
override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
}
- private fun rangeResult(start: Int, end: Int) = Page(
- data = data.subList(start, end),
- prevKey = if (start > 0) start else null,
- nextKey = if (end < data.size) end else null,
- itemsBefore = start,
- itemsAfter = data.size - end
- )
+ private fun rangeResult(start: Int, end: Int) =
+ Page(
+ data = data.subList(start, end),
+ prevKey = if (start > 0) start else null,
+ nextKey = if (end < data.size) end else null,
+ itemsBefore = start,
+ itemsAfter = data.size - end
+ )
- private data class Result(
- val type: LoadType,
- val pageResult: LoadResult<*, String>
- )
+ private data class Result(val type: LoadType, val pageResult: LoadResult<*, String>)
private class MockConsumer : LegacyPageFetcher.PageConsumer<String> {
private val results: MutableList<Result> = arrayListOf()
@@ -131,20 +128,17 @@
val config = Config(2, 2, true, 10, Config.MAX_SIZE_UNBOUNDED)
val pagingSource = ImmediateListDataSource(data)
- val initialResult = pagingSource.load(
- Refresh(
- key = start,
- loadSize = end - start,
- placeholdersEnabled = config.enablePlaceholders,
+ val initialResult =
+ pagingSource.load(
+ Refresh(
+ key = start,
+ loadSize = end - start,
+ placeholdersEnabled = config.enablePlaceholders,
+ )
)
- )
val initialData = (initialResult as Page).data
- val storage = PagedStorage(
- start,
- initialResult,
- data.size - initialData.size - start
- )
+ val storage = PagedStorage(start, initialResult, data.size - initialData.size - start)
consumer.storage = storage
@Suppress("UNCHECKED_CAST")
@@ -160,270 +154,218 @@
}
@Test
- fun simplePagerAppend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 2, 6)
+ fun simplePagerAppend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 2, 6)
- assertTrue(consumer.takeResults().isEmpty())
- assertTrue(consumer.takeStateChanges().isEmpty())
+ assertTrue(consumer.takeResults().isEmpty())
+ assertTrue(consumer.takeStateChanges().isEmpty())
- pager.tryScheduleAppend()
+ pager.tryScheduleAppend()
- assertTrue(consumer.takeResults().isEmpty())
- assertEquals(
- listOf(StateChange(APPEND, Loading)),
- consumer.takeStateChanges()
- )
+ assertTrue(consumer.takeResults().isEmpty())
+ assertEquals(listOf(StateChange(APPEND, Loading)), consumer.takeStateChanges())
- advanceUntilIdle()
+ advanceUntilIdle()
- assertEquals(listOf(Result(APPEND, rangeResult(6, 8))), consumer.takeResults())
- assertEquals(
- listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
- consumer.takeStateChanges()
- )
- }
+ assertEquals(listOf(Result(APPEND, rangeResult(6, 8))), consumer.takeResults())
+ assertEquals(
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = false))),
+ consumer.takeStateChanges()
+ )
+ }
@Test
- fun simplePagerPrepend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 4, 8)
+ fun simplePagerPrepend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 4, 8)
- pager.trySchedulePrepend()
+ pager.trySchedulePrepend()
- assertTrue(consumer.takeResults().isEmpty())
- assertEquals(
- listOf(StateChange(PREPEND, Loading)),
- consumer.takeStateChanges()
- )
+ assertTrue(consumer.takeResults().isEmpty())
+ assertEquals(listOf(StateChange(PREPEND, Loading)), consumer.takeStateChanges())
- advanceUntilIdle()
+ advanceUntilIdle()
- assertEquals(
- listOf(Result(PREPEND, rangeResult(2, 4))),
- consumer.takeResults()
- )
- assertEquals(
- listOf(
- StateChange(
- PREPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
- consumer.takeStateChanges()
- )
- }
+ assertEquals(listOf(Result(PREPEND, rangeResult(2, 4))), consumer.takeResults())
+ assertEquals(
+ listOf(StateChange(PREPEND, NotLoading(endOfPaginationReached = false))),
+ consumer.takeStateChanges()
+ )
+ }
@Test
- fun doubleAppend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 2, 6)
+ fun doubleAppend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 2, 6)
- pager.tryScheduleAppend()
- advanceUntilIdle()
+ pager.tryScheduleAppend()
+ advanceUntilIdle()
- assertEquals(
- listOf(
- Result(APPEND, rangeResult(6, 8))
- ),
- consumer.takeResults()
- )
+ assertEquals(listOf(Result(APPEND, rangeResult(6, 8))), consumer.takeResults())
- assertEquals(
- listOf(
- StateChange(APPEND, Loading),
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
- consumer.takeStateChanges()
- )
+ assertEquals(
+ listOf(
+ StateChange(APPEND, Loading),
+ StateChange(APPEND, NotLoading(endOfPaginationReached = false))
+ ),
+ consumer.takeStateChanges()
+ )
- pager.tryScheduleAppend()
- advanceUntilIdle()
+ pager.tryScheduleAppend()
+ advanceUntilIdle()
- assertEquals(
- listOf(
- Result(APPEND, rangeResult(8, 9))
- ),
- consumer.takeResults()
- )
+ assertEquals(listOf(Result(APPEND, rangeResult(8, 9))), consumer.takeResults())
- assertEquals(
- listOf(
- StateChange(APPEND, Loading),
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
- consumer.takeStateChanges()
- )
- }
+ assertEquals(
+ listOf(
+ StateChange(APPEND, Loading),
+ StateChange(APPEND, NotLoading(endOfPaginationReached = false))
+ ),
+ consumer.takeStateChanges()
+ )
+ }
@Test
- fun doublePrepend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 4, 8)
+ fun doublePrepend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 4, 8)
- pager.trySchedulePrepend()
- advanceUntilIdle()
+ pager.trySchedulePrepend()
+ advanceUntilIdle()
- assertEquals(
- listOf(
- Result(PREPEND, rangeResult(2, 4))
- ),
- consumer.takeResults()
- )
+ assertEquals(listOf(Result(PREPEND, rangeResult(2, 4))), consumer.takeResults())
- assertEquals(
- listOf(
- StateChange(PREPEND, Loading),
- StateChange(
- PREPEND, NotLoading(endOfPaginationReached = false)
+ assertEquals(
+ listOf(
+ StateChange(PREPEND, Loading),
+ StateChange(PREPEND, NotLoading(endOfPaginationReached = false))
+ ),
+ consumer.takeStateChanges()
+ )
+
+ pager.trySchedulePrepend()
+ advanceUntilIdle()
+
+ assertEquals(listOf(Result(PREPEND, rangeResult(0, 2))), consumer.takeResults())
+ assertEquals(
+ listOf(
+ StateChange(PREPEND, Loading),
+ StateChange(PREPEND, NotLoading(endOfPaginationReached = false))
+ ),
+ consumer.takeStateChanges()
+ )
+ }
+
+ @Test
+ fun emptyAppend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 0, 9)
+
+ pager.tryScheduleAppend()
+
+ // Pager triggers an immediate empty response here, so we don't need to flush the
+ // executor
+ assertEquals(listOf(Result(APPEND, Page.empty<Int, String>())), consumer.takeResults())
+ assertEquals(
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = true))),
+ consumer.takeStateChanges()
+ )
+ }
+
+ @Test
+ fun emptyPrepend() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 0, 9)
+
+ pager.trySchedulePrepend()
+
+ // Pager triggers an immediate empty response here, so we don't need to flush the
+ // executor
+ assertEquals(listOf(Result(PREPEND, Page.empty<Int, String>())), consumer.takeResults())
+ assertEquals(
+ listOf(StateChange(PREPEND, NotLoading(endOfPaginationReached = true))),
+ consumer.takeStateChanges()
+ )
+ }
+
+ @Test
+ fun append_invalidData() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 0, 3)
+
+ // try a normal append first
+ pager.tryScheduleAppend()
+ advanceUntilIdle()
+
+ assertThat(consumer.takeResults()).containsExactly(Result(APPEND, rangeResult(3, 5)))
+ assertThat(consumer.takeStateChanges())
+ .containsExactly(
+ StateChange(APPEND, Loading),
+ StateChange(APPEND, NotLoading.Incomplete)
)
- ),
- consumer.takeStateChanges()
- )
- pager.trySchedulePrepend()
- advanceUntilIdle()
+ // now make next append return LoadResult.Invalid
+ val pagingSource = pager.source as ImmediateListDataSource
+ pagingSource.invalidData = true
- assertEquals(
- listOf(
- Result(PREPEND, rangeResult(0, 2))
- ),
- consumer.takeResults()
- )
- assertEquals(
- listOf(
- StateChange(PREPEND, Loading),
- StateChange(
- PREPEND, NotLoading(endOfPaginationReached = false)
+ pager.tryScheduleAppend()
+ advanceUntilIdle()
+
+ // the load should return before returning any data
+ assertThat(consumer.takeResults()).isEmpty()
+ assertThat(consumer.takeStateChanges())
+ .containsExactly(
+ StateChange(APPEND, Loading),
)
- ),
- consumer.takeStateChanges()
- )
- }
+
+ // exception handler should invalidate the paging source and result in fetcher to be
+ // detached
+ assertTrue(pagingSource.invalid)
+ assertTrue(pager.isDetached)
+ }
@Test
- fun emptyAppend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 0, 9)
+ fun prepend_invalidData() =
+ runTest(testDispatcher) {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 6, 9)
- pager.tryScheduleAppend()
+ // try a normal prepend first
+ pager.trySchedulePrepend()
+ advanceUntilIdle()
- // Pager triggers an immediate empty response here, so we don't need to flush the executor
- assertEquals(
- listOf(Result(APPEND, Page.empty<Int, String>())),
- consumer.takeResults()
- )
- assertEquals(
- listOf(
- StateChange(APPEND, NotLoading(endOfPaginationReached = true))
- ),
- consumer.takeStateChanges()
- )
- }
-
- @Test
- fun emptyPrepend() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 0, 9)
-
- pager.trySchedulePrepend()
-
- // Pager triggers an immediate empty response here, so we don't need to flush the executor
- assertEquals(
- listOf(Result(PREPEND, Page.empty<Int, String>())),
- consumer.takeResults()
- )
- assertEquals(
- listOf(
- StateChange(
- PREPEND,
- NotLoading(endOfPaginationReached = true)
+ assertThat(consumer.takeResults()).containsExactly(Result(PREPEND, rangeResult(4, 6)))
+ assertThat(consumer.takeStateChanges())
+ .containsExactly(
+ StateChange(PREPEND, Loading),
+ StateChange(PREPEND, NotLoading.Incomplete)
)
- ),
- consumer.takeStateChanges()
- )
- }
- @Test
- fun append_invalidData() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 0, 3)
+ // now make next prepend throw error
+ val pagingSource = pager.source as ImmediateListDataSource
+ pagingSource.invalidData = true
- // try a normal append first
- pager.tryScheduleAppend()
- advanceUntilIdle()
+ pager.trySchedulePrepend()
+ advanceUntilIdle()
- assertThat(consumer.takeResults()).containsExactly(
- Result(APPEND, rangeResult(3, 5))
- )
- assertThat(consumer.takeStateChanges()).containsExactly(
- StateChange(APPEND, Loading),
- StateChange(APPEND, NotLoading.Incomplete)
- )
+ // the load should return before returning any data
+ assertThat(consumer.takeResults()).isEmpty()
+ assertThat(consumer.takeStateChanges())
+ .containsExactly(
+ StateChange(PREPEND, Loading),
+ )
- // now make next append return LoadResult.Invalid
- val pagingSource = pager.source as ImmediateListDataSource
- pagingSource.invalidData = true
-
- pager.tryScheduleAppend()
- advanceUntilIdle()
-
- // the load should return before returning any data
- assertThat(consumer.takeResults()).isEmpty()
- assertThat(consumer.takeStateChanges()).containsExactly(
- StateChange(APPEND, Loading),
- )
-
- // exception handler should invalidate the paging source and result in fetcher to be
- // detached
- assertTrue(pagingSource.invalid)
- assertTrue(pager.isDetached)
- }
-
- @Test
- fun prepend_invalidData() = runTest(testDispatcher) {
- val consumer = MockConsumer()
- val pager = createPager(consumer, 6, 9)
-
- // try a normal prepend first
- pager.trySchedulePrepend()
- advanceUntilIdle()
-
- assertThat(consumer.takeResults()).containsExactly(
- Result(PREPEND, rangeResult(4, 6))
- )
- assertThat(consumer.takeStateChanges()).containsExactly(
- StateChange(PREPEND, Loading),
- StateChange(PREPEND, NotLoading.Incomplete)
- )
-
- // now make next prepend throw error
- val pagingSource = pager.source as ImmediateListDataSource
- pagingSource.invalidData = true
-
- pager.trySchedulePrepend()
- advanceUntilIdle()
-
- // the load should return before returning any data
- assertThat(consumer.takeResults()).isEmpty()
- assertThat(consumer.takeStateChanges()).containsExactly(
- StateChange(PREPEND, Loading),
- )
-
- // exception handler should invalidate the paging source and result in fetcher to be
- // detached
- assertTrue(pagingSource.invalid)
- assertTrue(pager.isDetached)
- }
+ // exception handler should invalidate the paging source and result in fetcher to be
+ // detached
+ assertTrue(pagingSource.invalid)
+ assertTrue(pager.isDetached)
+ }
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPagingSourceTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPagingSourceTest.kt
index e359ca2..2a822a0 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPagingSourceTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/LegacyPagingSourceTest.kt
@@ -37,45 +37,42 @@
@OptIn(ExperimentalCoroutinesApi::class)
class LegacyPagingSourceTest {
- private val fakePagingState = PagingState(
- pages = listOf(
- Page<Int, String>(
- data = listOf("fakeData"),
- prevKey = null,
- nextKey = null
- )
- ),
- anchorPosition = 0,
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1
- ),
- leadingPlaceholderCount = 0
- )
+ private val fakePagingState =
+ PagingState(
+ pages =
+ listOf(
+ Page<Int, String>(data = listOf("fakeData"), prevKey = null, nextKey = null)
+ ),
+ anchorPosition = 0,
+ config = PagingConfig(pageSize = 1, prefetchDistance = 1),
+ leadingPlaceholderCount = 0
+ )
@Test
fun init_invalidDataSource() {
val testContext = EmptyCoroutineContext
- val dataSource = object : DataSource<Int, Int>(KeyType.ITEM_KEYED) {
- var isInvalidCalls = 0
+ val dataSource =
+ object : DataSource<Int, Int>(KeyType.ITEM_KEYED) {
+ var isInvalidCalls = 0
- override val isInvalid: Boolean
- get() {
- isInvalidCalls++
- return true
+ override val isInvalid: Boolean
+ get() {
+ isInvalidCalls++
+ return true
+ }
+
+ override suspend fun load(params: Params<Int>): BaseResult<Int> {
+ return BaseResult(listOf(), null, null)
}
- override suspend fun load(params: Params<Int>): BaseResult<Int> {
- return BaseResult(listOf(), null, null)
+ override fun getKeyInternal(item: Int): Int = 0
}
- override fun getKeyInternal(item: Int): Int = 0
- }
-
- val pagingSource = LegacyPagingSource(
- fetchContext = testContext,
- dataSource = dataSource,
- )
+ val pagingSource =
+ LegacyPagingSource(
+ fetchContext = testContext,
+ dataSource = dataSource,
+ )
assertEquals(1, dataSource.isInvalidCalls)
assertThat(pagingSource.invalid).isTrue()
@@ -85,34 +82,26 @@
@Test
fun item() {
@Suppress("DEPRECATION")
- val dataSource = object : ItemKeyedDataSource<Int, String>() {
- override fun loadInitial(
- params: LoadInitialParams<Int>,
- callback: LoadInitialCallback<String>
- ) {
- fail("loadInitial not expected")
- }
+ val dataSource =
+ object : ItemKeyedDataSource<Int, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<Int>,
+ callback: LoadInitialCallback<String>
+ ) {
+ fail("loadInitial not expected")
+ }
- override fun loadAfter(
- params: LoadParams<Int>,
- callback: LoadCallback<String>
- ) {
- fail("loadAfter not expected")
- }
+ override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<String>) {
+ fail("loadAfter not expected")
+ }
- override fun loadBefore(
- params: LoadParams<Int>,
- callback: LoadCallback<String>
- ) {
- fail("loadBefore not expected")
- }
+ override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<String>) {
+ fail("loadBefore not expected")
+ }
- override fun getKey(item: String) = item.hashCode()
- }
- val pagingSource = LegacyPagingSource(
- fetchContext = Dispatchers.Unconfined,
- dataSource
- )
+ override fun getKey(item: String) = item.hashCode()
+ }
+ val pagingSource = LegacyPagingSource(fetchContext = Dispatchers.Unconfined, dataSource)
// Check that jumpingSupported is disabled.
assertFalse { pagingSource.jumpingSupported }
@@ -133,32 +122,31 @@
@Test
fun page() {
@Suppress("DEPRECATION")
- val dataSource = object : PageKeyedDataSource<Int, String>() {
- override fun loadInitial(
- params: LoadInitialParams<Int>,
- callback: LoadInitialCallback<Int, String>
- ) {
- fail("loadInitial not expected")
- }
+ val dataSource =
+ object : PageKeyedDataSource<Int, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<Int>,
+ callback: LoadInitialCallback<Int, String>
+ ) {
+ fail("loadInitial not expected")
+ }
- override fun loadBefore(
- params: LoadParams<Int>,
- callback: LoadCallback<Int, String>
- ) {
- fail("loadBefore not expected")
- }
+ override fun loadBefore(
+ params: LoadParams<Int>,
+ callback: LoadCallback<Int, String>
+ ) {
+ fail("loadBefore not expected")
+ }
- override fun loadAfter(
- params: LoadParams<Int>,
- callback: LoadCallback<Int, String>
- ) {
- fail("loadAfter not expected")
+ override fun loadAfter(
+ params: LoadParams<Int>,
+ callback: LoadCallback<Int, String>
+ ) {
+ fail("loadAfter not expected")
+ }
}
- }
- val pagingSource = LegacyPagingSource(
- fetchContext = Dispatchers.Unconfined,
- dataSource = dataSource
- )
+ val pagingSource =
+ LegacyPagingSource(fetchContext = Dispatchers.Unconfined, dataSource = dataSource)
// Check that jumpingSupported is disabled.
assertFalse { pagingSource.jumpingSupported }
@@ -178,10 +166,11 @@
@Test
fun positional() {
- val pagingSource = LegacyPagingSource(
- fetchContext = Dispatchers.Unconfined,
- dataSource = createTestPositionalDataSource()
- )
+ val pagingSource =
+ LegacyPagingSource(
+ fetchContext = Dispatchers.Unconfined,
+ dataSource = createTestPositionalDataSource()
+ )
// Check that jumpingSupported is enabled.
assertTrue { pagingSource.jumpingSupported }
@@ -190,18 +179,9 @@
4,
pagingSource.getRefreshKey(
PagingState(
- pages = listOf(
- Page(
- data = listOf("fakeData"),
- prevKey = 4,
- nextKey = 5
- )
- ),
+ pages = listOf(Page(data = listOf("fakeData"), prevKey = 4, nextKey = 5)),
anchorPosition = 0,
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1
- ),
+ config = PagingConfig(pageSize = 1, prefetchDistance = 1),
leadingPlaceholderCount = 0
)
)
@@ -211,18 +191,9 @@
6,
pagingSource.getRefreshKey(
PagingState(
- pages = listOf(
- Page(
- data = listOf("fakeData"),
- prevKey = 4,
- nextKey = 5
- )
- ),
+ pages = listOf(Page(data = listOf("fakeData"), prevKey = 4, nextKey = 5)),
anchorPosition = 2,
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1
- ),
+ config = PagingConfig(pageSize = 1, prefetchDistance = 1),
leadingPlaceholderCount = 0
)
)
@@ -231,16 +202,15 @@
@Test
fun invalidateFromPagingSource() {
- val pagingSource = LegacyPagingSource(
- fetchContext = Dispatchers.Unconfined,
- dataSource = createTestPositionalDataSource()
- )
+ val pagingSource =
+ LegacyPagingSource(
+ fetchContext = Dispatchers.Unconfined,
+ dataSource = createTestPositionalDataSource()
+ )
val dataSource = pagingSource.dataSource
var kotlinInvalidated = false
- dataSource.addInvalidatedCallback {
- kotlinInvalidated = true
- }
+ dataSource.addInvalidatedCallback { kotlinInvalidated = true }
var javaInvalidated = false
dataSource.addInvalidatedCallback { javaInvalidated = true }
@@ -257,22 +227,23 @@
@Test
fun invalidateFromDataSource() {
- val pagingSource = LegacyPagingSource(
- fetchContext = Dispatchers.Unconfined,
- dataSource = createTestPositionalDataSource()
- )
+ val pagingSource =
+ LegacyPagingSource(
+ fetchContext = Dispatchers.Unconfined,
+ dataSource = createTestPositionalDataSource()
+ )
val dataSource = pagingSource.dataSource
var kotlinInvalidated = false
- dataSource.addInvalidatedCallback {
- kotlinInvalidated = true
- }
+ dataSource.addInvalidatedCallback { kotlinInvalidated = true }
var javaInvalidated = false
- dataSource.addInvalidatedCallback(object : DataSource.InvalidatedCallback {
- override fun onInvalidated() {
- javaInvalidated = true
+ dataSource.addInvalidatedCallback(
+ object : DataSource.InvalidatedCallback {
+ override fun onInvalidated() {
+ javaInvalidated = true
+ }
}
- })
+ )
assertFalse { pagingSource.invalid }
assertFalse { dataSource.isInvalid }
@@ -290,38 +261,35 @@
fun createDataSourceOnFetchDispatcher() = runTest {
val methodCalls = mutableMapOf<String, MutableList<Thread>>()
- val dataSourceFactory = object : DataSource.Factory<Int, String>() {
- override fun create(): DataSource<Int, String> {
- return ThreadCapturingDataSource { methodName ->
- methodCalls.getOrPut(methodName) {
- mutableListOf()
- }.add(Thread.currentThread())
+ val dataSourceFactory =
+ object : DataSource.Factory<Int, String>() {
+ override fun create(): DataSource<Int, String> {
+ return ThreadCapturingDataSource { methodName ->
+ methodCalls
+ .getOrPut(methodName) { mutableListOf() }
+ .add(Thread.currentThread())
+ }
}
}
- }
// create an executor special to the legacy data source
val executor = Executors.newSingleThreadExecutor()
// extract the thread instance from the executor. we'll use it to assert calls later
var dataSourceThread: Thread? = null
- executor.submit {
- dataSourceThread = Thread.currentThread()
- }.get()
+ executor.submit { dataSourceThread = Thread.currentThread() }.get()
- val pager = Pager(
- config = PagingConfig(10, enablePlaceholders = false),
- pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(
- executor.asCoroutineDispatcher()
+ val pager =
+ Pager(
+ config = PagingConfig(10, enablePlaceholders = false),
+ pagingSourceFactory =
+ dataSourceFactory.asPagingSourceFactory(executor.asCoroutineDispatcher())
)
- )
// collect from pager. we take only 2 paging data generations and only take 1 PageEvent
// from them
pager.flow.take(2).collectLatest { pagingData ->
// wait until first insert happens
- pagingData.flow.filter {
- it is PageEvent.Insert
- }.first()
+ pagingData.flow.filter { it is PageEvent.Insert }.first()
pagingData.uiReceiver.refresh()
}
// validate method calls (to ensure test did run as expected) and their threads.
@@ -338,28 +306,29 @@
@Test
fun dataSourceInvalidateBeforePagingSourceInvalidateCallbackAdded() {
- val dataSourceFactory = object : DataSource.Factory<Int, String>() {
- val dataSources = mutableListOf<DataSource<Int, String>>()
- var i = 0
+ val dataSourceFactory =
+ object : DataSource.Factory<Int, String>() {
+ val dataSources = mutableListOf<DataSource<Int, String>>()
+ var i = 0
- override fun create(): DataSource<Int, String> {
- return when (i++) {
- 0 -> createTestPositionalDataSource().apply {
- // Invalidate before we give LegacyPagingSource a chance to register
- // invalidate callback.
- invalidate()
- }
- else -> createTestPositionalDataSource()
- }.also { dataSources.add(it) }
+ override fun create(): DataSource<Int, String> {
+ return when (i++) {
+ 0 ->
+ createTestPositionalDataSource().apply {
+ // Invalidate before we give LegacyPagingSource a chance to register
+ // invalidate callback.
+ invalidate()
+ }
+ else -> createTestPositionalDataSource()
+ }.also { dataSources.add(it) }
+ }
}
- }
val testDispatcher = StandardTestDispatcher()
- val pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(
- fetchDispatcher = testDispatcher
- ).let {
- { it() as LegacyPagingSource }
- }
+ val pagingSourceFactory =
+ dataSourceFactory.asPagingSourceFactory(fetchDispatcher = testDispatcher).let {
+ { it() as LegacyPagingSource }
+ }
val pagingSource0 = pagingSourceFactory()
testDispatcher.scheduler.advanceUntilIdle()
@@ -397,21 +366,15 @@
}
}
- /**
- * A data source implementation which tracks method calls and their threads.
- */
+ /** A data source implementation which tracks method calls and their threads. */
@Suppress("DEPRECATION")
- class ThreadCapturingDataSource(
- private val recordMethodCall: (methodName: String) -> Unit
- ) : PositionalDataSource<String>() {
+ class ThreadCapturingDataSource(private val recordMethodCall: (methodName: String) -> Unit) :
+ PositionalDataSource<String>() {
init {
recordMethodCall("<init>")
}
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<String>) {
recordMethodCall("loadInitial")
callback.onResult(
data = emptyList(),
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index 3988d0c..7a820eb 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -71,1301 +71,37 @@
private val testScope = TestScope(UnconfinedTestDispatcher())
private val retryBus = ConflatedEventBus<Unit>()
private val pagingSourceFactory = suspend {
- TestPagingSource(loadDelay = 1000).also {
- currentPagingSource = it
- }
+ TestPagingSource(loadDelay = 1000).also { currentPagingSource = it }
}
private var currentPagingSource: TestPagingSource? = null
- private val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3
- )
+ private val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3
+ )
private val EXCEPTION = Exception()
@Test
- fun loadStates_prependDone() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 1, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(1..2)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(
- pageOffset = -1,
- range = 0..0,
- startState = NotLoading.Complete
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun loadStates_prependDoneThenDrop() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 1, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(1..2)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(
- pageOffset = -1,
- range = 0..0,
- startState = NotLoading.Complete
- )
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 2,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- appendLocal = Loading,
- prependLocal = NotLoading.Complete
- ),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 1
- ),
- createAppend(
- pageOffset = 1,
- range = 3..3,
- startState = NotLoading.Incomplete,
- endState = NotLoading.Incomplete
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun loadStates_appendDone() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 97, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 97..98)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 99..99, endState = NotLoading.Complete)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun loadStates_appendDoneThenDrop() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 97, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 97..98)
-
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(
- pageOffset = 1,
- range = 99..99,
- startState = NotLoading.Incomplete,
- endState = NotLoading.Complete
- )
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 1
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = NotLoading.Complete
- ),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 1
- ),
- createPrepend(
- pageOffset = -1,
- range = 96..96,
- startState = NotLoading.Incomplete,
- endState = NotLoading.Incomplete
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun loadStates_refreshStart() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 0, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(
- range = 0..1,
- startState = NotLoading.Complete,
- endState = NotLoading.Incomplete
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun loadStates_refreshEnd() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 98, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(
- range = 98..99,
- startState = NotLoading.Incomplete,
- endState = NotLoading.Complete
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun initialize() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun initialize_bufferedHint() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
-
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51),
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 49..49)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun prepend() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 49..49)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun prependAndDrop() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 4
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ fun loadStates_prependDone() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 1, config)
val fetcherState = collectFetcherState(pageFetcher)
advanceUntilIdle()
- // Make sure the job didn't complete exceptionally
- assertFalse { fetcherState.job.isCancelled }
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertFalse { fetcherState.job.isCancelled }
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 48..49)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = -1,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 3,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertFalse { fetcherState.job.isCancelled }
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 0,
- maxPageOffset = 0,
- placeholdersRemaining = 50
- ),
- createPrepend(pageOffset = -2, range = 46..47)
- )
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun prependAndSkipDrop_prefetchWindow() = testScope.runTest {
- withContext(coroutineContext) {
- val pageFetcher = PageFetcher(
- pagingSourceFactory = pagingSourceFactory,
- initialKey = 50,
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 5,
- maxSize = 5
- )
- )
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..54)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(
- pageOffset = -1,
- range = 49..49,
- startState = Loading
- ),
- createPrepend(pageOffset = -2, range = 48..48)
- )
-
- // Make sure the job didn't complete exceptionally
- assertFalse { fetcherState.job.isCancelled }
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun prependAndDropWithCancellation() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 4
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 48..49)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = -1,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 3,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- // Start hint processing until load starts, but hasn't finished.
- advanceTimeBy(500)
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 3,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Loading
- ),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 0,
- maxPageOffset = 0,
- placeholdersRemaining = 50
- ),
- createPrepend(pageOffset = -2, range = 46..47)
- )
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun prependMultiplePages() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..52)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
- createPrepend(pageOffset = -2, range = 48..48)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun prepend_viewportHintPrioritizesGenerationId() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..52)
- )
-
- // PREPEND a few pages.
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
- createPrepend(pageOffset = -2, range = 48..48)
- )
-
- // APPEND a few pages causing PREPEND pages to drop
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 4,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -2,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = -2,
- maxPageOffset = -2,
- placeholdersRemaining = 49
- ),
- createAppend(pageOffset = 1, range = 53..53, endState = Loading),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 50
- ),
- createAppend(pageOffset = 2, range = 54..54)
- )
-
- // PREPEND a page, this hint would normally be ignored, but has a newer generationId.
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 3,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 2
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 2,
- maxPageOffset = 2,
- placeholdersRemaining = 46
- ),
- createPrepend(pageOffset = -1, range = 49..49)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun rapidViewportHints() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 10,
- prefetchDistance = 5,
- enablePlaceholders = true,
- initialLoadSize = 10,
- maxSize = 100
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 0, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(0..9, startState = NotLoading.Complete)
- )
- withContext(coroutineContext) {
- val receiver = fetcherState.pagingDataList[0].hintReceiver
- // send a bunch of access hints while collection is paused
- (0..9).forEach { pos ->
- receiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = pos,
- presentedItemsBefore = pos,
- presentedItemsAfter = 9 - pos,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- }
- }
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- appendLocal = Loading,
- prependLocal = NotLoading.Complete
- ),
- createAppend(
- pageOffset = 1,
- range = 10..19,
- startState = NotLoading.Complete,
- endState = NotLoading.Incomplete
- ),
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun append() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(1, 52..52)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun appendMultiplePages() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..52)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 2,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(
- pageOffset = 1,
- range = 53..53,
- startState = NotLoading.Incomplete,
- endState = Loading
- ),
- createAppend(2, 54..54)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun appendAndDrop() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 4
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..53)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 1,
- indexInPage = 1,
- presentedItemsBefore = 3,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 1
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = 0,
- maxPageOffset = 0,
- placeholdersRemaining = 52
- ),
- createAppend(pageOffset = 2, range = 54..55)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun appendAndSkipDrop_prefetchWindow() = testScope.runTest {
- withContext(coroutineContext) {
- val pageFetcher = PageFetcher(
- pagingSourceFactory = pagingSourceFactory,
- initialKey = 50,
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 5,
- maxSize = 5
- )
- )
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..54)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 4,
- presentedItemsBefore = 4,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(
- pageOffset = 1,
- range = 55..55,
- endState = Loading
- ),
- createAppend(pageOffset = 2, range = 56..56)
- )
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun appendAndDropWithCancellation() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 4
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..53)
- )
-
- // Start hint processing until load starts, but hasn't finished.
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 1,
- indexInPage = 1,
- presentedItemsBefore = 3,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 1
- )
- )
- advanceTimeBy(500)
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 3,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 1
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(
- appendLocal = Loading,
- prependLocal = Loading
- ),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = 0,
- maxPageOffset = 0,
- placeholdersRemaining = 52
- ),
- createAppend(
- pageOffset = 2,
- range = 54..55,
- startState = NotLoading.Incomplete,
- endState = NotLoading.Incomplete
- )
- )
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun append_viewportHintPrioritizesGenerationId() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..52)
- )
-
- // APPEND a few pages.
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 2,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 53..53, endState = Loading),
- createAppend(pageOffset = 2, range = 54..54)
- )
-
- // PREPEND a few pages causing APPEND pages to drop
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 2
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 2,
- maxPageOffset = 2,
- placeholdersRemaining = 46
- ),
- createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
- Drop<Int>(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 47
- ),
- createPrepend(pageOffset = -2, range = 48..48)
- )
-
- // APPEND a page, this hint would normally be ignored, but has a newer generationId.
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 3,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = -2,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- Drop<Int>(
- loadType = PREPEND,
- minPageOffset = -2,
- maxPageOffset = -2,
- placeholdersRemaining = 49
- ),
- createAppend(pageOffset = 1, range = 53..53)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun invalidateNoScroll() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
-
- pageFetcher.refresh()
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(
- range = 0..1,
- startState = NotLoading.Complete,
- )
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun invalidateAfterScroll() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(1, 52..52)
- )
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(51..52)
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun close_cancelsCollectionBeforeInitialLoad() = testScope.runTest {
- // Infinitely suspending PagingSource which never finishes loading anything.
- val pagingSource = object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- delay(2000)
- fail("Should never get here")
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { _, job ->
-
- // Start the initial load, but do not let it finish.
- advanceTimeBy(500)
-
- // Close pager, then advance time by enough to allow initial load to finish.
- pager.close()
- advanceTimeBy(1500)
-
- assertTrue { !job.isActive }
- }
- }
-
- @Test
- fun retry() = testScope.runTest {
- withContext(coroutineContext) {
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, _ ->
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
+ createRefresh(1..2)
)
- pageSource.errorNextLoad = true
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
- )
-
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..52)
- )
- }
- }
- }
-
- @Test
- fun retryNothing() = testScope.runTest {
- withContext(coroutineContext) {
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, _ ->
-
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..52)
- )
- retryBus.send(Unit)
- advanceUntilIdle()
- assertTrue { state.newEvents().isEmpty() }
- }
- }
- }
-
- @Test
- fun retryTwice() = testScope.runTest {
- withContext(coroutineContext) {
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, _ ->
-
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
- pageSource.errorNextLoad = true
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
- )
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..52)
- )
- retryBus.send(Unit)
- advanceUntilIdle()
- assertTrue { state.newEvents().isEmpty() }
- }
- }
- }
-
- @Test
- fun retryBothDirections() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 4
- )
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, _ ->
- // Initial REFRESH
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
-
- // Failed APPEND
- pageSource.errorNextLoad = true
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
- )
-
- // Failed PREPEND
- pageSource.errorNextLoad = true
- pager.accessHint(
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
ViewportHint.Access(
pageOffset = 0,
indexInPage = 0,
@@ -1375,118 +111,260 @@
originalPageOffsetLast = 0
)
)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Error(LOAD_ERROR)
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Error(LOAD_ERROR),
- appendLocal = Error(LOAD_ERROR)
- ),
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 0..0, startState = NotLoading.Complete)
)
- // Retry should trigger in both directions.
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun loadStates_prependDoneThenDrop() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 1, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(1..2)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 0..0, startState = NotLoading.Complete)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Error(LOAD_ERROR),
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
appendLocal = Loading,
+ prependLocal = NotLoading.Complete
+ ),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 1
+ ),
+ createAppend(
+ pageOffset = 1,
+ range = 3..3,
+ startState = NotLoading.Incomplete,
+ endState = NotLoading.Incomplete
+ )
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun loadStates_appendDone() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 97, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 97..98)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 99..99, endState = NotLoading.Complete)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun loadStates_appendDoneThenDrop() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 97, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 97..98)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(
+ pageOffset = 1,
+ range = 99..99,
+ startState = NotLoading.Incomplete,
+ endState = NotLoading.Complete
+ )
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 1
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = NotLoading.Complete
+ ),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 1
),
createPrepend(
pageOffset = -1,
- range = 49..49,
+ range = 96..96,
startState = NotLoading.Incomplete,
- endState = Loading
- ),
- createAppend(pageOffset = 1, range = 52..52)
+ endState = NotLoading.Incomplete
+ )
)
- }
+
+ fetcherState.job.cancel()
}
- }
@Test
- fun retry_errorDoesNotEnableHints() = testScope.runTest {
- withContext(StandardTestDispatcher(testScheduler)) {
- val pageSource = object : PagingSource<Int, Int>() {
- var nextResult: LoadResult<Int, Int>? = null
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- val result = nextResult
- nextResult = null
- return result ?: LoadResult.Error(LOAD_ERROR)
- }
+ fun loadStates_refreshStart() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 0, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+ advanceUntilIdle()
- collectSnapshotData(pager) { pageEvents, _ ->
- // Successful REFRESH
- pageSource.nextResult = Page(
- data = listOf(0, 1),
- prevKey = -1,
- nextKey = 1,
- itemsBefore = 50,
- itemsAfter = 48
- )
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).containsExactly(
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
localLoadStateUpdate<Int>(refreshLocal = Loading),
- localRefresh(
- pages = listOf(TransformablePage(listOf(0, 1))),
- placeholdersBefore = 50,
- placeholdersAfter = 48,
+ createRefresh(
+ range = 0..1,
+ startState = NotLoading.Complete,
+ endState = NotLoading.Incomplete
)
)
- // Hint to trigger APPEND
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun loadStates_refreshEnd() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 98, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(
+ range = 98..99,
+ startState = NotLoading.Incomplete,
+ endState = NotLoading.Complete
)
)
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun initialize() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
)
- // Retry failed APPEND
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
- )
+ fetcherState.job.cancel()
+ }
- // This hint should be ignored even though in the non-error state it would
- // re-emit for APPEND due to greater presenterIndex value.
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 2,
- presentedItemsAfter = -1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).isEmpty()
+ @Test
+ fun initialize_bufferedHint() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- // Hint to trigger PREPEND
- pager.accessHint(
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
ViewportHint.Access(
pageOffset = 0,
indexInPage = 0,
@@ -1496,151 +374,1431 @@
originalPageOffsetLast = 0
)
)
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Error(LOAD_ERROR),
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Error(LOAD_ERROR),
- appendLocal = Error(LOAD_ERROR),
- ),
+ advanceUntilIdle()
+
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51),
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49)
)
- // Retry failed hints, both PREPEND and APPEND should trigger.
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Error(LOAD_ERROR),
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Loading,
- appendLocal = Loading
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Error(LOAD_ERROR),
- appendLocal = Loading,
- ),
- localLoadStateUpdate<Int>(
- prependLocal = Error(LOAD_ERROR),
- appendLocal = Error(LOAD_ERROR),
- ),
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun prepend() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
)
- // This hint should be ignored even though in the non-error state it would
- // re-emit for PREPEND due to smaller presenterIndex value.
- pager.accessHint(
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
ViewportHint.Access(
pageOffset = 0,
- indexInPage = -1,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun prependAndDrop() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 4
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ // Make sure the job didn't complete exceptionally
+ assertFalse { fetcherState.job.isCancelled }
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertFalse { fetcherState.job.isCancelled }
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 48..49)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = -1,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 3,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertFalse { fetcherState.job.isCancelled }
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 0,
+ maxPageOffset = 0,
+ placeholdersRemaining = 50
+ ),
+ createPrepend(pageOffset = -2, range = 46..47)
+ )
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun prependAndSkipDrop_prefetchWindow() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = pagingSourceFactory,
+ initialKey = 50,
+ config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 5,
+ maxSize = 5
+ )
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..54)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
+ createPrepend(pageOffset = -2, range = 48..48)
+ )
+
+ // Make sure the job didn't complete exceptionally
+ assertFalse { fetcherState.job.isCancelled }
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun prependAndDropWithCancellation() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 4
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 48..49)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = -1,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 3,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ // Start hint processing until load starts, but hasn't finished.
+ advanceTimeBy(500)
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ localLoadStateUpdate<Int>(prependLocal = Loading, appendLocal = Loading),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 0,
+ maxPageOffset = 0,
+ placeholdersRemaining = 50
+ ),
+ createPrepend(pageOffset = -2, range = 46..47)
+ )
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun prependMultiplePages() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..52)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
presentedItemsBefore = 0,
presentedItemsAfter = 2,
originalPageOffsetFirst = 0,
originalPageOffsetLast = 0
)
)
- advanceUntilIdle()
- assertThat(pageEvents.newEvents()).isEmpty()
- }
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
+ createPrepend(pageOffset = -2, range = 48..48)
+ )
- testScope.advanceUntilIdle()
+ fetcherState.job.cancel()
}
- }
@Test
- fun retryRefresh() = testScope.runTest {
- withContext(coroutineContext) {
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+ fun prepend_viewportHintPrioritizesGenerationId() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- collectSnapshotData(pager) { state, _ ->
-
- pageSource.errorNextLoad = true
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
localLoadStateUpdate<Int>(refreshLocal = Loading),
- localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
+ createRefresh(range = 50..52)
)
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
- }
- }
- }
-
- @Test
- fun retryRefreshWithBufferedHint() = testScope.runTest {
- withContext(coroutineContext) {
- val pageSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
- collectSnapshotData(pager) { state, _ ->
- pageSource.errorNextLoad = true
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
- )
- pager.accessHint(
+ // PREPEND a few pages.
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
ViewportHint.Access(
pageOffset = 0,
indexInPage = 0,
presentedItemsBefore = 0,
- presentedItemsAfter = 1,
+ presentedItemsAfter = 2,
originalPageOffsetFirst = 0,
originalPageOffsetLast = 0
)
)
- advanceUntilIdle()
- assertTrue { state.newEvents().isEmpty() }
-
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51),
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
+ createPrepend(pageOffset = -2, range = 48..48)
+ )
+
+ // APPEND a few pages causing PREPEND pages to drop
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 4,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -2,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = -2,
+ maxPageOffset = -2,
+ placeholdersRemaining = 49
+ ),
+ createAppend(pageOffset = 1, range = 53..53, endState = Loading),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 50
+ ),
+ createAppend(pageOffset = 2, range = 54..54)
+ )
+
+ // PREPEND a page, this hint would normally be ignored, but has a newer generationId.
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 3,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 2
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 2,
+ maxPageOffset = 2,
+ placeholdersRemaining = 46
+ ),
createPrepend(pageOffset = -1, range = 49..49)
)
- }
+
+ fetcherState.job.cancel()
}
- }
@Test
- fun retry_remotePrepend() = runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
+ fun rapidViewportHints() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 10,
+ prefetchDistance = 5,
+ enablePlaceholders = true,
+ initialLoadSize = 10,
+ maxSize = 100
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 0, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- return if (loadType == PREPEND) {
- MediatorResult.Error(EXCEPTION)
- } else {
- MediatorResult.Success(endOfPaginationReached = true)
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(0..9, startState = NotLoading.Complete)
+ )
+ withContext(coroutineContext) {
+ val receiver = fetcherState.pagingDataList[0].hintReceiver
+ // send a bunch of access hints while collection is paused
+ (0..9).forEach { pos ->
+ receiver.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = pos,
+ presentedItemsBefore = pos,
+ presentedItemsAfter = 9 - pos,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ }
+ }
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ appendLocal = Loading,
+ prependLocal = NotLoading.Complete
+ ),
+ createAppend(
+ pageOffset = 1,
+ range = 10..19,
+ startState = NotLoading.Complete,
+ endState = NotLoading.Incomplete
+ ),
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun append() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(1, 52..52)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun appendMultiplePages() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..52)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(
+ pageOffset = 1,
+ range = 53..53,
+ startState = NotLoading.Incomplete,
+ endState = Loading
+ ),
+ createAppend(2, 54..54)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun appendAndDrop() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 4
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..53)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 1,
+ indexInPage = 1,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 1
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = 0,
+ maxPageOffset = 0,
+ placeholdersRemaining = 52
+ ),
+ createAppend(pageOffset = 2, range = 54..55)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun appendAndSkipDrop_prefetchWindow() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = pagingSourceFactory,
+ initialKey = 50,
+ config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 5,
+ maxSize = 5
+ )
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..54)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 4,
+ presentedItemsBefore = 4,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 55..55, endState = Loading),
+ createAppend(pageOffset = 2, range = 56..56)
+ )
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun appendAndDropWithCancellation() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 4
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..53)
+ )
+
+ // Start hint processing until load starts, but hasn't finished.
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 1,
+ indexInPage = 1,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 1
+ )
+ )
+ advanceTimeBy(500)
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 3,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 1
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Loading, prependLocal = Loading),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = 0,
+ maxPageOffset = 0,
+ placeholdersRemaining = 52
+ ),
+ createAppend(
+ pageOffset = 2,
+ range = 54..55,
+ startState = NotLoading.Incomplete,
+ endState = NotLoading.Incomplete
+ )
+ )
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun append_viewportHintPrioritizesGenerationId() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..52)
+ )
+
+ // APPEND a few pages.
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 53..53, endState = Loading),
+ createAppend(pageOffset = 2, range = 54..54)
+ )
+
+ // PREPEND a few pages causing APPEND pages to drop
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 2
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 2,
+ maxPageOffset = 2,
+ placeholdersRemaining = 46
+ ),
+ createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
+ Drop<Int>(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 47
+ ),
+ createPrepend(pageOffset = -2, range = 48..48)
+ )
+
+ // APPEND a page, this hint would normally be ignored, but has a newer generationId.
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = -2,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ Drop<Int>(
+ loadType = PREPEND,
+ minPageOffset = -2,
+ maxPageOffset = -2,
+ placeholdersRemaining = 49
+ ),
+ createAppend(pageOffset = 1, range = 53..53)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun invalidateNoScroll() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(
+ range = 0..1,
+ startState = NotLoading.Complete,
+ )
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun invalidateAfterScroll() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(1, 52..52)
+ )
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(51..52)
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun close_cancelsCollectionBeforeInitialLoad() =
+ testScope.runTest {
+ // Infinitely suspending PagingSource which never finishes loading anything.
+ val pagingSource =
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ delay(2000)
+ fail("Should never get here")
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { _, job ->
+
+ // Start the initial load, but do not let it finish.
+ advanceTimeBy(500)
+
+ // Close pager, then advance time by enough to allow initial load to finish.
+ pager.close()
+ advanceTimeBy(1500)
+
+ assertTrue { !job.isActive }
+ }
+ }
+
+ @Test
+ fun retry() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, _ ->
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ pageSource.errorNextLoad = true
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+ )
+
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..52)
+ )
}
}
}
+ @Test
+ fun retryNothing() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, _ ->
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..52)
+ )
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertTrue { state.newEvents().isEmpty() }
+ }
+ }
+ }
+
+ @Test
+ fun retryTwice() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, _ ->
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+ pageSource.errorNextLoad = true
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+ )
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..52)
+ )
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertTrue { state.newEvents().isEmpty() }
+ }
+ }
+ }
+
+ @Test
+ fun retryBothDirections() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 4
+ )
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, _ ->
+ // Initial REFRESH
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+
+ // Failed APPEND
+ pageSource.errorNextLoad = true
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+ )
+
+ // Failed PREPEND
+ pageSource.errorNextLoad = true
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Error(LOAD_ERROR)
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Error(LOAD_ERROR),
+ appendLocal = Error(LOAD_ERROR)
+ ),
+ )
+
+ // Retry should trigger in both directions.
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Error(LOAD_ERROR),
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Loading,
+ ),
+ createPrepend(
+ pageOffset = -1,
+ range = 49..49,
+ startState = NotLoading.Incomplete,
+ endState = Loading
+ ),
+ createAppend(pageOffset = 1, range = 52..52)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun retry_errorDoesNotEnableHints() =
+ testScope.runTest {
+ withContext(StandardTestDispatcher(testScheduler)) {
+ val pageSource =
+ object : PagingSource<Int, Int>() {
+ var nextResult: LoadResult<Int, Int>? = null
+
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ val result = nextResult
+ nextResult = null
+ return result ?: LoadResult.Error(LOAD_ERROR)
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { pageEvents, _ ->
+ // Successful REFRESH
+ pageSource.nextResult =
+ Page(
+ data = listOf(0, 1),
+ prevKey = -1,
+ nextKey = 1,
+ itemsBefore = 50,
+ itemsAfter = 48
+ )
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localRefresh(
+ pages = listOf(TransformablePage(listOf(0, 1))),
+ placeholdersBefore = 50,
+ placeholdersAfter = 48,
+ )
+ )
+
+ // Hint to trigger APPEND
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+ )
+
+ // Retry failed APPEND
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Error(LOAD_ERROR)),
+ )
+
+ // This hint should be ignored even though in the non-error state it would
+ // re-emit for APPEND due to greater presenterIndex value.
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = -1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents()).isEmpty()
+
+ // Hint to trigger PREPEND
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Error(LOAD_ERROR),
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Error(LOAD_ERROR),
+ appendLocal = Error(LOAD_ERROR),
+ ),
+ )
+
+ // Retry failed hints, both PREPEND and APPEND should trigger.
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Error(LOAD_ERROR),
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Loading,
+ appendLocal = Loading
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Error(LOAD_ERROR),
+ appendLocal = Loading,
+ ),
+ localLoadStateUpdate<Int>(
+ prependLocal = Error(LOAD_ERROR),
+ appendLocal = Error(LOAD_ERROR),
+ ),
+ )
+
+ // This hint should be ignored even though in the non-error state it would
+ // re-emit for PREPEND due to smaller presenterIndex value.
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -1,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(pageEvents.newEvents()).isEmpty()
+ }
+
+ testScope.advanceUntilIdle()
+ }
+ }
+
+ @Test
+ fun retryRefresh() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, _ ->
+ pageSource.errorNextLoad = true
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
+ )
+
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun retryRefreshWithBufferedHint() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pageSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pageSource, config, retryFlow = retryBus.flow)
+ collectSnapshotData(pager) { state, _ ->
+ pageSource.errorNextLoad = true
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
+ )
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertTrue { state.newEvents().isEmpty() }
+
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51),
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun retry_remotePrepend() = runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+
+ return if (loadType == PREPEND) {
+ MediatorResult.Error(EXCEPTION)
+ } else {
+ MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ }
+
var createdPagingSource = false
val factory = suspend {
check(!createdPagingSource)
createdPagingSource = true
TestPagingSource(items = List(2) { it })
}
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = factory,
- config = config,
- remoteMediator = remoteMediator
- )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = factory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
pager.collectEvents {
awaitIdle()
@@ -1648,34 +1806,28 @@
awaitIdle()
retry()
awaitIdle()
- assertThat(
- remoteMediator.loadEventCounts()
- ).containsExactlyEntriesIn(
- mapOf(
- PREPEND to 3,
- APPEND to 1,
- REFRESH to 0
- )
- )
+ assertThat(remoteMediator.loadEventCounts())
+ .containsExactlyEntriesIn(mapOf(PREPEND to 3, APPEND to 1, REFRESH to 0))
}
}
@Test
fun retry_remoteAppend() = runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
- return if (loadType == APPEND) {
- MediatorResult.Error(EXCEPTION)
- } else {
- MediatorResult.Success(endOfPaginationReached = true)
+ return if (loadType == APPEND) {
+ MediatorResult.Error(EXCEPTION)
+ } else {
+ MediatorResult.Success(endOfPaginationReached = true)
+ }
}
}
- }
var createdPagingSource = false
val factory = suspend {
@@ -1683,12 +1835,13 @@
createdPagingSource = true
TestPagingSource(items = List(2) { it })
}
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = factory,
- config = config,
- remoteMediator = remoteMediator
- )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = factory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
pager.collectEvents {
// Resolve initial load.
@@ -1697,154 +1850,205 @@
awaitIdle()
retry()
awaitIdle()
- assertThat(
- remoteMediator.loadEventCounts()
- ).containsExactlyEntriesIn(
- mapOf(
- PREPEND to 1,
- APPEND to 3,
- REFRESH to 0
- )
- )
+ assertThat(remoteMediator.loadEventCounts())
+ .containsExactlyEntriesIn(mapOf(PREPEND to 1, APPEND to 3, REFRESH to 0))
}
}
@Test
- fun disablePlaceholders_refresh() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = false,
- initialLoadSize = 2,
- maxSize = 3
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
+ fun disablePlaceholders_refresh() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = false,
+ initialLoadSize = 2,
+ maxSize = 3
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localRefresh(createRefresh(range = 50..51).pages)
- )
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localRefresh(createRefresh(range = 50..51).pages)
+ )
- fetcherState.job.cancel()
- }
+ fetcherState.job.cancel()
+ }
@Test
- fun disablePlaceholders_prepend() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = false,
- initialLoadSize = 2,
- maxSize = 3
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
+ fun disablePlaceholders_prepend() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = false,
+ initialLoadSize = 2,
+ maxSize = 3
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localRefresh(createRefresh(range = 50..51).pages)
- )
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- localPrepend(createPrepend(-1, 49..49).pages)
- )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localRefresh(createRefresh(range = 50..51).pages)
+ )
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ localPrepend(createPrepend(-1, 49..49).pages)
+ )
- fetcherState.job.cancel()
- }
+ fetcherState.job.cancel()
+ }
@Test
- fun disablePlaceholders_append() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = false,
- initialLoadSize = 2,
- maxSize = 3
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
+ fun disablePlaceholders_append() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = false,
+ initialLoadSize = 2,
+ maxSize = 3
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localRefresh(createRefresh(range = 50..51).pages)
- )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localRefresh(createRefresh(range = 50..51).pages)
+ )
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- localAppend(createAppend(1, 52..52).pages)
- )
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ localAppend(createAppend(1, 52..52).pages)
+ )
- fetcherState.job.cancel()
- }
+ fetcherState.job.cancel()
+ }
@Test
- fun neverDropBelowTwoPages() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 3
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
+ fun neverDropBelowTwoPages() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 3
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..52)
- )
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 2,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 53..53)
- )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..52)
+ )
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 53..53)
+ )
- fetcherState.job.cancel()
- }
+ fetcherState.job.cancel()
+ }
@Test
- fun currentPagingState_pagesEmptyWithHint() = testScope.runTest {
- withContext(coroutineContext) {
+ fun currentPagingState_pagesEmptyWithHint() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pagingSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ assertThat(pager.currentPagingState())
+ .isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 0
+ )
+ )
+ }
+ }
+
+ /** Verify we re-use previous PagingState for remote refresh if there are no pages loaded. */
+ @Test
+ fun currentPagingState_ignoredOnEmptyPages() =
+ testScope.runTest {
+ val remoteMediator = RemoteMediatorMock()
val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = pagingSource,
+ config = config,
+ retryFlow = retryBus.flow,
+ remoteMediatorConnection = RemoteMediatorAccessor(testScope, remoteMediator)
+ )
pager.accessHint(
ViewportHint.Access(
pageOffset = 0,
@@ -1855,127 +2059,51 @@
originalPageOffsetLast = 0
)
)
- assertThat(pager.currentPagingState()).isEqualTo(
- PagingState<Int, Int>(
- pages = listOf(),
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 0
- )
- )
- }
- }
-
- /**
- * Verify we re-use previous PagingState for remote refresh if there are no pages loaded.
- */
- @Test
- fun currentPagingState_ignoredOnEmptyPages() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock()
- val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = pagingSource,
- config = config,
- retryFlow = retryBus.flow,
- remoteMediatorConnection = RemoteMediatorAccessor(testScope, remoteMediator)
- )
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- assertThat(pager.currentPagingState()).isEqualTo(
- PagingState<Int, Int>(
- pages = listOf(),
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 0
- )
- )
- }
-
- @Test
- fun currentPagingState_loadedIndex() = testScope.runTest {
- withContext(coroutineContext) {
- val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { _, _ ->
- advanceUntilIdle()
-
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ assertThat(pager.currentPagingState())
+ .isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 0
)
)
-
- val pagingState = pager.currentPagingState()
- assertNotNull(pagingState)
- assertEquals(51, pagingState.anchorPosition)
-
- // Assert from anchorPosition in placeholdersBefore
- assertEquals(50, pagingState.closestItemToPosition(10))
- // Assert from anchorPosition in loaded indices
- assertEquals(50, pagingState.closestItemToPosition(50))
- assertEquals(51, pagingState.closestItemToPosition(51))
- // Assert from anchorPosition in placeholdersAfter
- assertEquals(51, pagingState.closestItemToPosition(90))
-
- val loadedPage = Page(
- data = listOf(50, 51),
- prevKey = 49,
- nextKey = 52,
- itemsBefore = 50,
- itemsAfter = 48
- )
- assertEquals(listOf(loadedPage), pagingState.pages)
- // Assert from anchorPosition in placeholdersBefore
- assertEquals(loadedPage, pagingState.closestPageToPosition(10))
- // Assert from anchorPosition in loaded indices
- assertEquals(loadedPage, pagingState.closestPageToPosition(50))
- assertEquals(loadedPage, pagingState.closestPageToPosition(51))
- // Assert from anchorPosition in placeholdersAfter
- assertEquals(loadedPage, pagingState.closestPageToPosition(90))
- }
}
- }
@Test
- fun currentPagingState_placeholdersBefore() = testScope.runTest {
- withContext(coroutineContext) {
- val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+ fun currentPagingState_loadedIndex() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pagingSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- collectSnapshotData(pager) { _, _ ->
- advanceUntilIdle()
+ collectSnapshotData(pager) { _, _ ->
+ advanceUntilIdle()
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -40,
- presentedItemsBefore = -40,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
- val pagingState = pager.currentPagingState()
- assertNotNull(pagingState)
- assertEquals(10, pagingState.anchorPosition)
- assertEquals(
- listOf(
+ val pagingState = pager.currentPagingState()
+ assertNotNull(pagingState)
+ assertEquals(51, pagingState.anchorPosition)
+
+ // Assert from anchorPosition in placeholdersBefore
+ assertEquals(50, pagingState.closestItemToPosition(10))
+ // Assert from anchorPosition in loaded indices
+ assertEquals(50, pagingState.closestItemToPosition(50))
+ assertEquals(51, pagingState.closestItemToPosition(51))
+ // Assert from anchorPosition in placeholdersAfter
+ assertEquals(51, pagingState.closestItemToPosition(90))
+
+ val loadedPage =
Page(
data = listOf(50, 51),
prevKey = 49,
@@ -1983,1257 +2111,1371 @@
itemsBefore = 50,
itemsAfter = 48
)
- ),
- pagingState.pages
- )
-
- // Assert from anchorPosition in placeholdersBefore
- assertEquals(50, pagingState.closestItemToPosition(10))
- // Assert from anchorPosition in loaded indices
- assertEquals(50, pagingState.closestItemToPosition(50))
- assertEquals(51, pagingState.closestItemToPosition(51))
- // Assert from anchorPosition in placeholdersAfter
- assertEquals(51, pagingState.closestItemToPosition(90))
-
- val loadedPage = Page(
- data = listOf(50, 51),
- prevKey = 49,
- nextKey = 52,
- itemsBefore = 50,
- itemsAfter = 48
- )
- // Assert from anchorPosition in placeholdersBefore
- assertEquals(loadedPage, pagingState.closestPageToPosition(10))
- // Assert from anchorPosition in loaded indices
- assertEquals(loadedPage, pagingState.closestPageToPosition(50))
- assertEquals(loadedPage, pagingState.closestPageToPosition(51))
- // Assert from anchorPosition in placeholdersAfter
- assertEquals(loadedPage, pagingState.closestPageToPosition(90))
- }
- }
- }
-
- @Test
- fun currentPagingState_noHint() = testScope.runTest {
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = TestPagingSource(loadDelay = 100),
- config = config,
- retryFlow = retryBus.flow
- )
-
- assertThat(pager.currentPagingState()).isEqualTo(
- PagingState<Int, Int>(
- pages = listOf(),
- anchorPosition = null,
- config = config,
- leadingPlaceholderCount = 0,
- )
- )
- }
-
- @Test
- fun retry_ignoresNewSignalsWhileProcessing() = testScope.runTest {
- val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- collectSnapshotData(pager) { state, _ ->
- pagingSource.errorNextLoad = true
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
- )
-
- pagingSource.errorNextLoad = true
- retryBus.send(Unit)
- // Should be ignored by pager as it's still processing previous retry.
- retryBus.send(Unit)
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
- )
- }
- }
-
- /**
- * The case where all pages from presenter have been dropped in fetcher, so instead of
- * counting dropped pages against prefetchDistance, we should clamp that logic to only count
- * pages that have been loaded.
- */
- @Test
- fun doLoad_prependPresenterPagesDropped() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
-
- // Send a hint from a presenter state that only sees pages well after the pages loaded in
- // fetcher state:
- // [hint], [50, 51], [52], [53], [54], [55]
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 4,
- indexInPage = -6,
- presentedItemsBefore = -6,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 4,
- originalPageOffsetLast = 6
- )
- )
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
- createPrepend(pageOffset = -2, range = 48..48, startState = NotLoading.Incomplete),
- )
-
- fetcherState.job.cancel()
- }
-
- /**
- * The case where all pages from presenter have been dropped in fetcher, so instead of
- * counting dropped pages against prefetchDistance, we should clamp that logic to only count
- * pages that have been loaded.
- */
- @Test
- fun doLoad_appendPresenterPagesDropped() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
-
- // Send a hint from a presenter state that only sees pages well before the pages loaded in
- // fetcher state:
- // [46], [47], [48], [49], [50, 51], [hint]
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = -4,
- indexInPage = 6,
- presentedItemsBefore = 2,
- presentedItemsAfter = -6,
- originalPageOffsetFirst = -6,
- originalPageOffsetLast = -4
- )
- )
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- createAppend(pageOffset = 1, range = 52..52, endState = Loading),
- createAppend(pageOffset = 2, range = 53..53, endState = NotLoading.Incomplete),
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_initialLoadErrorTriggersLocal() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun initialize(): InitializeAction {
- return InitializeAction.LAUNCH_INITIAL_REFRESH
- }
-
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- return MediatorResult.Error(EXCEPTION)
+ assertEquals(listOf(loadedPage), pagingState.pages)
+ // Assert from anchorPosition in placeholdersBefore
+ assertEquals(loadedPage, pagingState.closestPageToPosition(10))
+ // Assert from anchorPosition in loaded indices
+ assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(51))
+ // Assert from anchorPosition in placeholdersAfter
+ assertEquals(loadedPage, pagingState.closestPageToPosition(90))
+ }
}
}
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = pagingSourceFactory,
- config = PagingConfig(1),
- remoteMediator = remoteMediator
- )
-
- val expected = listOf(
- listOf(
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Error(EXCEPTION),
- ),
- createRefresh(
- range = 0..2,
- remoteLoadStatesOf(
- refresh = Error(EXCEPTION),
- prependLocal = NotLoading.Complete,
- refreshRemote = Error(EXCEPTION),
- ),
- ),
- // since remote refresh failed and launch initial refresh is requested,
- // we won't receive any append/prepend events
- )
- )
-
- pager.assertEventByGeneration(expected)
- }
-
@Test
- fun remoteMediator_initialLoadTriggersPrepend() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- currentPagingSource!!.invalidate()
- return MediatorResult.Success(endOfPaginationReached = false)
- }
- }
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
+ fun currentPagingState_placeholdersBefore() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pagingSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- pager.pageEvents().take(4).toList()
- assertEquals(1, remoteMediator.loadEvents.size)
- assertEquals(PREPEND, remoteMediator.loadEvents[0].loadType)
- assertNotNull(remoteMediator.loadEvents[0].state)
- }
+ collectSnapshotData(pager) { _, _ ->
+ advanceUntilIdle()
- @Test
- fun remoteMediator_initialLoadTriggersAppend() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- currentPagingSource!!.invalidate()
- return MediatorResult.Success(endOfPaginationReached = false)
- }
- }
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val fetcher = PageFetcher(
- initialKey = 99,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
- // taking 4 events:
- // local load, local insert, append state change to loading, local load w/ new append result
- // 4th one is necessary as the Loading state change is done optimistically before the
- // remote mediator is invoked
- fetcher.pageEvents().take(4).toList()
- assertEquals(1, remoteMediator.loadEvents.size)
- assertEquals(APPEND, remoteMediator.loadEvents[0].loadType)
- assertNotNull(remoteMediator.loadEvents[0].state)
- }
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -40,
+ presentedItemsBefore = -40,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
- @Test
- fun remoteMediator_remoteRefreshCachesPreviousPagingState() = testScope.runTest {
- @OptIn(ExperimentalPagingApi::class)
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
- config = config,
- remoteMediator = remoteMediator
- )
-
- val state = collectFetcherState(pager)
-
- // Let the initial page load; loaded data should be [0]
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent<Int, Int>(
- loadType = REFRESH,
- state = PagingState(
- pages = listOf(),
- anchorPosition = null,
- config = config,
- leadingPlaceholderCount = 0,
- ),
- )
- )
-
- // Explicit call to refresh, which should trigger remote refresh with cached PagingState.
- pager.refresh()
- advanceUntilIdle()
-
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(
- loadType = REFRESH,
- state = PagingState(
- pages = listOf(
- Page(
- data = listOf(0),
- prevKey = null,
- nextKey = null,
- itemsBefore = 0,
- itemsAfter = 0,
+ val pagingState = pager.currentPagingState()
+ assertNotNull(pagingState)
+ assertEquals(10, pagingState.anchorPosition)
+ assertEquals(
+ listOf(
+ Page(
+ data = listOf(50, 51),
+ prevKey = 49,
+ nextKey = 52,
+ itemsBefore = 50,
+ itemsAfter = 48
+ )
),
- ),
- anchorPosition = null,
+ pagingState.pages
+ )
+
+ // Assert from anchorPosition in placeholdersBefore
+ assertEquals(50, pagingState.closestItemToPosition(10))
+ // Assert from anchorPosition in loaded indices
+ assertEquals(50, pagingState.closestItemToPosition(50))
+ assertEquals(51, pagingState.closestItemToPosition(51))
+ // Assert from anchorPosition in placeholdersAfter
+ assertEquals(51, pagingState.closestItemToPosition(90))
+
+ val loadedPage =
+ Page(
+ data = listOf(50, 51),
+ prevKey = 49,
+ nextKey = 52,
+ itemsBefore = 50,
+ itemsAfter = 48
+ )
+ // Assert from anchorPosition in placeholdersBefore
+ assertEquals(loadedPage, pagingState.closestPageToPosition(10))
+ // Assert from anchorPosition in loaded indices
+ assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(51))
+ // Assert from anchorPosition in placeholdersAfter
+ assertEquals(loadedPage, pagingState.closestPageToPosition(90))
+ }
+ }
+ }
+
+ @Test
+ fun currentPagingState_noHint() =
+ testScope.runTest {
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = TestPagingSource(loadDelay = 100),
config = config,
- leadingPlaceholderCount = 0,
- ),
- )
- )
-
- state.job.cancel()
- }
-
- @Test
- fun sourceOnlyInitialLoadState() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
- config = config,
- )
-
- val state = collectFetcherState(pager)
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- refreshLocal = Loading
- ),
- )
-
- advanceUntilIdle()
-
- assertThat(state.newEvents()).containsExactly(
- localRefresh(
- pages = listOf(
- TransformablePage(data = listOf(0)),
- ),
- source = loadStates(
- refresh = NotLoading.Incomplete,
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- )
-
- state.job.cancel()
- }
-
- @Test
- fun remoteInitialLoadState() = testScope.runTest {
- @OptIn(ExperimentalPagingApi::class)
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- withContext(coroutineContext) {
- delay(50)
- RemoteMediator.MediatorResult.Success(true)
- }
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = { TestPagingSource(items = listOf(0), loadDelay = 100) },
- config = config,
- remoteMediator = remoteMediator,
- )
-
- val state = collectFetcherState(pager)
- advanceTimeBy(1)
-
- assertThat(state.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading
- ),
- remoteLoadStateUpdate<Int>(
- refreshRemote = Loading,
- refreshLocal = Loading,
- ),
- )
-
- advanceUntilIdle()
-
- assertThat(state.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- refreshLocal = Loading,
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf(0))
- ),
- source = loadStates(
- refresh = NotLoading.Incomplete,
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Incomplete,
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
+ retryFlow = retryBus.flow
)
- ),
- )
- state.job.cancel()
- }
-
- @Test
- fun remoteMediator_remoteRefreshEndOfPaginationReached() = testScope.runTest {
- @OptIn(ExperimentalPagingApi::class)
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(true)
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
- config = config,
- remoteMediator = remoteMediator
- )
-
- val state = collectFetcherState(pager)
-
- advanceUntilIdle()
-
- assertThat(state.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = null
+ assertThat(pager.currentPagingState())
+ .isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
)
- ),
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Incomplete,
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- )
- )
- state.job.cancel()
- }
+ )
+ }
@Test
- fun remoteMediator_endOfPaginationNotReachedLoadStatePrepend() = testScope.runTest {
- val pagingSources = mutableListOf<TestPagingSource>()
- var remotePrependStarted = false
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- // on first advance, we let local refresh complete first before
- // triggering remote prepend load
- delay(300)
- super.load(loadType, state)
- remotePrependStarted = true
- // on second advance, we let super.load() start but don't return result yet
- delay(500)
- return MediatorResult.Success(endOfPaginationReached = false)
+ fun retry_ignoresNewSignalsWhileProcessing() =
+ testScope.runTest {
+ val pagingSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+ collectSnapshotData(pager) { state, _ ->
+ pagingSource.errorNextLoad = true
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
+ )
+
+ pagingSource.errorNextLoad = true
+ retryBus.send(Unit)
+ // Should be ignored by pager as it's still processing previous retry.
+ retryBus.send(Unit)
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ localLoadStateUpdate<Int>(refreshLocal = Error(LOAD_ERROR)),
+ )
}
}
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val fetcher = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = {
- pagingSourceFactory().also {
- pagingSources.add(it)
+
+ /**
+ * The case where all pages from presenter have been dropped in fetcher, so instead of counting
+ * dropped pages against prefetchDistance, we should clamp that logic to only count pages that
+ * have been loaded.
+ */
+ @Test
+ fun doLoad_prependPresenterPagesDropped() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+
+ // Send a hint from a presenter state that only sees pages well after the pages loaded
+ // in
+ // fetcher state:
+ // [hint], [50, 51], [52], [53], [54], [55]
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 4,
+ indexInPage = -6,
+ presentedItemsBefore = -6,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 4,
+ originalPageOffsetLast = 6
+ )
+ )
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49, startState = Loading),
+ createPrepend(
+ pageOffset = -2,
+ range = 48..48,
+ startState = NotLoading.Incomplete
+ ),
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ /**
+ * The case where all pages from presenter have been dropped in fetcher, so instead of counting
+ * dropped pages against prefetchDistance, we should clamp that logic to only count pages that
+ * have been loaded.
+ */
+ @Test
+ fun doLoad_appendPresenterPagesDropped() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+
+ // Send a hint from a presenter state that only sees pages well before the pages loaded
+ // in
+ // fetcher state:
+ // [46], [47], [48], [49], [50, 51], [hint]
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = -4,
+ indexInPage = 6,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = -6,
+ originalPageOffsetFirst = -6,
+ originalPageOffsetLast = -4
+ )
+ )
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ createAppend(pageOffset = 1, range = 52..52, endState = Loading),
+ createAppend(pageOffset = 2, range = 53..53, endState = NotLoading.Incomplete),
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun remoteMediator_initialLoadErrorTriggersLocal() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun initialize(): InitializeAction {
+ return InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ return MediatorResult.Error(EXCEPTION)
+ }
}
- },
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(fetcher)
- advanceTimeBy(1200) // let local refresh complete
- // assert first gen events
- val expectedFirstGen = listOf(
- remoteLoadStateUpdate(refreshLocal = Loading),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(0)
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = pagingSourceFactory,
+ config = PagingConfig(1),
+ remoteMediator = remoteMediator
+ )
+
+ val expected =
+ listOf(
+ listOf(
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ ),
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ refreshRemote = Error(EXCEPTION),
+ ),
+ createRefresh(
+ range = 0..2,
+ remoteLoadStatesOf(
+ refresh = Error(EXCEPTION),
+ prependLocal = NotLoading.Complete,
+ refreshRemote = Error(EXCEPTION),
+ ),
+ ),
+ // since remote refresh failed and launch initial refresh is requested,
+ // we won't receive any append/prepend events
)
- ),
- placeholdersAfter = 99,
- source = loadStates(prepend = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- prependLocal = NotLoading.Complete,
- prependRemote = Loading,
- ),
- )
- assertThat(fetcherState.newEvents()).containsExactlyElementsIn(expectedFirstGen).inOrder()
+ )
- // let remote prepend start loading but don't let it complete
- advanceTimeBy(300)
- assertTrue(remotePrependStarted)
-
- // invalidate first PagingSource while remote is prepending
- pagingSources[0].invalidate()
- assertTrue(pagingSources[0].invalid)
-
- // allow Mediator prepend and second gen local Refresh to complete
- // due to TestPagingSource loadDay(1000ms), the remote load will complete first
- advanceTimeBy(1300)
-
- val expectedSecondGen = listOf(
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- prependRemote = Loading,
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- prependRemote = NotLoading.Incomplete,
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(0)
- )
- ),
- placeholdersAfter = 99,
- source = loadStates(prepend = NotLoading.Complete)
- )
- )
- assertThat(fetcherState.newEvents().take(3))
- .containsExactlyElementsIn(expectedSecondGen).inOrder()
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_endOfPaginationReachedLoadStatePrepend() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- return MediatorResult.Success(endOfPaginationReached = true)
- }
+ pager.assertEventByGeneration(expected)
}
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val fetcher = PageFetcher(
- initialKey = 0,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
+ @Test
+ fun remoteMediator_initialLoadTriggersPrepend() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ currentPagingSource!!.invalidate()
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
- fetcher.assertEventByGeneration(
- listOf(
+ pager.pageEvents().take(4).toList()
+ assertEquals(1, remoteMediator.loadEvents.size)
+ assertEquals(PREPEND, remoteMediator.loadEvents[0].loadType)
+ assertNotNull(remoteMediator.loadEvents[0].state)
+ }
+
+ @Test
+ fun remoteMediator_initialLoadTriggersAppend() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ currentPagingSource!!.invalidate()
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val fetcher =
+ PageFetcher(
+ initialKey = 99,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ // taking 4 events:
+ // local load, local insert, append state change to loading, local load w/ new append
+ // result
+ // 4th one is necessary as the Loading state change is done optimistically before the
+ // remote mediator is invoked
+ fetcher.pageEvents().take(4).toList()
+ assertEquals(1, remoteMediator.loadEvents.size)
+ assertEquals(APPEND, remoteMediator.loadEvents[0].loadType)
+ assertNotNull(remoteMediator.loadEvents[0].state)
+ }
+
+ @Test
+ fun remoteMediator_remoteRefreshCachesPreviousPagingState() =
+ testScope.runTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val state = collectFetcherState(pager)
+
+ // Let the initial page load; loaded data should be [0]
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent<Int, Int>(
+ loadType = REFRESH,
+ state =
+ PagingState(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ // Explicit call to refresh, which should trigger remote refresh with cached
+ // PagingState.
+ pager.refresh()
+ advanceUntilIdle()
+
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(
+ loadType = REFRESH,
+ state =
+ PagingState(
+ pages =
+ listOf(
+ Page(
+ data = listOf(0),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = 0,
+ itemsAfter = 0,
+ ),
+ ),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ state.job.cancel()
+ }
+
+ @Test
+ fun sourceOnlyInitialLoadState() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+ config = config,
+ )
+
+ val state = collectFetcherState(pager)
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ )
+
+ advanceUntilIdle()
+
+ assertThat(state.newEvents())
+ .containsExactly(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(data = listOf(0)),
+ ),
+ source =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+
+ state.job.cancel()
+ }
+
+ @Test
+ fun remoteInitialLoadState() =
+ testScope.runTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ withContext(coroutineContext) {
+ delay(50)
+ RemoteMediator.MediatorResult.Success(true)
+ }
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { TestPagingSource(items = listOf(0), loadDelay = 100) },
+ config = config,
+ remoteMediator = remoteMediator,
+ )
+
+ val state = collectFetcherState(pager)
+ advanceTimeBy(1)
+
+ assertThat(state.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(refreshLocal = Loading),
+ remoteLoadStateUpdate<Int>(
+ refreshRemote = Loading,
+ refreshLocal = Loading,
+ ),
+ )
+
+ advanceUntilIdle()
+
+ assertThat(state.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ refreshLocal = Loading,
+ ),
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf(0))),
+ source =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ )
+ ),
+ )
+
+ state.job.cancel()
+ }
+
+ @Test
+ fun remoteMediator_remoteRefreshEndOfPaginationReached() =
+ testScope.runTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val state = collectFetcherState(pager)
+
+ advanceUntilIdle()
+
+ assertThat(state.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = null
+ )
+ ),
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ )
+ )
+ state.job.cancel()
+ }
+
+ @Test
+ fun remoteMediator_endOfPaginationNotReachedLoadStatePrepend() =
+ testScope.runTest {
+ val pagingSources = mutableListOf<TestPagingSource>()
+ var remotePrependStarted = false
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ // on first advance, we let local refresh complete first before
+ // triggering remote prepend load
+ delay(300)
+ super.load(loadType, state)
+ remotePrependStarted = true
+ // on second advance, we let super.load() start but don't return result yet
+ delay(500)
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val fetcher =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { pagingSourceFactory().also { pagingSources.add(it) } },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(fetcher)
+ advanceTimeBy(1200) // let local refresh complete
+
+ // assert first gen events
+ val expectedFirstGen =
listOf(
remoteLoadStateUpdate(refreshLocal = Loading),
remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(0)
- )
- ),
- placeholdersBefore = 0,
+ pages = listOf(TransformablePage(originalPageOffset = 0, data = listOf(0))),
placeholdersAfter = 99,
source = loadStates(prepend = NotLoading.Complete)
),
remoteLoadStateUpdate(
prependLocal = NotLoading.Complete,
- prependRemote = Loading
+ prependRemote = Loading,
+ ),
+ )
+ assertThat(fetcherState.newEvents())
+ .containsExactlyElementsIn(expectedFirstGen)
+ .inOrder()
+
+ // let remote prepend start loading but don't let it complete
+ advanceTimeBy(300)
+ assertTrue(remotePrependStarted)
+
+ // invalidate first PagingSource while remote is prepending
+ pagingSources[0].invalidate()
+ assertTrue(pagingSources[0].invalid)
+
+ // allow Mediator prepend and second gen local Refresh to complete
+ // due to TestPagingSource loadDay(1000ms), the remote load will complete first
+ advanceTimeBy(1300)
+
+ val expectedSecondGen =
+ listOf(
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ prependRemote = Loading,
),
remoteLoadStateUpdate(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete
+ refreshLocal = Loading,
+ prependRemote = NotLoading.Incomplete,
),
+ remoteRefresh(
+ pages = listOf(TransformablePage(originalPageOffset = 0, data = listOf(0))),
+ placeholdersAfter = 99,
+ source = loadStates(prepend = NotLoading.Complete)
+ )
)
- )
- )
- }
+ assertThat(fetcherState.newEvents().take(3))
+ .containsExactlyElementsIn(expectedSecondGen)
+ .inOrder()
+ fetcherState.job.cancel()
+ }
@Test
- fun remoteMediator_prependEndOfPaginationReachedLocalThenRemote() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- return MediatorResult.Success(endOfPaginationReached = true)
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val fetcher = PageFetcher(
- initialKey = 1,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
-
- fetcher.collectEvents {
- awaitEventCount(2)
- val refreshEvents = listOf(
- remoteLoadStateUpdate(refreshLocal = Loading),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(1, 2, 3)
- )
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 96,
- )
- )
- assertThat(eventsByGeneration[0]).isEqualTo(refreshEvents)
- accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- val postHintEvents = listOf(
- remoteLoadStateUpdate(prependLocal = Loading),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf(0)
- )
- ),
- placeholdersBefore = 0,
- source = loadStates(prepend = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- prependLocal = NotLoading.Complete,
- prependRemote = Loading
- ),
- remoteLoadStateUpdate(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- )
- awaitEventCount(refreshEvents.size + postHintEvents.size)
- assertEquals(
- eventsByGeneration[0],
- refreshEvents + postHintEvents
- )
- }
- }
-
- @Test
- fun remoteMediator_endOfPaginationNotReachedLoadStateAppend() = testScope.runTest {
- val pagingSources = mutableListOf<TestPagingSource>()
- var remoteAppendStarted = false
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- // on first advance, we let local refresh complete first before
- // triggering remote append load
- delay(300)
- super.load(loadType, state)
- remoteAppendStarted = true
- // on second advance, we let super.load() start but don't return result yet
- delay(500)
- return MediatorResult.Success(endOfPaginationReached = false)
- }
- }
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val fetcher = PageFetcher(
- initialKey = 99,
- pagingSourceFactory = {
- pagingSourceFactory().also {
- it.getRefreshKeyResult = 99
- pagingSources.add(it)
+ fun remoteMediator_endOfPaginationReachedLoadStatePrepend() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
}
- },
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(fetcher)
- advanceTimeBy(1200) // let local refresh complete
- // assert first gen events
- val expectedFirstGen = listOf(
- remoteLoadStateUpdate(refreshLocal = Loading),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(99)
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val fetcher =
+ PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ fetcher.assertEventByGeneration(
+ listOf(
+ listOf(
+ remoteLoadStateUpdate(refreshLocal = Loading),
+ remoteRefresh(
+ pages =
+ listOf(TransformablePage(originalPageOffset = 0, data = listOf(0))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 99,
+ source = loadStates(prepend = NotLoading.Complete)
+ ),
+ remoteLoadStateUpdate(
+ prependLocal = NotLoading.Complete,
+ prependRemote = Loading
+ ),
+ remoteLoadStateUpdate(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete
+ ),
)
- ),
- placeholdersBefore = 99,
- source = loadStates(append = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- appendRemote = Loading
- ),
- )
- assertThat(fetcherState.newEvents()).containsExactlyElementsIn(expectedFirstGen).inOrder()
-
- // let remote append start loading but don't let it complete
- advanceTimeBy(300)
- assertTrue(remoteAppendStarted)
-
- // invalidate first PagingSource while remote is loading an append
- pagingSources[0].invalidate()
- assertTrue(pagingSources[0].invalid)
-
- // allow Mediator append and second gen local Refresh to complete
- // due to TestPagingSource loadDay(1000ms), the remote load will complete first
- advanceTimeBy(1300)
-
- val expectedSecondGen = listOf(
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- appendRemote = Loading,
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- appendRemote = NotLoading.Incomplete,
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(99)
- )
- ),
- placeholdersBefore = 99,
- source = loadStates(append = NotLoading.Complete)
- ),
- )
- assertThat(fetcherState.newEvents().take(3))
- .containsExactlyElementsIn(expectedSecondGen).inOrder()
- fetcherState.job.cancel()
- }
+ )
+ )
+ }
@Test
- fun remoteMediator_endOfPaginationReachedLoadStateAppend() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- return MediatorResult.Success(endOfPaginationReached = true)
+ fun remoteMediator_prependEndOfPaginationReachedLocalThenRemote() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
+ )
+ val fetcher =
+ PageFetcher(
+ initialKey = 1,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ fetcher.collectEvents {
+ awaitEventCount(2)
+ val refreshEvents =
+ listOf(
+ remoteLoadStateUpdate(refreshLocal = Loading),
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf(1, 2, 3)
+ )
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 96,
+ )
+ )
+ assertThat(eventsByGeneration[0]).isEqualTo(refreshEvents)
+ accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ val postHintEvents =
+ listOf(
+ remoteLoadStateUpdate(prependLocal = Loading),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = -1, data = listOf(0))
+ ),
+ placeholdersBefore = 0,
+ source = loadStates(prepend = NotLoading.Complete)
+ ),
+ remoteLoadStateUpdate(
+ prependLocal = NotLoading.Complete,
+ prependRemote = Loading
+ ),
+ remoteLoadStateUpdate(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ ),
+ )
+ awaitEventCount(refreshEvents.size + postHintEvents.size)
+ assertEquals(eventsByGeneration[0], refreshEvents + postHintEvents)
}
}
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 99,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
+ @Test
+ fun remoteMediator_endOfPaginationNotReachedLoadStateAppend() =
+ testScope.runTest {
+ val pagingSources = mutableListOf<TestPagingSource>()
+ var remoteAppendStarted = false
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ // on first advance, we let local refresh complete first before
+ // triggering remote append load
+ delay(300)
+ super.load(loadType, state)
+ remoteAppendStarted = true
+ // on second advance, we let super.load() start but don't return result yet
+ delay(500)
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val fetcher =
+ PageFetcher(
+ initialKey = 99,
+ pagingSourceFactory = {
+ pagingSourceFactory().also {
+ it.getRefreshKeyResult = 99
+ pagingSources.add(it)
+ }
+ },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(fetcher)
+ advanceTimeBy(1200) // let local refresh complete
- val expected: List<List<PageEvent<Int>>> = listOf(
- listOf(
- remoteLoadStateUpdate(refreshLocal = Loading),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(99)
- )
+ // assert first gen events
+ val expectedFirstGen =
+ listOf(
+ remoteLoadStateUpdate(refreshLocal = Loading),
+ remoteRefresh(
+ pages =
+ listOf(TransformablePage(originalPageOffset = 0, data = listOf(99))),
+ placeholdersBefore = 99,
+ source = loadStates(append = NotLoading.Complete)
),
- placeholdersBefore = 99,
- placeholdersAfter = 0,
- source = loadStates(append = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- appendRemote = Loading,
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
- ),
- )
- )
- pager.assertEventByGeneration(expected)
- }
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ appendRemote = Loading
+ ),
+ )
+ assertThat(fetcherState.newEvents())
+ .containsExactlyElementsIn(expectedFirstGen)
+ .inOrder()
+
+ // let remote append start loading but don't let it complete
+ advanceTimeBy(300)
+ assertTrue(remoteAppendStarted)
+
+ // invalidate first PagingSource while remote is loading an append
+ pagingSources[0].invalidate()
+ assertTrue(pagingSources[0].invalid)
+
+ // allow Mediator append and second gen local Refresh to complete
+ // due to TestPagingSource loadDay(1000ms), the remote load will complete first
+ advanceTimeBy(1300)
+
+ val expectedSecondGen =
+ listOf(
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ appendRemote = Loading,
+ ),
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ appendRemote = NotLoading.Incomplete,
+ ),
+ remoteRefresh(
+ pages =
+ listOf(TransformablePage(originalPageOffset = 0, data = listOf(99))),
+ placeholdersBefore = 99,
+ source = loadStates(append = NotLoading.Complete)
+ ),
+ )
+ assertThat(fetcherState.newEvents().take(3))
+ .containsExactlyElementsIn(expectedSecondGen)
+ .inOrder()
+ fetcherState.job.cancel()
+ }
@Test
- fun remoteMediator_appendEndOfPaginationReachedLocalThenRemote() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- return MediatorResult.Success(endOfPaginationReached = true)
- }
+ fun remoteMediator_endOfPaginationReachedLoadStateAppend() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 99,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val expected: List<List<PageEvent<Int>>> =
+ listOf(
+ listOf(
+ remoteLoadStateUpdate(refreshLocal = Loading),
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 0, data = listOf(99))
+ ),
+ placeholdersBefore = 99,
+ placeholdersAfter = 0,
+ source = loadStates(append = NotLoading.Complete)
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ appendRemote = Loading,
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ ),
+ )
+ )
+ pager.assertEventByGeneration(expected)
}
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 3,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 96,
- pagingSourceFactory = pagingSourceFactory,
- config = config,
- remoteMediator = remoteMediator
- )
- pager.collectEvents {
- val initialEvents = listOf(
- remoteLoadStateUpdate(refreshLocal = Loading),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(96, 97, 98)
- )
- ),
- placeholdersBefore = 96,
- placeholdersAfter = 1,
+ @Test
+ fun remoteMediator_appendEndOfPaginationReachedLocalThenRemote() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ maxSize = 5
)
- )
- awaitEventCount(initialEvents.size)
- assertEvents(initialEvents, eventsByGeneration[0])
- accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 48,
- presentedItemsBefore = 48,
- presentedItemsAfter = -46,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ val pager =
+ PageFetcher(
+ initialKey = 96,
+ pagingSourceFactory = pagingSourceFactory,
+ config = config,
+ remoteMediator = remoteMediator
)
- )
- val postHintEvents = listOf(
- remoteLoadStateUpdate(appendLocal = Loading),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf(99)
+ pager.collectEvents {
+ val initialEvents =
+ listOf(
+ remoteLoadStateUpdate(refreshLocal = Loading),
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf(96, 97, 98)
+ )
+ ),
+ placeholdersBefore = 96,
+ placeholdersAfter = 1,
)
- ),
- source = loadStates(append = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- appendRemote = Loading
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
- ),
- )
- awaitEventCount(initialEvents.size + postHintEvents.size)
- assertThat(eventsByGeneration[0]).isEqualTo(initialEvents + postHintEvents)
+ )
+ awaitEventCount(initialEvents.size)
+ assertEvents(initialEvents, eventsByGeneration[0])
+ accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 48,
+ presentedItemsBefore = 48,
+ presentedItemsAfter = -46,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ val postHintEvents =
+ listOf(
+ remoteLoadStateUpdate(appendLocal = Loading),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 1, data = listOf(99))
+ ),
+ source = loadStates(append = NotLoading.Complete)
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ appendRemote = Loading
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ ),
+ )
+ awaitEventCount(initialEvents.size + postHintEvents.size)
+ assertThat(eventsByGeneration[0]).isEqualTo(initialEvents + postHintEvents)
+ }
}
- }
@Test
fun remoteMediator_immediateInvalidation() = runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun initialize(): InitializeAction {
- super.initialize()
- return InitializeAction.LAUNCH_INITIAL_REFRESH
- }
-
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- // Wait for remote events to get sent and observed by PageFetcher, but don't let
- // source REFRESH complete yet until we invalidate.
- advanceTimeBy(500)
- currentPagingSource!!.invalidate()
- // Wait for second generation to start before letting remote REFRESH finish, but
- // ensure that remote REFRESH finishes before source REFRESH does.
- delay(100)
- return MediatorResult.Success(endOfPaginationReached = false)
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 50,
- pagingSourceFactory = {
- pagingSourceFactory().also {
- it.getRefreshKeyResult = 30
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun initialize(): InitializeAction {
+ super.initialize()
+ return InitializeAction.LAUNCH_INITIAL_REFRESH
}
- },
- config = config,
- remoteMediator = remoteMediator
- )
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ // Wait for remote events to get sent and observed by PageFetcher, but don't let
+ // source REFRESH complete yet until we invalidate.
+ advanceTimeBy(500)
+ currentPagingSource!!.invalidate()
+ // Wait for second generation to start before letting remote REFRESH finish, but
+ // ensure that remote REFRESH finishes before source REFRESH does.
+ delay(100)
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 50,
+ pagingSourceFactory = {
+ pagingSourceFactory().also { it.getRefreshKeyResult = 30 }
+ },
+ config = config,
+ remoteMediator = remoteMediator
+ )
val fetcherState = collectFetcherState(pager)
advanceUntilIdle()
assertThat(fetcherState.pageEventLists).hasSize(2)
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- )
- assertThat(fetcherState.pageEventLists[1]).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- ),
- // getRefreshKey() = null is used over initialKey due to invalidation.
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(30)
- )
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
),
- placeholdersBefore = 30,
- placeholdersAfter = 69,
- ),
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_initialRefreshSuccess() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock() {
- override suspend fun initialize(): InitializeAction {
- super.initialize()
- return InitializeAction.LAUNCH_INITIAL_REFRESH
- }
-
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
-
- // Wait for advanceUntilIdle()
- delay(1)
-
- currentPagingSource!!.invalidate()
- return MediatorResult.Success(endOfPaginationReached = false)
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 50,
- pagingSourceFactory = {
- pagingSourceFactory().also {
- it.getRefreshKeyResult = 30
- }
- },
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(pager)
- advanceUntilIdle()
-
- assertThat(fetcherState.pageEventLists.size).isEqualTo(2)
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- ),
- )
- assertThat(fetcherState.pageEventLists[1]).containsExactly(
- // Invalidate happens before RemoteMediator returns.
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(30)
- )
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
),
- placeholdersBefore = 30,
- placeholdersAfter = 69,
- ),
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_initialRefreshSuccessEndOfPagination() = testScope.runTest {
- val remoteMediator = object : RemoteMediatorMock(loadDelay = 2000) {
- override suspend fun initialize(): InitializeAction {
- super.initialize()
- return InitializeAction.LAUNCH_INITIAL_REFRESH
- }
-
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- super.load(loadType, state)
- return MediatorResult.Success(endOfPaginationReached = true)
- }
- }
-
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 2,
- enablePlaceholders = true,
- initialLoadSize = 1,
- maxSize = 5
- )
- val pager = PageFetcher(
- initialKey = 50,
- pagingSourceFactory = {
- TestPagingSource().apply {
- nextLoadResult = Page(
- data = listOf(50),
- prevKey = null,
- nextKey = null,
- itemsBefore = 50,
- itemsAfter = 49
- )
- }
- },
- config = config,
- remoteMediator = remoteMediator
- )
-
- val fetcherState = collectFetcherState(pager)
-
- advanceTimeBy(1000)
- runCurrent()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf(50)
- )
- ),
- placeholdersBefore = 50,
- placeholdersAfter = 49,
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(refresh = Loading),
- ),
- )
-
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- refreshRemote = NotLoading.Incomplete,
- ),
- )
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun jump() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3,
- jumpThreshold = 10
)
- var didJump = false
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = pagingSourceFactory(),
- config = config,
- retryFlow = retryBus.flow,
- previousPagingState = null,
- ) {
- didJump = true
- }
- // Trigger collection on flow to init jump detection job.
- val job = launch { pager.pageEventFlow.collect { } }
+ assertThat(fetcherState.pageEventLists[1])
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ ),
+ // getRefreshKey() = null is used over initialKey due to invalidation.
+ remoteRefresh(
+ pages = listOf(TransformablePage(originalPageOffset = 0, data = listOf(30))),
+ placeholdersBefore = 30,
+ placeholdersAfter = 69,
+ ),
+ )
- advanceUntilIdle()
+ fetcherState.job.cancel()
+ }
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -50,
- presentedItemsBefore = -50,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ @Test
+ fun remoteMediator_initialRefreshSuccess() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock() {
+ override suspend fun initialize(): InitializeAction {
+ super.initialize()
+ return InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+
+ // Wait for advanceUntilIdle()
+ delay(1)
+
+ currentPagingSource!!.invalidate()
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
)
- )
+ val pager =
+ PageFetcher(
+ initialKey = 50,
+ pagingSourceFactory = {
+ pagingSourceFactory().also { it.getRefreshKeyResult = 30 }
+ },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pager)
advanceUntilIdle()
- assertTrue { didJump }
+ assertThat(fetcherState.pageEventLists.size).isEqualTo(2)
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ ),
+ )
+ assertThat(fetcherState.pageEventLists[1])
+ .containsExactly(
+ // Invalidate happens before RemoteMediator returns.
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ ),
+ remoteRefresh(
+ pages =
+ listOf(TransformablePage(originalPageOffset = 0, data = listOf(30))),
+ placeholdersBefore = 30,
+ placeholdersAfter = 69,
+ ),
+ )
- job.cancel()
+ fetcherState.job.cancel()
}
- }
+
+ @Test
+ fun remoteMediator_initialRefreshSuccessEndOfPagination() =
+ testScope.runTest {
+ val remoteMediator =
+ object : RemoteMediatorMock(loadDelay = 2000) {
+ override suspend fun initialize(): InitializeAction {
+ super.initialize()
+ return InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ super.load(loadType, state)
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager =
+ PageFetcher(
+ initialKey = 50,
+ pagingSourceFactory = {
+ TestPagingSource().apply {
+ nextLoadResult =
+ Page(
+ data = listOf(50),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = 50,
+ itemsAfter = 49
+ )
+ }
+ },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val fetcherState = collectFetcherState(pager)
+
+ advanceTimeBy(1000)
+ runCurrent()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(refreshLocal = Loading, refreshRemote = Loading),
+ remoteRefresh(
+ pages =
+ listOf(TransformablePage(originalPageOffset = 0, data = listOf(50))),
+ placeholdersBefore = 50,
+ placeholdersAfter = 49,
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator = loadStates(refresh = Loading),
+ ),
+ )
+
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ refreshRemote = NotLoading.Incomplete,
+ ),
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun jump() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3,
+ jumpThreshold = 10
+ )
+ var didJump = false
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = pagingSourceFactory(),
+ config = config,
+ retryFlow = retryBus.flow,
+ previousPagingState = null,
+ ) {
+ didJump = true
+ }
+ // Trigger collection on flow to init jump detection job.
+ val job = launch { pager.pageEventFlow.collect {} }
+
+ advanceUntilIdle()
+
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -50,
+ presentedItemsBefore = -50,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ assertTrue { didJump }
+
+ job.cancel()
+ }
+ }
@Test
fun jump_requiresPagingSourceOptIn() {
@@ -3248,208 +3490,39 @@
}
@Test
- fun jump_idempotent_prependOrAppend() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3,
- jumpThreshold = 10
- )
- var didJump = 0
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = pagingSourceFactory(),
- config = config,
- retryFlow = retryBus.flow,
- previousPagingState = null,
- ) {
- didJump++
- }
- // Trigger collection on flow to init jump detection job.
- val job = launch { pager.pageEventFlow.collect { } }
-
- advanceUntilIdle()
-
- // This would trigger both append and prepend because of processHint logic
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -50,
- presentedItemsBefore = -50,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
-
- // even though both append / prepend flows sent jumping hint, should only trigger
- // jump once
- assertThat(didJump).isEqualTo(1)
-
- job.cancel()
- }
-
- @Test
- fun jump_idempotent_multipleJumpHints() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3,
- jumpThreshold = 10
- )
- var didJump = 0
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = pagingSourceFactory(),
- config = config,
- retryFlow = retryBus.flow,
- previousPagingState = null,
- ) {
- didJump++
- }
- // Trigger collection on flow to init jump detection job.
- val job = launch { pager.pageEventFlow.collect { } }
-
- advanceUntilIdle()
-
- // This would trigger both append and prepend because of processHint logic
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -50,
- presentedItemsBefore = -50,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
-
- // send second jump hint as well
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -50,
- presentedItemsBefore = -50,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
-
- advanceUntilIdle()
-
- // even though both append / prepend flows sent jumping hint, and a second jump hint
- // was sent, they should only trigger jump once
- assertThat(didJump).isEqualTo(1)
-
- job.cancel()
- }
-
- @Test
- fun keyReuse_unsupported_success() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = object : PagingSource<Int, Int>() {
- var loads = 0
-
- override val keyReuseSupported: Boolean
- get() = true
-
- override suspend fun load(params: LoadParams<Int>) = when (params) {
- is LoadParams.Refresh -> Page(listOf(0), 0, 0)
- else -> Page<Int, Int>(
- listOf(),
- if (loads < 3) loads else null,
- if (loads < 3) loads else null
- )
- }.also {
- loads++
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- },
- config = config,
- retryFlow = retryBus.flow
- )
-
- // Trigger collection on flow.
- val job = launch {
- pager.pageEventFlow.collect { }
- }
-
- advanceUntilIdle()
-
- // Trigger first prepend with key = 0
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ fun jump_idempotent_prependOrAppend() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3,
+ jumpThreshold = 10
)
- )
- advanceUntilIdle()
-
- // Trigger second prepend with key = 0
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
-
- job.cancel()
- }
- }
-
- @Test
- fun keyReuse_unsupported_failure() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = object : PagingSource<Int, Int>() {
- override val keyReuseSupported = false
-
- override suspend fun load(params: LoadParams<Int>) = when (params) {
- is LoadParams.Refresh -> Page(listOf(0, 0), 0, 0)
- else -> Page(listOf(0), 0, 0)
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- },
- config = config,
- retryFlow = retryBus.flow
- )
-
- // Trigger collection on flow.
- launch {
- // Assert second prepend re-using key = 0 leads to IllegalStateException
- assertFailsWith<IllegalStateException> {
- pager.pageEventFlow.collect { }
+ var didJump = 0
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = pagingSourceFactory(),
+ config = config,
+ retryFlow = retryBus.flow,
+ previousPagingState = null,
+ ) {
+ didJump++
}
- }
+ // Trigger collection on flow to init jump detection job.
+ val job = launch { pager.pageEventFlow.collect {} }
advanceUntilIdle()
- // Trigger first prepend with key = 0
+ // This would trigger both append and prepend because of processHint logic
pager.accessHint(
ViewportHint.Access(
pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
+ indexInPage = -50,
+ presentedItemsBefore = -50,
presentedItemsAfter = 0,
originalPageOffsetFirst = 0,
originalPageOffsetLast = 0
@@ -3457,128 +3530,310 @@
)
advanceUntilIdle()
- // Trigger second prepend with key = 0
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- }
- }
-
- @Test
- fun keyReuse_supported() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = object : PagingSource<Int, Int>() {
- var loads = 0
-
- override val keyReuseSupported: Boolean
- get() = true
-
- override suspend fun load(params: LoadParams<Int>) = when (params) {
- is LoadParams.Refresh -> Page(listOf(0), 0, 0)
- else -> Page<Int, Int>(
- listOf(),
- if (loads < 3) 0 else null,
- if (loads < 3) 0 else null
- )
- }.also {
- loads++
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- },
- config = config,
- retryFlow = retryBus.flow
- )
-
- // Trigger collection on flow.
- val job = launch {
- pager.pageEventFlow.collect { }
- }
-
- advanceUntilIdle()
-
- // Trigger first prepend with key = 0
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
-
- // Trigger second prepend with key = 0
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
+ // even though both append / prepend flows sent jumping hint, should only trigger
+ // jump once
+ assertThat(didJump).isEqualTo(1)
job.cancel()
}
- }
@Test
- fun initializeHintAfterEmpty() = testScope.runTest {
- val pageFetcherSnapshot = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = TestPagingSource(),
- config = config,
- retryFlow = emptyFlow(),
- )
- collectSnapshotData(pageFetcherSnapshot) { state, _ ->
+ fun jump_idempotent_multipleJumpHints() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3,
+ jumpThreshold = 10
+ )
+ var didJump = 0
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = pagingSourceFactory(),
+ config = config,
+ retryFlow = retryBus.flow,
+ previousPagingState = null,
+ ) {
+ didJump++
+ }
+ // Trigger collection on flow to init jump detection job.
+ val job = launch { pager.pageEventFlow.collect {} }
+
advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(range = 50..51),
+
+ // This would trigger both append and prepend because of processHint logic
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -50,
+ presentedItemsBefore = -50,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- pageFetcherSnapshot.accessHint(ViewportHint.Initial(0, 0, 0, 0))
- advanceUntilIdle()
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- localLoadStateUpdate<Int>(
- appendLocal = Loading,
- prependLocal = Loading
- ),
- createPrepend(pageOffset = -1, range = 49..49, endState = Loading),
- createAppend(pageOffset = 1, range = 52..52),
+ // send second jump hint as well
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -50,
+ presentedItemsBefore = -50,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
+
+ advanceUntilIdle()
+
+ // even though both append / prepend flows sent jumping hint, and a second jump hint
+ // was sent, they should only trigger jump once
+ assertThat(didJump).isEqualTo(1)
+
+ job.cancel()
}
- }
+
+ @Test
+ fun keyReuse_unsupported_success() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource =
+ object : PagingSource<Int, Int>() {
+ var loads = 0
+
+ override val keyReuseSupported: Boolean
+ get() = true
+
+ override suspend fun load(params: LoadParams<Int>) =
+ when (params) {
+ is LoadParams.Refresh -> Page(listOf(0), 0, 0)
+ else ->
+ Page<Int, Int>(
+ listOf(),
+ if (loads < 3) loads else null,
+ if (loads < 3) loads else null
+ )
+ }.also { loads++ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? =
+ null
+ },
+ config = config,
+ retryFlow = retryBus.flow
+ )
+
+ // Trigger collection on flow.
+ val job = launch { pager.pageEventFlow.collect {} }
+
+ advanceUntilIdle()
+
+ // Trigger first prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ // Trigger second prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun keyReuse_unsupported_failure() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource =
+ object : PagingSource<Int, Int>() {
+ override val keyReuseSupported = false
+
+ override suspend fun load(params: LoadParams<Int>) =
+ when (params) {
+ is LoadParams.Refresh -> Page(listOf(0, 0), 0, 0)
+ else -> Page(listOf(0), 0, 0)
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? =
+ null
+ },
+ config = config,
+ retryFlow = retryBus.flow
+ )
+
+ // Trigger collection on flow.
+ launch {
+ // Assert second prepend re-using key = 0 leads to IllegalStateException
+ assertFailsWith<IllegalStateException> { pager.pageEventFlow.collect {} }
+ }
+
+ advanceUntilIdle()
+
+ // Trigger first prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ // Trigger second prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ }
+ }
+
+ @Test
+ fun keyReuse_supported() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource =
+ object : PagingSource<Int, Int>() {
+ var loads = 0
+
+ override val keyReuseSupported: Boolean
+ get() = true
+
+ override suspend fun load(params: LoadParams<Int>) =
+ when (params) {
+ is LoadParams.Refresh -> Page(listOf(0), 0, 0)
+ else ->
+ Page<Int, Int>(
+ listOf(),
+ if (loads < 3) 0 else null,
+ if (loads < 3) 0 else null
+ )
+ }.also { loads++ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? =
+ null
+ },
+ config = config,
+ retryFlow = retryBus.flow
+ )
+
+ // Trigger collection on flow.
+ val job = launch { pager.pageEventFlow.collect {} }
+
+ advanceUntilIdle()
+
+ // Trigger first prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ // Trigger second prepend with key = 0
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun initializeHintAfterEmpty() =
+ testScope.runTest {
+ val pageFetcherSnapshot =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = TestPagingSource(),
+ config = config,
+ retryFlow = emptyFlow(),
+ )
+ collectSnapshotData(pageFetcherSnapshot) { state, _ ->
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(range = 50..51),
+ )
+
+ pageFetcherSnapshot.accessHint(ViewportHint.Initial(0, 0, 0, 0))
+ advanceUntilIdle()
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ localLoadStateUpdate<Int>(appendLocal = Loading, prependLocal = Loading),
+ createPrepend(pageOffset = -1, range = 49..49, endState = Loading),
+ createAppend(pageOffset = 1, range = 52..52),
+ )
+ }
+ }
@OptIn(DelicateCoroutinesApi::class)
@Test
fun pageEventSentAfterChannelClosed() = runTest {
- val pager = PageFetcherSnapshot(
- initialKey = 50,
- pagingSource = TestPagingSource(loadDelay = 100),
- config = config,
- retryFlow = retryBus.flow
- )
+ val pager =
+ PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = TestPagingSource(loadDelay = 100),
+ config = config,
+ retryFlow = retryBus.flow
+ )
- val deferred = GlobalScope.async {
- pager.pageEventFlow.collect { }
- }
+ val deferred = GlobalScope.async { pager.pageEventFlow.collect {} }
pager.close()
deferred.await()
@@ -3586,77 +3841,89 @@
@Test
fun generationalViewportHint_shouldPrioritizeOver_presenterUpdates() {
- val prependHint = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = -10,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ val prependHint =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = -10,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
- val prependHintWithPresenterUpdate = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Access(
- pageOffset = -10,
- indexInPage = 0,
- presentedItemsBefore = -5,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = -10,
- originalPageOffsetLast = 0
+ val prependHintWithPresenterUpdate =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Access(
+ pageOffset = -10,
+ indexInPage = 0,
+ presentedItemsBefore = -5,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = -10,
+ originalPageOffsetLast = 0
+ )
)
- )
assertTrue { prependHintWithPresenterUpdate.shouldPrioritizeOver(prependHint, PREPEND) }
- val appendHint = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = -10,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ val appendHint =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = -10,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
- val appendHintWithPresenterUpdate = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Access(
- pageOffset = 10,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = -5,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 10
+ val appendHintWithPresenterUpdate =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Access(
+ pageOffset = 10,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = -5,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 10
+ )
)
- )
assertTrue { appendHintWithPresenterUpdate.shouldPrioritizeOver(appendHint, APPEND) }
}
@Test
fun generationalViewportHint_shouldPrioritizeAccessOverInitial() {
- val accessHint = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ val accessHint =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
- val initialHint = GenerationalViewportHint(
- generationId = 0,
- hint = ViewportHint.Initial(
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ val initialHint =
+ GenerationalViewportHint(
+ generationId = 0,
+ hint =
+ ViewportHint.Initial(
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
assertTrue { accessHint.shouldPrioritizeOver(initialHint, PREPEND) }
assertFalse { initialHint.shouldPrioritizeOver(accessHint, PREPEND) }
@@ -3665,142 +3932,153 @@
}
@Test
- fun close_cancelsCollectionFromLoadResultInvalid() = testScope.runTest {
- val pagingSource = object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- return LoadResult.Invalid()
- }
+ fun close_cancelsCollectionFromLoadResultInvalid() =
+ testScope.runTest {
+ val pagingSource =
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ return LoadResult.Invalid()
+ }
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
- fail("should not reach here")
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
+ fail("should not reach here")
+ }
+ }
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { _, job ->
+
+ // Start initial load but this load should return LoadResult.Invalid
+ // wait some time for the invalid result handler to close the page event flow
+ advanceTimeBy(1000)
+
+ assertTrue { !job.isActive }
}
}
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { _, job ->
-
- // Start initial load but this load should return LoadResult.Invalid
- // wait some time for the invalid result handler to close the page event flow
- advanceTimeBy(1000)
-
- assertTrue { !job.isActive }
- }
- }
@Test
- fun refresh_cancelsCollectionFromLoadResultInvalid() = testScope.runTest {
- val pagingSource = TestPagingSource()
- pagingSource.nextLoadResult = LoadResult.Invalid()
-
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, job ->
-
- // Start initial load but this load should return LoadResult.Invalid
- // Wait some time for the result handler to close the page event flow
- advanceUntilIdle()
-
- // The flow's last page event should be the original Loading event before it
- // was closed by the invalid result handler
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- )
- // make sure no more new events are sent to UI
- assertThat(state.newEvents()).isEmpty()
- assertTrue(pagingSource.invalid)
- assertTrue { !job.isActive }
- }
- }
-
- @Test
- fun append_cancelsCollectionFromLoadResultInvalid() = testScope.runTest {
- val pagingSource = TestPagingSource()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-
- collectSnapshotData(pager) { state, job ->
-
- advanceUntilIdle()
-
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
- // append a page
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- // now return LoadResult.Invalid
+ fun refresh_cancelsCollectionFromLoadResultInvalid() =
+ testScope.runTest {
+ val pagingSource = TestPagingSource()
pagingSource.nextLoadResult = LoadResult.Invalid()
- advanceUntilIdle()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- // Only a Loading update for Append should be sent and it should not complete
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- )
- assertTrue(pagingSource.invalid)
- assertThat(state.newEvents()).isEmpty()
- assertThat(!job.isActive)
+ collectSnapshotData(pager) { state, job ->
+
+ // Start initial load but this load should return LoadResult.Invalid
+ // Wait some time for the result handler to close the page event flow
+ advanceUntilIdle()
+
+ // The flow's last page event should be the original Loading event before it
+ // was closed by the invalid result handler
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ )
+ // make sure no more new events are sent to UI
+ assertThat(state.newEvents()).isEmpty()
+ assertTrue(pagingSource.invalid)
+ assertTrue { !job.isActive }
+ }
}
- }
@Test
- fun prepend_cancelsCollectionFromLoadResultInvalid() = testScope.runTest {
- val pagingSource = TestPagingSource()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+ fun append_cancelsCollectionFromLoadResultInvalid() =
+ testScope.runTest {
+ val pagingSource = TestPagingSource()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- collectSnapshotData(pager) { state, job ->
+ collectSnapshotData(pager) { state, job ->
+ advanceUntilIdle()
- advanceUntilIdle()
-
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51)
- )
- // now prepend
- pager.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -1,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+ // append a page
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
- // now return LoadResult.Invalid.
- pagingSource.nextLoadResult = LoadResult.Invalid()
+ // now return LoadResult.Invalid
+ pagingSource.nextLoadResult = LoadResult.Invalid()
- advanceUntilIdle()
+ advanceUntilIdle()
- // Only a Loading update for Prepend should be sent and it should not complete
- assertThat(state.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- )
- assertTrue(pagingSource.invalid)
- assertThat(state.newEvents()).isEmpty()
- assertThat(!job.isActive)
+ // Only a Loading update for Append should be sent and it should not complete
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ )
+ assertTrue(pagingSource.invalid)
+ assertThat(state.newEvents()).isEmpty()
+ assertThat(!job.isActive)
+ }
}
- }
+
+ @Test
+ fun prepend_cancelsCollectionFromLoadResultInvalid() =
+ testScope.runTest {
+ val pagingSource = TestPagingSource()
+ val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+ collectSnapshotData(pager) { state, job ->
+ advanceUntilIdle()
+
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51)
+ )
+ // now prepend
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -1,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ // now return LoadResult.Invalid.
+ pagingSource.nextLoadResult = LoadResult.Invalid()
+
+ advanceUntilIdle()
+
+ // Only a Loading update for Prepend should be sent and it should not complete
+ assertThat(state.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ )
+ assertTrue(pagingSource.invalid)
+ assertThat(state.newEvents()).isEmpty()
+ assertThat(!job.isActive)
+ }
+ }
internal class CollectedPageEvents<T : Any>(val pageEvents: ArrayList<PageEvent<T>>) {
var lastIndex = 0
- fun newEvents(): List<PageEvent<T>> = when {
- pageEvents.isEmpty() -> pageEvents.toList()
- lastIndex > pageEvents.lastIndex -> listOf()
- else -> pageEvents.lastIndex.let {
- val result = pageEvents.slice(lastIndex..it)
- lastIndex = it + 1
- result
+
+ fun newEvents(): List<PageEvent<T>> =
+ when {
+ pageEvents.isEmpty() -> pageEvents.toList()
+ lastIndex > pageEvents.lastIndex -> listOf()
+ else ->
+ pageEvents.lastIndex.let {
+ val result = pageEvents.slice(lastIndex..it)
+ lastIndex = it + 1
+ result
+ }
}
- }
}
@Suppress("SuspendFunctionOnCoroutineScope")
@@ -3818,9 +4096,7 @@
}
internal fun <T : Any> PageFetcher<*, T>.pageEvents(): Flow<PageEvent<T>> {
- return flow.flatMapLatest {
- it.flow
- }
+ return flow.flatMapLatest { it.flow }
}
internal suspend fun <T : Any> PageFetcher<*, T>.collectEvents(
@@ -3829,20 +4105,22 @@
val collectionScope = MultiGenerationCollectionScopeImpl<T>()
val eventsByGeneration = collectionScope.eventsByGeneration
coroutineScope {
- val collectionJob = launch(start = CoroutineStart.LAZY) {
- flow.flatMapLatest { data ->
- collectionScope.uiReceiver = data.uiReceiver
- collectionScope.hintReceiver = data.hintReceiver
- val generationEvents = mutableListOf<PageEvent<T>>().also {
- eventsByGeneration.add(it)
- }
- collectionScope.generationCount.value = eventsByGeneration.size
- data.flow.onEach {
- generationEvents.add(it)
- collectionScope.eventCount.value += 1
- }
- }.collect() // just keep collecting, block will cancel eventually
- }
+ val collectionJob =
+ launch(start = CoroutineStart.LAZY) {
+ flow
+ .flatMapLatest { data ->
+ collectionScope.uiReceiver = data.uiReceiver
+ collectionScope.hintReceiver = data.hintReceiver
+ val generationEvents =
+ mutableListOf<PageEvent<T>>().also { eventsByGeneration.add(it) }
+ collectionScope.generationCount.value = eventsByGeneration.size
+ data.flow.onEach {
+ generationEvents.add(it)
+ collectionScope.eventCount.value += 1
+ }
+ }
+ .collect() // just keep collecting, block will cancel eventually
+ }
launch {
collectionScope.stopped.await()
collectionJob.cancel()
@@ -3867,8 +4145,9 @@
}
testScope.runCurrent()
expected.forEachIndexed { index, list ->
- assertThat(actual.getOrNull(index)
- ?: emptyList<PageEvent<T>>()).containsExactlyElementsIn(list).inOrder()
+ assertThat(actual.getOrNull(index) ?: emptyList<PageEvent<T>>())
+ .containsExactlyElementsIn(list)
+ .inOrder()
}
assertThat(actual.size).isEqualTo(expected.size)
}
@@ -3879,7 +4158,9 @@
val eventsByGeneration: List<List<PageEvent<T>>>
val uiReceiver: UiReceiver?
val hintReceiver: HintReceiver?
+
suspend fun stop()
+
fun accessHint(viewportHint: ViewportHint) {
hintReceiver!!.accessHint(viewportHint)
}
@@ -3894,9 +4175,7 @@
}
suspend fun awaitEventCount(limit: Int) {
- eventCount.takeWhile {
- it < limit
- }.collect()
+ eventCount.takeWhile { it < limit }.collect()
}
}
@@ -3908,6 +4187,7 @@
override var hintReceiver: HintReceiver? = null,
) : MultiGenerationCollectionScope<T> {
val stopped = CompletableDeferred<Unit>()
+
override suspend fun stop() {
stopped.complete(Unit)
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherTest.kt
index 88e7a2f..9e7c825 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PageFetcherTest.kt
@@ -57,228 +57,241 @@
class PageFetcherTest {
private val testScope = TestScope(UnconfinedTestDispatcher())
private val pagingSourceFactory = suspend { TestPagingSource() }
- private val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3
- )
-
- @Test
- fun initialize() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
- fetcherState.job.cancel()
- }
-
- @Test
- fun refresh() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(2, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
- fetcherState.job.cancel()
- }
-
- @Test
- fun refresh_sourceEndOfPaginationReached() = testScope.runTest {
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
+ private val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3
)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(2, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
- fetcherState.job.cancel()
- }
@Test
- fun refresh_remoteEndOfPaginationReached() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
- }
+ fun initialize() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+ fetcherState.job.cancel()
}
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
- assertEquals(1, remoteMediator.loadEvents.size)
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(2, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
- assertEquals(2, remoteMediator.loadEvents.size)
- fetcherState.job.cancel()
- }
@Test
- fun refresh_fromPagingSource() = testScope.runTest {
- var pagingSource: PagingSource<Int, Int>? = null
- val pagingSourceFactory = suspend {
- TestPagingSource().also { pagingSource = it }
+ fun refresh() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ fetcherState.job.cancel()
}
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
-
- val oldPagingSource = pagingSource
- oldPagingSource?.invalidate()
- advanceUntilIdle()
-
- assertEquals(2, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
- assertNotEquals(oldPagingSource, pagingSource)
- assertTrue { oldPagingSource!!.invalid }
- fetcherState.job.cancel()
- }
@Test
- fun refresh_callsInvalidate() = testScope.runTest {
- var pagingSource: PagingSource<Int, Int>? = null
- val pagingSourceFactory = suspend {
- TestPagingSource().also { pagingSource = it }
+ fun refresh_sourceEndOfPaginationReached() =
+ testScope.runTest {
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ fetcherState.job.cancel()
}
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- var didCallInvalidate = false
- pagingSource?.registerInvalidatedCallback { didCallInvalidate = true }
-
- advanceUntilIdle()
-
- assertEquals(1, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(2, fetcherState.pagingDataList.size)
- assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
- assertTrue { didCallInvalidate }
- fetcherState.job.cancel()
- }
@Test
- fun refresh_invalidatePropagatesThroughLoadResultInvalid() = testScope.runTest {
- val pagingSources = mutableListOf<TestPagingSource>()
- val pageFetcher = PageFetcher(
- pagingSourceFactory = {
- TestPagingSource().also {
- // make this initial load return LoadResult.Invalid to see if new paging
- // source is generated
- if (pagingSources.size == 0) it.nextLoadResult = LoadResult.Invalid()
- it.getRefreshKeyResult = 30
- pagingSources.add(it)
+ fun refresh_remoteEndOfPaginationReached() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
}
- },
- initialKey = 50,
- config = config,
- )
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
- val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
+ advanceUntilIdle()
- // should have two PagingData returned, one for each paging source
- assertThat(fetcherState.pagingDataList.size).isEqualTo(2)
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+ assertEquals(1, remoteMediator.loadEvents.size)
- // First PagingData only returns a loading state because invalidation prevents load
- // completion
- assertTrue(pagingSources[0].invalid)
- assertThat(fetcherState.pageEventLists[0]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading)
- )
- // previous load() returning LoadResult.Invalid should trigger a new paging source
- // retrying with the same load params, this should return a refresh starting
- // from getRefreshKey() = 30
- assertTrue(!pagingSources[1].invalid)
- assertThat(fetcherState.pageEventLists[1]).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(30..31)
- )
+ pageFetcher.refresh()
+ advanceUntilIdle()
- assertThat(pagingSources[0]).isNotEqualTo(pagingSources[1])
- fetcherState.job.cancel()
- }
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ assertEquals(2, remoteMediator.loadEvents.size)
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun refresh_fromPagingSource() =
+ testScope.runTest {
+ var pagingSource: PagingSource<Int, Int>? = null
+ val pagingSourceFactory = suspend { TestPagingSource().also { pagingSource = it } }
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+
+ val oldPagingSource = pagingSource
+ oldPagingSource?.invalidate()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ assertNotEquals(oldPagingSource, pagingSource)
+ assertTrue { oldPagingSource!!.invalid }
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun refresh_callsInvalidate() =
+ testScope.runTest {
+ var pagingSource: PagingSource<Int, Int>? = null
+ val pagingSourceFactory = suspend { TestPagingSource().also { pagingSource = it } }
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ var didCallInvalidate = false
+ pagingSource?.registerInvalidatedCallback { didCallInvalidate = true }
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ assertTrue { didCallInvalidate }
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun refresh_invalidatePropagatesThroughLoadResultInvalid() =
+ testScope.runTest {
+ val pagingSources = mutableListOf<TestPagingSource>()
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = {
+ TestPagingSource().also {
+ // make this initial load return LoadResult.Invalid to see if new paging
+ // source is generated
+ if (pagingSources.size == 0) it.nextLoadResult = LoadResult.Invalid()
+ it.getRefreshKeyResult = 30
+ pagingSources.add(it)
+ }
+ },
+ initialKey = 50,
+ config = config,
+ )
+
+ val fetcherState = collectFetcherState(pageFetcher)
+ advanceUntilIdle()
+
+ // should have two PagingData returned, one for each paging source
+ assertThat(fetcherState.pagingDataList.size).isEqualTo(2)
+
+ // First PagingData only returns a loading state because invalidation prevents load
+ // completion
+ assertTrue(pagingSources[0].invalid)
+ assertThat(fetcherState.pageEventLists[0])
+ .containsExactly(localLoadStateUpdate<Int>(refreshLocal = Loading))
+ // previous load() returning LoadResult.Invalid should trigger a new paging source
+ // retrying with the same load params, this should return a refresh starting
+ // from getRefreshKey() = 30
+ assertTrue(!pagingSources[1].invalid)
+ assertThat(fetcherState.pageEventLists[1])
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(30..31)
+ )
+
+ assertThat(pagingSources[0]).isNotEqualTo(pagingSources[1])
+ fetcherState.job.cancel()
+ }
@Test
fun append_invalidatePropagatesThroughLoadResultInvalid() =
testScope.runTest {
val pagingSources = mutableListOf<TestPagingSource>()
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
- initialKey = 50,
- config = config,
- )
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
+ initialKey = 50,
+ config = config,
+ )
val fetcherState = collectFetcherState(pageFetcher)
advanceUntilIdle()
assertThat(fetcherState.pageEventLists.size).isEqualTo(1)
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51),
- )
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51),
+ )
// append a page
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
)
- )
// now return LoadResult.Invalid
pagingSources[0].nextLoadResult = LoadResult.Invalid()
advanceUntilIdle()
// make sure the append load never completes
- assertThat(fetcherState.pageEventLists[0].last()).isEqualTo(
- localLoadStateUpdate<Int>(appendLocal = Loading),
- )
+ assertThat(fetcherState.pageEventLists[0].last())
+ .isEqualTo(
+ localLoadStateUpdate<Int>(appendLocal = Loading),
+ )
// the invalid result handler should exit the append load loop gracefully and allow
// fetcher to generate a new paging source
@@ -286,10 +299,11 @@
assertTrue(pagingSources[0].invalid)
// second generation should load refresh with cached append load params
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(51..52)
- )
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(51..52)
+ )
fetcherState.job.cancel()
}
@@ -298,39 +312,44 @@
fun prepend_invalidatePropagatesThroughLoadResultInvalid() =
testScope.runTest {
val pagingSources = mutableListOf<TestPagingSource>()
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
- initialKey = 50,
- config = config,
- )
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
+ initialKey = 50,
+ config = config,
+ )
val fetcherState = collectFetcherState(pageFetcher)
advanceUntilIdle()
assertThat(fetcherState.pageEventLists.size).isEqualTo(1)
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(50..51),
- )
- // prepend a page
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -1,
- presentedItemsBefore = 0,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(50..51),
)
- )
+ // prepend a page
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -1,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
// now return LoadResult.Invalid
pagingSources[0].nextLoadResult = LoadResult.Invalid()
advanceUntilIdle()
// make sure the prepend load never completes
- assertThat(fetcherState.pageEventLists[0].last()).isEqualTo(
- localLoadStateUpdate<Int>(prependLocal = Loading),
- )
+ assertThat(fetcherState.pageEventLists[0].last())
+ .isEqualTo(
+ localLoadStateUpdate<Int>(prependLocal = Loading),
+ )
// the invalid result should exit the prepend load loop gracefully and allow fetcher to
// generate a new paging source
@@ -338,314 +357,31 @@
assertTrue(pagingSources[0].invalid)
// second generation should load refresh with cached prepend load params
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(refreshLocal = Loading),
- createRefresh(49..50)
- )
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(refreshLocal = Loading),
+ createRefresh(49..50)
+ )
fetcherState.job.cancel()
}
@Test
- fun refresh_closesCollection() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ fun refresh_closesCollection() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- var pagingDataCount = 0
- var didFinish = false
- val job = launch {
- pageFetcher.flow.collect { pagedData ->
- pagingDataCount++
- pagedData.flow
- .onCompletion {
- didFinish = true
- }
- // Return immediately to avoid blocking cancellation. This is analogous to
- // logic which would process a single PageEvent and doesn't suspend
- // indefinitely, which is what we expect to happen.
- .collect { }
- }
- }
-
- advanceUntilIdle()
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(2, pagingDataCount)
- assertTrue { didFinish }
- job.cancel()
- }
-
- @Test
- fun refresh_closesUncollectedPageEventCh() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
-
- val pagingDatas = mutableListOf<PagingData<Int>>()
- val didFinish = mutableListOf<Boolean>()
- val job = launch {
- pageFetcher.flow.collectIndexed { index, pagingData ->
- pagingDatas.add(pagingData)
- if (index != 1) {
- pagingData.flow
- .onStart {
- didFinish.add(false)
- }
- .onCompletion {
- if (index < 2) didFinish[index] = true
- }
+ var pagingDataCount = 0
+ var didFinish = false
+ val job = launch {
+ pageFetcher.flow.collect { pagedData ->
+ pagingDataCount++
+ pagedData.flow
+ .onCompletion { didFinish = true }
// Return immediately to avoid blocking cancellation. This is analogous to
// logic which would process a single PageEvent and doesn't suspend
// indefinitely, which is what we expect to happen.
- .collect { }
- }
- }
- }
-
- advanceUntilIdle()
-
- pageFetcher.refresh()
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertEquals(3, pagingDatas.size)
- withContext(coroutineContext) {
- // This should complete immediately without advanceUntilIdle().
- val deferred = async { pagingDatas[1].flow.collect { } }
- deferred.await()
- }
-
- assertEquals(listOf(true, false), didFinish)
- job.cancel()
- }
-
- @Test
- fun invalidate_unregistersListener() = testScope.runTest {
- var i = 0
- val pagingSources = mutableListOf<PagingSource<Int, Int>>()
- val pageFetcher = PageFetcher(
- pagingSourceFactory = {
- TestPagingSource().also {
- pagingSources.add(it)
-
- if (i == 0) {
- // Force PageFetcher to create a second PagingSource before finding a
- // valid one when instantiating first generation.
- it.invalidate()
- }
- i++
- }
- },
- initialKey = 50,
- config = config
- )
-
- val state = collectFetcherState(pageFetcher)
-
- // Wait for first generation to instantiate.
- advanceUntilIdle()
-
- // Providing an invalid PagingSource should automatically trigger invalidation
- // regardless of when the invalidation callback is registered.
- assertThat(pagingSources).hasSize(2)
-
- // The first PagingSource is immediately invalid, so we shouldn't keep an invalidate
- // listener registered on it.
- assertThat(pagingSources[0].invalidateCallbackCount).isEqualTo(0)
- assertThat(pagingSources[1].invalidateCallbackCount).isEqualTo(1)
-
- // Trigger new generation, should unregister from older PagingSource.
- pageFetcher.refresh()
- advanceUntilIdle()
- assertThat(pagingSources).hasSize(3)
- assertThat(pagingSources[1].invalidateCallbackCount).isEqualTo(0)
- assertThat(pagingSources[2].invalidateCallbackCount).isEqualTo(1)
-
- state.job.cancel()
- }
-
- @Test
- fun collectTwice() = testScope.runTest {
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
- val fetcherState2 = collectFetcherState(pageFetcher)
- advanceUntilIdle()
- fetcherState.job.cancel()
- fetcherState2.job.cancel()
- advanceUntilIdle()
- assertThat(fetcherState.pagingDataList.size).isEqualTo(1)
- assertThat(fetcherState2.pagingDataList.size).isEqualTo(1)
- assertThat(fetcherState.pageEventLists.first()).isNotEmpty()
- assertThat(fetcherState2.pageEventLists.first()).isNotEmpty()
- }
-
- @Test
- fun remoteMediator_initializeSkip() = testScope.runTest {
- val remoteMediatorMock = RemoteMediatorMock().apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config, remoteMediatorMock)
-
- advanceUntilIdle()
-
- // Assert onInitialize is not called until collection.
- assertTrue { remoteMediatorMock.initializeEvents.isEmpty() }
-
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, remoteMediatorMock.initializeEvents.size)
- assertEquals(0, remoteMediatorMock.loadEvents.size)
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_initializeLaunch() = testScope.runTest {
- val remoteMediatorMock = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- }
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config, remoteMediatorMock)
-
- advanceUntilIdle()
-
- // Assert onInitialize is not called until collection.
- assertTrue { remoteMediatorMock.initializeEvents.isEmpty() }
-
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- assertEquals(1, remoteMediatorMock.initializeEvents.size)
- assertEquals(1, remoteMediatorMock.loadEvents.size)
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun remoteMediator_load() = testScope.runTest {
- val remoteMediatorMock = RemoteMediatorMock()
- val pageFetcher = PageFetcher(pagingSourceFactory, 97, config, remoteMediatorMock)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
-
- // Assert onBoundary is not called for non-terminal page load.
- assertTrue { remoteMediatorMock.loadEvents.isEmpty() }
-
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 0,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
-
- advanceUntilIdle()
-
- // Assert onBoundary is called for terminal page load.
- assertEquals(1, remoteMediatorMock.loadEvents.size)
- assertEquals(APPEND, remoteMediatorMock.loadEvents[0].loadType)
-
- fetcherState.job.cancel()
- }
-
- @Test
- fun jump() = testScope.runTest {
- withContext(coroutineContext) {
- val pagingSources = mutableListOf<PagingSource<Int, Int>>()
- val pagingSourceFactory = suspend {
- TestPagingSource().also { pagingSources.add(it) }
- }
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2,
- maxSize = 3,
- jumpThreshold = 10
- )
- val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- val fetcherState = collectFetcherState(pageFetcher)
-
- advanceUntilIdle()
- assertThat(fetcherState.newEvents()).isEqualTo(
- listOf(
- localLoadStateUpdate(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
- )
-
- // Jump due to sufficiently large presentedItemsBefore
- fetcherState.pagingDataList[0].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- // indexInPage value is incorrect, but should not be considered for jumps
- indexInPage = 0,
- presentedItemsBefore = -20,
- presentedItemsAfter = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertTrue { pagingSources[0].invalid }
- // Assert no new events added to current generation
- assertEquals(2, fetcherState.pageEventLists[0].size)
- assertThat(fetcherState.newEvents()).isEqualTo(
- listOf(
- localLoadStateUpdate(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
- )
-
- // Jump due to sufficiently large presentedItemsAfter
- fetcherState.pagingDataList[1].hintReceiver.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- // indexInPage value is incorrect, but should not be considered for jumps
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = -20,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
- )
- advanceUntilIdle()
- assertTrue { pagingSources[1].invalid }
- // Assert no new events added to current generation
- assertEquals(2, fetcherState.pageEventLists[1].size)
- assertThat(fetcherState.newEvents()).isEqualTo(
- listOf(
- localLoadStateUpdate(refreshLocal = Loading),
- createRefresh(range = 50..51)
- )
- )
-
- fetcherState.job.cancel()
- }
- }
-
- @Test
- fun checksFactoryForNewInstance() = testScope.runTest {
- withContext(coroutineContext) {
- val pagingSource = TestPagingSource()
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- initialLoadSize = 2
- )
- val pageFetcher = PageFetcher(
- pagingSourceFactory = suspend { pagingSource },
- initialKey = 50,
- config = config
- )
- val job = testScope.launch {
- assertFailsWith<IllegalStateException> {
- pageFetcher.flow.collect { }
+ .collect {}
}
}
@@ -654,611 +390,952 @@
pageFetcher.refresh()
advanceUntilIdle()
- assertTrue { job.isCompleted }
+ assertEquals(2, pagingDataCount)
+ assertTrue { didFinish }
+ job.cancel()
}
- }
@Test
- fun pagingSourceInvalidBeforeCallbackAdded() = testScope.runTest {
- var invalidatesFromAdapter = 0
- var i = 0
- var pagingSource: TestPagingSource? = null
- val pager = Pager(PagingConfig(10)) {
- i++
- TestPagingSource().also {
- if (i == 1) {
- it.invalidate()
- }
+ fun refresh_closesUncollectedPageEventCh() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
- advanceUntilIdle()
- it.registerInvalidatedCallback { invalidatesFromAdapter++ }
- pagingSource = it
- }
- }
-
- @OptIn(ExperimentalStdlibApi::class)
- val job = launch {
- pager.flow.collectLatest { pagingData ->
- TestPagingDataPresenter<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
- .collectFrom(pagingData)
- }
- }
-
- advanceUntilIdle()
- pagingSource!!.invalidate()
- advanceUntilIdle()
-
- // InvalidatedCallbacks added after a PagingSource is already invalid should be
- // immediately triggered, so both listeners we add should be triggered.
- assertEquals(2, invalidatesFromAdapter)
- job.cancel()
- }
-
- @Test
- fun pagingSourceInvalidBeforeCallbackAddedCancelsInitialLoad() = testScope.runTest {
- val pagingSources = mutableListOf<PagingSource<Int, Int>>()
- val loadedPages = mutableListOf<Page<Int, Int>>()
-
- var i = 0
- val pager = Pager(PagingConfig(10)) {
- object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- // Suspend and await advanceUntilIdle() before allowing load to complete.
- delay(1000)
- return Page.empty<Int, Int>().also {
- loadedPages.add(it)
- }
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>) = null
- }.also {
- pagingSources.add(it)
-
- if (i++ == 0) {
- it.invalidate()
- }
- }
- }
-
- @OptIn(ExperimentalStdlibApi::class)
- val job = launch {
- pager.flow.collectLatest { pagingData ->
- TestPagingDataPresenter<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
- .collectFrom(pagingData)
- }
- }
-
- // First PagingSource starts immediately invalid and creates a new PagingSource, but does
- // not finish initial page load.
- runCurrent()
- assertThat(pagingSources).hasSize(2)
- assertThat(pagingSources[0].invalid).isTrue()
- assertThat(loadedPages).hasSize(0)
-
- advanceUntilIdle()
-
- // After draining tasks, we should immediately get a second generation which triggers
- // page load, skipping the initial load from first generation due to cancellation.
- assertThat(pagingSources[1].invalid).isFalse()
- assertThat(loadedPages).hasSize(1)
-
- job.cancel()
- }
-
- @Test
- fun cachesPreviousPagingStateOnEmptyPages() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 3,
- )
-
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- }
- val pageFetcher = PageFetcher(
- pagingSourceFactory = suspend {
- TestPagingSource(loadDelay = 1000).also {
- it.getRefreshKeyResult = 30
- }
- },
- initialKey = 50,
- config = config,
- remoteMediator = remoteMediator,
- )
-
- var receiver: HintReceiver? = null
- val job = launch {
- pageFetcher.flow.collectLatest {
- receiver = it.hintReceiver
- it.flow.collect { }
- }
- }
-
- // Allow initial load to finish, so PagingState has non-zero pages.
- advanceUntilIdle()
-
- // Verify remote refresh is called with initial empty case.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(
- loadType = REFRESH,
- state = PagingState(
- pages = listOf(),
- anchorPosition = null,
- config = config,
- leadingPlaceholderCount = 0,
- ),
- )
- )
-
- // Trigger refresh, instantiating second generation.
- pageFetcher.refresh()
-
- // Allow remote refresh to get triggered, but do not let paging source complete initial load
- // for second generation.
- advanceTimeBy(500)
-
- // Verify remote refresh is called with PagingState from first generation.
- val pagingState = PagingState(
- pages = listOf(
- Page(
- data = listOf(50, 51, 52),
- prevKey = 49,
- nextKey = 53,
- itemsBefore = 50,
- itemsAfter = 47,
- )
- ),
- anchorPosition = null,
- config = config,
- leadingPlaceholderCount = 50,
- )
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- // Trigger a hint, which would normally populate anchorPosition. In real world scenario,
- // this would happen as a result of UI still presenting first generation since second
- // generation never finished loading yet.
- receiver?.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
-
- // Trigger refresh instantiating third generation before second has a chance to complete
- // initial load.
- pageFetcher.refresh()
-
- // Wait for all non-canceled loads to complete.
- advanceUntilIdle()
-
- // Verify remote refresh is called with PagingState from first generation, since second
- // generation never loaded any pages.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- job.cancel()
- }
-
- @Test
- fun cachesPreviousPagingStateOnNullHint() = testScope.runTest {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 3,
- )
-
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- }
- val pageFetcher = PageFetcher(
- pagingSourceFactory = suspend {
- TestPagingSource(loadDelay = 1000).also {
- it.getRefreshKeyResult = 30
- }
- },
- initialKey = 50,
- config = config,
- remoteMediator = remoteMediator,
- )
-
- var receiver: HintReceiver? = null
- val job = launch {
- pageFetcher.flow.collectLatest {
- receiver = it.hintReceiver
- it.flow.collect { }
- }
- }
-
- // Allow initial load to finish, so PagingState has non-zero pages.
- advanceUntilIdle()
-
- // Trigger a hint to populate anchorPosition, this should cause PageFetcher to cache this
- // PagingState and use it in next remoteRefresh
- receiver?.accessHint(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 2,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
-
- // Verify remote refresh is called with initial empty case.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(
- loadType = REFRESH,
- state = PagingState(
- pages = listOf(),
- anchorPosition = null,
- config = config,
- leadingPlaceholderCount = 0,
- ),
- )
- )
-
- // Trigger refresh, instantiating second generation.
- pageFetcher.refresh()
-
- // Allow remote refresh to get triggered, and let paging source load finish.
- advanceUntilIdle()
-
- // Verify remote refresh is called with PagingState from first generation.
- val pagingState = PagingState(
- pages = listOf(
- Page(
- data = listOf(50, 51, 52),
- prevKey = 49,
- nextKey = 53,
- itemsBefore = 50,
- itemsAfter = 47,
- )
- ),
- anchorPosition = 50,
- config = config,
- leadingPlaceholderCount = 50,
- )
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- // Trigger refresh instantiating third generation before second has a chance to complete
- // initial load.
- pageFetcher.refresh()
-
- // Wait for all non-canceled loads to complete.
- advanceUntilIdle()
-
- // Verify remote refresh is called with PagingState from first generation, since second
- // generation never loaded any pages.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- job.cancel()
- }
-
- @Test
- fun invalidate_prioritizesGetRefreshKeyReturningNull() = testScope.runTest {
- val loadRequests = mutableListOf<LoadParams<Int>>()
- val pageFetcher = PageFetcher(
- config = PagingConfig(pageSize = 1),
- initialKey = 0,
- pagingSourceFactory = {
- object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- loadRequests.add(params)
- return LoadResult.Error(Exception("ignored"))
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
- // Should prioritize `null` returned here on invalidation.
- return null
- }
- }
- }
- )
-
- val job = launch {
- pageFetcher.flow.collectLatest {
- it.flow.collect { }
- }
- }
-
- advanceUntilIdle()
- assertThat(loadRequests).hasSize(1)
- assertThat(loadRequests[0].key).isEqualTo(0)
-
- pageFetcher.refresh()
- advanceUntilIdle()
- assertThat(loadRequests).hasSize(2)
- assertThat(loadRequests[1].key).isEqualTo(null)
-
- job.cancel()
- }
-
- @Test
- fun invalidateBeforeAccessPreservesPagingState() = testScope.runTest {
- withContext(coroutineContext) {
- val config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 3,
- )
- val pagingSources = mutableListOf<TestPagingSource>()
- val pageFetcher = PageFetcher(
- pagingSourceFactory = suspend {
- TestPagingSource(loadDelay = 1000).also {
- pagingSources.add(it)
- }
- },
- initialKey = 50,
- config = config,
- )
-
- lateinit var pagingData: PagingData<Int>
+ val pagingDatas = mutableListOf<PagingData<Int>>()
+ val didFinish = mutableListOf<Boolean>()
val job = launch {
- pageFetcher.flow.collectLatest {
- pagingData = it
- it.flow.collect { }
+ pageFetcher.flow.collectIndexed { index, pagingData ->
+ pagingDatas.add(pagingData)
+ if (index != 1) {
+ pagingData.flow
+ .onStart { didFinish.add(false) }
+ .onCompletion { if (index < 2) didFinish[index] = true }
+ // Return immediately to avoid blocking cancellation. This is analogous
+ // to
+ // logic which would process a single PageEvent and doesn't suspend
+ // indefinitely, which is what we expect to happen.
+ .collect {}
+ }
}
}
advanceUntilIdle()
- // Trigger access to allow PagingState to get populated for next generation.
- pagingData.hintReceiver.accessHint(
+ pageFetcher.refresh()
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(3, pagingDatas.size)
+ withContext(coroutineContext) {
+ // This should complete immediately without advanceUntilIdle().
+ val deferred = async { pagingDatas[1].flow.collect {} }
+ deferred.await()
+ }
+
+ assertEquals(listOf(true, false), didFinish)
+ job.cancel()
+ }
+
+ @Test
+ fun invalidate_unregistersListener() =
+ testScope.runTest {
+ var i = 0
+ val pagingSources = mutableListOf<PagingSource<Int, Int>>()
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = {
+ TestPagingSource().also {
+ pagingSources.add(it)
+
+ if (i == 0) {
+ // Force PageFetcher to create a second PagingSource before finding
+ // a
+ // valid one when instantiating first generation.
+ it.invalidate()
+ }
+ i++
+ }
+ },
+ initialKey = 50,
+ config = config
+ )
+
+ val state = collectFetcherState(pageFetcher)
+
+ // Wait for first generation to instantiate.
+ advanceUntilIdle()
+
+ // Providing an invalid PagingSource should automatically trigger invalidation
+ // regardless of when the invalidation callback is registered.
+ assertThat(pagingSources).hasSize(2)
+
+ // The first PagingSource is immediately invalid, so we shouldn't keep an invalidate
+ // listener registered on it.
+ assertThat(pagingSources[0].invalidateCallbackCount).isEqualTo(0)
+ assertThat(pagingSources[1].invalidateCallbackCount).isEqualTo(1)
+
+ // Trigger new generation, should unregister from older PagingSource.
+ pageFetcher.refresh()
+ advanceUntilIdle()
+ assertThat(pagingSources).hasSize(3)
+ assertThat(pagingSources[1].invalidateCallbackCount).isEqualTo(0)
+ assertThat(pagingSources[2].invalidateCallbackCount).isEqualTo(1)
+
+ state.job.cancel()
+ }
+
+ @Test
+ fun collectTwice() =
+ testScope.runTest {
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+ val fetcherState2 = collectFetcherState(pageFetcher)
+ advanceUntilIdle()
+ fetcherState.job.cancel()
+ fetcherState2.job.cancel()
+ advanceUntilIdle()
+ assertThat(fetcherState.pagingDataList.size).isEqualTo(1)
+ assertThat(fetcherState2.pagingDataList.size).isEqualTo(1)
+ assertThat(fetcherState.pageEventLists.first()).isNotEmpty()
+ assertThat(fetcherState2.pageEventLists.first()).isNotEmpty()
+ }
+
+ @Test
+ fun remoteMediator_initializeSkip() =
+ testScope.runTest {
+ val remoteMediatorMock =
+ RemoteMediatorMock().apply { initializeResult = SKIP_INITIAL_REFRESH }
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config, remoteMediatorMock)
+
+ advanceUntilIdle()
+
+ // Assert onInitialize is not called until collection.
+ assertTrue { remoteMediatorMock.initializeEvents.isEmpty() }
+
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, remoteMediatorMock.initializeEvents.size)
+ assertEquals(0, remoteMediatorMock.loadEvents.size)
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun remoteMediator_initializeLaunch() =
+ testScope.runTest {
+ val remoteMediatorMock =
+ RemoteMediatorMock().apply { initializeResult = LAUNCH_INITIAL_REFRESH }
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config, remoteMediatorMock)
+
+ advanceUntilIdle()
+
+ // Assert onInitialize is not called until collection.
+ assertTrue { remoteMediatorMock.initializeEvents.isEmpty() }
+
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, remoteMediatorMock.initializeEvents.size)
+ assertEquals(1, remoteMediatorMock.loadEvents.size)
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun remoteMediator_load() =
+ testScope.runTest {
+ val remoteMediatorMock = RemoteMediatorMock()
+ val pageFetcher = PageFetcher(pagingSourceFactory, 97, config, remoteMediatorMock)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ // Assert onBoundary is not called for non-terminal page load.
+ assertTrue { remoteMediatorMock.loadEvents.isEmpty() }
+
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+
+ advanceUntilIdle()
+
+ // Assert onBoundary is called for terminal page load.
+ assertEquals(1, remoteMediatorMock.loadEvents.size)
+ assertEquals(APPEND, remoteMediatorMock.loadEvents[0].loadType)
+
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun jump() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pagingSources = mutableListOf<PagingSource<Int, Int>>()
+ val pagingSourceFactory = suspend {
+ TestPagingSource().also { pagingSources.add(it) }
+ }
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2,
+ maxSize = 3,
+ jumpThreshold = 10
+ )
+ val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+ assertThat(fetcherState.newEvents())
+ .isEqualTo(
+ listOf(
+ localLoadStateUpdate(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+ )
+
+ // Jump due to sufficiently large presentedItemsBefore
+ fetcherState.pagingDataList[0]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ // indexInPage value is incorrect, but should not be considered for
+ // jumps
+ indexInPage = 0,
+ presentedItemsBefore = -20,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertTrue { pagingSources[0].invalid }
+ // Assert no new events added to current generation
+ assertEquals(2, fetcherState.pageEventLists[0].size)
+ assertThat(fetcherState.newEvents())
+ .isEqualTo(
+ listOf(
+ localLoadStateUpdate(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+ )
+
+ // Jump due to sufficiently large presentedItemsAfter
+ fetcherState.pagingDataList[1]
+ .hintReceiver
+ .accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ // indexInPage value is incorrect, but should not be considered for
+ // jumps
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = -20,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ advanceUntilIdle()
+ assertTrue { pagingSources[1].invalid }
+ // Assert no new events added to current generation
+ assertEquals(2, fetcherState.pageEventLists[1].size)
+ assertThat(fetcherState.newEvents())
+ .isEqualTo(
+ listOf(
+ localLoadStateUpdate(refreshLocal = Loading),
+ createRefresh(range = 50..51)
+ )
+ )
+
+ fetcherState.job.cancel()
+ }
+ }
+
+ @Test
+ fun checksFactoryForNewInstance() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pagingSource = TestPagingSource()
+ val config = PagingConfig(pageSize = 1, prefetchDistance = 1, initialLoadSize = 2)
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = suspend { pagingSource },
+ initialKey = 50,
+ config = config
+ )
+ val job =
+ testScope.launch {
+ assertFailsWith<IllegalStateException> { pageFetcher.flow.collect {} }
+ }
+
+ advanceUntilIdle()
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertTrue { job.isCompleted }
+ }
+ }
+
+ @Test
+ fun pagingSourceInvalidBeforeCallbackAdded() =
+ testScope.runTest {
+ var invalidatesFromAdapter = 0
+ var i = 0
+ var pagingSource: TestPagingSource? = null
+ val pager =
+ Pager(PagingConfig(10)) {
+ i++
+ TestPagingSource().also {
+ if (i == 1) {
+ it.invalidate()
+ }
+
+ advanceUntilIdle()
+ it.registerInvalidatedCallback { invalidatesFromAdapter++ }
+ pagingSource = it
+ }
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ val job = launch {
+ pager.flow.collectLatest { pagingData ->
+ TestPagingDataPresenter<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
+ .collectFrom(pagingData)
+ }
+ }
+
+ advanceUntilIdle()
+ pagingSource!!.invalidate()
+ advanceUntilIdle()
+
+ // InvalidatedCallbacks added after a PagingSource is already invalid should be
+ // immediately triggered, so both listeners we add should be triggered.
+ assertEquals(2, invalidatesFromAdapter)
+ job.cancel()
+ }
+
+ @Test
+ fun pagingSourceInvalidBeforeCallbackAddedCancelsInitialLoad() =
+ testScope.runTest {
+ val pagingSources = mutableListOf<PagingSource<Int, Int>>()
+ val loadedPages = mutableListOf<Page<Int, Int>>()
+
+ var i = 0
+ val pager =
+ Pager(PagingConfig(10)) {
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(
+ params: LoadParams<Int>
+ ): LoadResult<Int, Int> {
+ // Suspend and await advanceUntilIdle() before allowing load to
+ // complete.
+ delay(1000)
+ return Page.empty<Int, Int>().also { loadedPages.add(it) }
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>) = null
+ }
+ .also {
+ pagingSources.add(it)
+
+ if (i++ == 0) {
+ it.invalidate()
+ }
+ }
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ val job = launch {
+ pager.flow.collectLatest { pagingData ->
+ TestPagingDataPresenter<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
+ .collectFrom(pagingData)
+ }
+ }
+
+ // First PagingSource starts immediately invalid and creates a new PagingSource, but
+ // does
+ // not finish initial page load.
+ runCurrent()
+ assertThat(pagingSources).hasSize(2)
+ assertThat(pagingSources[0].invalid).isTrue()
+ assertThat(loadedPages).hasSize(0)
+
+ advanceUntilIdle()
+
+ // After draining tasks, we should immediately get a second generation which triggers
+ // page load, skipping the initial load from first generation due to cancellation.
+ assertThat(pagingSources[1].invalid).isFalse()
+ assertThat(loadedPages).hasSize(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cachesPreviousPagingStateOnEmptyPages() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ )
+
+ val remoteMediator =
+ RemoteMediatorMock().apply { initializeResult = LAUNCH_INITIAL_REFRESH }
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory =
+ suspend {
+ TestPagingSource(loadDelay = 1000).also { it.getRefreshKeyResult = 30 }
+ },
+ initialKey = 50,
+ config = config,
+ remoteMediator = remoteMediator,
+ )
+
+ var receiver: HintReceiver? = null
+ val job = launch {
+ pageFetcher.flow.collectLatest {
+ receiver = it.hintReceiver
+ it.flow.collect {}
+ }
+ }
+
+ // Allow initial load to finish, so PagingState has non-zero pages.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with initial empty case.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(
+ loadType = REFRESH,
+ state =
+ PagingState(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ // Trigger refresh, instantiating second generation.
+ pageFetcher.refresh()
+
+ // Allow remote refresh to get triggered, but do not let paging source complete initial
+ // load
+ // for second generation.
+ advanceTimeBy(500)
+
+ // Verify remote refresh is called with PagingState from first generation.
+ val pagingState =
+ PagingState(
+ pages =
+ listOf(
+ Page(
+ data = listOf(50, 51, 52),
+ prevKey = 49,
+ nextKey = 53,
+ itemsBefore = 50,
+ itemsAfter = 47,
+ )
+ ),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ // Trigger a hint, which would normally populate anchorPosition. In real world scenario,
+ // this would happen as a result of UI still presenting first generation since second
+ // generation never finished loading yet.
+ receiver?.accessHint(
ViewportHint.Access(
pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 1,
- presentedItemsAfter = 1,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
originalPageOffsetFirst = 0,
originalPageOffsetLast = 0,
)
)
- advanceUntilIdle()
- // Invalidate first generation, instantiating second generation.
- pagingSources[0].invalidate()
-
- // Invalidate second generation before it has a chance to complete initial load.
- advanceTimeBy(500)
- pagingSources[1].invalidate()
+ // Trigger refresh instantiating third generation before second has a chance to complete
+ // initial load.
+ pageFetcher.refresh()
// Wait for all non-canceled loads to complete.
advanceUntilIdle()
- // Verify 3 generations were instantiated.
- assertThat(pagingSources.size).isEqualTo(3)
-
- // First generation should use initialKey.
- assertThat(pagingSources[0].getRefreshKeyCalls).isEmpty()
-
- // Second generation should receive getRefreshKey call with state from first generation.
- assertThat(pagingSources[1].getRefreshKeyCalls).isEqualTo(
- listOf(
- PagingState(
- pages = pagingSources[0].loadedPages,
- anchorPosition = 51,
- config = config,
- leadingPlaceholderCount = 50,
- )
+ // Verify remote refresh is called with PagingState from first generation, since second
+ // generation never loaded any pages.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
)
- )
- // Verify second generation was invalidated before any pages loaded.
- assertThat(pagingSources[1].loadedPages).isEmpty()
+ job.cancel()
+ }
- // Third generation should receive getRefreshKey call with state from first generation.
- assertThat(pagingSources[0].loadedPages.size).isEqualTo(1)
- assertThat(pagingSources[2].getRefreshKeyCalls).isEqualTo(
- listOf(
- PagingState(
- pages = pagingSources[0].loadedPages,
- anchorPosition = 51,
- config = config,
- leadingPlaceholderCount = 50,
- )
+ @Test
+ fun cachesPreviousPagingStateOnNullHint() =
+ testScope.runTest {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
)
- )
+ val remoteMediator =
+ RemoteMediatorMock().apply { initializeResult = LAUNCH_INITIAL_REFRESH }
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory =
+ suspend {
+ TestPagingSource(loadDelay = 1000).also { it.getRefreshKeyResult = 30 }
+ },
+ initialKey = 50,
+ config = config,
+ remoteMediator = remoteMediator,
+ )
+
+ var receiver: HintReceiver? = null
+ val job = launch {
+ pageFetcher.flow.collectLatest {
+ receiver = it.hintReceiver
+ it.flow.collect {}
+ }
+ }
+
+ // Allow initial load to finish, so PagingState has non-zero pages.
advanceUntilIdle()
- // Trigger APPEND in third generation.
- pagingData.hintReceiver.accessHint(
+
+ // Trigger a hint to populate anchorPosition, this should cause PageFetcher to cache
+ // this
+ // PagingState and use it in next remoteRefresh
+ receiver?.accessHint(
ViewportHint.Access(
pageOffset = 0,
- indexInPage = 2,
- presentedItemsBefore = 2,
- presentedItemsAfter = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
originalPageOffsetFirst = 0,
originalPageOffsetLast = 0,
)
)
- advanceUntilIdle()
- // Invalidate third generation, instantiating fourth generation with new PagingState.
- pagingSources[2].invalidate()
- advanceUntilIdle()
-
- // Fourth generation should receive getRefreshKey call with state from third generation.
- assertThat(pagingSources[2].loadedPages.size).isEqualTo(2)
- assertThat(pagingSources[3].getRefreshKeyCalls).isEqualTo(
- listOf(
- PagingState(
- pages = pagingSources[2].loadedPages,
- anchorPosition = 53,
- config = config,
- leadingPlaceholderCount = 51,
+ // Verify remote refresh is called with initial empty case.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(
+ loadType = REFRESH,
+ state =
+ PagingState(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
)
)
- )
+
+ // Trigger refresh, instantiating second generation.
+ pageFetcher.refresh()
+
+ // Allow remote refresh to get triggered, and let paging source load finish.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with PagingState from first generation.
+ val pagingState =
+ PagingState(
+ pages =
+ listOf(
+ Page(
+ data = listOf(50, 51, 52),
+ prevKey = 49,
+ nextKey = 53,
+ itemsBefore = 50,
+ itemsAfter = 47,
+ )
+ ),
+ anchorPosition = 50,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ // Trigger refresh instantiating third generation before second has a chance to complete
+ // initial load.
+ pageFetcher.refresh()
+
+ // Wait for all non-canceled loads to complete.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with PagingState from first generation, since second
+ // generation never loaded any pages.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
job.cancel()
}
- }
@Test
- fun refresh_sourceEndOfPaginationReached_loadStates() = testScope.runTest {
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
- )
- val fetcherState = collectFetcherState(pageFetcher)
+ fun invalidate_prioritizesGetRefreshKeyReturningNull() =
+ testScope.runTest {
+ val loadRequests = mutableListOf<LoadParams<Int>>()
+ val pageFetcher =
+ PageFetcher(
+ config = PagingConfig(pageSize = 1),
+ initialKey = 0,
+ pagingSourceFactory = {
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(
+ params: LoadParams<Int>
+ ): LoadResult<Int, Int> {
+ loadRequests.add(params)
+ return LoadResult.Error(Exception("ignored"))
+ }
- advanceUntilIdle()
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
+ // Should prioritize `null` returned here on invalidation.
+ return null
+ }
+ }
+ }
+ )
- assertEquals(1, fetcherState.pagingDataList.size)
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- EMPTY_SOURCE_REFRESH,
- )
+ val job = launch { pageFetcher.flow.collectLatest { it.flow.collect {} } }
- pageFetcher.refresh()
- advanceUntilIdle()
+ advanceUntilIdle()
+ assertThat(loadRequests).hasSize(1)
+ assertThat(loadRequests[0].key).isEqualTo(0)
- assertEquals(2, fetcherState.pagingDataList.size)
- assertThat(fetcherState.newEvents()).containsExactly(
- localLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- EMPTY_SOURCE_REFRESH,
- )
- fetcherState.job.cancel()
- }
+ pageFetcher.refresh()
+ advanceUntilIdle()
+ assertThat(loadRequests).hasSize(2)
+ assertThat(loadRequests[1].key).isEqualTo(null)
+
+ job.cancel()
+ }
@Test
- fun refresh_remoteEndOfPaginationReached_loadStates() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- // Wait for advanceUntilIdle()
- delay(1)
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ fun invalidateBeforeAccessPreservesPagingState() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ )
+ val pagingSources = mutableListOf<TestPagingSource>()
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory =
+ suspend {
+ TestPagingSource(loadDelay = 1000).also { pagingSources.add(it) }
+ },
+ initialKey = 50,
+ config = config,
+ )
+
+ lateinit var pagingData: PagingData<Int>
+ val job = launch {
+ pageFetcher.flow.collectLatest {
+ pagingData = it
+ it.flow.collect {}
+ }
+ }
+
+ advanceUntilIdle()
+
+ // Trigger access to allow PagingState to get populated for next generation.
+ pagingData.hintReceiver.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+ advanceUntilIdle()
+
+ // Invalidate first generation, instantiating second generation.
+ pagingSources[0].invalidate()
+
+ // Invalidate second generation before it has a chance to complete initial load.
+ advanceTimeBy(500)
+ pagingSources[1].invalidate()
+
+ // Wait for all non-canceled loads to complete.
+ advanceUntilIdle()
+
+ // Verify 3 generations were instantiated.
+ assertThat(pagingSources.size).isEqualTo(3)
+
+ // First generation should use initialKey.
+ assertThat(pagingSources[0].getRefreshKeyCalls).isEmpty()
+
+ // Second generation should receive getRefreshKey call with state from first
+ // generation.
+ assertThat(pagingSources[1].getRefreshKeyCalls)
+ .isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[0].loadedPages,
+ anchorPosition = 51,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ )
+ )
+
+ // Verify second generation was invalidated before any pages loaded.
+ assertThat(pagingSources[1].loadedPages).isEmpty()
+
+ // Third generation should receive getRefreshKey call with state from first
+ // generation.
+ assertThat(pagingSources[0].loadedPages.size).isEqualTo(1)
+ assertThat(pagingSources[2].getRefreshKeyCalls)
+ .isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[0].loadedPages,
+ anchorPosition = 51,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ )
+ )
+
+ advanceUntilIdle()
+ // Trigger APPEND in third generation.
+ pagingData.hintReceiver.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+ advanceUntilIdle()
+
+ // Invalidate third generation, instantiating fourth generation with new
+ // PagingState.
+ pagingSources[2].invalidate()
+ advanceUntilIdle()
+
+ // Fourth generation should receive getRefreshKey call with state from third
+ // generation.
+ assertThat(pagingSources[2].loadedPages.size).isEqualTo(2)
+ assertThat(pagingSources[3].getRefreshKeyCalls)
+ .isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[2].loadedPages,
+ anchorPosition = 53,
+ config = config,
+ leadingPlaceholderCount = 51,
+ )
+ )
+ )
+
+ job.cancel()
}
}
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- // all remote States should be updated within single LoadStateUpdate
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- EMPTY_REMOTE_REFRESH,
- )
-
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertThat(fetcherState.newEvents()).containsExactly(
- // Remote state carried over from previous generation.
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- EMPTY_REMOTE_REFRESH,
- )
-
- fetcherState.job.cancel()
- }
-
- /**
- * Check that rapid remote events are not dropped and don't cause redundant events.
- */
@Test
- fun injectRemoteEvents_fastRemoteEvents() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
- }
+ fun refresh_sourceEndOfPaginationReached_loadStates() =
+ testScope.runTest {
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ ),
+ EMPTY_SOURCE_REFRESH,
+ )
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ localLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ ),
+ EMPTY_SOURCE_REFRESH,
+ )
+ fetcherState.job.cancel()
}
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
+ @Test
+ fun refresh_remoteEndOfPaginationReached_loadStates() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ // Wait for advanceUntilIdle()
+ delay(1)
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- remoteLoadStateUpdate<Int>(
- refreshLocal = Loading,
- refreshRemote = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- remoteRefresh<Int>(
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Incomplete,
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- ),
- )
- fetcherState.job.cancel()
- }
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ // all remote States should be updated within single LoadStateUpdate
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ EMPTY_REMOTE_REFRESH,
+ )
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ // Remote state carried over from previous generation.
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ EMPTY_REMOTE_REFRESH,
+ )
+
+ fetcherState.job.cancel()
+ }
+
+ /** Check that rapid remote events are not dropped and don't cause redundant events. */
+ @Test
+ fun injectRemoteEvents_fastRemoteEvents() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ remoteLoadStateUpdate<Int>(
+ refreshLocal = Loading,
+ refreshRemote = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ remoteRefresh<Int>(
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ fetcherState.job.cancel()
+ }
@Suppress("DEPRECATION")
// b/220884819
@@ -1267,265 +1344,284 @@
val neverEmitCh = Channel<Int>()
var generation = 0
- val pageFetcher = PageFetcher(
- pagingSourceFactory = {
- generation++
- object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- // Wait for advanceUntilIdle()
- delay(1)
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = {
+ generation++
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ // Wait for advanceUntilIdle()
+ delay(1)
- return when (generation) {
- 1 -> Page(
- data = listOf(),
- prevKey = null,
- nextKey = null
- )
- else -> Page(
- data = listOf(3, 4, 5),
- prevKey = 2,
- nextKey = 6
- )
+ return when (generation) {
+ 1 -> Page(data = listOf(), prevKey = null, nextKey = null)
+ else -> Page(data = listOf(3, 4, 5), prevKey = 2, nextKey = 6)
+ }
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
+ },
+ initialKey = 0,
+ config = config,
+ remoteMediator =
+ object : RemoteMediator<Int, Int>() {
+ override suspend fun initialize(): InitializeAction = SKIP_INITIAL_REFRESH
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ // Wait for advanceUntilIdle()
+ delay(1)
+
+ if (loadType == REFRESH) {
+ return MediatorResult.Success(endOfPaginationReached = false)
+ }
+
+ neverEmitCh.receiveCatching()
+ return MediatorResult.Error(Exception("Unexpected"))
}
}
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
- },
- initialKey = 0,
- config = config,
- remoteMediator = object : RemoteMediator<Int, Int>() {
- override suspend fun initialize(): InitializeAction = SKIP_INITIAL_REFRESH
-
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- // Wait for advanceUntilIdle()
- delay(1)
-
- if (loadType == REFRESH) {
- return MediatorResult.Success(endOfPaginationReached = false)
- }
-
- neverEmitCh.receiveCatching()
- return MediatorResult.Error(Exception("Unexpected"))
- }
- }
- )
+ )
val fetcherState = collectFetcherState(pageFetcher)
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- source = loadStates(
- refresh = Loading,
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ source =
+ loadStates(
+ refresh = Loading,
+ ),
),
- ),
- )
+ )
// Let initial source refresh complete and kick off remote prepend / append.
advanceUntilIdle()
// First generation loads empty list and triggers remote loads.
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf())
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf())),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
+ remoteLoadStateUpdate<Int>(
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ ),
),
- ),
- remoteLoadStateUpdate<Int>(
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
+ remoteLoadStateUpdate<Int>(
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ ),
),
- mediator = loadStates(
- prepend = Loading,
- ),
- ),
- remoteLoadStateUpdate<Int>(
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
- ),
- ),
- )
+ )
// Trigger remote + source refresh in a new generation.
pageFetcher.refresh()
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ ),
),
- ),
- )
+ )
// Let remote and source refresh finish.
advanceUntilIdle()
// Second generation loads some data and has more to load from source.
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ ),
),
- ),
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
- )
- ),
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf(3, 4, 5))),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ )
),
- ),
- )
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf(3, 4, 5))),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ ),
+ ),
+ )
// Trigger remote + source refresh in a third generation.
pageFetcher.refresh()
// Start of third generation should have the exact same load states as before, so we
// should only get new events for kicking off new loads.
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ ),
),
- ),
- )
+ )
// Let remote and source refresh finish.
advanceUntilIdle()
- assertThat(fetcherState.newEvents()).containsExactly(
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
+ assertThat(fetcherState.newEvents())
+ .containsExactly(
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ ),
),
- ),
- remoteLoadStateUpdate<Int>(
- source = loadStates(refresh = Loading),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
- )
- ),
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf(3, 4, 5))),
- mediator = loadStates(
- prepend = Loading,
- append = Loading,
+ remoteLoadStateUpdate<Int>(
+ source = loadStates(refresh = Loading),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ )
),
- ),
- )
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf(3, 4, 5))),
+ mediator =
+ loadStates(
+ prepend = Loading,
+ append = Loading,
+ ),
+ ),
+ )
neverEmitCh.close()
fetcherState.job.cancel()
}
@Test
- fun injectRemoteEvents_doesNotKeepOldGenerationActive() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
- }
- }
- val pageFetcher = PageFetcher(
- pagingSourceFactory = { TestPagingSource(items = emptyList()) },
- initialKey = 0,
- config = config,
- remoteMediator = remoteMediator
- )
- val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
-
- // Clear out all events.
- val firstGenerationEventCount = fetcherState.pageEventLists[0].size
-
- // Let new generation and some new remote events emit.
- pageFetcher.refresh()
- advanceUntilIdle()
-
- assertThat(firstGenerationEventCount).isEqualTo(fetcherState.pageEventLists[0].size)
-
- fetcherState.job.cancel()
- }
-
- /**
- * See b/183345509
- */
- @Test
- fun remoteRefreshTriggeredDespiteImmediateInvalidation() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock().apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
- }
- }
- var generation = 0
- val pageFetcher = PageFetcher(
- pagingSourceFactory = {
- TestPagingSource(items = emptyList()).also {
- if (generation++ == 0) it.invalidate()
+ fun injectRemoteEvents_doesNotKeepOldGenerationActive() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
}
- },
- initialKey = 0,
- config = config,
- remoteMediator = remoteMediator
- )
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+ advanceUntilIdle()
- val fetcherState = collectFetcherState(pageFetcher)
- advanceUntilIdle()
+ // Clear out all events.
+ val firstGenerationEventCount = fetcherState.pageEventLists[0].size
- assertThat(remoteMediator.newLoadEvents).isNotEmpty()
- fetcherState.job.cancel()
- }
+ // Let new generation and some new remote events emit.
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertThat(firstGenerationEventCount).isEqualTo(fetcherState.pageEventLists[0].size)
+
+ fetcherState.job.cancel()
+ }
+
+ /** See b/183345509 */
+ @Test
+ fun remoteRefreshTriggeredDespiteImmediateInvalidation() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ var generation = 0
+ val pageFetcher =
+ PageFetcher(
+ pagingSourceFactory = {
+ TestPagingSource(items = emptyList()).also {
+ if (generation++ == 0) it.invalidate()
+ }
+ },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val fetcherState = collectFetcherState(pageFetcher)
+ advanceUntilIdle()
+
+ assertThat(remoteMediator.newLoadEvents).isNotEmpty()
+ fetcherState.job.cancel()
+ }
companion object {
internal val EMPTY_SOURCE_REFRESH =
localRefresh<Int>(
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- )
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ )
)
internal val EMPTY_REMOTE_REFRESH =
remoteRefresh<Int>(
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Incomplete,
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
)
}
}
@@ -1545,9 +1641,7 @@
}
val pageEvents = pageEventLists.lastOrNull()?.toMutableList() ?: listOf()
- return pageEvents.drop(lastIndex + 1).also {
- lastIndex = pageEvents.lastIndex
- }
+ return pageEvents.drop(lastIndex + 1).also { lastIndex = pageEvents.lastIndex }
}
}
@@ -1559,9 +1653,7 @@
fetcher.flow.collectIndexed { index, pagingData ->
pagingDataList.add(index, pagingData)
pageEventLists.add(index, ArrayList())
- launch {
- pagingData.flow.toList(pageEventLists[index])
- }
+ launch { pagingData.flow.toList(pageEventLists[index]) }
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigBuilderTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
index 7dab52b..0a35dc9 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
@@ -22,35 +22,23 @@
class PagedListConfigBuilderTest {
@Test
fun defaults() {
- @Suppress("DEPRECATION")
- val config = PagedList.Config.Builder()
- .setPageSize(10)
- .build()
+ @Suppress("DEPRECATION") val config = PagedList.Config.Builder().setPageSize(10).build()
assertEquals(10, config.pageSize)
assertEquals(30, config.initialLoadSizeHint)
assertEquals(true, config.enablePlaceholders)
assertEquals(10, config.prefetchDistance)
- @Suppress("DEPRECATION")
- assertEquals(PagedList.Config.MAX_SIZE_UNBOUNDED, config.maxSize)
+ @Suppress("DEPRECATION") assertEquals(PagedList.Config.MAX_SIZE_UNBOUNDED, config.maxSize)
}
@Test(expected = IllegalArgumentException::class)
fun maxSizeTooSmall() {
@Suppress("DEPRECATION")
- PagedList.Config.Builder()
- .setPageSize(20)
- .setPrefetchDistance(15)
- .setMaxSize(49)
- .build()
+ PagedList.Config.Builder().setPageSize(20).setPrefetchDistance(15).setMaxSize(49).build()
}
@Test
fun maxSizeAccepted() {
@Suppress("DEPRECATION")
- PagedList.Config.Builder()
- .setPageSize(20)
- .setPrefetchDistance(15)
- .setMaxSize(50)
- .build()
+ PagedList.Config.Builder().setPageSize(20).setPrefetchDistance(15).setMaxSize(50).build()
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigTest.kt
index 976a9a0..8aeee64 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListConfigTest.kt
@@ -27,7 +27,6 @@
assertEquals(30, config.initialLoadSizeHint)
assertEquals(true, config.enablePlaceholders)
assertEquals(10, config.prefetchDistance)
- @Suppress("DEPRECATION")
- assertEquals(PagedList.Config.MAX_SIZE_UNBOUNDED, config.maxSize)
+ @Suppress("DEPRECATION") assertEquals(PagedList.Config.MAX_SIZE_UNBOUNDED, config.maxSize)
}
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListTest.kt
index 3a92d47..5649b9a 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/PagedListTest.kt
@@ -39,19 +39,17 @@
private val ITEMS = List(100) { "$it" }
private val config = Config(10)
- private val pagingSource = object : PagingSource<Int, String>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> =
- when (params) {
- is LoadParams.Refresh -> LoadResult.Page(
- data = listOf("a"),
- prevKey = null,
- nextKey = null
- )
- else -> throw NotImplementedError("Test should fail if we get here")
- }
+ private val pagingSource =
+ object : PagingSource<Int, String>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> =
+ when (params) {
+ is LoadParams.Refresh ->
+ LoadResult.Page(data = listOf("a"), prevKey = null, nextKey = null)
+ else -> throw NotImplementedError("Test should fail if we get here")
+ }
- override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
- }
+ override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
+ }
}
private val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
@@ -67,23 +65,25 @@
}
}
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(TestPositionalDataSource(ITEMS), 100)
- .setNotifyExecutor(TestExecutor())
- .setFetchExecutor(slowFetchExecutor)
- .build()
+ val pagedList =
+ PagedList.Builder(TestPositionalDataSource(ITEMS), 100)
+ .setNotifyExecutor(TestExecutor())
+ .setFetchExecutor(slowFetchExecutor)
+ .build()
// if build succeeds without flushing an executor, success!
assertEquals(ITEMS, pagedList)
}
@Test
fun createNoInitialPageThrow() = runTest {
- val pagingSource = object : PagingSource<Int, String>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
- throw IllegalStateException()
- }
+ val pagingSource =
+ object : PagingSource<Int, String>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
+ throw IllegalStateException()
+ }
- override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
- }
+ override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
+ }
assertFailsWith<IllegalStateException> {
@Suppress("DEPRECATION")
PagedList.create(
@@ -102,13 +102,14 @@
@Test
fun createNoInitialPageError() = runTest {
val exception = IllegalStateException()
- val pagingSource = object : PagingSource<Int, String>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
- return LoadResult.Error(exception)
- }
+ val pagingSource =
+ object : PagingSource<Int, String>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
+ return LoadResult.Error(exception)
+ }
- override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
- }
+ override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
+ }
// create doesn't differentiate between throw vs error runnable, which is why
// PagedList.Builder without the initial page is deprecated
@@ -129,53 +130,58 @@
@Test
fun createNoInitialPageInvalidResult() = runTest {
- val pagingSource = object : PagingSource<Int, String>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
- return LoadResult.Invalid()
+ val pagingSource =
+ object : PagingSource<Int, String>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
+ return LoadResult.Invalid()
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, String>): Int? {
+ fail("should not reach here")
+ }
}
- override fun getRefreshKey(state: PagingState<Int, String>): Int? {
- fail("should not reach here")
+ val expectedException =
+ assertFailsWith<IllegalStateException> {
+ @Suppress("DEPRECATION")
+ PagedList.create(
+ pagingSource,
+ initialPage = null,
+ testCoroutineScope,
+ Dispatchers.Default,
+ Dispatchers.IO,
+ boundaryCallback = null,
+ Config(10),
+ key = 0
+ )
}
- }
-
- val expectedException = assertFailsWith<IllegalStateException> {
- @Suppress("DEPRECATION")
- PagedList.create(
- pagingSource,
- initialPage = null,
- testCoroutineScope,
- Dispatchers.Default,
- Dispatchers.IO,
- boundaryCallback = null,
- Config(10),
- key = 0
+ assertThat(expectedException.message)
+ .isEqualTo(
+ "Failed to create PagedList. The provided PagingSource returned " +
+ "LoadResult.Invalid, but a LoadResult.Page was expected. To use a " +
+ "PagingSource which supports invalidation, use a PagedList builder that " +
+ "accepts a factory method for PagingSource or DataSource.Factory, such as " +
+ "LivePagedList."
)
- }
- assertThat(expectedException.message).isEqualTo(
- "Failed to create PagedList. The provided PagingSource returned " +
- "LoadResult.Invalid, but a LoadResult.Page was expected. To use a " +
- "PagingSource which supports invalidation, use a PagedList builder that " +
- "accepts a factory method for PagingSource or DataSource.Factory, such as " +
- "LivePagedList."
- )
}
@Test
fun defaults() = runTest {
- val initialPage = pagingSource.load(
- PagingSource.LoadParams.Refresh(
- key = null,
- loadSize = 10,
- placeholdersEnabled = false,
- )
- ) as PagingSource.LoadResult.Page
+ val initialPage =
+ pagingSource.load(
+ PagingSource.LoadParams.Refresh(
+ key = null,
+ loadSize = 10,
+ placeholdersEnabled = false,
+ )
+ ) as PagingSource.LoadResult.Page
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(pagingSource, initialPage, config)
- .setNotifyDispatcher(Dispatchers.Default)
- .setFetchDispatcher(Dispatchers.IO)
- .build()
+ val pagedList =
+ PagedList.Builder(pagingSource, initialPage, config)
+ .setNotifyDispatcher(Dispatchers.Default)
+ .setFetchDispatcher(Dispatchers.IO)
+ .build()
assertEquals(pagingSource, pagedList.pagingSource)
assertEquals(config, pagedList.config)
@@ -187,11 +193,12 @@
val exception = Exception()
@Suppress("DEPRECATION")
- val loadStateManager = object : PagedList.LoadStateManager() {
- override fun onStateChanged(type: LoadType, state: LoadState) {
- onStateChangeCalls++
+ val loadStateManager =
+ object : PagedList.LoadStateManager() {
+ override fun onStateChanged(type: LoadType, state: LoadState) {
+ onStateChangeCalls++
+ }
}
- }
loadStateManager.setState(REFRESH, LoadState.Error(exception))
loadStateManager.setState(REFRESH, LoadState.Error(exception))
@@ -204,23 +211,25 @@
val notifyDispatcher = TestDispatcher()
@Suppress("DEPRECATION")
- val pagedList = object : PagedList<String>(
- pagingSource,
- testCoroutineScope,
- notifyDispatcher,
- PagedStorage(),
- config
- ) {
- override val lastKey: Any? = null
+ val pagedList =
+ object :
+ PagedList<String>(
+ pagingSource,
+ testCoroutineScope,
+ notifyDispatcher,
+ PagedStorage(),
+ config
+ ) {
+ override val lastKey: Any? = null
- override val isDetached: Boolean = true
+ override val isDetached: Boolean = true
- override fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {}
+ override fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {}
- override fun loadAroundInternal(index: Int) {}
+ override fun loadAroundInternal(index: Int) {}
- override fun detach() {}
- }
+ override fun detach() {}
+ }
assertTrue { notifyDispatcher.queue.isEmpty() }
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/SingleRunnerTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/SingleRunnerTest.kt
index 5c7dc6f..7218958 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/SingleRunnerTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/SingleRunnerTest.kt
@@ -45,105 +45,102 @@
private val testScope = TestScope()
@Test
- fun cancelsPreviousRun() = testScope.runTest {
- val runner = SingleRunner()
- val job = launch(Dispatchers.Unconfined) {
+ fun cancelsPreviousRun() =
+ testScope.runTest {
+ val runner = SingleRunner()
+ val job =
+ launch(Dispatchers.Unconfined) { runner.runInIsolation { delay(Long.MAX_VALUE) } }
+
runner.runInIsolation {
- delay(Long.MAX_VALUE)
+ // Immediately return.
}
- }
- runner.runInIsolation {
- // Immediately return.
+ assertFalse { job.isCancelled }
+ assertTrue { job.isCompleted }
}
- assertFalse { job.isCancelled }
- assertTrue { job.isCompleted }
- }
-
@Test
- fun previousRunCanCancelItself() = testScope.runTest {
- val runner = SingleRunner()
- val job = launch(Dispatchers.Unconfined) {
- runner.runInIsolation {
- throw CancellationException()
- }
- }
- assertTrue { job.isCancelled }
- assertTrue { job.isCompleted }
- }
-
- @Test
- fun preventsCompletionUntilBlockCompletes() = testScope.runTest {
- val runner = SingleRunner()
- val job = testScope.launch {
- runner.runInIsolation {
- delay(1000)
- }
- }
-
- advanceTimeBy(500)
- runCurrent()
- assertFalse { job.isCompleted }
-
- advanceTimeBy(500)
- runCurrent()
- assertTrue { job.isCompleted }
- }
-
- @Test
- fun orderedExecution() = testScope.runTest {
- val jobStartList = mutableListOf<Int>()
-
- val runner = SingleRunner()
- for (index in 0..9) {
- launch {
- runner.runInIsolation {
- jobStartList.add(index)
- delay(Long.MAX_VALUE)
+ fun previousRunCanCancelItself() =
+ testScope.runTest {
+ val runner = SingleRunner()
+ val job =
+ launch(Dispatchers.Unconfined) {
+ runner.runInIsolation { throw CancellationException() }
}
- }
+ assertTrue { job.isCancelled }
+ assertTrue { job.isCompleted }
}
- runCurrent()
-
- // Cancel previous job.
- runner.runInIsolation {
- // Immediately return.
- }
-
- assertEquals(List(10) { it }, jobStartList)
- }
@Test
- fun racingCoroutines() = testScope.runTest {
- val runner = SingleRunner()
- val output = mutableListOf<Char>()
- withContext(coroutineContext) {
- launch {
- ('0' until '4').forEach {
+ fun preventsCompletionUntilBlockCompletes() =
+ testScope.runTest {
+ val runner = SingleRunner()
+ val job = testScope.launch { runner.runInIsolation { delay(1000) } }
+
+ advanceTimeBy(500)
+ runCurrent()
+ assertFalse { job.isCompleted }
+
+ advanceTimeBy(500)
+ runCurrent()
+ assertTrue { job.isCompleted }
+ }
+
+ @Test
+ fun orderedExecution() =
+ testScope.runTest {
+ val jobStartList = mutableListOf<Int>()
+
+ val runner = SingleRunner()
+ for (index in 0..9) {
+ launch {
runner.runInIsolation {
- output.add(it)
- delay(100)
+ jobStartList.add(index)
+ delay(Long.MAX_VALUE)
}
}
}
+ runCurrent()
- launch {
- ('a' until 'e').forEach {
- runner.runInIsolation {
- output.add(it)
- delay(40)
+ // Cancel previous job.
+ runner.runInIsolation {
+ // Immediately return.
+ }
+
+ assertEquals(List(10) { it }, jobStartList)
+ }
+
+ @Test
+ fun racingCoroutines() =
+ testScope.runTest {
+ val runner = SingleRunner()
+ val output = mutableListOf<Char>()
+ withContext(coroutineContext) {
+ launch {
+ ('0' until '4').forEach {
+ runner.runInIsolation {
+ output.add(it)
+ delay(100)
+ }
}
}
+
+ launch {
+ ('a' until 'e').forEach {
+ runner.runInIsolation {
+ output.add(it)
+ delay(40)
+ }
+ }
+ }
+ // don't let delays finish to ensure they are really cancelled
+ advanceTimeBy(1)
}
- // don't let delays finish to ensure they are really cancelled
- advanceTimeBy(1)
+ // Despite launching separately, with different delays, we should see these always
+ // interleave in the same order, since the delays aren't allowed to run in parallel and
+ // each launch will cancel the other one's delay.
+ assertThat(output.joinToString("")).isEqualTo("0a1b2c3d")
}
- // Despite launching separately, with different delays, we should see these always
- // interleave in the same order, since the delays aren't allowed to run in parallel and
- // each launch will cancel the other one's delay.
- assertThat(output.joinToString("")).isEqualTo("0a1b2c3d")
- }
@OptIn(DelicateCoroutinesApi::class)
@Test
@@ -159,70 +156,48 @@
// a code path which ignores coroutine cancellation
firstStarted.countDown()
repeat(10) {
- @Suppress("BlockingMethodInNonBlockingContext")
- Thread.sleep(100)
+ @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(100)
output.add(it)
}
}
}
- val job2 = GlobalScope.launch {
- @Suppress("BlockingMethodInNonBlockingContext")
- firstStarted.await()
- singleRunner.runInIsolation {
- repeat(10) {
- output.add(it + 10)
- }
+ val job2 =
+ GlobalScope.launch {
+ @Suppress("BlockingMethodInNonBlockingContext") firstStarted.await()
+ singleRunner.runInIsolation { repeat(10) { output.add(it + 10) } }
}
- }
- runBlocking {
- withTimeout(10.seconds) {
- job2.join()
- }
- }
- assertThat(output).isEqualTo(
- // if cancellation is ignored, make sure we wait for it to finish.
- (0 until 20).toList()
- )
+ runBlocking { withTimeout(10.seconds) { job2.join() } }
+ assertThat(output)
+ .isEqualTo(
+ // if cancellation is ignored, make sure we wait for it to finish.
+ (0 until 20).toList()
+ )
}
@Test
- fun priority() = testScope.runTest {
- val runner = SingleRunner()
- val output = mutableListOf<String>()
- launch {
- runner.runInIsolation(
- priority = 2
- ) {
- output.add("a")
- delay(10)
- output.add("b")
- delay(100)
- output.add("unexpected")
+ fun priority() =
+ testScope.runTest {
+ val runner = SingleRunner()
+ val output = mutableListOf<String>()
+ launch {
+ runner.runInIsolation(priority = 2) {
+ output.add("a")
+ delay(10)
+ output.add("b")
+ delay(100)
+ output.add("unexpected")
+ }
}
- }
- runCurrent()
+ runCurrent()
- // should not run
- runner.runInIsolation(
- priority = 1
- ) {
- output.add("unexpected - 2")
+ // should not run
+ runner.runInIsolation(priority = 1) { output.add("unexpected - 2") }
+ advanceTimeBy(20)
+ runner.runInIsolation(priority = 3) { output.add("c") }
+ advanceUntilIdle()
+ // now lower priority can run since higher priority is complete
+ runner.runInIsolation(priority = 1) { output.add("d") }
+ assertThat(output).containsExactly("a", "b", "c", "d")
}
- advanceTimeBy(20)
- runner.runInIsolation(
- priority = 3
- ) {
- output.add("c")
- }
- advanceUntilIdle()
- // now lower priority can run since higher priority is complete
- runner.runInIsolation(
- priority = 1
- ) {
- output.add("d")
- }
- assertThat(output)
- .containsExactly("a", "b", "c", "d")
- }
}
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
index 64d059c4..d158788 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
@@ -26,15 +26,15 @@
val dataSource = WrapperItemKeyedDataSource(TestItemKeyedDataSource(listOf(0))) { it }
var kotlinInvalidated = false
- dataSource.addInvalidatedCallback {
- kotlinInvalidated = true
- }
+ dataSource.addInvalidatedCallback { kotlinInvalidated = true }
var javaInvalidated = false
- dataSource.addInvalidatedCallback(object : DataSource.InvalidatedCallback {
- override fun onInvalidated() {
- javaInvalidated = true
+ dataSource.addInvalidatedCallback(
+ object : DataSource.InvalidatedCallback {
+ override fun onInvalidated() {
+ javaInvalidated = true
+ }
}
- })
+ )
dataSource.invalidate()
assertTrue { dataSource.isInvalid }
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
index 7a4d7d1..87a2460 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
@@ -26,15 +26,15 @@
val dataSource = WrapperPageKeyedDataSource(TestPageKeyedDataSource(listOf(0))) { it }
var kotlinInvalidated = false
- dataSource.addInvalidatedCallback {
- kotlinInvalidated = true
- }
+ dataSource.addInvalidatedCallback { kotlinInvalidated = true }
var javaInvalidated = false
- dataSource.addInvalidatedCallback(object : DataSource.InvalidatedCallback {
- override fun onInvalidated() {
- javaInvalidated = true
+ dataSource.addInvalidatedCallback(
+ object : DataSource.InvalidatedCallback {
+ override fun onInvalidated() {
+ javaInvalidated = true
+ }
}
- })
+ )
dataSource.invalidate()
assertTrue { dataSource.isInvalid }
diff --git a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
index 9bdfb70..e64d8d1 100644
--- a/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
+++ b/paging/paging-common/src/commonJvmAndroidTest/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
@@ -26,15 +26,15 @@
val dataSource = WrapperPositionalDataSource(TestPositionalDataSource(listOf(0))) { it }
var kotlinInvalidated = false
- dataSource.addInvalidatedCallback {
- kotlinInvalidated = true
- }
+ dataSource.addInvalidatedCallback { kotlinInvalidated = true }
var javaInvalidated = false
- dataSource.addInvalidatedCallback(object : DataSource.InvalidatedCallback {
- override fun onInvalidated() {
- javaInvalidated = true
+ dataSource.addInvalidatedCallback(
+ object : DataSource.InvalidatedCallback {
+ override fun onInvalidated() {
+ javaInvalidated = true
+ }
}
- })
+ )
dataSource.invalidate()
assertTrue { dataSource.isInvalid }
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPageEventFlow.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPageEventFlow.kt
index c9b5d84..c910754 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPageEventFlow.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPageEventFlow.kt
@@ -35,56 +35,55 @@
* An intermediate flow producer that flattens previous page events and gives any new downstream
* just those events instead of the full history.
*/
-internal class CachedPageEventFlow<T : Any>(
- src: Flow<PageEvent<T>>,
- scope: CoroutineScope
-) {
+internal class CachedPageEventFlow<T : Any>(src: Flow<PageEvent<T>>, scope: CoroutineScope) {
private val pageController = FlattenedPageController<T>()
/**
- * Shared flow for downstreams where we dispatch each event coming from upstream.
- * This only has reply = 1 so it does not keep the previous events. Meanwhile, it still buffers
- * them for active subscribers.
- * A final `null` value is emitted as the end of stream message once the job is complete.
+ * Shared flow for downstreams where we dispatch each event coming from upstream. This only has
+ * reply = 1 so it does not keep the previous events. Meanwhile, it still buffers them for
+ * active subscribers. A final `null` value is emitted as the end of stream message once the job
+ * is complete.
*/
- private val mutableSharedSrc = MutableSharedFlow<IndexedValue<PageEvent<T>>?>(
- replay = 1,
- extraBufferCapacity = Channel.UNLIMITED,
- onBufferOverflow = BufferOverflow.SUSPEND
- )
+ private val mutableSharedSrc =
+ MutableSharedFlow<IndexedValue<PageEvent<T>>?>(
+ replay = 1,
+ extraBufferCapacity = Channel.UNLIMITED,
+ onBufferOverflow = BufferOverflow.SUSPEND
+ )
/**
* Shared flow used for downstream which also sends the history. Each downstream connects to
- * this where it first receives a history event and then any other event that was emitted by
- * the upstream.
+ * this where it first receives a history event and then any other event that was emitted by the
+ * upstream.
*/
- private val sharedForDownstream = mutableSharedSrc.onSubscription {
- val history = pageController.getStateAsEvents()
- // start the job if it has not started yet. We do this after capturing the history so that
- // the first subscriber does not receive any history.
- job.start()
- history.forEach {
- emit(it)
+ private val sharedForDownstream =
+ mutableSharedSrc.onSubscription {
+ val history = pageController.getStateAsEvents()
+ // start the job if it has not started yet. We do this after capturing the history so
+ // that
+ // the first subscriber does not receive any history.
+ job.start()
+ history.forEach { emit(it) }
}
- }
- /**
- * The actual job that collects the upstream.
- */
- private val job = scope.launch(start = CoroutineStart.LAZY) {
- src.withIndex()
- .collect {
- mutableSharedSrc.emit(it)
- pageController.record(it)
+ /** The actual job that collects the upstream. */
+ private val job =
+ scope
+ .launch(start = CoroutineStart.LAZY) {
+ src.withIndex().collect {
+ mutableSharedSrc.emit(it)
+ pageController.record(it)
+ }
}
- }.also {
- it.invokeOnCompletion {
- // Emit a final `null` message to the mutable shared flow.
- // Even though, this tryEmit might technically fail, it shouldn't because we have
- // unlimited buffer in the shared flow.
- mutableSharedSrc.tryEmit(null)
- }
- }
+ .also {
+ it.invokeOnCompletion {
+ // Emit a final `null` message to the mutable shared flow.
+ // Even though, this tryEmit might technically fail, it shouldn't because we
+ // have
+ // unlimited buffer in the shared flow.
+ mutableSharedSrc.tryEmit(null)
+ }
+ }
fun close() {
job.cancel()
@@ -109,8 +108,8 @@
}
/**
- * Returns cached data as PageEvent.Insert. Null if cached data is empty (for example on
- * initial refresh).
+ * Returns cached data as PageEvent.Insert. Null if cached data is empty (for example on initial
+ * refresh).
*/
internal fun getCachedEvent(): PageEvent.Insert<T>? = pageController.getCachedEvent()
}
@@ -120,9 +119,7 @@
private val lock = Mutex()
private var maxEventIndex = -1
- /**
- * Record the event.
- */
+ /** Record the event. */
suspend fun record(event: IndexedValue<PageEvent<T>>) {
lock.withLock {
maxEventIndex = event.index
@@ -130,26 +127,22 @@
}
}
- /**
- * Create a list of events that represents the current state of the list.
- */
+ /** Create a list of events that represents the current state of the list. */
suspend fun getStateAsEvents(): List<IndexedValue<PageEvent<T>>> {
return lock.withLock {
// condensed events to bring downstream up to the current state
val catchupEvents = list.getAsEvents()
val startEventIndex = maxEventIndex - catchupEvents.size + 1
catchupEvents.mapIndexed { index, pageEvent ->
- IndexedValue(
- index = startEventIndex + index,
- value = pageEvent
- )
+ IndexedValue(index = startEventIndex + index, value = pageEvent)
}
}
}
- fun getCachedEvent(): PageEvent.Insert<T>? = list.getAsEvents().firstOrNull()?.let {
- if (it is PageEvent.Insert && it.loadType == LoadType.REFRESH) it else null
- }
+ fun getCachedEvent(): PageEvent.Insert<T>? =
+ list.getAsEvents().firstOrNull()?.let {
+ if (it is PageEvent.Insert && it.loadType == LoadType.REFRESH) it else null
+ }
}
/**
@@ -165,9 +158,9 @@
private val pages = ArrayDeque<TransformablePage<T>>()
/**
- * Note - this is initialized without remote state, since we don't know if we have remote
- * data once we start getting events. This is fine, since downstream needs to handle this
- * anyway - remote state being added after initial, empty, PagingData.
+ * Note - this is initialized without remote state, since we don't know if we have remote data
+ * once we start getting events. This is fine, since downstream needs to handle this anyway -
+ * remote state being added after initial, empty, PagingData.
*/
private val sourceStates = MutableLoadStateCollection()
private var mediatorStates: LoadStates? = null
@@ -177,6 +170,7 @@
* to new downstream subscribers.
*/
private var receivedFirstEvent: Boolean = false
+
fun add(event: PageEvent<T>) {
receivedFirstEvent = true
when (event) {
@@ -219,9 +213,7 @@
}
LoadType.PREPEND -> {
placeholdersBefore = event.placeholdersBefore
- (event.pages.size - 1 downTo 0).forEach {
- pages.addFirst(event.pages[it])
- }
+ (event.pages.size - 1 downTo 0).forEach { pages.addFirst(event.pages[it]) }
}
LoadType.APPEND -> {
placeholdersAfter = event.placeholdersAfter
@@ -267,12 +259,7 @@
)
)
} else {
- events.add(
- PageEvent.LoadStateUpdate(
- source = source,
- mediator = mediatorStates
- )
- )
+ events.add(PageEvent.LoadStateUpdate(source = source, mediator = mediatorStates))
}
return events
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPagingData.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPagingData.kt
index 35e6aed..3ec9188 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPagingData.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CachedPagingData.kt
@@ -29,9 +29,9 @@
import kotlinx.coroutines.flow.shareIn
/**
- * A PagingData wrapper that makes it "efficiently" share-able between multiple downstreams.
- * It flattens all previous pages such that a new subscriber will get all of them at once (and
- * also not deal with dropped pages, intermediate loading state changes etc).
+ * A PagingData wrapper that makes it "efficiently" share-able between multiple downstreams. It
+ * flattens all previous pages such that a new subscriber will get all of them at once (and also not
+ * deal with dropped pages, intermediate loading state changes etc).
*/
private class MulticastedPagingData<T : Any>(
val scope: CoroutineScope,
@@ -39,23 +39,21 @@
// used in tests
val tracker: ActiveFlowTracker? = null
) {
- private val accumulated = CachedPageEventFlow(
- src = parent.flow,
- scope = scope
- ).also {
- tracker?.onNewCachedEventFlow(it)
- }
+ private val accumulated =
+ CachedPageEventFlow(src = parent.flow, scope = scope).also {
+ tracker?.onNewCachedEventFlow(it)
+ }
- fun asPagingData() = PagingData(
- flow = accumulated.downstreamFlow.onStart {
- tracker?.onStart(PAGE_EVENT_FLOW)
- }.onCompletion {
- tracker?.onComplete(PAGE_EVENT_FLOW)
- },
- uiReceiver = parent.uiReceiver,
- hintReceiver = parent.hintReceiver,
- cachedPageEvent = { accumulated.getCachedEvent() }
- )
+ fun asPagingData() =
+ PagingData(
+ flow =
+ accumulated.downstreamFlow
+ .onStart { tracker?.onStart(PAGE_EVENT_FLOW) }
+ .onCompletion { tracker?.onComplete(PAGE_EVENT_FLOW) },
+ uiReceiver = parent.uiReceiver,
+ hintReceiver = parent.hintReceiver,
+ cachedPageEvent = { accumulated.getCachedEvent() }
+ )
suspend fun close() = accumulated.close()
}
@@ -64,18 +62,18 @@
* Caches the [PagingData] such that any downstream collection from this flow will share the same
* [PagingData].
*
- * The flow is kept active as long as the given [scope] is active. To avoid leaks, make sure to
- * use a [scope] that is already managed (like a ViewModel scope) or manually cancel it when you
- * don't need paging anymore.
+ * The flow is kept active as long as the given [scope] is active. To avoid leaks, make sure to use
+ * a [scope] that is already managed (like a ViewModel scope) or manually cancel it when you don't
+ * need paging anymore.
*
* A common use case for this caching is to cache [PagingData] in a ViewModel. This can ensure that,
* upon configuration change (e.g. rotation), then new Activity will receive the existing data
* immediately rather than fetching it from scratch.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
* Note that this does not turn the `Flow<PagingData>` into a hot stream. It won't execute any
* unnecessary code unless it is being collected.
@@ -85,9 +83,8 @@
* @param scope The coroutine scope where this page cache will be kept alive.
*/
@CheckResult
-public fun <T : Any> Flow<PagingData<T>>.cachedIn(
- scope: CoroutineScope
-): Flow<PagingData<T>> = cachedIn(scope, null)
+public fun <T : Any> Flow<PagingData<T>>.cachedIn(scope: CoroutineScope): Flow<PagingData<T>> =
+ cachedIn(scope, null)
internal fun <T : Any> Flow<PagingData<T>>.cachedIn(
scope: CoroutineScope,
@@ -95,35 +92,30 @@
tracker: ActiveFlowTracker? = null
): Flow<PagingData<T>> {
return this.simpleMapLatest {
- MulticastedPagingData(
+ MulticastedPagingData(scope = scope, parent = it, tracker = tracker)
+ }
+ .simpleRunningReduce { prev, next ->
+ prev.close()
+ next
+ }
+ .map { it.asPagingData() }
+ .onStart { tracker?.onStart(PAGED_DATA_FLOW) }
+ .onCompletion { tracker?.onComplete(PAGED_DATA_FLOW) }
+ .shareIn(
scope = scope,
- parent = it,
- tracker = tracker
+ started = SharingStarted.Lazily,
+ // replay latest multicasted paging data since it is re-connectable.
+ replay = 1
)
- }.simpleRunningReduce { prev, next ->
- prev.close()
- next
- }.map {
- it.asPagingData()
- }.onStart {
- tracker?.onStart(PAGED_DATA_FLOW)
- }.onCompletion {
- tracker?.onComplete(PAGED_DATA_FLOW)
- }.shareIn(
- scope = scope,
- started = SharingStarted.Lazily,
- // replay latest multicasted paging data since it is re-connectable.
- replay = 1
- )
}
-/**
- * This is only used for testing to ensure we don't leak resources
- */
+/** This is only used for testing to ensure we don't leak resources */
@VisibleForTesting
internal interface ActiveFlowTracker {
fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>)
+
suspend fun onStart(flowType: FlowType)
+
suspend fun onComplete(flowType: FlowType)
enum class FlowType {
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CancelableChannelFlow.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CancelableChannelFlow.kt
index d84eea1..7e7e4a1 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CancelableChannelFlow.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CancelableChannelFlow.kt
@@ -27,9 +27,7 @@
block: suspend SimpleProducerScope<T>.() -> Unit
): Flow<T> {
return simpleChannelFlow<T> {
- controller.invokeOnCompletion {
- close()
- }
+ controller.invokeOnCompletion { close() }
this.block()
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CombinedLoadStates.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CombinedLoadStates.kt
index 7cb35cd..bd608cd 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CombinedLoadStates.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CombinedLoadStates.kt
@@ -33,41 +33,39 @@
*/
public class CombinedLoadStates(
/**
- * Convenience for combined behavior of [REFRESH][LoadType.REFRESH] [LoadState], which
- * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
- * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
- * remote load was applied.
+ * Convenience for combined behavior of [REFRESH][LoadType.REFRESH] [LoadState], which generally
+ * defers to [mediator] if it exists, but if previously was [LoadState.Loading], awaits for both
+ * [source] and [mediator] to become [LoadState.NotLoading] to ensure the remote load was
+ * applied.
*
- * For use cases that require reacting to [LoadState] of [source] and [mediator]
- * specifically, e.g., showing cached data when network loads via [mediator] fail,
- * [LoadStates] exposed via [source] and [mediator] should be used directly.
+ * For use cases that require reacting to [LoadState] of [source] and [mediator] specifically,
+ * e.g., showing cached data when network loads via [mediator] fail, [LoadStates] exposed via
+ * [source] and [mediator] should be used directly.
*/
public val refresh: LoadState,
/**
- * Convenience for combined behavior of [PREPEND][LoadType.REFRESH] [LoadState], which
- * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
- * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
- * remote load was applied.
+ * Convenience for combined behavior of [PREPEND][LoadType.REFRESH] [LoadState], which generally
+ * defers to [mediator] if it exists, but if previously was [LoadState.Loading], awaits for both
+ * [source] and [mediator] to become [LoadState.NotLoading] to ensure the remote load was
+ * applied.
*
- * For use cases that require reacting to [LoadState] of [source] and [mediator]
- * specifically, e.g., showing cached data when network loads via [mediator] fail,
- * [LoadStates] exposed via [source] and [mediator] should be used directly.
+ * For use cases that require reacting to [LoadState] of [source] and [mediator] specifically,
+ * e.g., showing cached data when network loads via [mediator] fail, [LoadStates] exposed via
+ * [source] and [mediator] should be used directly.
*/
public val prepend: LoadState,
/**
- * Convenience for combined behavior of [APPEND][LoadType.REFRESH] [LoadState], which
- * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
- * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
- * remote load was applied.
+ * Convenience for combined behavior of [APPEND][LoadType.REFRESH] [LoadState], which generally
+ * defers to [mediator] if it exists, but if previously was [LoadState.Loading], awaits for both
+ * [source] and [mediator] to become [LoadState.NotLoading] to ensure the remote load was
+ * applied.
*
- * For use cases that require reacting to [LoadState] of [source] and [mediator]
- * specifically, e.g., showing cached data when network loads via [mediator] fail,
- * [LoadStates] exposed via [source] and [mediator] should be used directly.
+ * For use cases that require reacting to [LoadState] of [source] and [mediator] specifically,
+ * e.g., showing cached data when network loads via [mediator] fail, [LoadStates] exposed via
+ * [source] and [mediator] should be used directly.
*/
public val append: LoadState,
- /**
- * [LoadStates] corresponding to loads from a [PagingSource].
- */
+ /** [LoadStates] corresponding to loads from a [PagingSource]. */
public val source: LoadStates,
/**
@@ -107,24 +105,17 @@
}
internal fun forEach(op: (LoadType, Boolean, LoadState) -> Unit) {
- source.forEach { type, state ->
- op(type, false, state)
- }
- mediator?.forEach { type, state ->
- op(type, true, state)
- }
+ source.forEach { type, state -> op(type, false, state) }
+ mediator?.forEach { type, state -> op(type, true, state) }
}
- /**
- * Returns true when [source] and [mediator] is in [NotLoading] for all [LoadType]
- */
+ /** Returns true when [source] and [mediator] is in [NotLoading] for all [LoadType] */
public val isIdle = source.isIdle && mediator?.isIdle ?: true
/**
* Returns true if either [source] or [mediator] has a [LoadType] that is in [LoadState.Error]
*/
- @get:JvmName("hasError")
- public val hasError = source.hasError || mediator?.hasError ?: false
+ @get:JvmName("hasError") public val hasError = source.hasError || mediator?.hasError ?: false
}
/**
@@ -154,7 +145,5 @@
public suspend fun Flow<CombinedLoadStates>.awaitNotLoading():
@JvmSuppressWildcards CombinedLoadStates? {
- return debounce(1).filter {
- it.isIdle || it.hasError
- }.firstOrNull()
+ return debounce(1).filter { it.isIdle || it.hasError }.firstOrNull()
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CompatLegacyPagingSource.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CompatLegacyPagingSource.kt
index a655684..2d1eb95 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/CompatLegacyPagingSource.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/CompatLegacyPagingSource.kt
@@ -25,6 +25,5 @@
*/
internal interface CompatLegacyPagingSource {
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public fun setPageSize(pageSize: Int)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun setPageSize(pageSize: Int)
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ExperimentalPagingApi.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ExperimentalPagingApi.kt
index 9f2c625..c67adae 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ExperimentalPagingApi.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ExperimentalPagingApi.kt
@@ -20,6 +20,4 @@
* Marks experimental Paging APIs, which may have known issues that would likely be solved by a
* source-incompatible change in newer versions of the artifact that supplies it.
*/
-@RequiresOptIn
-@Retention(AnnotationRetention.BINARY)
-public annotation class ExperimentalPagingApi
+@RequiresOptIn @Retention(AnnotationRetention.BINARY) public annotation class ExperimentalPagingApi
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/FlowExt.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/FlowExt.kt
index a1e281c..e4a1184 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/FlowExt.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/FlowExt.kt
@@ -37,15 +37,12 @@
import kotlinx.coroutines.yield
/**
- * This File includes custom flow operators that we implement to avoid using experimental APIs
- * from coroutines. Eventually, this file should be deleted once those APIs become stable.
+ * This File includes custom flow operators that we implement to avoid using experimental APIs from
+ * coroutines. Eventually, this file should be deleted once those APIs become stable.
*/
-
private val NULL = Any()
-/**
- * Temporary `scan` operator on Flow without experimental APIs.
- */
+/** Temporary `scan` operator on Flow without experimental APIs. */
internal fun <T, R> Flow<T>.simpleScan(
initial: R,
operation: suspend (accumulator: R, value: T) -> R
@@ -58,55 +55,42 @@
}
}
-/**
- * Temporary `runningReduce` operator on Flow without experimental APIs.
- */
+/** Temporary `runningReduce` operator on Flow without experimental APIs. */
internal fun <T> Flow<T>.simpleRunningReduce(
operation: suspend (accumulator: T, value: T) -> T
): Flow<T> = flow {
var accumulator: Any? = NULL
collect { value ->
- accumulator = if (accumulator === NULL) {
- value
- } else {
- @Suppress("UNCHECKED_CAST")
- operation(accumulator as T, value)
- }
- @Suppress("UNCHECKED_CAST")
- emit(accumulator as T)
+ accumulator =
+ if (accumulator === NULL) {
+ value
+ } else {
+ @Suppress("UNCHECKED_CAST") operation(accumulator as T, value)
+ }
+ @Suppress("UNCHECKED_CAST") emit(accumulator as T)
}
}
-/**
- * This is a similar implementation to transformLatest using a channel Flow.
- */
+/** This is a similar implementation to transformLatest using a channel Flow. */
internal fun <T, R> Flow<T>.simpleTransformLatest(
transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = simpleChannelFlow {
val origin = this@simpleTransformLatest
val collector = ChannelFlowCollector(this@simpleChannelFlow)
- origin.collectLatest { value ->
- collector.transform(value)
- }
+ origin.collectLatest { value -> collector.transform(value) }
}
-/**
- * flatMapLatest without experimental APIs
- */
+/** flatMapLatest without experimental APIs */
internal inline fun <T, R> Flow<T>.simpleFlatMapLatest(
crossinline transform: suspend (value: T) -> Flow<R>
): Flow<R> = simpleTransformLatest { emitAll(transform(it)) }
-/**
- * mapLatest without experimental APIs
- */
+/** mapLatest without experimental APIs */
internal inline fun <T, R> Flow<T>.simpleMapLatest(
crossinline transform: suspend (value: T) -> R
): Flow<R> = simpleTransformLatest { emit(transform(it)) }
-internal class ChannelFlowCollector<T>(
- val channel: SendChannel<T>
-) : FlowCollector<T> {
+internal class ChannelFlowCollector<T>(val channel: SendChannel<T>) : FlowCollector<T> {
override suspend fun emit(value: T) {
channel.send(value)
}
@@ -117,19 +101,19 @@
* [transform] is always guaranteed to get called for every emission from either Flow after the
* initial call (which waits for the first emission from both Flows).
*
- * The emissions for both Flows are also guaranteed to get buffered, so if one Flow emits
- * multiple times before the other does, [transform] will get called for each emission from the
- * first Flow instead of just once with the latest values.
+ * The emissions for both Flows are also guaranteed to get buffered, so if one Flow emits multiple
+ * times before the other does, [transform] will get called for each emission from the first Flow
+ * instead of just once with the latest values.
*
* @param transform The transform to apply on each update. This is first called after awaiting an
- * initial emission from both Flows, and then is guaranteed to be called for every emission from
- * either Flow.
+ * initial emission from both Flows, and then is guaranteed to be called for every emission from
+ * either Flow.
*
- * For convenience, [CombineSource] is also passed to the transform, which indicates the
- * origin of the update with the following possible values:
- * * [INITIAL]: Initial emission from both Flows
- * * [RECEIVER]: Triggered by new emission from receiver
- * * [OTHER]: Triggered by new emission from [otherFlow]
+ * For convenience, [CombineSource] is also passed to the transform, which indicates the origin of
+ * the update with the following possible values:
+ * * [INITIAL]: Initial emission from both Flows
+ * * [RECEIVER]: Triggered by new emission from receiver
+ * * [OTHER]: Triggered by new emission from [otherFlow]
*/
internal suspend inline fun <T1, T2, R> Flow<T1>.combineWithoutBatching(
otherFlow: Flow<T2>,
@@ -137,9 +121,10 @@
): Flow<R> {
return simpleChannelFlow {
val incompleteFlows = AtomicInt(2)
- val unbatchedFlowCombiner = UnbatchedFlowCombiner<T1, T2> { t1, t2, updateFrom ->
- send(transform(t1, t2, updateFrom))
- }
+ val unbatchedFlowCombiner =
+ UnbatchedFlowCombiner<T1, T2> { t1, t2, updateFrom ->
+ send(transform(t1, t2, updateFrom))
+ }
val parentJob = Job()
arrayOf(this@combineWithoutBatching, otherFlow).forEachIndexed { index, flow ->
launch(parentJob) {
@@ -166,9 +151,8 @@
* Helper class for [UnbatchedFlowCombiner], which handles dispatching the combined values in the
* correct order, and with [CombineSource].
*
- * NOTE: This implementation relies on the fact that [onNext] is called in-order for emissions
- * from the same Flow. This means that concurrently calling [onNext] with the same index will not
- * work.
+ * NOTE: This implementation relies on the fact that [onNext] is called in-order for emissions from
+ * the same Flow. This means that concurrently calling [onNext] with the same index will not work.
*
* @see combineWithoutBatching
*/
@@ -197,14 +181,14 @@
values[index] = value
if (values.none { it === NULL }) {
- val updateFrom = when {
- isInitial -> INITIAL
- index == 0 -> RECEIVER
- else -> OTHER
- }
+ val updateFrom =
+ when {
+ isInitial -> INITIAL
+ index == 0 -> RECEIVER
+ else -> OTHER
+ }
- @Suppress("UNCHECKED_CAST")
- send(values[0] as T1, values[1] as T2, updateFrom)
+ @Suppress("UNCHECKED_CAST") send(values[0] as T1, values[1] as T2, updateFrom)
initialDispatched.complete(Unit)
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
index 8f48b22..5efd6be 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
@@ -28,9 +28,8 @@
import kotlinx.coroutines.flow.MutableSharedFlow
/**
- * Helper class to handle UI hints.
- * It processes incoming hints and keeps a min/max (prepend/append) values and provides them as a
- * flow to [PageFetcherSnapshot].
+ * Helper class to handle UI hints. It processes incoming hints and keeps a min/max (prepend/append)
+ * values and provides them as a flow to [PageFetcherSnapshot].
*/
internal class HintHandler {
private val state = State()
@@ -42,31 +41,23 @@
val lastAccessHint: ViewportHint.Access?
get() = state.lastAccessHint
- /**
- * Returns a flow of hints for the given [loadType].
- */
- fun hintFor(loadType: LoadType): Flow<ViewportHint> = when (loadType) {
- PREPEND -> state.prependFlow
- APPEND -> state.appendFlow
- else -> throw IllegalArgumentException("invalid load type for hints")
- }
+ /** Returns a flow of hints for the given [loadType]. */
+ fun hintFor(loadType: LoadType): Flow<ViewportHint> =
+ when (loadType) {
+ PREPEND -> state.prependFlow
+ APPEND -> state.appendFlow
+ else -> throw IllegalArgumentException("invalid load type for hints")
+ }
/**
- * Resets the hint for the given [loadType].
- * Note that this won't update [lastAccessHint] or the other load type.
+ * Resets the hint for the given [loadType]. Note that this won't update [lastAccessHint] or the
+ * other load type.
*/
- fun forceSetHint(
- loadType: LoadType,
- viewportHint: ViewportHint
- ) {
- require(
- loadType == PREPEND || loadType == APPEND
- ) {
+ fun forceSetHint(loadType: LoadType, viewportHint: ViewportHint) {
+ require(loadType == PREPEND || loadType == APPEND) {
"invalid load type for reset: $loadType"
}
- state.modify(
- accessHint = null
- ) { prependHint, appendHint ->
+ state.modify(accessHint = null) { prependHint, appendHint ->
if (loadType == PREPEND) {
prependHint.value = viewportHint
} else {
@@ -75,23 +66,15 @@
}
}
- /**
- * Processes the hint coming from UI.
- */
+ /** Processes the hint coming from UI. */
fun processHint(viewportHint: ViewportHint) {
state.modify(viewportHint as? ViewportHint.Access) { prependHint, appendHint ->
- if (viewportHint.shouldPrioritizeOver(
- previous = prependHint.value,
- loadType = PREPEND
- )
+ if (
+ viewportHint.shouldPrioritizeOver(previous = prependHint.value, loadType = PREPEND)
) {
prependHint.value = viewportHint
}
- if (viewportHint.shouldPrioritizeOver(
- previous = appendHint.value,
- loadType = APPEND
- )
- ) {
+ if (viewportHint.shouldPrioritizeOver(previous = appendHint.value, loadType = APPEND)) {
appendHint.value = viewportHint
}
}
@@ -102,15 +85,16 @@
private val append = HintFlow()
var lastAccessHint: ViewportHint.Access? = null
private set
+
val prependFlow
get() = prepend.flow
+
val appendFlow
get() = append.flow
+
private val lock = ReentrantLock()
- /**
- * Modifies the state inside a lock where it gets access to the mutable values.
- */
+ /** Modifies the state inside a lock where it gets access to the mutable values. */
fun modify(
accessHint: ViewportHint.Access?,
block: (prepend: HintFlow, append: HintFlow) -> Unit
@@ -125,8 +109,8 @@
}
/**
- * Like a StateFlow that holds the value but does not do de-duping.
- * Note that, this class is not thread safe.
+ * Like a StateFlow that holds the value but does not do de-duping. Note that, this class is not
+ * thread safe.
*/
private inner class HintFlow {
var value: ViewportHint? = null
@@ -136,10 +120,12 @@
_flow.tryEmit(value)
}
}
- private val _flow = MutableSharedFlow<ViewportHint>(
- replay = 1,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
+
+ private val _flow =
+ MutableSharedFlow<ViewportHint>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
val flow: Flow<ViewportHint>
get() = _flow
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintReceiver.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintReceiver.kt
index 22dabae..e15866d 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintReceiver.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintReceiver.kt
@@ -16,9 +16,7 @@
package androidx.paging
-/**
- * Fetcher-side callbacks for presenter-side access events communicated through [PagingData].
- */
+/** Fetcher-side callbacks for presenter-side access events communicated through [PagingData]. */
internal interface HintReceiver {
fun accessHint(viewportHint: ViewportHint)
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
index 7c83d58..304bcc6 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
@@ -20,14 +20,10 @@
import androidx.paging.internal.ReentrantLock
import androidx.paging.internal.withLock
-/**
- * Helper class for thread-safe invalidation callback tracking + triggering on registration.
- */
+/** Helper class for thread-safe invalidation callback tracking + triggering on registration. */
internal class InvalidateCallbackTracker<T>(
private val callbackInvoker: (T) -> Unit,
- /**
- * User-provided override of DataSource.isInvalid
- */
+ /** User-provided override of DataSource.isInvalid */
private val invalidGetter: (() -> Boolean)? = null,
) {
private val lock = ReentrantLock()
@@ -35,8 +31,7 @@
internal var invalid = false
private set
- @VisibleForTesting
- internal fun callbackCount() = callbacks.size
+ @VisibleForTesting internal fun callbackCount() = callbacks.size
internal fun registerInvalidatedCallback(callback: T) {
// This isn't sufficient, but is the best we can do in cases where DataSource.isInvalid
@@ -51,14 +46,15 @@
return
}
- val callImmediately = lock.withLock {
- if (invalid) {
- true // call immediately
- } else {
- callbacks.add(callback)
- false // don't call, not invalid yet.
+ val callImmediately =
+ lock.withLock {
+ if (invalid) {
+ true // call immediately
+ } else {
+ callbacks.add(callback)
+ false // don't call, not invalid yet.
+ }
}
- }
if (callImmediately) {
callbackInvoker(callback)
@@ -66,22 +62,19 @@
}
internal fun unregisterInvalidatedCallback(callback: T) {
- lock.withLock {
- callbacks.remove(callback)
- }
+ lock.withLock { callbacks.remove(callback) }
}
internal fun invalidate(): Boolean {
if (invalid) return false
- val callbacksToInvoke = lock.withLock {
- if (invalid) return false
+ val callbacksToInvoke =
+ lock.withLock {
+ if (invalid) return false
- invalid = true
- callbacks.toList().also {
- callbacks.clear()
+ invalid = true
+ callbacks.toList().also { callbacks.clear() }
}
- }
callbacksToInvoke.forEach(callbackInvoker)
return true
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
index 21f520e..94e85135 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
@@ -23,11 +23,11 @@
/**
* Wrapper class for a [PagingSource] factory intended for usage in [Pager] construction.
*
- * Calling [invalidate] on this [InvalidatingPagingSourceFactory] will forward invalidate signals
- * to all active [PagingSource]s that were produced by calling [invoke].
+ * Calling [invalidate] on this [InvalidatingPagingSourceFactory] will forward invalidate signals to
+ * all active [PagingSource]s that were produced by calling [invoke].
*
- * This class is thread-safe for concurrent calls to any mutative operations including both
- * [invoke] and [invalidate].
+ * This class is thread-safe for concurrent calls to any mutative operations including both [invoke]
+ * and [invalidate].
*
* @param pagingSourceFactory The [PagingSource] factory that returns a PagingSource when called
*/
@@ -38,19 +38,14 @@
private var pagingSources: List<PagingSource<Key, Value>> = emptyList()
- @VisibleForTesting
- internal fun pagingSources() = pagingSources
+ @VisibleForTesting internal fun pagingSources() = pagingSources
/**
- * @return [PagingSource] which will be invalidated when this factory's [invalidate] method
- * is called
+ * @return [PagingSource] which will be invalidated when this factory's [invalidate] method is
+ * called
*/
override fun invoke(): PagingSource<Key, Value> {
- return pagingSourceFactory().also {
- lock.withLock {
- pagingSources = pagingSources + it
- }
- }
+ return pagingSourceFactory().also { lock.withLock { pagingSources = pagingSources + it } }
}
/**
@@ -58,11 +53,7 @@
* [InvalidatingPagingSourceFactory]
*/
public fun invalidate() {
- val previousList = lock.withLock {
- pagingSources.also {
- pagingSources = emptyList()
- }
- }
+ val previousList = lock.withLock { pagingSources.also { pagingSources = emptyList() } }
for (pagingSource in previousList) {
if (!pagingSource.invalid) {
pagingSource.invalidate()
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ItemSnapshotList.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ItemSnapshotList.kt
index 1512288..ca94d29 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ItemSnapshotList.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ItemSnapshotList.kt
@@ -28,17 +28,13 @@
* Number of placeholders before the presented [items], 0 if
* [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
*/
- @IntRange(from = 0)
- public val placeholdersBefore: Int,
+ @IntRange(from = 0) public val placeholdersBefore: Int,
/**
* Number of placeholders after the presented [items], 0 if
* [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
*/
- @IntRange(from = 0)
- public val placeholdersAfter: Int,
- /**
- * The presented data, excluding placeholders.
- */
+ @IntRange(from = 0) public val placeholdersAfter: Int,
+ /** The presented data, excluding placeholders. */
public val items: List<T>
) : AbstractList<T?>() {
@@ -67,9 +63,10 @@
items[index - placeholdersBefore]
}
in (placeholdersBefore + items.size) until size -> null
- else -> throw IndexOutOfBoundsException(
- "Illegal attempt to access index $index in ItemSnapshotList of size $size"
- )
+ else ->
+ throw IndexOutOfBoundsException(
+ "Illegal attempt to access index $index in ItemSnapshotList of size $size"
+ )
}
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
index 2841b9f..b00e5cd 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadState.kt
@@ -24,36 +24,31 @@
* [androidx.paging.AsyncPagingDataDiffer.addLoadStateListener]
*
* @param endOfPaginationReached `false` if there is more data to load in the [LoadType] this
- * [LoadState] is associated with, `true` otherwise. This parameter informs [Pager] if it
- * should continue to make requests for additional data in this direction or if it should
- * halt as the end of the dataset has been reached.
+ * [LoadState] is associated with, `true` otherwise. This parameter informs [Pager] if it should
+ * continue to make requests for additional data in this direction or if it should halt as the end
+ * of the dataset has been reached.
*
* Note: The [LoadType] [REFRESH][LoadType.REFRESH] always has [LoadState.endOfPaginationReached]
* set to `false`.
*
* @see LoadType
*/
-public sealed class LoadState(
- public val endOfPaginationReached: Boolean
-) {
+public sealed class LoadState(public val endOfPaginationReached: Boolean) {
/**
* Indicates the [PagingData] is not currently loading, and no error currently observed.
*
* @param endOfPaginationReached `false` if there is more data to load in the [LoadType] this
- * [LoadState] is associated with, `true` otherwise. This parameter informs [Pager] if it
- * should continue to make requests for additional data in this direction or if it should
- * halt as the end of the dataset has been reached.
+ * [LoadState] is associated with, `true` otherwise. This parameter informs [Pager] if it
+ * should continue to make requests for additional data in this direction or if it should halt
+ * as the end of the dataset has been reached.
*/
- public class NotLoading(
- endOfPaginationReached: Boolean
- ) : LoadState(endOfPaginationReached) {
+ public class NotLoading(endOfPaginationReached: Boolean) : LoadState(endOfPaginationReached) {
override fun toString(): String {
return "NotLoading(endOfPaginationReached=$endOfPaginationReached)"
}
override fun equals(other: Any?): Boolean {
- return other is NotLoading &&
- endOfPaginationReached == other.endOfPaginationReached
+ return other is NotLoading && endOfPaginationReached == other.endOfPaginationReached
}
override fun hashCode(): Int {
@@ -66,17 +61,14 @@
}
}
- /**
- * Loading is in progress.
- */
+ /** Loading is in progress. */
public object Loading : LoadState(false) {
override fun toString(): String {
return "Loading(endOfPaginationReached=$endOfPaginationReached)"
}
override fun equals(other: Any?): Boolean {
- return other is Loading &&
- endOfPaginationReached == other.endOfPaginationReached
+ return other is Loading && endOfPaginationReached == other.endOfPaginationReached
}
override fun hashCode(): Int {
@@ -88,12 +80,9 @@
* Loading hit an error.
*
* @param error [Throwable] that caused the load operation to generate this error state.
- *
* @see androidx.paging.PagedList#retry
*/
- public class Error(
- public val error: Throwable
- ) : LoadState(false) {
+ public class Error(public val error: Throwable) : LoadState(false) {
override fun equals(other: Any?): Boolean {
return other is Error &&
endOfPaginationReached == other.endOfPaginationReached &&
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadStates.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadStates.kt
index c0626db..f8267cae 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadStates.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadStates.kt
@@ -20,9 +20,7 @@
import androidx.paging.LoadState.NotLoading
import kotlin.jvm.JvmName
-/**
- * Collection of pagination [LoadState]s - refresh, prepend, and append.
- */
+/** Collection of pagination [LoadState]s - refresh, prepend, and append. */
public data class LoadStates(
/** [LoadState] corresponding to [LoadType.REFRESH] loads. */
public val refresh: LoadState,
@@ -40,43 +38,36 @@
internal fun modifyState(loadType: LoadType, newState: LoadState): LoadStates {
return when (loadType) {
- LoadType.APPEND -> copy(
- append = newState
- )
- LoadType.PREPEND -> copy(
- prepend = newState
- )
- LoadType.REFRESH -> copy(
- refresh = newState
- )
+ LoadType.APPEND -> copy(append = newState)
+ LoadType.PREPEND -> copy(prepend = newState)
+ LoadType.REFRESH -> copy(refresh = newState)
}
}
- internal fun get(loadType: LoadType) = when (loadType) {
- LoadType.REFRESH -> refresh
- LoadType.APPEND -> append
- LoadType.PREPEND -> prepend
- }
+ internal fun get(loadType: LoadType) =
+ when (loadType) {
+ LoadType.REFRESH -> refresh
+ LoadType.APPEND -> append
+ LoadType.PREPEND -> prepend
+ }
- /**
- * Returns true if either one of [refresh], [append], or [prepend] is in [Error] state.
- */
+ /** Returns true if either one of [refresh], [append], or [prepend] is in [Error] state. */
@get:JvmName("hasError")
- public val hasError = refresh is LoadState.Error || append is LoadState.Error ||
- prepend is LoadState.Error
+ public val hasError =
+ refresh is LoadState.Error || append is LoadState.Error || prepend is LoadState.Error
/**
- * Returns true if all three LoadState [refresh], [append], and [prepend] are
- * in [NotLoading] state.
+ * Returns true if all three LoadState [refresh], [append], and [prepend] are in [NotLoading]
+ * state.
*/
- public val isIdle = refresh is NotLoading && append is NotLoading &&
- prepend is NotLoading
+ public val isIdle = refresh is NotLoading && append is NotLoading && prepend is NotLoading
internal companion object {
- val IDLE = LoadStates(
- refresh = NotLoading.Incomplete,
- prepend = NotLoading.Incomplete,
- append = NotLoading.Incomplete
- )
+ val IDLE =
+ LoadStates(
+ refresh = NotLoading.Incomplete,
+ prepend = NotLoading.Incomplete,
+ append = NotLoading.Incomplete
+ )
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadType.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadType.kt
index a1fc9e6..5a5da6f 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadType.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/LoadType.kt
@@ -27,18 +27,14 @@
*/
public enum class LoadType {
/**
- * [PagingData] content being refreshed, which can be a result of [PagingSource]
- * invalidation, refresh that may contain content updates, or the initial load.
+ * [PagingData] content being refreshed, which can be a result of [PagingSource] invalidation,
+ * refresh that may contain content updates, or the initial load.
*/
REFRESH,
- /**
- * Load at the start of a [PagingData].
- */
+ /** Load at the start of a [PagingData]. */
PREPEND,
- /**
- * Load at the end of a [PagingData].
- */
+ /** Load at the end of a [PagingData]. */
APPEND
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
index 9bd6f0f..51ba4a0 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
@@ -46,18 +46,17 @@
}
// load states are de-duplicated
- fun set(type: LoadType, remote: Boolean, state: LoadState) =
- dispatchNewState { currState ->
- var source = currState?.source ?: LoadStates.IDLE
- var mediator = currState?.mediator
+ fun set(type: LoadType, remote: Boolean, state: LoadState) = dispatchNewState { currState ->
+ var source = currState?.source ?: LoadStates.IDLE
+ var mediator = currState?.mediator
- if (remote) {
- mediator = LoadStates.IDLE.modifyState(type, state)
- } else {
- source = source.modifyState(type, state)
- }
- computeNewState(currState, source, mediator)
+ if (remote) {
+ mediator = LoadStates.IDLE.modifyState(type, state)
+ } else {
+ source = source.modifyState(type, state)
}
+ computeNewState(currState, source, mediator)
+ }
fun get(type: LoadType, remote: Boolean): LoadState? {
val state = _stateFlow.value
@@ -81,15 +80,15 @@
}
/**
- * Computes and dispatches the new CombinedLoadStates. No-op if new value is same as
- * previous value.
+ * Computes and dispatches the new CombinedLoadStates. No-op if new value is same as previous
+ * value.
*
* We manually de-duplicate emissions to StateFlow and to listeners even though
* [MutableStateFlow.update] de-duplicates automatically in that duplicated values are set but
* not sent to collectors. However it doesn't indicate whether the new value is indeed a
- * duplicate or not, so we still need to manually compare previous/updated values before
- * sending to listeners. Because of that, we manually de-dupe both stateFlow and listener
- * emissions to ensure they are in sync.
+ * duplicate or not, so we still need to manually compare previous/updated values before sending
+ * to listeners. Because of that, we manually de-dupe both stateFlow and listener emissions to
+ * ensure they are in sync.
*/
private fun dispatchNewState(
computeNewState: (currState: CombinedLoadStates?) -> CombinedLoadStates
@@ -113,25 +112,27 @@
newSource: LoadStates,
newRemote: LoadStates?
): CombinedLoadStates {
- val refresh = computeHelperState(
- previousState = previousState?.refresh ?: NotLoading.Incomplete,
- sourceRefreshState = newSource.refresh,
- sourceState = newSource.refresh,
- remoteState = newRemote?.refresh
-
- )
- val prepend = computeHelperState(
- previousState = previousState?.prepend ?: NotLoading.Incomplete,
- sourceRefreshState = newSource.refresh,
- sourceState = newSource.prepend,
- remoteState = newRemote?.prepend
- )
- val append = computeHelperState(
- previousState = previousState?.append ?: NotLoading.Incomplete,
- sourceRefreshState = newSource.refresh,
- sourceState = newSource.append,
- remoteState = newRemote?.append
- )
+ val refresh =
+ computeHelperState(
+ previousState = previousState?.refresh ?: NotLoading.Incomplete,
+ sourceRefreshState = newSource.refresh,
+ sourceState = newSource.refresh,
+ remoteState = newRemote?.refresh
+ )
+ val prepend =
+ computeHelperState(
+ previousState = previousState?.prepend ?: NotLoading.Incomplete,
+ sourceRefreshState = newSource.refresh,
+ sourceState = newSource.prepend,
+ remoteState = newRemote?.prepend
+ )
+ val append =
+ computeHelperState(
+ previousState = previousState?.append ?: NotLoading.Incomplete,
+ sourceRefreshState = newSource.refresh,
+ sourceState = newSource.append,
+ remoteState = newRemote?.append
+ )
return CombinedLoadStates(
refresh = refresh,
@@ -143,11 +144,11 @@
}
/**
- * Computes the next value for the convenience helpers in [CombinedLoadStates], which
- * generally defers to remote state, but waits for both source and remote states to become
- * [NotLoading] before moving to that state. This provides a reasonable default for the common
- * use-case where you generally want to wait for both RemoteMediator to return and for the
- * update to get applied before signaling to UI that a network fetch has "finished".
+ * Computes the next value for the convenience helpers in [CombinedLoadStates], which generally
+ * defers to remote state, but waits for both source and remote states to become [NotLoading]
+ * before moving to that state. This provides a reasonable default for the common use-case where
+ * you generally want to wait for both RemoteMediator to return and for the update to get
+ * applied before signaling to UI that a network fetch has "finished".
*/
private fun computeHelperState(
previousState: LoadState,
@@ -158,11 +159,12 @@
if (remoteState == null) return sourceState
return when (previousState) {
- is Loading -> when {
- sourceRefreshState is NotLoading && remoteState is NotLoading -> remoteState
- remoteState is Error -> remoteState
- else -> previousState
- }
+ is Loading ->
+ when {
+ sourceRefreshState is NotLoading && remoteState is NotLoading -> remoteState
+ remoteState is Error -> remoteState
+ else -> previousState
+ }
else -> remoteState
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableLoadStateCollection.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableLoadStateCollection.kt
index f9884e4..dbf8a92 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableLoadStateCollection.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/MutableLoadStateCollection.kt
@@ -23,23 +23,26 @@
var prepend: LoadState = NotLoading.Incomplete
var append: LoadState = NotLoading.Incomplete
- fun snapshot() = LoadStates(
- refresh = refresh,
- prepend = prepend,
- append = append,
- )
+ fun snapshot() =
+ LoadStates(
+ refresh = refresh,
+ prepend = prepend,
+ append = append,
+ )
- fun get(loadType: LoadType) = when (loadType) {
- LoadType.REFRESH -> refresh
- LoadType.APPEND -> append
- LoadType.PREPEND -> prepend
- }
+ fun get(loadType: LoadType) =
+ when (loadType) {
+ LoadType.REFRESH -> refresh
+ LoadType.APPEND -> append
+ LoadType.PREPEND -> prepend
+ }
- fun set(type: LoadType, state: LoadState) = when (type) {
- LoadType.REFRESH -> refresh = state
- LoadType.APPEND -> append = state
- LoadType.PREPEND -> prepend = state
- }
+ fun set(type: LoadType, state: LoadState) =
+ when (type) {
+ LoadType.REFRESH -> refresh = state
+ LoadType.APPEND -> append = state
+ LoadType.PREPEND -> prepend = state
+ }
fun set(states: LoadStates) {
refresh = states.refresh
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageEvent.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageEvent.kt
index 3522681..4220801 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageEvent.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageEvent.kt
@@ -34,9 +34,9 @@
* generation.
*
* @param sourceLoadStates source [LoadStates] to emit if non-null, ignored otherwise, allowing
- * the presenter receiving this event to maintain the previous state.
+ * the presenter receiving this event to maintain the previous state.
* @param mediatorLoadStates mediator [LoadStates] to emit if non-null, ignored otherwise,
- * allowing the presenter receiving this event to maintain its previous state.
+ * allowing the presenter receiving this event to maintain its previous state.
*/
data class StaticList<T : Any>(
val data: List<T>,
@@ -82,7 +82,8 @@
// Intentional to prefer Refresh, Prepend, Append constructors from Companion.
@Suppress("DataClassPrivateConstructor")
- data class Insert<T : Any> private constructor(
+ data class Insert<T : Any>
+ private constructor(
val loadType: LoadType,
val pages: List<TransformablePage<T>>,
val placeholdersBefore: Int,
@@ -113,14 +114,15 @@
internal inline fun <R : Any> transformPages(
transform: (List<TransformablePage<T>>) -> List<TransformablePage<R>>
- ): Insert<R> = Insert(
- loadType = loadType,
- pages = transform(pages),
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = sourceLoadStates,
- mediatorLoadStates = mediatorLoadStates,
- )
+ ): Insert<R> =
+ Insert(
+ loadType = loadType,
+ pages = transform(pages),
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = sourceLoadStates,
+ mediatorLoadStates = mediatorLoadStates,
+ )
override suspend fun <R : Any> map(transform: suspend (T) -> R): PageEvent<R> = mapPages {
TransformablePage(
@@ -175,58 +177,63 @@
placeholdersAfter: Int,
sourceLoadStates: LoadStates,
mediatorLoadStates: LoadStates? = null
- ) = Insert(
- REFRESH,
- pages,
- placeholdersBefore,
- placeholdersAfter,
- sourceLoadStates,
- mediatorLoadStates,
- )
+ ) =
+ Insert(
+ REFRESH,
+ pages,
+ placeholdersBefore,
+ placeholdersAfter,
+ sourceLoadStates,
+ mediatorLoadStates,
+ )
fun <T : Any> Prepend(
pages: List<TransformablePage<T>>,
placeholdersBefore: Int,
sourceLoadStates: LoadStates,
mediatorLoadStates: LoadStates? = null
- ) = Insert(
- PREPEND,
- pages,
- placeholdersBefore,
- -1,
- sourceLoadStates,
- mediatorLoadStates,
- )
+ ) =
+ Insert(
+ PREPEND,
+ pages,
+ placeholdersBefore,
+ -1,
+ sourceLoadStates,
+ mediatorLoadStates,
+ )
fun <T : Any> Append(
pages: List<TransformablePage<T>>,
placeholdersAfter: Int,
sourceLoadStates: LoadStates,
mediatorLoadStates: LoadStates? = null
- ) = Insert(
- APPEND,
- pages,
- -1,
- placeholdersAfter,
- sourceLoadStates,
- mediatorLoadStates,
- )
+ ) =
+ Insert(
+ APPEND,
+ pages,
+ -1,
+ placeholdersAfter,
+ sourceLoadStates,
+ mediatorLoadStates,
+ )
/**
* Empty refresh, used to convey initial state.
*
* Note - has no remote state, so remote state may be added over time
*/
- val EMPTY_REFRESH_LOCAL: Insert<Any> = Refresh(
- pages = listOf(TransformablePage.EMPTY_INITIAL_PAGE),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- sourceLoadStates = LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Complete,
- append = LoadState.NotLoading.Complete,
- ),
- )
+ val EMPTY_REFRESH_LOCAL: Insert<Any> =
+ Refresh(
+ pages = listOf(TransformablePage.EMPTY_INITIAL_PAGE),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ sourceLoadStates =
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Complete,
+ append = LoadState.NotLoading.Complete,
+ ),
+ )
}
override fun toString(): String {
@@ -248,13 +255,9 @@
// TODO: b/195658070 consider refactoring Drop events to carry full source/mediator states.
data class Drop<T : Any>(
val loadType: LoadType,
- /**
- * Smallest [TransformablePage.originalPageOffsets] to drop; inclusive.
- */
+ /** Smallest [TransformablePage.originalPageOffsets] to drop; inclusive. */
val minPageOffset: Int,
- /**
- * Largest [TransformablePage.originalPageOffsets] to drop; inclusive
- */
+ /** Largest [TransformablePage.originalPageOffsets] to drop; inclusive */
val maxPageOffset: Int,
val placeholdersRemaining: Int
) : PageEvent<T>() {
@@ -267,21 +270,23 @@
}
}
- val pageCount get() = maxPageOffset - minPageOffset + 1
+ val pageCount
+ get() = maxPageOffset - minPageOffset + 1
override fun toString(): String {
- val direction = when (loadType) {
- APPEND -> "end"
- PREPEND -> "front"
- else -> throw IllegalArgumentException(
- "Drop load type must be PREPEND or APPEND"
- )
- }
+ val direction =
+ when (loadType) {
+ APPEND -> "end"
+ PREPEND -> "front"
+ else ->
+ throw IllegalArgumentException("Drop load type must be PREPEND or APPEND")
+ }
return """PageEvent.Drop from the $direction (
| minPageOffset: $minPageOffset
| maxPageOffset: $maxPageOffset
| placeholdersRemaining: $placeholdersRemaining
- |)""".trimMargin()
+ |)"""
+ .trimMargin()
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcher.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcher.kt
index 88e80cc..409cfdd 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcher.kt
@@ -33,8 +33,7 @@
private val pagingSourceFactory: suspend () -> PagingSource<Key, Value>,
private val initialKey: Key?,
private val config: PagingConfig,
- @OptIn(ExperimentalPagingApi::class)
- remoteMediator: RemoteMediator<Key, Value>? = null
+ @OptIn(ExperimentalPagingApi::class) remoteMediator: RemoteMediator<Key, Value>? = null
) {
/**
* Channel of refresh signals that would trigger a new instance of [PageFetcherSnapshot].
@@ -42,7 +41,7 @@
* `false` otherwise.
*
* NOTE: This channel is conflated, which means it has a buffer size of 1, and will always
- * broadcast the latest value received.
+ * broadcast the latest value received.
*/
private val refreshEvents = ConflatedEventBus<Boolean>()
@@ -52,17 +51,15 @@
// the paging.
val flow: Flow<PagingData<Value>> = simpleChannelFlow {
@OptIn(ExperimentalPagingApi::class)
- val remoteMediatorAccessor = remoteMediator?.let {
- RemoteMediatorAccessor(this, it)
- }
+ val remoteMediatorAccessor = remoteMediator?.let { RemoteMediatorAccessor(this, it) }
- refreshEvents
- .flow
+ refreshEvents.flow
.onStart {
@OptIn(ExperimentalPagingApi::class)
emit(remoteMediatorAccessor?.initialize() == LAUNCH_INITIAL_REFRESH)
}
- .simpleScan(null) { previousGeneration: GenerationInfo<Key, Value>?,
+ .simpleScan(null) {
+ previousGeneration: GenerationInfo<Key, Value>?,
triggerRemoteRefresh: Boolean ->
// Enable refresh if this is the first generation and we have LAUNCH_INITIAL_REFRESH
// or if this generation was started due to [refresh] being invoked.
@@ -70,17 +67,19 @@
remoteMediatorAccessor?.allowRefresh()
}
- val pagingSource = generateNewPagingSource(
- previousPagingSource = previousGeneration?.snapshot?.pagingSource
- )
+ val pagingSource =
+ generateNewPagingSource(
+ previousPagingSource = previousGeneration?.snapshot?.pagingSource
+ )
var previousPagingState = previousGeneration?.snapshot?.currentPagingState()
// If cached PagingState had pages loaded, but previous generation didn't, use
// the cached PagingState to handle cases where invalidation happens too quickly,
// so that getRefreshKey and remote refresh at least have some data to work with.
- if (previousPagingState?.pages.isNullOrEmpty() &&
- previousGeneration?.state?.pages?.isNotEmpty() == true
+ if (
+ previousPagingState?.pages.isNullOrEmpty() &&
+ previousGeneration?.state?.pages?.isNotEmpty() == true
) {
previousPagingState = previousGeneration.state
}
@@ -89,43 +88,51 @@
// re-use last PagingState that successfully loaded pages and has an anchorPosition.
// This prevents rapid invalidation from deleting the anchorPosition if the
// previous generation didn't have time to load before getting invalidated.
- if (previousPagingState?.anchorPosition == null &&
- previousGeneration?.state?.anchorPosition != null
+ if (
+ previousPagingState?.anchorPosition == null &&
+ previousGeneration?.state?.anchorPosition != null
) {
previousPagingState = previousGeneration.state
}
- val initialKey: Key? = when (previousPagingState) {
- null -> initialKey
- else -> pagingSource.getRefreshKey(previousPagingState).also {
- log(DEBUG) { "Refresh key $it returned from PagingSource $pagingSource" }
+ val initialKey: Key? =
+ when (previousPagingState) {
+ null -> initialKey
+ else ->
+ pagingSource.getRefreshKey(previousPagingState).also {
+ log(DEBUG) {
+ "Refresh key $it returned from PagingSource $pagingSource"
+ }
+ }
}
- }
previousGeneration?.snapshot?.close()
previousGeneration?.job?.cancel()
GenerationInfo(
- snapshot = PageFetcherSnapshot(
- initialKey = initialKey,
- pagingSource = pagingSource,
- config = config,
- retryFlow = retryEvents.flow,
- // Only trigger remote refresh on refresh signals that do not originate from
- // initialization or PagingSource invalidation.
- remoteMediatorConnection = remoteMediatorAccessor,
- jumpCallback = this@PageFetcher::refresh,
- previousPagingState = previousPagingState,
- ),
+ snapshot =
+ PageFetcherSnapshot(
+ initialKey = initialKey,
+ pagingSource = pagingSource,
+ config = config,
+ retryFlow = retryEvents.flow,
+ // Only trigger remote refresh on refresh signals that do not originate
+ // from
+ // initialization or PagingSource invalidation.
+ remoteMediatorConnection = remoteMediatorAccessor,
+ jumpCallback = this@PageFetcher::refresh,
+ previousPagingState = previousPagingState,
+ ),
state = previousPagingState,
job = Job(),
)
}
.filterNotNull()
.simpleMapLatest { generation ->
- val downstreamFlow = generation.snapshot
- .injectRemoteEvents(generation.job, remoteMediatorAccessor)
- .onEach { log(VERBOSE) { "Sent $it" } }
+ val downstreamFlow =
+ generation.snapshot
+ .injectRemoteEvents(generation.job, remoteMediatorAccessor)
+ .onEach { log(VERBOSE) { "Sent $it" } }
PagingData(
flow = downstreamFlow,
@@ -217,7 +224,8 @@
An instance of PagingSource was re-used when Pager expected to create a new
instance. Ensure that the pagingSourceFactory passed to Pager always returns a
new instance of PagingSource.
- """.trimIndent()
+ """
+ .trimIndent()
}
// Hook up refresh signals from PagingSource.
@@ -237,9 +245,9 @@
override fun refresh() = [email protected]()
}
- inner class PagerHintReceiver<Key : Any, Value : Any> constructor(
- @get:VisibleForTesting
- internal val pageFetcherSnapshot: PageFetcherSnapshot<Key, Value>,
+ inner class PagerHintReceiver<Key : Any, Value : Any>
+ constructor(
+ @get:VisibleForTesting internal val pageFetcherSnapshot: PageFetcherSnapshot<Key, Value>,
) : HintReceiver {
override fun accessHint(viewportHint: ViewportHint) {
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 2ca05b6..858e532 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -49,9 +49,9 @@
import kotlinx.coroutines.launch
/**
- * Holds a generation of pageable data, a snapshot of data loaded by [PagingSource]. An instance
- * of [PageFetcherSnapshot] and its corresponding [PageFetcherSnapshotState] should be launched
- * within a scope that is cancelled when [PagingSource.invalidate] is called.
+ * Holds a generation of pageable data, a snapshot of data loaded by [PagingSource]. An instance of
+ * [PageFetcherSnapshot] and its corresponding [PageFetcherSnapshotState] should be launched within
+ * a scope that is cancelled when [PagingSource.invalidate] is called.
*/
internal class PageFetcherSnapshot<Key : Any, Value : Any>(
internal val initialKey: Key?,
@@ -77,113 +77,124 @@
private val pageEventChannelFlowJob = Job()
- val pageEventFlow: Flow<PageEvent<Value>> = cancelableChannelFlow<PageEvent<Value>>(
- pageEventChannelFlowJob
- ) {
- check(pageEventChCollected.compareAndSet(false, true)) {
- "Attempt to collect twice from pageEventFlow, which is an illegal operation. Did you " +
- "forget to call Flow<PagingData<*>>.cachedIn(coroutineScope)?"
- }
-
- // Start collection on pageEventCh, which the rest of this class uses to send PageEvents
- // to this flow.
- launch {
- pageEventCh.consumeAsFlow().collect {
- // Protect against races where a subsequent call to submitData invoked close(),
- // but a pageEvent arrives after closing causing ClosedSendChannelException.
- try {
- send(it)
- } catch (e: ClosedSendChannelException) {
- // Safe to drop PageEvent here, since collection has been cancelled.
+ val pageEventFlow: Flow<PageEvent<Value>> =
+ cancelableChannelFlow<PageEvent<Value>>(pageEventChannelFlowJob) {
+ check(pageEventChCollected.compareAndSet(false, true)) {
+ "Attempt to collect twice from pageEventFlow, which is an illegal operation. Did you " +
+ "forget to call Flow<PagingData<*>>.cachedIn(coroutineScope)?"
}
- }
- }
- // Wrap collection behind a RendezvousChannel to prevent the RetryChannel from buffering
- // retry signals.
- val retryChannel = Channel<Unit>(Channel.RENDEZVOUS)
- launch { retryFlow.collect { retryChannel.trySend(it) } }
-
- // Start collection on retry signals.
- launch {
- retryChannel.consumeAsFlow()
- .collect {
- val (sourceLoadStates, remotePagingState) = stateHolder.withLock { state ->
- state.sourceLoadStates.snapshot() to state.currentPagingState(
- hintHandler.lastAccessHint
- )
- }
- // tell remote mediator to retry and it will trigger necessary work / change
- // its state as necessary.
- remoteMediatorConnection?.retryFailed(remotePagingState)
- // change source (local) states
- sourceLoadStates.forEach { loadType, loadState ->
- if (loadState !is Error) return@forEach
-
- // Reset error state before sending hint.
- if (loadType != REFRESH) {
- stateHolder.withLock { state -> state.setLoading(loadType) }
+ // Start collection on pageEventCh, which the rest of this class uses to send
+ // PageEvents
+ // to this flow.
+ launch {
+ pageEventCh.consumeAsFlow().collect {
+ // Protect against races where a subsequent call to submitData invoked
+ // close(),
+ // but a pageEvent arrives after closing causing ClosedSendChannelException.
+ try {
+ send(it)
+ } catch (e: ClosedSendChannelException) {
+ // Safe to drop PageEvent here, since collection has been cancelled.
}
+ }
+ }
- retryLoadError(
- loadType = loadType,
- viewportHint = when (loadType) {
- // ViewportHint is only used when retrying source PREPEND / APPEND.
- REFRESH -> null
- else -> stateHolder.withLock { state ->
- state.failedHintsByLoadType[loadType]
+ // Wrap collection behind a RendezvousChannel to prevent the RetryChannel from
+ // buffering
+ // retry signals.
+ val retryChannel = Channel<Unit>(Channel.RENDEZVOUS)
+ launch { retryFlow.collect { retryChannel.trySend(it) } }
+
+ // Start collection on retry signals.
+ launch {
+ retryChannel.consumeAsFlow().collect {
+ val (sourceLoadStates, remotePagingState) =
+ stateHolder.withLock { state ->
+ state.sourceLoadStates.snapshot() to
+ state.currentPagingState(hintHandler.lastAccessHint)
+ }
+ // tell remote mediator to retry and it will trigger necessary work / change
+ // its state as necessary.
+ remoteMediatorConnection?.retryFailed(remotePagingState)
+ // change source (local) states
+ sourceLoadStates.forEach { loadType, loadState ->
+ if (loadState !is Error) return@forEach
+
+ // Reset error state before sending hint.
+ if (loadType != REFRESH) {
+ stateHolder.withLock { state -> state.setLoading(loadType) }
+ }
+
+ retryLoadError(
+ loadType = loadType,
+ viewportHint =
+ when (loadType) {
+ // ViewportHint is only used when retrying source PREPEND /
+ // APPEND.
+ REFRESH -> null
+ else ->
+ stateHolder.withLock { state ->
+ state.failedHintsByLoadType[loadType]
+ }
+ }
+ )
+
+ // If retrying REFRESH from PagingSource succeeds, start collection on
+ // ViewportHints for PREPEND / APPEND loads.
+ if (loadType == REFRESH) {
+ val newRefreshState =
+ stateHolder.withLock { state ->
+ state.sourceLoadStates.get(REFRESH)
+ }
+
+ if (newRefreshState !is Error) {
+ startConsumingHints()
}
}
- )
-
- // If retrying REFRESH from PagingSource succeeds, start collection on
- // ViewportHints for PREPEND / APPEND loads.
- if (loadType == REFRESH) {
- val newRefreshState = stateHolder.withLock { state ->
- state.sourceLoadStates.get(REFRESH)
- }
-
- if (newRefreshState !is Error) {
- startConsumingHints()
- }
}
}
}
- }
- // NOTE: We always try to enqueue on init, but this request will only go through if
- // [RemoteMediatorConnection.refreshEnabled] is `true`. It is important for it to be done
- // this way to ensure that we always have a valid [LoadStates] for [PagingSource] before we
- // trigger remote load, in case remote emits multiple [LoadStates] before [PagingSource]
- // starts, which would cause us to drop remote [LoadStates] emissions since we wait for
- // valid events from both.
- remoteMediatorConnection?.let {
- val pagingState = previousPagingState ?: stateHolder.withLock { state ->
- state.currentPagingState(null)
+ // NOTE: We always try to enqueue on init, but this request will only go through if
+ // [RemoteMediatorConnection.refreshEnabled] is `true`. It is important for it to be
+ // done
+ // this way to ensure that we always have a valid [LoadStates] for [PagingSource]
+ // before we
+ // trigger remote load, in case remote emits multiple [LoadStates] before
+ // [PagingSource]
+ // starts, which would cause us to drop remote [LoadStates] emissions since we wait
+ // for
+ // valid events from both.
+ remoteMediatorConnection?.let {
+ val pagingState =
+ previousPagingState
+ ?: stateHolder.withLock { state -> state.currentPagingState(null) }
+ it.requestRefreshIfAllowed(pagingState)
+ }
+
+ // Setup finished, start the initial load even if RemoteMediator throws an error.
+ doInitialLoad()
+
+ // Only start collection on ViewportHints if the initial load succeeded.
+ if (
+ stateHolder.withLock { state -> state.sourceLoadStates.get(REFRESH) } !is Error
+ ) {
+ startConsumingHints()
+ }
}
- it.requestRefreshIfAllowed(pagingState)
- }
-
- // Setup finished, start the initial load even if RemoteMediator throws an error.
- doInitialLoad()
-
- // Only start collection on ViewportHints if the initial load succeeded.
- if (stateHolder.withLock { state -> state.sourceLoadStates.get(REFRESH) } !is Error) {
- startConsumingHints()
- }
- }.onStart {
- // Immediately emit the initial load state when creating a new generation to give
- // PageFetcher a real event from source side as soon as possible. This allows PageFetcher
- // to operate on this stream in a way that waits for a real event (for example, by using
- // Flow.combine) without the consequence of getting "stuck".
- emit(LoadStateUpdate(stateHolder.withLock { it.sourceLoadStates.snapshot() }))
- }
+ .onStart {
+ // Immediately emit the initial load state when creating a new generation to give
+ // PageFetcher a real event from source side as soon as possible. This allows
+ // PageFetcher
+ // to operate on this stream in a way that waits for a real event (for example, by
+ // using
+ // Flow.combine) without the consequence of getting "stuck".
+ emit(LoadStateUpdate(stateHolder.withLock { it.sourceLoadStates.snapshot() }))
+ }
@Suppress("SuspendFunctionOnCoroutineScope")
- private suspend fun retryLoadError(
- loadType: LoadType,
- viewportHint: ViewportHint?
- ) {
+ private suspend fun retryLoadError(loadType: LoadType, viewportHint: ViewportHint?) {
when (loadType) {
REFRESH -> {
doInitialLoad()
@@ -216,8 +227,9 @@
// Pseudo-tiling via invalidation on jumps.
if (config.jumpThreshold != COUNT_UNDEFINED) {
launch {
- val jumpHint = merge(hintHandler.hintFor(APPEND), hintHandler.hintFor(PREPEND))
- .firstOrNull { hint ->
+ val jumpHint =
+ merge(hintHandler.hintFor(APPEND), hintHandler.hintFor(PREPEND)).firstOrNull {
+ hint ->
hint.presentedItemsBefore * -1 > config.jumpThreshold ||
hint.presentedItemsAfter * -1 > config.jumpThreshold
}
@@ -229,56 +241,60 @@
}
launch {
- stateHolder.withLock { state -> state.consumePrependGenerationIdAsFlow() }
+ stateHolder
+ .withLock { state -> state.consumePrependGenerationIdAsFlow() }
.collectAsGenerationalViewportHints(PREPEND)
}
launch {
- stateHolder.withLock { state -> state.consumeAppendGenerationIdAsFlow() }
+ stateHolder
+ .withLock { state -> state.consumeAppendGenerationIdAsFlow() }
.collectAsGenerationalViewportHints(APPEND)
}
}
/**
* Maps a [Flow] of generation ids from [PageFetcherSnapshotState] to [ViewportHint]s from
- * [hintHandler] with back-pressure handling via conflation by prioritizing hints which
- * either update presenter state or those that would load the most items.
+ * [hintHandler] with back-pressure handling via conflation by prioritizing hints which either
+ * update presenter state or those that would load the most items.
*
* @param loadType [PREPEND] or [APPEND]
*/
- private suspend fun Flow<Int>.collectAsGenerationalViewportHints(
- loadType: LoadType
- ) = simpleFlatMapLatest { generationId ->
- // Reset state to Idle and setup a new flow for consuming incoming load hints.
- // Subsequent generationIds are normally triggered by cancellation.
- stateHolder.withLock { state ->
- // Skip this generationId of loads if there is no more to load in this
- // direction. In the case of the terminal page getting dropped, a new
- // generationId will be sent after load state is updated to Idle.
- if (state.sourceLoadStates.get(loadType) == NotLoading.Complete) {
- return@simpleFlatMapLatest flowOf()
- } else if (state.sourceLoadStates.get(loadType) !is Error) {
- state.sourceLoadStates.set(loadType, NotLoading.Incomplete)
+ private suspend fun Flow<Int>.collectAsGenerationalViewportHints(loadType: LoadType) =
+ simpleFlatMapLatest { generationId ->
+ // Reset state to Idle and setup a new flow for consuming incoming load hints.
+ // Subsequent generationIds are normally triggered by cancellation.
+ stateHolder.withLock { state ->
+ // Skip this generationId of loads if there is no more to load in this
+ // direction. In the case of the terminal page getting dropped, a new
+ // generationId will be sent after load state is updated to Idle.
+ if (state.sourceLoadStates.get(loadType) == NotLoading.Complete) {
+ return@simpleFlatMapLatest flowOf()
+ } else if (state.sourceLoadStates.get(loadType) !is Error) {
+ state.sourceLoadStates.set(loadType, NotLoading.Incomplete)
+ }
+ }
+
+ hintHandler
+ .hintFor(loadType)
+ // Prevent infinite loop when competing PREPEND / APPEND cancel each other
+ .drop(if (generationId == 0) 0 else 1)
+ .map { hint -> GenerationalViewportHint(generationId, hint) }
}
- }
+ .simpleRunningReduce { previous, next ->
+ // Prioritize new hints that would load the maximum number of items.
+ if (next.shouldPrioritizeOver(previous, loadType)) next else previous
+ }
+ .conflate()
+ .collect { generationalHint -> doLoad(loadType, generationalHint) }
- hintHandler.hintFor(loadType)
- // Prevent infinite loop when competing PREPEND / APPEND cancel each other
- .drop(if (generationId == 0) 0 else 1)
- .map { hint -> GenerationalViewportHint(generationId, hint) }
- }.simpleRunningReduce { previous, next ->
- // Prioritize new hints that would load the maximum number of items.
- if (next.shouldPrioritizeOver(previous, loadType)) next else previous
- }.conflate().collect { generationalHint ->
- doLoad(loadType, generationalHint)
- }
-
- private fun loadParams(loadType: LoadType, key: Key?) = LoadParams.create(
- loadType = loadType,
- key = key,
- loadSize = if (loadType == REFRESH) config.initialLoadSize else config.pageSize,
- placeholdersEnabled = config.enablePlaceholders,
- )
+ private fun loadParams(loadType: LoadType, key: Key?) =
+ LoadParams.create(
+ loadType = loadType,
+ key = key,
+ loadSize = if (loadType == REFRESH) config.initialLoadSize else config.pageSize,
+ placeholdersEnabled = config.enablePlaceholders,
+ )
private suspend fun doInitialLoad() {
stateHolder.withLock { state -> state.setLoading(REFRESH) }
@@ -291,29 +307,21 @@
is Page<Key, Value> -> {
// Atomically update load states + pages while still holding the mutex, otherwise
// remote state can race here and lead to confusing load states.
- val insertApplied = stateHolder.withLock { state ->
- val insertApplied = state.insert(0, REFRESH, result)
+ val insertApplied =
+ stateHolder.withLock { state ->
+ val insertApplied = state.insert(0, REFRESH, result)
- // Update loadStates which are sent along with this load's Insert PageEvent.
- state.sourceLoadStates.set(
- type = REFRESH,
- state = NotLoading.Incomplete
- )
- if (result.prevKey == null) {
- state.sourceLoadStates.set(
- type = PREPEND,
- state = NotLoading.Complete
- )
- }
- if (result.nextKey == null) {
- state.sourceLoadStates.set(
- type = APPEND,
- state = NotLoading.Complete
- )
- }
+ // Update loadStates which are sent along with this load's Insert PageEvent.
+ state.sourceLoadStates.set(type = REFRESH, state = NotLoading.Incomplete)
+ if (result.prevKey == null) {
+ state.sourceLoadStates.set(type = PREPEND, state = NotLoading.Complete)
+ }
+ if (result.nextKey == null) {
+ state.sourceLoadStates.set(type = APPEND, state = NotLoading.Complete)
+ }
- insertApplied
- }
+ insertApplied
+ }
// Send insert event after load state updates, so that endOfPaginationReached is
// correctly reflected in the insert event. Note that we only send the event if the
@@ -322,9 +330,7 @@
log(DEBUG) { loadResultLog(REFRESH, initialKey, result) }
stateHolder.withLock { state ->
- with(state) {
- pageEventCh.send(result.toPageEvent(REFRESH))
- }
+ with(state) { pageEventCh.send(result.toPageEvent(REFRESH)) }
}
} else {
log(VERBOSE) { loadResultLog(REFRESH, initialKey, null) }
@@ -333,9 +339,10 @@
// Launch any RemoteMediator boundary calls after applying initial insert.
if (remoteMediatorConnection != null) {
if (result.prevKey == null || result.nextKey == null) {
- val pagingState = stateHolder.withLock { state ->
- state.currentPagingState(hintHandler.lastAccessHint)
- }
+ val pagingState =
+ stateHolder.withLock { state ->
+ state.currentPagingState(hintHandler.lastAccessHint)
+ }
if (result.prevKey == null) {
remoteMediatorConnection.requestLoad(PREPEND, pagingState)
@@ -362,10 +369,7 @@
}
// TODO: Consider making this a transform operation which emits PageEvents
- private suspend fun doLoad(
- loadType: LoadType,
- generationalHint: GenerationalViewportHint
- ) {
+ private suspend fun doLoad(loadType: LoadType, generationalHint: GenerationalViewportHint) {
require(loadType != REFRESH) { "Use doInitialLoad for LoadType == REFRESH" }
// If placeholder counts differ between the hint and PageFetcherSnapshotState, then
@@ -407,13 +411,16 @@
}
}
- var loadKey: Key? = stateHolder.withLock { state ->
- state.nextLoadKeyOrNull(
- loadType,
- generationalHint.generationId,
- generationalHint.hint.presentedItemsBeyondAnchor(loadType) + itemsLoaded,
- )?.also { state.setLoading(loadType) }
- }
+ var loadKey: Key? =
+ stateHolder.withLock { state ->
+ state
+ .nextLoadKeyOrNull(
+ loadType,
+ generationalHint.generationId,
+ generationalHint.hint.presentedItemsBeyondAnchor(loadType) + itemsLoaded,
+ )
+ ?.also { state.setLoading(loadType) }
+ }
// Keep track of whether endOfPaginationReached so we can update LoadState accordingly when
// this load loop terminates due to fulfilling prefetchDistance.
@@ -426,13 +433,15 @@
is Page<Key, Value> -> {
// First, check for common error case where the same key is re-used to load
// new pages, often resulting in infinite loops.
- val nextKey = when (loadType) {
- PREPEND -> result.prevKey
- APPEND -> result.nextKey
- else -> throw IllegalArgumentException(
- "Use doInitialLoad for LoadType == REFRESH"
- )
- }
+ val nextKey =
+ when (loadType) {
+ PREPEND -> result.prevKey
+ APPEND -> result.nextKey
+ else ->
+ throw IllegalArgumentException(
+ "Use doInitialLoad for LoadType == REFRESH"
+ )
+ }
check(pagingSource.keyReuseSupported || nextKey != loadKey) {
val keyFieldName = if (loadType == PREPEND) "prevKey" else "nextKey"
@@ -440,12 +449,14 @@
| sequential Pages loaded from a PagingSource. Re-using load keys in
| PagingSource is often an error, and must be explicitly enabled by
| overriding PagingSource.keyReuseSupported.
- """.trimMargin()
+ """
+ .trimMargin()
}
- val insertApplied = stateHolder.withLock { state ->
- state.insert(generationalHint.generationId, loadType, result)
- }
+ val insertApplied =
+ stateHolder.withLock { state ->
+ state.insert(generationalHint.generationId, loadType, result)
+ }
// Break if insert was skipped due to cancellation
if (!insertApplied) {
@@ -459,8 +470,9 @@
// Set endOfPaginationReached to false if no more data to load in current
// direction.
- if ((loadType == PREPEND && result.prevKey == null) ||
- (loadType == APPEND && result.nextKey == null)
+ if (
+ (loadType == PREPEND && result.prevKey == null) ||
+ (loadType == APPEND && result.nextKey == null)
) {
endOfPaginationReached = true
}
@@ -484,10 +496,11 @@
}
}
- val dropType = when (loadType) {
- PREPEND -> APPEND
- else -> PREPEND
- }
+ val dropType =
+ when (loadType) {
+ PREPEND -> APPEND
+ else -> PREPEND
+ }
stateHolder.withLock { state ->
state.dropEventOrNull(dropType, generationalHint.hint)?.let { event ->
@@ -495,28 +508,28 @@
pageEventCh.send(event)
}
- loadKey = state.nextLoadKeyOrNull(
- loadType,
- generationalHint.generationId,
- generationalHint.hint.presentedItemsBeyondAnchor(loadType) + itemsLoaded,
- )
+ loadKey =
+ state.nextLoadKeyOrNull(
+ loadType,
+ generationalHint.generationId,
+ generationalHint.hint.presentedItemsBeyondAnchor(loadType) + itemsLoaded,
+ )
// Update load state to success if this is the final load result for this
// load hint, and only if we didn't error out.
if (loadKey == null && state.sourceLoadStates.get(loadType) !is Error) {
state.sourceLoadStates.set(
type = loadType,
- state = when {
- endOfPaginationReached -> NotLoading.Complete
- else -> NotLoading.Incomplete
- }
+ state =
+ when {
+ endOfPaginationReached -> NotLoading.Complete
+ else -> NotLoading.Incomplete
+ }
)
}
// Send page event for successful insert, now that PagerState has been updated.
- val pageEvent = with(state) {
- result.toPageEvent(loadType)
- }
+ val pageEvent = with(state) { result.toPageEvent(loadType) }
pageEventCh.send(pageEvent)
}
@@ -524,9 +537,10 @@
val endsPrepend = params is LoadParams.Prepend && result.prevKey == null
val endsAppend = params is LoadParams.Append && result.nextKey == null
if (remoteMediatorConnection != null && (endsPrepend || endsAppend)) {
- val pagingState = stateHolder.withLock { state ->
- state.currentPagingState(hintHandler.lastAccessHint)
- }
+ val pagingState =
+ stateHolder.withLock { state ->
+ state.currentPagingState(hintHandler.lastAccessHint)
+ }
if (endsPrepend) {
remoteMediatorConnection.requestLoad(PREPEND, pagingState)
@@ -578,9 +592,7 @@
}
}
- /**
- * The next load key for a [loadType] or `null` if we should stop loading in that direction.
- */
+ /** The next load key for a [loadType] or `null` if we should stop loading in that direction. */
private fun PageFetcherSnapshotState<Key, Value>.nextLoadKeyOrNull(
loadType: LoadType,
generationId: Int,
@@ -621,8 +633,7 @@
* [PageFetcherSnapshot] to load more items.
*
* @param previous [GenerationalViewportHint] that would normally be processed next if [this]
- * [GenerationalViewportHint] was not sent.
- *
+ * [GenerationalViewportHint] was not sent.
* @return `true` if [this] [GenerationalViewportHint] should be prioritized over [previous].
*/
internal fun GenerationalViewportHint.shouldPrioritizeOver(
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshotState.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshotState.kt
index a7a7f16..c501168 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshotState.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageFetcherSnapshotState.kt
@@ -40,9 +40,8 @@
*
* Note: This class is not thread-safe and must be guarded by a lock!
*/
-internal class PageFetcherSnapshotState<Key : Any, Value : Any> private constructor(
- private val config: PagingConfig
-) {
+internal class PageFetcherSnapshotState<Key : Any, Value : Any>
+private constructor(private val config: PagingConfig) {
private val _pages = mutableListOf<Page<Key, Value>>()
internal val pages: List<Page<Key, Value>> = _pages
internal var initialPageIndex = 0
@@ -53,36 +52,36 @@
private var _placeholdersBefore = 0
- /**
- * Always greater than or equal to 0.
- */
+ /** Always greater than or equal to 0. */
internal var placeholdersBefore
- get() = when {
- config.enablePlaceholders -> _placeholdersBefore
- else -> 0
- }
- set(value) {
- _placeholdersBefore = when (value) {
- COUNT_UNDEFINED -> 0
- else -> value
+ get() =
+ when {
+ config.enablePlaceholders -> _placeholdersBefore
+ else -> 0
}
+ set(value) {
+ _placeholdersBefore =
+ when (value) {
+ COUNT_UNDEFINED -> 0
+ else -> value
+ }
}
private var _placeholdersAfter = 0
- /**
- * Always greater than or equal to 0.
- */
+ /** Always greater than or equal to 0. */
internal var placeholdersAfter
- get() = when {
- config.enablePlaceholders -> _placeholdersAfter
- else -> 0
- }
- set(value) {
- _placeholdersAfter = when (value) {
- COUNT_UNDEFINED -> 0
- else -> value
+ get() =
+ when {
+ config.enablePlaceholders -> _placeholdersAfter
+ else -> 0
}
+ set(value) {
+ _placeholdersAfter =
+ when (value) {
+ COUNT_UNDEFINED -> 0
+ else -> value
+ }
}
// Load generation ids used to respect cancellation in cases where suspending code continues to
@@ -92,40 +91,45 @@
private val prependGenerationIdCh = Channel<Int>(Channel.CONFLATED)
private val appendGenerationIdCh = Channel<Int>(Channel.CONFLATED)
- internal fun generationId(loadType: LoadType): Int = when (loadType) {
- REFRESH -> throw IllegalArgumentException("Cannot get loadId for loadType: REFRESH")
- PREPEND -> prependGenerationId
- APPEND -> appendGenerationId
- }
+ internal fun generationId(loadType: LoadType): Int =
+ when (loadType) {
+ REFRESH -> throw IllegalArgumentException("Cannot get loadId for loadType: REFRESH")
+ PREPEND -> prependGenerationId
+ APPEND -> appendGenerationId
+ }
/**
- * Cache previous ViewportHint which triggered any failed PagingSource APPEND / PREPEND that
- * we can later retry. This is so we always trigger loads based on hints, instead of having
- * two different ways to trigger.
+ * Cache previous ViewportHint which triggered any failed PagingSource APPEND / PREPEND that we
+ * can later retry. This is so we always trigger loads based on hints, instead of having two
+ * different ways to trigger.
*/
internal val failedHintsByLoadType = mutableMapOf<LoadType, ViewportHint>()
// Only track the local load states, remote states are injected from PageFetcher. This class
// only tracks state within a single generation from source side.
- internal var sourceLoadStates = MutableLoadStateCollection().apply {
- // Synchronously initialize REFRESH with Loading.
- // NOTE: It is important that we do this synchronously on init, since PageFetcherSnapshot
- // expects to send this initial state immediately. It is always correct for a new
- // generation to immediately begin loading refresh, so rather than start with NotLoading
- // then updating to Loading, we simply start with Loading immediately to create less
- // churn downstream.
- set(REFRESH, Loading)
- }
+ internal var sourceLoadStates =
+ MutableLoadStateCollection().apply {
+ // Synchronously initialize REFRESH with Loading.
+ // NOTE: It is important that we do this synchronously on init, since
+ // PageFetcherSnapshot
+ // expects to send this initial state immediately. It is always correct for a new
+ // generation to immediately begin loading refresh, so rather than start with NotLoading
+ // then updating to Loading, we simply start with Loading immediately to create less
+ // churn downstream.
+ set(REFRESH, Loading)
+ }
private set
fun consumePrependGenerationIdAsFlow(): Flow<Int> {
- return prependGenerationIdCh.consumeAsFlow()
- .onStart { prependGenerationIdCh.trySend(prependGenerationId) }
+ return prependGenerationIdCh.consumeAsFlow().onStart {
+ prependGenerationIdCh.trySend(prependGenerationId)
+ }
}
fun consumeAppendGenerationIdAsFlow(): Flow<Int> {
- return appendGenerationIdCh.consumeAsFlow()
- .onStart { appendGenerationIdCh.trySend(appendGenerationId) }
+ return appendGenerationIdCh.consumeAsFlow().onStart {
+ appendGenerationIdCh.trySend(appendGenerationId)
+ }
}
/**
@@ -134,43 +138,45 @@
* Note: This method should be called after state updated by [insert]
*
* TODO: Move this into Pager, which owns pageEventCh, since this logic is sensitive to its
- * implementation.
+ * implementation.
*/
internal fun Page<Key, Value>.toPageEvent(loadType: LoadType): PageEvent<Value> {
- val sourcePageIndex = when (loadType) {
- REFRESH -> 0
- PREPEND -> 0 - initialPageIndex
- APPEND -> pages.size - initialPageIndex - 1
- }
+ val sourcePageIndex =
+ when (loadType) {
+ REFRESH -> 0
+ PREPEND -> 0 - initialPageIndex
+ APPEND -> pages.size - initialPageIndex - 1
+ }
val pages = listOf(TransformablePage(sourcePageIndex, data))
// Mediator state is always set to null here because PageFetcherSnapshot is not responsible
// for Mediator state. Instead, PageFetcher will inject it if there is a remote mediator.
return when (loadType) {
- REFRESH -> Refresh(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = sourceLoadStates.snapshot(),
- mediatorLoadStates = null,
- )
- PREPEND -> Prepend(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- sourceLoadStates = sourceLoadStates.snapshot(),
- mediatorLoadStates = null,
- )
- APPEND -> Append(
- pages = pages,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = sourceLoadStates.snapshot(),
- mediatorLoadStates = null,
- )
+ REFRESH ->
+ Refresh(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = sourceLoadStates.snapshot(),
+ mediatorLoadStates = null,
+ )
+ PREPEND ->
+ Prepend(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ sourceLoadStates = sourceLoadStates.snapshot(),
+ mediatorLoadStates = null,
+ )
+ APPEND ->
+ Append(
+ pages = pages,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = sourceLoadStates.snapshot(),
+ mediatorLoadStates = null,
+ )
}
}
- /**
- * @return true if insert was applied, false otherwise.
- */
+ /** @return true if insert was applied, false otherwise. */
@CheckResult
fun insert(loadId: Int, loadType: LoadType, page: Page<Key, Value>): Boolean {
when (loadType) {
@@ -191,11 +197,12 @@
_pages.add(0, page)
initialPageIndex++
- placeholdersBefore = if (page.itemsBefore == COUNT_UNDEFINED) {
- (placeholdersBefore - page.data.size).coerceAtLeast(0)
- } else {
- page.itemsBefore
- }
+ placeholdersBefore =
+ if (page.itemsBefore == COUNT_UNDEFINED) {
+ (placeholdersBefore - page.data.size).coerceAtLeast(0)
+ } else {
+ page.itemsBefore
+ }
// Clear error on successful insert
failedHintsByLoadType.remove(PREPEND)
@@ -207,11 +214,12 @@
if (loadId != appendGenerationId) return false
_pages.add(page)
- placeholdersAfter = if (page.itemsAfter == COUNT_UNDEFINED) {
- (placeholdersAfter - page.data.size).coerceAtLeast(0)
- } else {
- page.itemsAfter
- }
+ placeholdersAfter =
+ if (page.itemsAfter == COUNT_UNDEFINED) {
+ (placeholdersAfter - page.data.size).coerceAtLeast(0)
+ } else {
+ page.itemsAfter
+ }
// Clear error on successful insert
failedHintsByLoadType.remove(APPEND)
@@ -254,8 +262,8 @@
/**
* @return [PageEvent.Drop] for [loadType] that would allow this [PageFetcherSnapshotState] to
- * respect [PagingConfig.maxSize], `null` if no pages should be dropped for the provided
- * [loadType].
+ * respect [PagingConfig.maxSize], `null` if no pages should be dropped for the provided
+ * [loadType].
*/
fun dropEventOrNull(loadType: LoadType, hint: ViewportHint): PageEvent.Drop<Value>? {
if (config.maxSize == MAX_SIZE_UNBOUNDED) return null
@@ -274,14 +282,16 @@
var pagesToDrop = 0
var itemsToDrop = 0
while (pagesToDrop < pages.size && storageCount - itemsToDrop > config.maxSize) {
- val pageSize = when (loadType) {
- PREPEND -> pages[pagesToDrop].data.size
- else -> pages[pages.lastIndex - pagesToDrop].data.size
- }
- val itemsAfterDrop = when (loadType) {
- PREPEND -> hint.presentedItemsBefore - itemsToDrop - pageSize
- else -> hint.presentedItemsAfter - itemsToDrop - pageSize
- }
+ val pageSize =
+ when (loadType) {
+ PREPEND -> pages[pagesToDrop].data.size
+ else -> pages[pages.lastIndex - pagesToDrop].data.size
+ }
+ val itemsAfterDrop =
+ when (loadType) {
+ PREPEND -> hint.presentedItemsBefore - itemsToDrop - pageSize
+ else -> hint.presentedItemsAfter - itemsToDrop - pageSize
+ }
// Do not drop pages that would fulfill prefetchDistance.
if (itemsAfterDrop < config.prefetchDistance) break
@@ -291,105 +301,124 @@
return when (pagesToDrop) {
0 -> null
- else -> PageEvent.Drop(
- loadType = loadType,
- minPageOffset = when (loadType) {
- // originalPageOffset of the first page.
- PREPEND -> -initialPageIndex
- // maxPageOffset - pagesToDrop; We subtract one from pagesToDrop, since this
- // value is inclusive.
- else -> pages.lastIndex - initialPageIndex - (pagesToDrop - 1)
- },
- maxPageOffset = when (loadType) {
- // minPageOffset + pagesToDrop; We subtract on from pagesToDrop, since this
- // value is inclusive.
- PREPEND -> (pagesToDrop - 1) - initialPageIndex
- // originalPageOffset of the last page.
- else -> pages.lastIndex - initialPageIndex
- },
- placeholdersRemaining = when {
- !config.enablePlaceholders -> 0
- loadType == PREPEND -> placeholdersBefore + itemsToDrop
- else -> placeholdersAfter + itemsToDrop
- }
- )
+ else ->
+ PageEvent.Drop(
+ loadType = loadType,
+ minPageOffset =
+ when (loadType) {
+ // originalPageOffset of the first page.
+ PREPEND -> -initialPageIndex
+ // maxPageOffset - pagesToDrop; We subtract one from pagesToDrop, since
+ // this
+ // value is inclusive.
+ else -> pages.lastIndex - initialPageIndex - (pagesToDrop - 1)
+ },
+ maxPageOffset =
+ when (loadType) {
+ // minPageOffset + pagesToDrop; We subtract on from pagesToDrop, since
+ // this
+ // value is inclusive.
+ PREPEND -> (pagesToDrop - 1) - initialPageIndex
+ // originalPageOffset of the last page.
+ else -> pages.lastIndex - initialPageIndex
+ },
+ placeholdersRemaining =
+ when {
+ !config.enablePlaceholders -> 0
+ loadType == PREPEND -> placeholdersBefore + itemsToDrop
+ else -> placeholdersAfter + itemsToDrop
+ }
+ )
}
}
- internal fun currentPagingState(viewportHint: ViewportHint.Access?) = PagingState<Key, Value>(
- pages = pages.toList(),
- anchorPosition = viewportHint?.let { hint ->
- // Translate viewportHint to anchorPosition based on fetcher state (pre-transformation),
- // so start with fetcher count of placeholdersBefore.
- var anchorPosition = placeholdersBefore
+ internal fun currentPagingState(viewportHint: ViewportHint.Access?) =
+ PagingState<Key, Value>(
+ pages = pages.toList(),
+ anchorPosition =
+ viewportHint?.let { hint ->
+ // Translate viewportHint to anchorPosition based on fetcher state
+ // (pre-transformation),
+ // so start with fetcher count of placeholdersBefore.
+ var anchorPosition = placeholdersBefore
- // Compute fetcher state pageOffsets.
- val fetcherPageOffsetFirst = -initialPageIndex
- val fetcherPageOffsetLast = pages.lastIndex - initialPageIndex
+ // Compute fetcher state pageOffsets.
+ val fetcherPageOffsetFirst = -initialPageIndex
+ val fetcherPageOffsetLast = pages.lastIndex - initialPageIndex
- // ViewportHint is based off of presenter state, which may race with fetcher state.
- // Since computing anchorPosition relies on hint.indexInPage, which accounts for
- // placeholders in presenter state, we need iterate through pages to incrementally
- // build anchorPosition and adjust the value we use for placeholdersBefore accordingly.
- for (pageOffset in fetcherPageOffsetFirst until hint.pageOffset) {
- // Aside from incrementing anchorPosition normally using the loaded page's
- // size, there are 4 race-cases to consider:
- // - Fetcher has extra PREPEND pages
- // - Simply add the size of the loaded page to anchorPosition to sync with
- // presenter; don't need to do anything special to handle this.
- // - Fetcher is missing PREPEND pages
- // - Already accounted for in placeholdersBefore; so don't need to do anything.
- // - Fetcher has extra APPEND pages
- // - Already accounted for in hint.indexInPage (value can be greater than
- // page size to denote placeholders access).
- // - Fetcher is missing APPEND pages
- // - Increment anchorPosition using config.pageSize to estimate size of the
- // missing page.
- anchorPosition += when {
- // Fetcher is missing APPEND pages, i.e., viewportHint points to an item
- // after a page that was dropped. Estimate how much to increment anchorPosition
- // by using PagingConfig.pageSize.
- pageOffset > fetcherPageOffsetLast -> config.pageSize
- // pageOffset refers to a loaded page; increment anchorPosition with data.size.
- else -> pages[pageOffset + initialPageIndex].data.size
- }
- }
+ // ViewportHint is based off of presenter state, which may race with fetcher
+ // state.
+ // Since computing anchorPosition relies on hint.indexInPage, which accounts for
+ // placeholders in presenter state, we need iterate through pages to
+ // incrementally
+ // build anchorPosition and adjust the value we use for placeholdersBefore
+ // accordingly.
+ for (pageOffset in fetcherPageOffsetFirst until hint.pageOffset) {
+ // Aside from incrementing anchorPosition normally using the loaded page's
+ // size, there are 4 race-cases to consider:
+ // - Fetcher has extra PREPEND pages
+ // - Simply add the size of the loaded page to anchorPosition to sync
+ // with
+ // presenter; don't need to do anything special to handle this.
+ // - Fetcher is missing PREPEND pages
+ // - Already accounted for in placeholdersBefore; so don't need to do
+ // anything.
+ // - Fetcher has extra APPEND pages
+ // - Already accounted for in hint.indexInPage (value can be greater
+ // than
+ // page size to denote placeholders access).
+ // - Fetcher is missing APPEND pages
+ // - Increment anchorPosition using config.pageSize to estimate size of
+ // the
+ // missing page.
+ anchorPosition +=
+ when {
+ // Fetcher is missing APPEND pages, i.e., viewportHint points to an
+ // item
+ // after a page that was dropped. Estimate how much to increment
+ // anchorPosition
+ // by using PagingConfig.pageSize.
+ pageOffset > fetcherPageOffsetLast -> config.pageSize
+ // pageOffset refers to a loaded page; increment anchorPosition with
+ // data.size.
+ else -> pages[pageOffset + initialPageIndex].data.size
+ }
+ }
- // Handle the page referenced by hint.pageOffset. Increment anchorPosition by
- // hint.indexInPage, which accounts for placeholders and may not be within the bounds
- // of page.data.indices.
- anchorPosition += hint.indexInPage
+ // Handle the page referenced by hint.pageOffset. Increment anchorPosition by
+ // hint.indexInPage, which accounts for placeholders and may not be within the
+ // bounds
+ // of page.data.indices.
+ anchorPosition += hint.indexInPage
- // In the special case where viewportHint references a missing PREPEND page, we need
- // to decrement anchorPosition using config.pageSize as an estimate, otherwise we
- // would be double counting it since it's accounted for in both indexInPage and
- // placeholdersBefore.
- if (hint.pageOffset < fetcherPageOffsetFirst) {
- anchorPosition -= config.pageSize
- }
+ // In the special case where viewportHint references a missing PREPEND page, we
+ // need
+ // to decrement anchorPosition using config.pageSize as an estimate, otherwise
+ // we
+ // would be double counting it since it's accounted for in both indexInPage and
+ // placeholdersBefore.
+ if (hint.pageOffset < fetcherPageOffsetFirst) {
+ anchorPosition -= config.pageSize
+ }
- return@let anchorPosition
- },
- config = config,
- leadingPlaceholderCount = placeholdersBefore
- )
+ return@let anchorPosition
+ },
+ config = config,
+ leadingPlaceholderCount = placeholdersBefore
+ )
/**
* Wrapper for [PageFetcherSnapshotState], which protects access behind a [Mutex] to prevent
* race scenarios.
*/
- internal class Holder<Key : Any, Value : Any>(
- private val config: PagingConfig
- ) {
+ internal class Holder<Key : Any, Value : Any>(private val config: PagingConfig) {
private val lock = Mutex()
private val state = PageFetcherSnapshotState<Key, Value>(config)
suspend inline fun <T> withLock(
block: (state: PageFetcherSnapshotState<Key, Value>) -> T
): T {
- return lock.withLock {
- block(state)
- }
+ return lock.withLock { block(state) }
}
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageStore.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageStore.kt
index 500e6e5..fafecac 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageStore.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PageStore.kt
@@ -23,8 +23,8 @@
import androidx.paging.internal.BUGANIZER_URL
/**
- * Presents post-transform paging data as a list, with list update notifications when
- * PageEvents are dispatched.
+ * Presents post-transform paging data as a list, with list update notifications when PageEvents are
+ * dispatched.
*/
internal class PageStore<T : Any>(
pages: List<TransformablePage<T>>,
@@ -42,12 +42,16 @@
private val pages: MutableList<TransformablePage<T>> = pages.toMutableList()
override var dataCount: Int = pages.fullCount()
private set
+
private val originalPageOffsetFirst: Int
get() = pages.first().originalPageOffsets.minOrNull()!!
+
private val originalPageOffsetLast: Int
get() = pages.last().originalPageOffsets.maxOrNull()!!
+
override var placeholdersBefore: Int = placeholdersBefore
private set
+
override var placeholdersAfter: Int = placeholdersAfter
private set
@@ -73,11 +77,7 @@
}
fun snapshot(): ItemSnapshotList<T> {
- return ItemSnapshotList(
- placeholdersBefore,
- placeholdersAfter,
- pages.flatMap { it.data }
- )
+ return ItemSnapshotList(placeholdersBefore, placeholdersAfter, pages.flatMap { it.data })
}
override fun getItem(index: Int): T {
@@ -107,12 +107,14 @@
return when (pageEvent) {
is PageEvent.Insert -> insertPage(pageEvent)
is PageEvent.Drop -> dropPages(pageEvent)
- else -> throw IllegalStateException(
- """Paging received an event to process StaticList or LoadStateUpdate while
+ else ->
+ throw IllegalStateException(
+ """Paging received an event to process StaticList or LoadStateUpdate while
|processing Inserts and Drops. If you see this exception, it is most
|likely a bug in the library. Please file a bug so we can fix it at:
- |$BUGANIZER_URL""".trimMargin()
- )
+ |$BUGANIZER_URL"""
+ .trimMargin()
+ )
}
}
@@ -151,12 +153,14 @@
private fun insertPage(insert: PageEvent.Insert<T>): PagingDataEvent<T> {
val insertSize = insert.pages.fullCount()
return when (insert.loadType) {
- REFRESH -> throw IllegalStateException(
- """Paging received a refresh event in the middle of an actively loading generation
+ REFRESH ->
+ throw IllegalStateException(
+ """Paging received a refresh event in the middle of an actively loading generation
|of PagingData. If you see this exception, it is most likely a bug in the library.
|Please file a bug so we can fix it at:
- |$BUGANIZER_URL""".trimMargin()
- )
+ |$BUGANIZER_URL"""
+ .trimMargin()
+ )
PREPEND -> {
val oldPlaceholdersBefore = placeholdersBefore
// update all states
@@ -207,14 +211,12 @@
}
/**
- * Helper which converts a [PageEvent.Drop] to a [PagingDataEvent] by
- * dropping all pages that depend on the n-lowest or n-highest originalPageOffsets.
+ * Helper which converts a [PageEvent.Drop] to a [PagingDataEvent] by dropping all pages that
+ * depend on the n-lowest or n-highest originalPageOffsets.
*/
private fun dropPages(drop: PageEvent.Drop<T>): PagingDataEvent<T> {
// update states
- val itemDropCount = dropPagesWithOffsets(
- drop.minPageOffset..drop.maxPageOffset
- )
+ val itemDropCount = dropPagesWithOffsets(drop.minPageOffset..drop.maxPageOffset)
dataCount -= itemDropCount
return if (drop.loadType == PREPEND) {
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/Pager.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/Pager.kt
index af068a4..482cf53 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/Pager.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/Pager.kt
@@ -22,7 +22,6 @@
/**
* Primary entry point into Paging; constructor for a reactive stream of [PagingData]. The same
* Pager instance should be reused within an instance of ViewModel. For example in your ViewModel:
- *
* ```
* // create a Pager instance and store to a variable
* val pager = Pager(
@@ -50,7 +49,8 @@
*/
public class Pager<Key : Any, Value : Any>
// Experimental usage is propagated to public API via constructor argument.
-@ExperimentalPagingApi constructor(
+@ExperimentalPagingApi
+constructor(
config: PagingConfig,
initialKey: Key? = null,
remoteMediator: RemoteMediator<Key, Value>?,
@@ -81,20 +81,20 @@
* new instance of [PagingData] with cached data pre-loaded.
*/
@OptIn(androidx.paging.ExperimentalPagingApi::class)
- public val flow: Flow<PagingData<Value>> = PageFetcher(
- pagingSourceFactory = if (
- pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
- ) {
- pagingSourceFactory::create
- } else {
- // cannot pass it as is since it is not a suspend function. Hence, we wrap it in {}
- // which means we are calling the original factory inside a suspend function
- {
- pagingSourceFactory()
- }
- },
- initialKey = initialKey,
- config = config,
- remoteMediator = remoteMediator
- ).flow
+ public val flow: Flow<PagingData<Value>> =
+ PageFetcher(
+ pagingSourceFactory =
+ if (pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>) {
+ pagingSourceFactory::create
+ } else {
+ // cannot pass it as is since it is not a suspend function. Hence, we wrap
+ // it in {}
+ // which means we are calling the original factory inside a suspend function
+ { pagingSourceFactory() }
+ },
+ initialKey = initialKey,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ .flow
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingConfig.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingConfig.kt
index 6dcf0fe..df6e7d6 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingConfig.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingConfig.kt
@@ -26,28 +26,29 @@
* An object used to configure loading behavior within a [Pager], as it loads content from a
* [PagingSource].
*/
-public class PagingConfig @JvmOverloads public constructor(
+public class PagingConfig
+@JvmOverloads
+public constructor(
/**
* Defines the number of items loaded at once from the [PagingSource].
*
* Should be several times the number of visible items onscreen.
*
- * Configuring your page size depends on how your data is being loaded and used. Smaller
- * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
- * improve loading throughput, to a point (avoid loading more than 2MB from SQLite at
- * once, since it incurs extra cost).
+ * Configuring your page size depends on how your data is being loaded and used. Smaller page
+ * sizes improve memory usage, latency, and avoid GC churn. Larger pages generally improve
+ * loading throughput, to a point (avoid loading more than 2MB from SQLite at once, since it
+ * incurs extra cost).
*
- * If you're loading data for very large, social-media style cards that take up most of
- * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
- * displaying dozens of items in a tiled grid, which can present items during a scroll
- * much more quickly, consider closer to 100.
+ * If you're loading data for very large, social-media style cards that take up most of a
+ * screen, and your database isn't a bottleneck, 10-20 may make sense. If you're displaying
+ * dozens of items in a tiled grid, which can present items during a scroll much more quickly,
+ * consider closer to 100.
*
- * Note: [pageSize] is used to inform [PagingSource.LoadParams.loadSize], but is not enforced.
- * A [PagingSource] may completely ignore this value and still return a valid
+ * Note: [pageSize] is used to inform [PagingSource.LoadParams.loadSize], but is not enforced. A
+ * [PagingSource] may completely ignore this value and still return a valid
* [Page][PagingSource.LoadResult.Page].
*/
- @JvmField
- public val pageSize: Int,
+ @JvmField public val pageSize: Int,
/**
* Prefetch distance which defines how far from the edge of loaded content an access must be to
@@ -58,27 +59,22 @@
* data that's already been accessed.
*
* A value of 0 indicates that no list items will be loaded until they are specifically
- * requested. This is generally not recommended, so that users don't observe a
- * placeholder item (with placeholders) or end of list (without) while scrolling.
+ * requested. This is generally not recommended, so that users don't observe a placeholder item
+ * (with placeholders) or end of list (without) while scrolling.
*/
- @JvmField
- @IntRange(from = 0)
- public val prefetchDistance: Int = pageSize,
+ @JvmField @IntRange(from = 0) public val prefetchDistance: Int = pageSize,
/**
- * Defines whether [PagingData] may display `null` placeholders, if the [PagingSource]
- * provides them.
+ * Defines whether [PagingData] may display `null` placeholders, if the [PagingSource] provides
+ * them.
*
- * [PagingData] will present `null` placeholders for not-yet-loaded content if two
- * conditions are met:
- *
- * 1) Its [PagingSource] can count all unloaded items (so that the number of nulls to
- * present is known).
- *
+ * [PagingData] will present `null` placeholders for not-yet-loaded content if two conditions
+ * are met:
+ * 1) Its [PagingSource] can count all unloaded items (so that the number of nulls to present is
+ * known).
* 2) [enablePlaceholders] is set to `true`
*/
- @JvmField
- public val enablePlaceholders: Boolean = true,
+ @JvmField public val enablePlaceholders: Boolean = true,
/**
* Defines requested load size for initial load from [PagingSource], typically larger than
@@ -107,22 +103,20 @@
* [maxSize] is best effort, not a guarantee. In practice, if [maxSize] is many times
* [pageSize], the number of items held by [PagingData] will not grow above this number.
* Exceptions are made as necessary to guarantee:
- * * Pages are never dropped until there are more than two pages loaded. Note that
- * a [PagingSource] may not be held strictly to [requested pageSize][PagingConfig.pageSize], so
- * two pages may be larger than expected.
- * * Pages are never dropped if they are within a prefetch window (defined to be
- * `pageSize + (2 * prefetchDistance)`) of the most recent load.
+ * * Pages are never dropped until there are more than two pages loaded. Note that a
+ * [PagingSource] may not be held strictly to [requested pageSize][PagingConfig.pageSize], so
+ * two pages may be larger than expected.
+ * * Pages are never dropped if they are within a prefetch window (defined to be `pageSize +
+ * (2 * prefetchDistance)`) of the most recent load.
*
* @see PagingConfig.MAX_SIZE_UNBOUNDED
*/
- @JvmField
- @IntRange(from = 2)
- public val maxSize: Int = MAX_SIZE_UNBOUNDED,
+ @JvmField @IntRange(from = 2) public val maxSize: Int = MAX_SIZE_UNBOUNDED,
/**
* Defines a threshold for the number of items scrolled outside the bounds of loaded items
- * before Paging should give up on loading pages incrementally, and instead jump to the
- * user's position by triggering REFRESH via invalidate.
+ * before Paging should give up on loading pages incrementally, and instead jump to the user's
+ * position by triggering REFRESH via invalidate.
*
* Defaults to [COUNT_UNDEFINED], which disables invalidation due to scrolling large distances.
*
@@ -132,8 +126,7 @@
* @see PagingSource.getRefreshKey
* @see PagingSource.jumpingSupported
*/
- @JvmField
- public val jumpThreshold: Int = COUNT_UNDEFINED
+ @JvmField public val jumpThreshold: Int = COUNT_UNDEFINED
) {
init {
if (!enablePlaceholders && prefetchDistance == 0) {
@@ -161,8 +154,7 @@
* When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
* unbounded, and pages will never be dropped.
*/
- @Suppress("MinMaxConstant")
- public const val MAX_SIZE_UNBOUNDED: Int = Int.MAX_VALUE
+ @Suppress("MinMaxConstant") public const val MAX_SIZE_UNBOUNDED: Int = Int.MAX_VALUE
internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingData.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingData.kt
index edb541f..3876125 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingData.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingData.kt
@@ -24,32 +24,35 @@
/**
* Container for Paged data from a single generation of loads.
*
- * Each refresh of data (generally either pushed by local storage, or pulled from the network)
- * will have a separate corresponding [PagingData].
+ * Each refresh of data (generally either pushed by local storage, or pulled from the network) will
+ * have a separate corresponding [PagingData].
*/
-public class PagingData<T : Any> internal constructor(
+public class PagingData<T : Any>
+internal constructor(
internal val flow: Flow<PageEvent<T>>,
internal val uiReceiver: UiReceiver,
internal val hintReceiver: HintReceiver,
/**
- * A lambda returning a nullable PageEvent.Insert containing data which can be accessed
- * and displayed synchronously without requiring collection.
+ * A lambda returning a nullable PageEvent.Insert containing data which can be accessed and
+ * displayed synchronously without requiring collection.
*
* For example, the data may be real loaded data that has been cached via [cachedIn].
*/
private val cachedPageEvent: () -> PageEvent.Insert<T>? = { null }
) {
public companion object {
- internal val NOOP_UI_RECEIVER = object : UiReceiver {
- override fun retry() {}
+ internal val NOOP_UI_RECEIVER =
+ object : UiReceiver {
+ override fun retry() {}
- override fun refresh() {}
- }
+ override fun refresh() {}
+ }
- internal val NOOP_HINT_RECEIVER = object : HintReceiver {
- override fun accessHint(viewportHint: ViewportHint) {}
- }
+ internal val NOOP_HINT_RECEIVER =
+ object : HintReceiver {
+ override fun accessHint(viewportHint: ViewportHint) {}
+ }
/**
* Create a [PagingData] that immediately displays an empty list of items when submitted to
@@ -58,40 +61,43 @@
*/
@Suppress("UNCHECKED_CAST")
@JvmStatic // Convenience for Java developers.
- public fun <T : Any> empty(): PagingData<T> = PagingData(
- flow = flowOf(
- PageEvent.StaticList(
- data = listOf(),
- sourceLoadStates = null,
- mediatorLoadStates = null,
- )
- ),
- uiReceiver = NOOP_UI_RECEIVER,
- hintReceiver = NOOP_HINT_RECEIVER,
- cachedPageEvent = {
- PageEvent.Insert.Refresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
+ public fun <T : Any> empty(): PagingData<T> =
+ PagingData(
+ flow =
+ flowOf(
+ PageEvent.StaticList(
data = listOf(),
+ sourceLoadStates = null,
+ mediatorLoadStates = null,
)
),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- sourceLoadStates = LoadStates.IDLE,
- mediatorLoadStates = null
- )
- }
- )
+ uiReceiver = NOOP_UI_RECEIVER,
+ hintReceiver = NOOP_HINT_RECEIVER,
+ cachedPageEvent = {
+ PageEvent.Insert.Refresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf(),
+ )
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ sourceLoadStates = LoadStates.IDLE,
+ mediatorLoadStates = null
+ )
+ }
+ )
/**
* Create a [PagingData] that immediately displays an empty list of items when submitted to
* a presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
*
* @param sourceLoadStates [LoadStates] of [PagingSource] to pass forward to a presenter.
- * E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
+ * E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
* @param mediatorLoadStates [LoadStates] of [RemoteMediator] to pass forward to a
- * presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
+ * presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
*/
@Suppress("UNCHECKED_CAST")
@JvmOverloads
@@ -99,62 +105,67 @@
public fun <T : Any> empty(
sourceLoadStates: LoadStates,
mediatorLoadStates: LoadStates? = null,
- ): PagingData<T> = PagingData(
- flow = flowOf(
- PageEvent.StaticList(
- data = listOf(),
- sourceLoadStates = sourceLoadStates,
- mediatorLoadStates = mediatorLoadStates,
- )
- ),
- uiReceiver = NOOP_UI_RECEIVER,
- hintReceiver = NOOP_HINT_RECEIVER,
- cachedPageEvent = {
- PageEvent.Insert.Refresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
+ ): PagingData<T> =
+ PagingData(
+ flow =
+ flowOf(
+ PageEvent.StaticList(
data = listOf(),
+ sourceLoadStates = sourceLoadStates,
+ mediatorLoadStates = mediatorLoadStates,
)
),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- sourceLoadStates = sourceLoadStates,
- mediatorLoadStates = mediatorLoadStates
- )
- }
- )
+ uiReceiver = NOOP_UI_RECEIVER,
+ hintReceiver = NOOP_HINT_RECEIVER,
+ cachedPageEvent = {
+ PageEvent.Insert.Refresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf(),
+ )
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ sourceLoadStates = sourceLoadStates,
+ mediatorLoadStates = mediatorLoadStates
+ )
+ }
+ )
/**
- * Create a [PagingData] that immediately displays a static list of items when submitted
- * to a presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter]
- * and dispatches [LoadState.NotLoading] on all LoadStates to the presenter.
+ * Create a [PagingData] that immediately displays a static list of items when submitted to
+ * a presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter] and
+ * dispatches [LoadState.NotLoading] on all LoadStates to the presenter.
*
* @param data Static list of [T] to display.
*/
@JvmStatic // Convenience for Java developers.
public fun <T : Any> from(
data: List<T>,
- ): PagingData<T> = PagingData(
- flow = flowOf(
- PageEvent.StaticList(
- data = data,
- sourceLoadStates = null,
- mediatorLoadStates = null,
- )
- ),
- uiReceiver = NOOP_UI_RECEIVER,
- hintReceiver = NOOP_HINT_RECEIVER,
- cachedPageEvent = {
- PageEvent.Insert.Refresh(
- pages = listOf(TransformablePage(0, data)),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- sourceLoadStates = LoadStates.IDLE,
- mediatorLoadStates = null
- )
- }
- )
+ ): PagingData<T> =
+ PagingData(
+ flow =
+ flowOf(
+ PageEvent.StaticList(
+ data = data,
+ sourceLoadStates = null,
+ mediatorLoadStates = null,
+ )
+ ),
+ uiReceiver = NOOP_UI_RECEIVER,
+ hintReceiver = NOOP_HINT_RECEIVER,
+ cachedPageEvent = {
+ PageEvent.Insert.Refresh(
+ pages = listOf(TransformablePage(0, data)),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ sourceLoadStates = LoadStates.IDLE,
+ mediatorLoadStates = null
+ )
+ }
+ )
/**
* Create a [PagingData] that immediately displays a static list of items when submitted to
@@ -162,9 +173,9 @@
*
* @param data Static list of [T] to display.
* @param sourceLoadStates [LoadStates] of [PagingSource] to pass forward to a presenter.
- * E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
+ * E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
* @param mediatorLoadStates [LoadStates] of [RemoteMediator] to pass forward to a
- * presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
+ * presenter. E.g., [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
*/
@JvmOverloads
@JvmStatic // Convenience for Java developers.
@@ -172,26 +183,28 @@
data: List<T>,
sourceLoadStates: LoadStates,
mediatorLoadStates: LoadStates? = null,
- ): PagingData<T> = PagingData(
- flow = flowOf(
- PageEvent.StaticList(
- data = data,
- sourceLoadStates = sourceLoadStates,
- mediatorLoadStates = mediatorLoadStates,
- )
- ),
- uiReceiver = NOOP_UI_RECEIVER,
- hintReceiver = NOOP_HINT_RECEIVER,
- cachedPageEvent = {
- PageEvent.Insert.Refresh(
- pages = listOf(TransformablePage(0, data)),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- sourceLoadStates = sourceLoadStates,
- mediatorLoadStates = mediatorLoadStates
- )
- }
- )
+ ): PagingData<T> =
+ PagingData(
+ flow =
+ flowOf(
+ PageEvent.StaticList(
+ data = data,
+ sourceLoadStates = sourceLoadStates,
+ mediatorLoadStates = mediatorLoadStates,
+ )
+ ),
+ uiReceiver = NOOP_UI_RECEIVER,
+ hintReceiver = NOOP_HINT_RECEIVER,
+ cachedPageEvent = {
+ PageEvent.Insert.Refresh(
+ pages = listOf(TransformablePage(0, data)),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ sourceLoadStates = sourceLoadStates,
+ mediatorLoadStates = mediatorLoadStates
+ )
+ }
+ )
}
internal fun cachedEvent(): PageEvent.Insert<T>? = cachedPageEvent()
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataEvent.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataEvent.kt
index 1cf0858..4f18bcb 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataEvent.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataEvent.kt
@@ -19,20 +19,20 @@
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
-/**
- * Events captured from a [PagingData] that was submitted to the [PagingDataPresenter]
- */
+/** Events captured from a [PagingData] that was submitted to the [PagingDataPresenter] */
public sealed class PagingDataEvent<T : Any> {
/**
* A prepend load event
*
* @param [inserted] The list of newly prepended items.
* @param [newPlaceholdersBefore] The count of null items leading the list of loaded data when
- * new data has been prepended.
+ * new data has been prepended.
* @param [oldPlaceholdersBefore] The count of null items leading the list of loaded data prior
- * to new data being prepended
+ * to new data being prepended
*/
- public class Prepend<T : Any> @RestrictTo(LIBRARY_GROUP) constructor(
+ public class Prepend<T : Any>
+ @RestrictTo(LIBRARY_GROUP)
+ constructor(
val inserted: List<T>,
val newPlaceholdersBefore: Int,
val oldPlaceholdersBefore: Int,
@@ -46,7 +46,8 @@
}
override fun hashCode(): Int {
- return inserted.hashCode() + newPlaceholdersBefore.hashCode() +
+ return inserted.hashCode() +
+ newPlaceholdersBefore.hashCode() +
oldPlaceholdersBefore.hashCode()
}
@@ -57,7 +58,8 @@
| newPlaceholdersBefore: $newPlaceholdersBefore
| oldPlaceholdersBefore: $oldPlaceholdersBefore
|)
- |""".trimMargin()
+ |"""
+ .trimMargin()
}
}
@@ -65,15 +67,17 @@
* An append load event
*
* @param [startIndex] The first index where this append is applied. If placeholders are
- * enabled, represents the index of the first placeholder replaced with a real item.
- * Otherwise, it represents the index of where the first new item is inserted.
+ * enabled, represents the index of the first placeholder replaced with a real item.
+ * Otherwise, it represents the index of where the first new item is inserted.
* @param [inserted] The list of newly appended items.
* @param [newPlaceholdersAfter] The count of null items trailing the list of loaded data when
- * new data has been appended.
+ * new data has been appended.
* @param [oldPlaceholdersAfter] The count of null items trailing the list of loaded data prior
- * to new data being appended.
+ * to new data being appended.
*/
- public class Append<T : Any> @RestrictTo(LIBRARY_GROUP) constructor(
+ public class Append<T : Any>
+ @RestrictTo(LIBRARY_GROUP)
+ constructor(
val startIndex: Int,
val inserted: List<T>,
val newPlaceholdersAfter: Int,
@@ -88,7 +92,9 @@
}
override fun hashCode(): Int {
- return startIndex.hashCode() + inserted.hashCode() + newPlaceholdersAfter.hashCode() +
+ return startIndex.hashCode() +
+ inserted.hashCode() +
+ newPlaceholdersAfter.hashCode() +
oldPlaceholdersAfter.hashCode()
}
@@ -100,19 +106,22 @@
| newPlaceholdersBefore: $newPlaceholdersAfter
| oldPlaceholdersBefore: $oldPlaceholdersAfter
|)
- |""".trimMargin()
+ |"""
+ .trimMargin()
}
}
/**
* A refresh load event
*
- * @param [newList] A [PlaceholderPaddedList] that contains the metadata of the new list
- * that is presented upon this refresh event
+ * @param [newList] A [PlaceholderPaddedList] that contains the metadata of the new list that is
+ * presented upon this refresh event
* @param [previousList] A [PlaceholderPaddedList] that contains the metadata of the list
- * presented prior to this refresh load event
+ * presented prior to this refresh load event
*/
- public class Refresh<T : Any> @RestrictTo(LIBRARY_GROUP) constructor(
+ public class Refresh<T : Any>
+ @RestrictTo(LIBRARY_GROUP)
+ constructor(
val newList: PlaceholderPaddedList<T>,
val previousList: PlaceholderPaddedList<T>,
) : PagingDataEvent<T>() {
@@ -146,7 +155,8 @@
| size: ${previousList.size}
| dataCount: ${previousList.dataCount}
| )
- |""".trimMargin()
+ |"""
+ .trimMargin()
}
}
@@ -155,11 +165,13 @@
*
* @param [dropCount] A count of items dropped from loaded items
* @param [newPlaceholdersBefore] The count of null items leading the list of loaded data after
- * items were dropped.
+ * items were dropped.
* @param [oldPlaceholdersBefore] The count of null items leading the list of loaded data prior
- * to items being dropped.
+ * to items being dropped.
*/
- public class DropPrepend<T : Any> @RestrictTo(LIBRARY_GROUP) constructor(
+ public class DropPrepend<T : Any>
+ @RestrictTo(LIBRARY_GROUP)
+ constructor(
val dropCount: Int,
val newPlaceholdersBefore: Int,
val oldPlaceholdersBefore: Int,
@@ -173,7 +185,8 @@
}
override fun hashCode(): Int {
- return dropCount.hashCode() + newPlaceholdersBefore.hashCode() +
+ return dropCount.hashCode() +
+ newPlaceholdersBefore.hashCode() +
oldPlaceholdersBefore.hashCode()
}
@@ -183,23 +196,26 @@
| newPlaceholdersBefore: $newPlaceholdersBefore
| oldPlaceholdersBefore: $oldPlaceholdersBefore
|)
- |""".trimMargin()
+ |"""
+ .trimMargin()
}
}
/**
* A drop event from the end of the list
*
- * @param [startIndex] The first index where this drop is applied. If placeholders are
- * enabled, represents the index of the first dropped item that was replaced with a placeholder.
- * Otherwise, it represents the index of where the first real item was dropped.
+ * @param [startIndex] The first index where this drop is applied. If placeholders are enabled,
+ * represents the index of the first dropped item that was replaced with a placeholder.
+ * Otherwise, it represents the index of where the first real item was dropped.
* @param [dropCount] A count of items dropped from loaded items
* @param [newPlaceholdersAfter] The count of null items trailing the list of loaded data after
- * items were dropped.
+ * items were dropped.
* @param [oldPlaceholdersAfter] The count of null items trailing the list of loaded data prior
- * to items being dropped.
+ * to items being dropped.
*/
- public class DropAppend<T : Any> @RestrictTo(LIBRARY_GROUP) constructor(
+ public class DropAppend<T : Any>
+ @RestrictTo(LIBRARY_GROUP)
+ constructor(
val startIndex: Int,
val dropCount: Int,
val newPlaceholdersAfter: Int,
@@ -215,7 +231,9 @@
}
override fun hashCode(): Int {
- return startIndex.hashCode() + dropCount.hashCode() + newPlaceholdersAfter.hashCode() +
+ return startIndex.hashCode() +
+ dropCount.hashCode() +
+ newPlaceholdersAfter.hashCode() +
oldPlaceholdersAfter.hashCode()
}
@@ -226,7 +244,8 @@
| newPlaceholdersBefore: $newPlaceholdersAfter
| oldPlaceholdersBefore: $oldPlaceholdersAfter
|)
- |""".trimMargin()
+ |"""
+ .trimMargin()
}
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
index 18032f5..97d46f9 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
@@ -39,74 +39,71 @@
import kotlinx.coroutines.withContext
/**
- * The class that connects the UI layer to the underlying Paging operations. Takes input from
- * UI presenters and outputs Paging events (Loads, LoadStateUpdate) in response.
+ * The class that connects the UI layer to the underlying Paging operations. Takes input from UI
+ * presenters and outputs Paging events (Loads, LoadStateUpdate) in response.
*
- * Paging front ends that implement this class will be able to access loaded data, LoadStates,
- * and callbacks from LoadState or Page updates. This class also exposes the
- * [PagingDataEvent] from a [PagingData] for custom logic on how to present Loads, Drops, and
- * other Paging events.
+ * Paging front ends that implement this class will be able to access loaded data, LoadStates, and
+ * callbacks from LoadState or Page updates. This class also exposes the [PagingDataEvent] from a
+ * [PagingData] for custom logic on how to present Loads, Drops, and other Paging events.
*
- * For implementation examples, refer to [AsyncPagingDataDiffer] for RecyclerView,
- * or [LazyPagingItems] for Compose.
+ * For implementation examples, refer to [AsyncPagingDataDiffer] for RecyclerView, or
+ * [LazyPagingItems] for Compose.
*
- * @param [mainContext] The coroutine context that core Paging operations will run on.
- * Defaults to [Dispatchers.Main]. Main operations executed within this context include
- * but are not limited to:
+ * @param [mainContext] The coroutine context that core Paging operations will run on. Defaults to
+ * [Dispatchers.Main]. Main operations executed within this context include but are not limited
+ * to:
* 1. flow collection on a [PagingData] for Loads, LoadStateUpdate etc.
* 2. emitting [CombinedLoadStates] to the [loadStateFlow]
* 3. invoking LoadState and PageUpdate listeners
* 4. invoking [presentPagingDataEvent]
*
- * @param [cachedPagingData] a [PagingData] that will initialize this PagingDataPresenter with
- * any LoadStates or loaded data contained within it.
+ * @param [cachedPagingData] a [PagingData] that will initialize this PagingDataPresenter with any
+ * LoadStates or loaded data contained within it.
*/
-public abstract class PagingDataPresenter<T : Any> (
+public abstract class PagingDataPresenter<T : Any>(
private val mainContext: CoroutineContext = Dispatchers.Main,
cachedPagingData: PagingData<T>? = null,
) {
private var hintReceiver: HintReceiver? = null
private var uiReceiver: UiReceiver? = null
private var pageStore: PageStore<T> = PageStore.initial(cachedPagingData?.cachedEvent())
- private val combinedLoadStatesCollection = MutableCombinedLoadStateCollection().apply {
- cachedPagingData?.cachedEvent()?.let { set(it.sourceLoadStates, it.mediatorLoadStates) }
- }
+ private val combinedLoadStatesCollection =
+ MutableCombinedLoadStateCollection().apply {
+ cachedPagingData?.cachedEvent()?.let { set(it.sourceLoadStates, it.mediatorLoadStates) }
+ }
private val onPagesUpdatedListeners = CopyOnWriteArrayList<() -> Unit>()
private val collectFromRunner = SingleRunner()
/**
- * Track whether [lastAccessedIndex] points to a loaded item in the list or a placeholder
- * after applying transformations to loaded pages. `true` if [lastAccessedIndex] points to a
+ * Track whether [lastAccessedIndex] points to a loaded item in the list or a placeholder after
+ * applying transformations to loaded pages. `true` if [lastAccessedIndex] points to a
* placeholder, `false` if [lastAccessedIndex] points to a loaded item after transformations.
*
* [lastAccessedIndexUnfulfilled] is used to track whether resending [lastAccessedIndex] as a
- * hint is necessary, since in cases of aggressive filtering, an index may be unfulfilled
- * after being sent to [PageFetcher], which is only capable of handling prefetchDistance
- * before transformations.
+ * hint is necessary, since in cases of aggressive filtering, an index may be unfulfilled after
+ * being sent to [PageFetcher], which is only capable of handling prefetchDistance before
+ * transformations.
*/
- @Volatile
- private var lastAccessedIndexUnfulfilled: Boolean = false
+ @Volatile private var lastAccessedIndexUnfulfilled: Boolean = false
/**
- * Track last index access so it can be forwarded to new generations after DiffUtil runs and
- * it is transformed to an index in the new list.
+ * Track last index access so it can be forwarded to new generations after DiffUtil runs and it
+ * is transformed to an index in the new list.
*/
- @Volatile
- private var lastAccessedIndex: Int = 0
+ @Volatile private var lastAccessedIndex: Int = 0
/**
* Handler for [PagingDataEvent] emitted by [PagingData].
*
- * When a [PagingData] is submitted to this PagingDataPresenter through [collectFrom],
- * page loads, drops, or LoadStateUpdates will be emitted to presenters as [PagingDataEvent]
- * through this method.
+ * When a [PagingData] is submitted to this PagingDataPresenter through [collectFrom], page
+ * loads, drops, or LoadStateUpdates will be emitted to presenters as [PagingDataEvent] through
+ * this method.
*
- * Presenter layers that communicate directly with [PagingDataPresenter] should override
- * this method to handle the [PagingDataEvent] accordingly. For example by diffing two
+ * Presenter layers that communicate directly with [PagingDataPresenter] should override this
+ * method to handle the [PagingDataEvent] accordingly. For example by diffing two
* [PagingDataEvent.Refresh] lists, or appending the inserted list of data from
* [PagingDataEvent.Prepend] or [PagingDataEvent.Append].
- *
*/
public abstract suspend fun presentPagingDataEvent(
event: PagingDataEvent<T>,
@@ -121,31 +118,31 @@
/**
* The hint receiver of a new generation is set only after it has been
* presented. This ensures that:
- *
* 1. while new generation is still loading, access hints (and jump hints) will
- * be sent to current generation.
- *
+ * be sent to current generation.
* 2. the access hint sent from presentNewList will have the correct
- * placeholders and indexInPage adjusted according to new pageStore's most
- * recent state
+ * placeholders and indexInPage adjusted according to new pageStore's most
+ * recent state
*
* Ensuring that viewport hints are sent to the correct generation helps
- * synchronize fetcher/pageStore in the correct calculation of the
- * next anchorPosition.
+ * synchronize fetcher/pageStore in the correct calculation of the next
+ * anchorPosition.
*/
when {
event is StaticList -> {
presentNewList(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = event.data,
- )
- ),
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = event.data,
+ )
+ ),
placeholdersBefore = 0,
placeholdersAfter = 0,
- dispatchLoadStates = event.sourceLoadStates != null ||
- event.mediatorLoadStates != null,
+ dispatchLoadStates =
+ event.sourceLoadStates != null ||
+ event.mediatorLoadStates != null,
sourceLoadStates = event.sourceLoadStates,
mediatorLoadStates = event.mediatorLoadStates,
newHintReceiver = pagingData.hintReceiver
@@ -172,7 +169,8 @@
remoteLoadStates = event.mediatorLoadStates,
)
- // If index points to a placeholder after transformations, resend it unless
+ // If index points to a placeholder after transformations, resend it
+ // unless
// there are no more items to load.
val source = combinedLoadStatesCollection.stateFlow.value?.source
checkNotNull(source) {
@@ -181,16 +179,17 @@
}
val prependDone = source.prepend.endOfPaginationReached
val appendDone = source.append.endOfPaginationReached
- val canContinueLoading = !(event.loadType == PREPEND && prependDone) &&
- !(event.loadType == APPEND && appendDone)
+ val canContinueLoading =
+ !(event.loadType == PREPEND && prependDone) &&
+ !(event.loadType == APPEND && appendDone)
/**
- * If the insert is empty due to aggressive filtering, another hint
- * must be sent to fetcher-side to notify that PagingDataPresenter
- * received the page, since fetcher estimates prefetchDistance based on
- * page indices presented by PagingDataPresenter and we cannot rely on a
- * new item being bound to trigger another hint since the presented
- * page is empty.
+ * If the insert is empty due to aggressive filtering, another hint must
+ * be sent to fetcher-side to notify that PagingDataPresenter received
+ * the page, since fetcher estimates prefetchDistance based on page
+ * indices presented by PagingDataPresenter and we cannot rely on a new
+ * item being bound to trigger another hint since the presented page is
+ * empty.
*/
val emptyInsert = event.pages.all { it.data.isEmpty() }
if (!canContinueLoading) {
@@ -199,10 +198,11 @@
// index.
lastAccessedIndexUnfulfilled = false
} else if (lastAccessedIndexUnfulfilled || emptyInsert) {
- val shouldResendHint = emptyInsert ||
- lastAccessedIndex < pageStore.placeholdersBefore ||
- lastAccessedIndex > pageStore.placeholdersBefore +
- pageStore.dataCount
+ val shouldResendHint =
+ emptyInsert ||
+ lastAccessedIndex < pageStore.placeholdersBefore ||
+ lastAccessedIndex >
+ pageStore.placeholdersBefore + pageStore.dataCount
if (shouldResendHint) {
hintReceiver?.accessHint(
@@ -292,8 +292,8 @@
* within the same generation of [PagingData].
*
* [LoadState.Error] can be generated from two types of load requests:
- * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
- * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
+ * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
+ * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
*/
public fun retry() {
log(DEBUG) { "Retry signal received" }
@@ -321,9 +321,7 @@
uiReceiver?.refresh()
}
- /**
- * @return Total number of presented items, including placeholders.
- */
+ /** @return Total number of presented items, including placeholders. */
public val size: Int
get() = pageStore.size
@@ -332,45 +330,43 @@
* current [PagingData] changes.
*
* This flow is conflated. It buffers the last update to [CombinedLoadStates] and immediately
- * delivers the current load states on collection, unless this [PagingDataPresenter] has not been
- * hooked up to a [PagingData] yet, and thus has no state to emit.
+ * delivers the current load states on collection, unless this [PagingDataPresenter] has not
+ * been hooked up to a [PagingData] yet, and thus has no state to emit.
*
* @sample androidx.paging.samples.loadStateFlowSample
*/
public val loadStateFlow: StateFlow<CombinedLoadStates?> =
combinedLoadStatesCollection.stateFlow
- private val _onPagesUpdatedFlow: MutableSharedFlow<Unit> = MutableSharedFlow(
- replay = 0,
- extraBufferCapacity = 64,
- onBufferOverflow = DROP_OLDEST,
- )
+ private val _onPagesUpdatedFlow: MutableSharedFlow<Unit> =
+ MutableSharedFlow(
+ replay = 0,
+ extraBufferCapacity = 64,
+ onBufferOverflow = DROP_OLDEST,
+ )
/**
- * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
- * actual items presented don't change.
+ * A hot [Flow] that emits after the pages presented to the UI are updated, even if the actual
+ * items presented don't change.
*
* An update is triggered from one of the following:
- * * [collectFrom] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [collectFrom] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
- * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
- * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
- * trigger multiple times for each intermediate update that was presented while your collector
- * was still working. To avoid this behavior, you can
- * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
- * update, which is useful in cases where you are simply updating UI and don't care about
- * tracking the exact number of page updates.
+ * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay 0 items
+ * with a buffer of size 64. If a collector lags behind page updates, it may trigger multiple
+ * times for each intermediate update that was presented while your collector was still working.
+ * To avoid this behavior, you can [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so
+ * that you only receive the latest update, which is useful in cases where you are simply
+ * updating UI and don't care about tracking the exact number of page updates.
*/
public val onPagesUpdatedFlow: Flow<Unit>
get() = _onPagesUpdatedFlow.asSharedFlow()
init {
- addOnPagesUpdatedListener {
- _onPagesUpdatedFlow.tryEmit(Unit)
- }
+ addOnPagesUpdatedListener { _onPagesUpdatedFlow.tryEmit(Unit) }
}
/**
@@ -378,13 +374,12 @@
* actual items presented don't change.
*
* An update is triggered from one of the following:
- * * [collectFrom] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [collectFrom] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
* @param listener called after pages presented are updated.
- *
* @see removeOnPagesUpdatedListener
*/
public fun addOnPagesUpdatedListener(listener: () -> Unit) {
@@ -395,7 +390,6 @@
* Remove a previously registered listener for updates to presented pages.
*
* @param listener Previously registered listener.
- *
* @see addOnPagesUpdatedListener
*/
public fun removeOnPagesUpdatedListener(listener: () -> Unit) {
@@ -409,11 +403,10 @@
* reflect the current [CombinedLoadStates].
*
* When a new listener is added, it will be immediately called with the current
- * [CombinedLoadStates], unless this [PagingDataPresenter] has not been hooked up to a [PagingData]
- * yet, and thus has no state to emit.
+ * [CombinedLoadStates], unless this [PagingDataPresenter] has not been hooked up to a
+ * [PagingData] yet, and thus has no state to emit.
*
* @param listener [LoadStates] listener to receive updates.
- *
* @see removeLoadStateListener
*
* @sample androidx.paging.samples.addLoadStateListenerSample
@@ -449,11 +442,12 @@
lastAccessedIndexUnfulfilled = false
- val newPageStore = PageStore(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- )
+ val newPageStore =
+ PageStore(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ )
// must capture previousList states here before we update pageStore
val previousList = pageStore as PlaceholderPaddedList<T>
@@ -499,8 +493,8 @@
}
/**
- * Payloads used to dispatch change events.
- * Could become a public API post 3.0 in case developers want to handle it more effectively.
+ * Payloads used to dispatch change events. Could become a public API post 3.0 in case developers
+ * want to handle it more effectively.
*
* Sending these change payloads is critical for the common case where DefaultItemAnimator won't
* animate them and re-use the same view holder if possible.
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataTransforms.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataTransforms.kt
index 9f2c851..ea03c2f 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataTransforms.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataTransforms.kt
@@ -29,25 +29,27 @@
internal inline fun <T : Any, R : Any> PagingData<T>.transform(
crossinline transform: suspend (PageEvent<T>) -> PageEvent<R>
-) = PagingData(
- flow = flow.map { transform(it) },
- uiReceiver = uiReceiver,
- hintReceiver = hintReceiver,
-)
+) =
+ PagingData(
+ flow = flow.map { transform(it) },
+ uiReceiver = uiReceiver,
+ hintReceiver = hintReceiver,
+ )
/**
- * Returns a [PagingData] containing the result of applying the given [transform] to each
- * element, as it is loaded.
+ * Returns a [PagingData] containing the result of applying the given [transform] to each element,
+ * as it is loaded.
*/
@CheckResult
@JvmSynthetic
-public fun <T : Any, R : Any> PagingData<T>.map(
- transform: suspend (T) -> R
-): PagingData<R> = transform { it.map(transform) }
+public fun <T : Any, R : Any> PagingData<T>.map(transform: suspend (T) -> R): PagingData<R> =
+ transform {
+ it.map(transform)
+ }
/**
- * Returns a [PagingData] of all elements returned from applying the given [transform]
- * to each element, as it is loaded.
+ * Returns a [PagingData] of all elements returned from applying the given [transform] to each
+ * element, as it is loaded.
*/
@CheckResult
@JvmSynthetic
@@ -55,32 +57,31 @@
transform: suspend (T) -> Iterable<R>
): PagingData<R> = transform { it.flatMap(transform) }
-/**
- * Returns a [PagingData] containing only elements matching the given [predicate]
- */
+/** Returns a [PagingData] containing only elements matching the given [predicate] */
@CheckResult
@JvmSynthetic
-public fun <T : Any> PagingData<T>.filter(
- predicate: suspend (T) -> Boolean
-): PagingData<T> = transform { it.filter(predicate) }
+public fun <T : Any> PagingData<T>.filter(predicate: suspend (T) -> Boolean): PagingData<T> =
+ transform {
+ it.filter(predicate)
+ }
/**
- * Returns a [PagingData] containing each original element, with an optional separator
- * generated by [generator], given the elements before and after (or null, in boundary
- * conditions).
+ * Returns a [PagingData] containing each original element, with an optional separator generated by
+ * [generator], given the elements before and after (or null, in boundary conditions).
*
- * Note that this transform is applied asynchronously, as pages are loaded. Potential
- * separators between pages are only computed once both pages are loaded.
+ * Note that this transform is applied asynchronously, as pages are loaded. Potential separators
+ * between pages are only computed once both pages are loaded.
*
- * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and
- * footer are added.
- *
- * @param generator Generator function used to construct a separator item given the item before
- * and the item after. For terminal separators (header and footer), the arguments passed to the
- * generator, `before` and `after`, will be `null` respectively. In cases where the fully paginated
- * list is empty, a single separator will be added where both `before` and `after` items are `null`.
+ * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and footer
+ * are added.
+ * @param generator Generator function used to construct a separator item given the item before and
+ * the item after. For terminal separators (header and footer), the arguments passed to the
+ * generator, `before` and `after`, will be `null` respectively. In cases where the fully
+ * paginated list is empty, a single separator will be added where both `before` and `after` items
+ * are `null`.
*
* @sample androidx.paging.samples.insertSeparatorsSample
+ *
* @sample androidx.paging.samples.insertSeparatorsUiModelSample
*/
@CheckResult
@@ -104,24 +105,21 @@
}
/**
- * Returns a [PagingData] containing each original element, with the passed header [item] added
- * to the start of the list.
+ * Returns a [PagingData] containing each original element, with the passed header [item] added to
+ * the start of the list.
*
* The header [item] is added to a loaded page which marks the end of the data stream in the
- * [LoadType.PREPEND] direction by returning null in [PagingSource.LoadResult.Page.prevKey]. It
- * will be removed if the first page in the list is dropped, which can happen in the case of loaded
- * pages exceeding [PagingConfig.maxSize].
+ * [LoadType.PREPEND] direction by returning null in [PagingSource.LoadResult.Page.prevKey]. It will
+ * be removed if the first page in the list is dropped, which can happen in the case of loaded pages
+ * exceeding [PagingConfig.maxSize].
*
- * Note: This operation is not idempotent, calling it multiple times will continually add
- * more headers to the start of the list, which can be useful if multiple header items are
- * required.
+ * Note: This operation is not idempotent, calling it multiple times will continually add more
+ * headers to the start of the list, which can be useful if multiple header items are required.
*
- * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and
- * footer are added.
- *
+ * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and footer
+ * are added.
* @param item The header to add to the front of the list once it is fully loaded in the
- * [LoadType.PREPEND] direction.
- *
+ * [LoadType.PREPEND] direction.
* @see [insertFooterItem]
*/
@CheckResult
@@ -129,29 +127,25 @@
public fun <T : Any> PagingData<T>.insertHeaderItem(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
item: T,
-): PagingData<T> = insertSeparators(terminalSeparatorType) { before, _ ->
- if (before == null) item else null
-}
+): PagingData<T> =
+ insertSeparators(terminalSeparatorType) { before, _ -> if (before == null) item else null }
/**
- * Returns a [PagingData] containing each original element, with the passed footer [item] added
- * to the end of the list.
+ * Returns a [PagingData] containing each original element, with the passed footer [item] added to
+ * the end of the list.
*
* The footer [item] is added to a loaded page which marks the end of the data stream in the
* [LoadType.APPEND] direction, either by returning null in [PagingSource.LoadResult.Page.nextKey].
* It will be removed if the last page in the list is dropped, which can happen in the case of
* loaded pages exceeding [PagingConfig.maxSize].
*
- * Note: This operation is not idempotent, calling it multiple times will continually add
- * more footers to the end of the list, which can be useful if multiple footer items are
- * required.
+ * Note: This operation is not idempotent, calling it multiple times will continually add more
+ * footers to the end of the list, which can be useful if multiple footer items are required.
*
- * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and
- * footer are added.
- *
+ * @param terminalSeparatorType [TerminalSeparatorType] used to configure when the header and footer
+ * are added.
* @param item The footer to add to the end of the list once it is fully loaded in the
- * [LoadType.APPEND] direction.
- *
+ * [LoadType.APPEND] direction.
* @see [insertHeaderItem]
*/
@CheckResult
@@ -159,6 +153,5 @@
public fun <T : Any> PagingData<T>.insertFooterItem(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
item: T,
-): PagingData<T> = insertSeparators(terminalSeparatorType) { _, after ->
- if (after == null) item else null
-}
+): PagingData<T> =
+ insertSeparators(terminalSeparatorType) { _, after -> if (after == null) item else null }
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingLogger.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingLogger.kt
index 8a2a59e..66b8d8c 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingLogger.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingLogger.kt
@@ -26,6 +26,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public expect object PagingLogger {
public fun isLoggable(level: Int): Boolean
+
public fun log(level: Int, message: String, tr: Throwable? = null)
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingSource.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingSource.kt
index 1a95da2..d2c272f 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingSource.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingSource.kt
@@ -21,9 +21,9 @@
import androidx.paging.LoadType.REFRESH
/**
- * Base class for an abstraction of pageable static data from some source, where loading pages
- * of data is typically an expensive operation. Some examples of common [PagingSource]s might be
- * from network or from a database.
+ * Base class for an abstraction of pageable static data from some source, where loading pages of
+ * data is typically an expensive operation. Some examples of common [PagingSource]s might be from
+ * network or from a database.
*
* An instance of a [PagingSource] is used to load pages of data for an instance of [PagingData].
*
@@ -33,8 +33,8 @@
*
* ### Loading Pages
*
- * [PagingData] queries data from its [PagingSource] in response to loading hints generated as
- * the user scrolls in a `RecyclerView`.
+ * [PagingData] queries data from its [PagingSource] in response to loading hints generated as the
+ * user scrolls in a `RecyclerView`.
*
* To control how and when a [PagingData] queries data from its [PagingSource], see [PagingConfig],
* which defines behavior such as [PagingConfig.pageSize] and [PagingConfig.prefetchDistance].
@@ -43,10 +43,10 @@
*
* A [PagingSource] / [PagingData] pair is a snapshot of the data set. A new [PagingData] /
* [PagingData] must be created if an update occurs, such as a reorder, insert, delete, or content
- * update occurs. A [PagingSource] must detect that it cannot continue loading its snapshot
- * (for instance, when Database query notices a table being invalidated), and call [invalidate].
- * Then a new [PagingSource] / [PagingData] pair would be created to represent data from the new
- * state of the database query.
+ * update occurs. A [PagingSource] must detect that it cannot continue loading its snapshot (for
+ * instance, when Database query notices a table being invalidated), and call [invalidate]. Then a
+ * new [PagingSource] / [PagingData] pair would be created to represent data from the new state of
+ * the database query.
*
* ### Presenting Data to UI
*
@@ -55,31 +55,29 @@
* [PagingDataAdapter][androidx.paging.PagingDataAdapter].
*
* @param Key Type of key which define what data to load. E.g. [Int] to represent either a page
- * number or item position, or [String] if your network uses Strings as next tokens returned with
- * each response.
+ * number or item position, or [String] if your network uses Strings as next tokens returned with
+ * each response.
* @param Value Type of data loaded in by this [PagingSource]. E.g., the type of data that will be
- * passed to a [PagingDataAdapter][androidx.paging.PagingDataAdapter] to be displayed in a
- * `RecyclerView`.
+ * passed to a [PagingDataAdapter][androidx.paging.PagingDataAdapter] to be displayed in a
+ * `RecyclerView`.
*
* @sample androidx.paging.samples.pageKeyedPagingSourceSample
+ *
* @sample androidx.paging.samples.itemKeyedPagingSourceSample
*
* @see Pager
*/
public abstract class PagingSource<Key : Any, Value : Any> {
- private val invalidateCallbackTracker = InvalidateCallbackTracker<() -> Unit>(
- callbackInvoker = { it() }
- )
+ private val invalidateCallbackTracker =
+ InvalidateCallbackTracker<() -> Unit>(callbackInvoker = { it() })
internal val invalidateCallbackCount: Int
- @VisibleForTesting
- get() = invalidateCallbackTracker.callbackCount()
+ @VisibleForTesting get() = invalidateCallbackTracker.callbackCount()
- /**
- * Params for a load request on a [PagingSource] from [PagingSource.load].
- */
- public sealed class LoadParams<Key : Any> constructor(
+ /** Params for a load request on a [PagingSource] from [PagingSource.load]. */
+ public sealed class LoadParams<Key : Any>
+ constructor(
/**
* Requested number of items to load.
*
@@ -97,17 +95,17 @@
/**
* Key for the page to be loaded.
*
- * [key] can be `null` only if this [LoadParams] is [Refresh], and either no `initialKey`
- * is provided to the [Pager] or [PagingSource.getRefreshKey] from the previous
- * [PagingSource] returns `null`.
+ * [key] can be `null` only if this [LoadParams] is [Refresh], and either no `initialKey` is
+ * provided to the [Pager] or [PagingSource.getRefreshKey] from the previous [PagingSource]
+ * returns `null`.
*
* The value of [key] is dependent on the type of [LoadParams]:
- * * [Refresh]
- * * On initial load, the nullable `initialKey` passed to the [Pager].
- * * On subsequent loads due to invalidation or refresh, the result of
- * [PagingSource.getRefreshKey].
- * * [Prepend] - [LoadResult.Page.prevKey] of the loaded page at the front of the list.
- * * [Append] - [LoadResult.Page.nextKey] of the loaded page at the end of the list.
+ * * [Refresh]
+ * * On initial load, the nullable `initialKey` passed to the [Pager].
+ * * On subsequent loads due to invalidation or refresh, the result of
+ * [PagingSource.getRefreshKey].
+ * * [Prepend] - [LoadResult.Page.prevKey] of the loaded page at the front of the list.
+ * * [Append] - [LoadResult.Page.nextKey] of the loaded page at the end of the list.
*/
public abstract val key: Key?
@@ -115,40 +113,46 @@
* Params for an initial load request on a [PagingSource] from [PagingSource.load] or a
* refresh triggered by [invalidate].
*/
- public class Refresh<Key : Any> constructor(
+ public class Refresh<Key : Any>
+ constructor(
override val key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
- ) : LoadParams<Key>(
- loadSize = loadSize,
- placeholdersEnabled = placeholdersEnabled,
- )
+ ) :
+ LoadParams<Key>(
+ loadSize = loadSize,
+ placeholdersEnabled = placeholdersEnabled,
+ )
/**
* Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
* appended to the end of the list.
*/
- public class Append<Key : Any> constructor(
+ public class Append<Key : Any>
+ constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
- ) : LoadParams<Key>(
- loadSize = loadSize,
- placeholdersEnabled = placeholdersEnabled,
- )
+ ) :
+ LoadParams<Key>(
+ loadSize = loadSize,
+ placeholdersEnabled = placeholdersEnabled,
+ )
/**
* Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
* prepended to the start of the list.
*/
- public class Prepend<Key : Any> constructor(
+ public class Prepend<Key : Any>
+ constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
- ) : LoadParams<Key>(
- loadSize = loadSize,
- placeholdersEnabled = placeholdersEnabled,
- )
+ ) :
+ LoadParams<Key>(
+ loadSize = loadSize,
+ placeholdersEnabled = placeholdersEnabled,
+ )
internal companion object {
fun <Key : Any> create(
@@ -156,33 +160,31 @@
key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
- ): LoadParams<Key> = when (loadType) {
- LoadType.REFRESH -> Refresh(
- key = key,
- loadSize = loadSize,
- placeholdersEnabled = placeholdersEnabled,
- )
- LoadType.PREPEND -> Prepend(
- loadSize = loadSize,
- key = requireNotNull(key) {
- "key cannot be null for prepend"
- },
- placeholdersEnabled = placeholdersEnabled,
- )
- LoadType.APPEND -> Append(
- loadSize = loadSize,
- key = requireNotNull(key) {
- "key cannot be null for append"
- },
- placeholdersEnabled = placeholdersEnabled,
- )
- }
+ ): LoadParams<Key> =
+ when (loadType) {
+ LoadType.REFRESH ->
+ Refresh(
+ key = key,
+ loadSize = loadSize,
+ placeholdersEnabled = placeholdersEnabled,
+ )
+ LoadType.PREPEND ->
+ Prepend(
+ loadSize = loadSize,
+ key = requireNotNull(key) { "key cannot be null for prepend" },
+ placeholdersEnabled = placeholdersEnabled,
+ )
+ LoadType.APPEND ->
+ Append(
+ loadSize = loadSize,
+ key = requireNotNull(key) { "key cannot be null for append" },
+ placeholdersEnabled = placeholdersEnabled,
+ )
+ }
}
}
- /**
- * Result of a load request from [PagingSource.load].
- */
+ /** Result of a load request from [PagingSource.load]. */
public sealed class LoadResult<Key : Any, Value : Any> {
/**
* Error result object for [PagingSource.load].
@@ -193,13 +195,13 @@
*
* @sample androidx.paging.samples.pageKeyedPagingSourceSample
*/
- public data class Error<Key : Any, Value : Any>(
- val throwable: Throwable
- ) : LoadResult<Key, Value>() {
+ public data class Error<Key : Any, Value : Any>(val throwable: Throwable) :
+ LoadResult<Key, Value>() {
override fun toString(): String {
return """LoadResult.Error(
| throwable: $throwable
- |) """.trimMargin()
+ |) """
+ .trimMargin()
}
}
@@ -209,15 +211,15 @@
* This return type can be used to terminate future load requests on this [PagingSource]
* when the [PagingSource] is not longer valid due to changes in the underlying dataset.
*
- * For example, if the underlying database gets written into but the [PagingSource] does
- * not invalidate in time, it may return inconsistent results if its implementation depends
- * on the immutability of the backing dataset it loads from (e.g., LIMIT OFFSET style db
+ * For example, if the underlying database gets written into but the [PagingSource] does not
+ * invalidate in time, it may return inconsistent results if its implementation depends on
+ * the immutability of the backing dataset it loads from (e.g., LIMIT OFFSET style db
* implementations). In this scenario, it is recommended to check for invalidation after
- * loading and to return LoadResult.Invalid, which causes Paging to discard any
- * pending or future load requests to this PagingSource and invalidate it.
+ * loading and to return LoadResult.Invalid, which causes Paging to discard any pending or
+ * future load requests to this PagingSource and invalidate it.
*
- * Returning [Invalid] will trigger Paging to [invalidate] this [PagingSource] and
- * terminate any future attempts to [load] from this [PagingSource]
+ * Returning [Invalid] will trigger Paging to [invalidate] this [PagingSource] and terminate
+ * any future attempts to [load] from this [PagingSource]
*/
public class Invalid<Key : Any, Value : Any> : LoadResult<Key, Value>() {
override fun toString(): String {
@@ -231,12 +233,12 @@
* As a convenience, iterating on this object will iterate through its loaded [data].
*
* @sample androidx.paging.samples.pageKeyedPage
+ *
* @sample androidx.paging.samples.pageIndexedPage
*/
- public data class Page<Key : Any, Value : Any> constructor(
- /**
- * Loaded data
- */
+ public data class Page<Key : Any, Value : Any>
+ constructor(
+ /** Loaded data */
val data: List<Value>,
/**
* [Key] for previous page if more data can be loaded in that direction, `null`
@@ -251,14 +253,12 @@
* Count of items before the loaded data. Must be implemented if
* [jumping][PagingSource.jumpingSupported] is enabled. Optional otherwise.
*/
- @IntRange(from = COUNT_UNDEFINED.toLong())
- val itemsBefore: Int = COUNT_UNDEFINED,
+ @IntRange(from = COUNT_UNDEFINED.toLong()) val itemsBefore: Int = COUNT_UNDEFINED,
/**
* Count of items after the loaded data. Must be implemented if
* [jumping][PagingSource.jumpingSupported] is enabled. Optional otherwise.
*/
- @IntRange(from = COUNT_UNDEFINED.toLong())
- val itemsAfter: Int = COUNT_UNDEFINED
+ @IntRange(from = COUNT_UNDEFINED.toLong()) val itemsAfter: Int = COUNT_UNDEFINED
) : LoadResult<Key, Value>(), Iterable<Value> {
/**
@@ -266,9 +266,9 @@
*
* @param data Loaded data
* @param prevKey [Key] for previous page if more data can be loaded in that direction,
- * `null` otherwise.
+ * `null` otherwise.
* @param nextKey [Key] for next page if more data can be loaded in that direction,
- * `null` otherwise.
+ * `null` otherwise.
*/
public constructor(
data: List<Value>,
@@ -299,7 +299,8 @@
| prevKey: $prevKey
| itemsBefore: $itemsBefore
| itemsAfter: $itemsAfter
- |) """.trimMargin()
+ |) """
+ .trimMargin()
}
public companion object {
@@ -335,8 +336,8 @@
get() = false
/**
- * `true` if this [PagingSource] expects to re-use keys to load distinct pages
- * without a call to [invalidate], `false` otherwise.
+ * `true` if this [PagingSource] expects to re-use keys to load distinct pages without a call to
+ * [invalidate], `false` otherwise.
*/
public open val keyReuseSupported: Boolean
get() = false
@@ -372,7 +373,7 @@
* triggered immediately.
*
* @param onInvalidatedCallback The callback that will be invoked on thread that invalidates the
- * [PagingSource].
+ * [PagingSource].
*/
public fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
invalidateCallbackTracker.registerInvalidatedCallback(onInvalidatedCallback)
@@ -398,27 +399,26 @@
* Provide a [Key] used for the initial [load] for the next [PagingSource] due to invalidation
* of this [PagingSource]. The [Key] is provided to [load] via [LoadParams.key].
*
- * The [Key] returned by this method should cause [load] to load enough items to
- * fill the viewport *around* the last accessed position, allowing the next generation to
- * transparently animate in. The last accessed position can be retrieved via
- * [state.anchorPosition][PagingState.anchorPosition], which is typically
- * the *top-most* or *bottom-most* item in the viewport due to access being triggered by binding
- * items as they scroll into view.
+ * The [Key] returned by this method should cause [load] to load enough items to fill the
+ * viewport *around* the last accessed position, allowing the next generation to transparently
+ * animate in. The last accessed position can be retrieved via
+ * [state.anchorPosition][PagingState.anchorPosition], which is typically the *top-most* or
+ * *bottom-most* item in the viewport due to access being triggered by binding items as they
+ * scroll into view.
*
- * For example, if items are loaded based on integer position keys, you can return
- * `( (state.anchorPosition ?: 0) - state.config.initialLoadSize / 2).coerceAtLeast(0)`.
+ * For example, if items are loaded based on integer position keys, you can return `(
+ * (state.anchorPosition ?: 0) - state.config.initialLoadSize / 2).coerceAtLeast(0)`.
*
* Alternately, if items contain a key used to load, get the key from the item in the page at
* index [state.anchorPosition][PagingState.anchorPosition] then try to center it based on
* `state.config.initialLoadSize`.
*
* @param state [PagingState] of the currently fetched data, which includes the most recently
- * accessed position in the list via [PagingState.anchorPosition].
- *
+ * accessed position in the list via [PagingState.anchorPosition].
* @return [Key] passed to [load] after invalidation used for initial load of the next
- * generation. The [Key] returned by [getRefreshKey] should load pages centered around
- * user's current viewport. If the correct [Key] cannot be determined, `null` can be returned
- * to allow [load] decide what default key to use.
+ * generation. The [Key] returned by [getRefreshKey] should load pages centered around user's
+ * current viewport. If the correct [Key] cannot be determined, `null` can be returned to
+ * allow [load] decide what default key to use.
*/
public abstract fun getRefreshKey(state: PagingState<Key, Value>): Key?
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingState.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingState.kt
index 2242b6b..ddca871 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingState.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingState.kt
@@ -23,10 +23,9 @@
* Snapshot state of Paging system including the loaded [pages], the last accessed [anchorPosition],
* and the [config] used.
*/
-public class PagingState<Key : Any, Value : Any> constructor(
- /**
- * Loaded pages of data in the list.
- */
+public class PagingState<Key : Any, Value : Any>
+constructor(
+ /** Loaded pages of data in the list. */
public val pages: List<Page<Key, Value>>,
/**
* Most recently accessed index in the list, including placeholders.
@@ -35,15 +34,12 @@
* generated before or during the first load.
*/
public val anchorPosition: Int?,
- /**
- * [PagingConfig] that was given when initializing the [PagingData] stream.
- */
+ /** [PagingConfig] that was given when initializing the [PagingData] stream. */
public val config: PagingConfig,
/**
* Number of placeholders before the first loaded item if placeholders are enabled, otherwise 0.
*/
- @IntRange(from = 0)
- private val leadingPlaceholderCount: Int
+ @IntRange(from = 0) private val leadingPlaceholderCount: Int
) {
override fun equals(other: Any?): Boolean {
@@ -55,20 +51,21 @@
}
override fun hashCode(): Int {
- return pages.hashCode() + anchorPosition.hashCode() + config.hashCode() +
+ return pages.hashCode() +
+ anchorPosition.hashCode() +
+ config.hashCode() +
leadingPlaceholderCount.hashCode()
}
/**
* Coerces [anchorPosition] to closest loaded value in [pages].
*
- * This function can be called with [anchorPosition] to fetch the loaded item that is closest
- * to the last accessed index in the list.
+ * This function can be called with [anchorPosition] to fetch the loaded item that is closest to
+ * the last accessed index in the list.
*
* @param anchorPosition Index in the list, including placeholders.
- *
- * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
- * all loaded [pages] are empty.
+ * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if all
+ * loaded [pages] are empty.
*/
public fun closestItemToPosition(anchorPosition: Int): Value? {
if (pages.all { it.data.isEmpty() }) return null
@@ -89,13 +86,12 @@
/**
* Coerces an index in the list, including placeholders, to closest loaded page in [pages].
*
- * This function can be called with [anchorPosition] to fetch the loaded page that is closest
- * to the last accessed index in the list.
+ * This function can be called with [anchorPosition] to fetch the loaded page that is closest to
+ * the last accessed index in the list.
*
* @param anchorPosition Index in the list, including placeholders.
- *
- * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
- * all loaded [pages] are empty.
+ * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if all
+ * loaded [pages] are empty.
*/
public fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? {
if (pages.all { it.data.isEmpty() }) return null
@@ -110,13 +106,13 @@
/**
* @return `true` if all loaded pages are empty or no pages were loaded when this [PagingState]
- * was created, `false` otherwise.
+ * was created, `false` otherwise.
*/
public fun isEmpty(): Boolean = pages.all { it.data.isEmpty() }
/**
* @return The first loaded item in the list or `null` if all loaded pages are empty or no pages
- * were loaded when this [PagingState] was created.
+ * were loaded when this [PagingState] was created.
*/
public fun firstItemOrNull(): Value? {
return pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
@@ -124,7 +120,7 @@
/**
* @return The last loaded item in the list or `null` if all loaded pages are empty or no pages
- * were loaded when this [PagingState] was created.
+ * were loaded when this [PagingState] was created.
*/
public fun lastItemOrNull(): Value? {
return pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PlaceholderPaddedList.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PlaceholderPaddedList.kt
index f9dc9ec..4d31aa2 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PlaceholderPaddedList.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PlaceholderPaddedList.kt
@@ -19,15 +19,15 @@
/**
* Interface to paged list that could contain placeholders.
*
- * Contains a paged list's snapshot state. For example, in the context of
- * [PagingDataEvent.Refresh] exposed by [PagingDataPresenter], each [PlaceholderPaddedList]
- * represents a generation of paged data whereby a new generation is distinguished with
- * a refresh load.
+ * Contains a paged list's snapshot state. For example, in the context of [PagingDataEvent.Refresh]
+ * exposed by [PagingDataPresenter], each [PlaceholderPaddedList] represents a generation of paged
+ * data whereby a new generation is distinguished with a refresh load.
*/
public interface PlaceholderPaddedList<T> {
public val placeholdersBefore: Int
public val placeholdersAfter: Int
public val size: Int
public val dataCount: Int
+
public fun getItem(index: Int): T
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediator.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediator.kt
index cb6f185..dea52cb 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediator.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediator.kt
@@ -21,7 +21,6 @@
import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingSource.LoadResult
import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
-import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
import kotlin.jvm.JvmName
/**
@@ -31,13 +30,14 @@
* A [RemoteMediator] is registered by passing it to [Pager]'s constructor.
*
* [RemoteMediator] allows hooking into the following events:
- * * Stream initialization
- * * [REFRESH] signal driven from UI
- * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most
- * recent [LoadResult.Page] in the [PREPEND] or [APPEND] direction has [LoadResult.Page.prevKey]
- * or [LoadResult.Page.nextKey] set to `null` respectively.
+ * * Stream initialization
+ * * [REFRESH] signal driven from UI
+ * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most recent
+ * [LoadResult.Page] in the [PREPEND] or [APPEND] direction has [LoadResult.Page.prevKey] or
+ * [LoadResult.Page.nextKey] set to `null` respectively.
*
* @sample androidx.paging.samples.remoteMediatorItemKeyedSample
+ *
* @sample androidx.paging.samples.remoteMediatorPageKeyedSample
*/
@ExperimentalPagingApi
@@ -45,46 +45,46 @@
/**
* Callback triggered when Paging needs to request more data from a remote source due to any of
* the following events:
- * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH]
- * * [REFRESH] signal driven from UI
- * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most
- * recent [LoadResult.Page] in the [PREPEND] or [APPEND] direction has
- * [LoadResult.Page.prevKey] or [LoadResult.Page.nextKey] set to `null` respectively.
+ * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH]
+ * * [REFRESH] signal driven from UI
+ * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most
+ * recent [LoadResult.Page] in the [PREPEND] or [APPEND] direction has
+ * [LoadResult.Page.prevKey] or [LoadResult.Page.nextKey] set to `null` respectively.
*
* It is the responsibility of this method to update the backing dataset and trigger
- * [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new
- * items found by [load].
+ * [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new items
+ * found by [load].
*
- * The runtime and result of this method defines the remote [LoadState] behavior sent to the
- * UI via [CombinedLoadStates].
+ * The runtime and result of this method defines the remote [LoadState] behavior sent to the UI
+ * via [CombinedLoadStates].
*
- * This method is never called concurrently *unless* [Pager.flow] has multiple collectors.
- * Note that Paging might cancel calls to this function if it is currently executing a
- * [PREPEND] or [APPEND] and a [REFRESH] is requested. In that case, [REFRESH] has higher
- * priority and will be executed after the previous call is cancelled. If the [load] call with
- * [REFRESH] returns an error, Paging will call [load] with the previously cancelled [APPEND]
- * or [PREPEND] request. If [REFRESH] succeeds, it won't make the [APPEND] or [PREPEND] requests
- * unless they are necessary again after the [REFRESH] is applied to the UI.
+ * This method is never called concurrently *unless* [Pager.flow] has multiple collectors. Note
+ * that Paging might cancel calls to this function if it is currently executing a [PREPEND] or
+ * [APPEND] and a [REFRESH] is requested. In that case, [REFRESH] has higher priority and will
+ * be executed after the previous call is cancelled. If the [load] call with [REFRESH] returns
+ * an error, Paging will call [load] with the previously cancelled [APPEND] or [PREPEND]
+ * request. If [REFRESH] succeeds, it won't make the [APPEND] or [PREPEND] requests unless they
+ * are necessary again after the [REFRESH] is applied to the UI.
*
* @param loadType [LoadType] of the condition which triggered this callback.
- * * [PREPEND] indicates the end of pagination in the [PREPEND] direction was reached. This
- * occurs when [PagingSource.load] returns a [LoadResult.Page] with
- * [LoadResult.Page.prevKey] == `null`.
- * * [APPEND] indicates the end of pagination in the [APPEND] direction was reached. This
- * occurs when [PagingSource.load] returns a [LoadResult.Page] with
- * [LoadResult.Page.nextKey] == `null`.
- * * [REFRESH] indicates this method was triggered due to a requested refresh. Generally, this
- * means that a request to load remote data and **replace** all local data was made. This can
- * happen when:
- * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH]
- * * An explicit call to refresh driven by the UI
- * @param state A copy of the state including the list of pages currently held in memory of the
- * currently presented [PagingData] at the time of starting the load. E.g. for
- * load(loadType = APPEND), you can use the page or item at the end as input for what to load
- * from the network.
+ * * [PREPEND] indicates the end of pagination in the [PREPEND] direction was reached. This
+ * occurs when [PagingSource.load] returns a [LoadResult.Page] with
+ * [LoadResult.Page.prevKey] == `null`.
+ * * [APPEND] indicates the end of pagination in the [APPEND] direction was reached. This
+ * occurs when [PagingSource.load] returns a [LoadResult.Page] with
+ * [LoadResult.Page.nextKey] == `null`.
+ * * [REFRESH] indicates this method was triggered due to a requested refresh. Generally,
+ * this means that a request to load remote data and **replace** all local data was made.
+ * This can happen when:
+ * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH]
+ * * An explicit call to refresh driven by the UI
*
+ * @param state A copy of the state including the list of pages currently held in memory of the
+ * currently presented [PagingData] at the time of starting the load. E.g. for load(loadType =
+ * APPEND), you can use the page or item at the end as input for what to load from the
+ * network.
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
- * there's more data available.
+ * there's more data available.
*/
public abstract suspend fun load(
loadType: LoadType,
@@ -97,23 +97,19 @@
* This function runs to completion before any loading is performed.
*
* @return [InitializeAction] used to control whether [load] with load type [REFRESH] will be
- * immediately dispatched when the first [PagingData] is submitted:
- * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch [load] asynchronously with load type
- * [REFRESH], to update paginated content when the stream is initialized.
- * Note: This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
- * [REFRESH] succeeds.
- * * [SKIP_INITIAL_REFRESH] to wait for a refresh request from the UI before dispatching [load]
- * asynchronously with load type [REFRESH].
+ * immediately dispatched when the first [PagingData] is submitted:
+ * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch [load] asynchronously with load type
+ * [REFRESH], to update paginated content when the stream is initialized. Note: This also
+ * prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until [REFRESH]
+ * succeeds.
+ * * [SKIP_INITIAL_REFRESH] to wait for a refresh request from the UI before dispatching
+ * [load] asynchronously with load type [REFRESH].
*/
public open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
- /**
- * Return type of [load], which determines [LoadState].
- */
+ /** Return type of [load], which determines [LoadState]. */
public sealed class MediatorResult {
- /**
- * Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error].
- */
+ /** Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error]. */
public class Error(public val throwable: Throwable) : MediatorResult()
/**
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
index 60e1cd7..21703e9e 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
@@ -27,24 +27,15 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
-/**
- * Interface provided to the snapshot to trigger load events.
- */
+/** Interface provided to the snapshot to trigger load events. */
internal interface RemoteMediatorConnection<Key : Any, Value : Any> {
- fun requestRefreshIfAllowed(
- pagingState: PagingState<Key, Value>
- )
+ fun requestRefreshIfAllowed(pagingState: PagingState<Key, Value>)
- fun requestLoad(
- loadType: LoadType,
- pagingState: PagingState<Key, Value>
- )
+ fun requestLoad(loadType: LoadType, pagingState: PagingState<Key, Value>)
fun retryFailed(pagingState: PagingState<Key, Value>)
- /**
- * Allow a single call to [requestRefreshIfAllowed] to successfully get enqueued.
- */
+ /** Allow a single call to [requestRefreshIfAllowed] to successfully get enqueued. */
fun allowRefresh()
}
@@ -63,9 +54,7 @@
delegate: RemoteMediator<Key, Value>
): RemoteMediatorAccessor<Key, Value> = RemoteMediatorAccessImpl(scope, delegate)
-/**
- * Simple wrapper around the local state of accessor to ensure we don't concurrently change it.
- */
+/** Simple wrapper around the local state of accessor to ensure we don't concurrently change it. */
private class AccessorStateHolder<Key : Any, Value : Any> {
private val lock = ReentrantLock()
@@ -77,9 +66,7 @@
fun <R> use(block: (AccessorState<Key, Value>) -> R): R {
return lock.withLock {
- block(internalState).also {
- _loadStates.value = internalState.computeLoadStates()
- }
+ block(internalState).also { _loadStates.value = internalState.computeLoadStates() }
}
}
}
@@ -95,14 +82,10 @@
*/
private class AccessorState<Key : Any, Value : Any> {
// TODO this can be a bit flag instead
- private val blockStates = Array<BlockState>(LoadType.values().size) {
- UNBLOCKED
- }
+ private val blockStates = Array<BlockState>(LoadType.values().size) { UNBLOCKED }
// keep these as error states to avoid recreating them all the time
- private val errors = Array<LoadState.Error?>(LoadType.values().size) {
- null
- }
+ private val errors = Array<LoadState.Error?>(LoadType.values().size) { null }
private val pendingRequests = ArrayDeque<PendingRequest<Key, Value>>()
/**
@@ -130,9 +113,7 @@
private fun computeLoadTypeState(loadType: LoadType): LoadState {
val blockState = blockStates[loadType.ordinal]
- val hasPending = pendingRequests.any {
- it.loadType == loadType
- }
+ val hasPending = pendingRequests.any { it.loadType == loadType }
// Boundary requests maybe queue in pendingRequest before getting launched later when
// refresh resolves if their block state is REQUIRES_REFRESH.
if (hasPending && blockState != REQUIRES_REFRESH) {
@@ -146,33 +127,28 @@
// b) it might be blocked due to refresh being required first -> Incomplete
// c) it might have never run -> Incomplete
return when (blockState) {
- COMPLETED -> when (loadType) {
- LoadType.REFRESH -> LoadState.NotLoading.Incomplete
- else -> LoadState.NotLoading.Complete
- }
+ COMPLETED ->
+ when (loadType) {
+ LoadType.REFRESH -> LoadState.NotLoading.Incomplete
+ else -> LoadState.NotLoading.Complete
+ }
REQUIRES_REFRESH -> LoadState.NotLoading.Incomplete
UNBLOCKED -> LoadState.NotLoading.Incomplete
}
}
/**
- * Tries to add a new pending request for the provided [loadType], and launches it
- * immediately if it should run.
+ * Tries to add a new pending request for the provided [loadType], and launches it immediately
+ * if it should run.
*
- * In cases where pending request for the provided [loadType] already exists, the
- * [pagingState] will just be updated in the existing request instead of queuing up multiple
- * requests. This effectively de-dupes requests by [loadType], but always keeps the most
- * recent request.
+ * In cases where pending request for the provided [loadType] already exists, the [pagingState]
+ * will just be updated in the existing request instead of queuing up multiple requests. This
+ * effectively de-dupes requests by [loadType], but always keeps the most recent request.
*
* @return `true` if fetchers should be launched, `false` otherwise.
*/
- fun add(
- loadType: LoadType,
- pagingState: PagingState<Key, Value>
- ): Boolean {
- val existing = pendingRequests.firstOrNull {
- it.loadType == loadType
- }
+ fun add(loadType: LoadType, pagingState: PagingState<Key, Value>): Boolean {
+ val existing = pendingRequests.firstOrNull { it.loadType == loadType }
// De-dupe requests with the same LoadType, just update PagingState and return.
if (existing != null) {
existing.pagingState = pagingState
@@ -217,25 +193,25 @@
blockStates[loadType.ordinal] = state
}
- fun getPendingRefresh() = pendingRequests.firstOrNull {
- it.loadType == LoadType.REFRESH
- }?.pagingState
+ fun getPendingRefresh() =
+ pendingRequests.firstOrNull { it.loadType == LoadType.REFRESH }?.pagingState
- fun getPendingBoundary() = pendingRequests.firstOrNull {
- it.loadType != LoadType.REFRESH && blockStates[it.loadType.ordinal] == UNBLOCKED
- }?.let {
- // make a copy
- it.loadType to it.pagingState
- }
+ fun getPendingBoundary() =
+ pendingRequests
+ .firstOrNull {
+ it.loadType != LoadType.REFRESH && blockStates[it.loadType.ordinal] == UNBLOCKED
+ }
+ ?.let {
+ // make a copy
+ it.loadType to it.pagingState
+ }
fun clearPendingRequests() {
pendingRequests.clear()
}
fun clearPendingRequest(loadType: LoadType) {
- pendingRequests.removeAll {
- it.loadType == loadType
- }
+ pendingRequests.removeAll { it.loadType == loadType }
}
fun clearErrors() {
@@ -296,9 +272,7 @@
loadType: LoadType,
pagingState: PagingState<Key, Value>,
) {
- val newRequest = use {
- it.add(loadType, pagingState)
- }
+ val newRequest = use { it.add(loadType, pagingState) }
if (newRequest) {
when (loadType) {
@@ -311,66 +285,69 @@
private fun launchRefresh() {
scope.launch {
var launchAppendPrepend = false
- isolationRunner.runInIsolation(
- priority = PRIORITY_REFRESH
- ) {
- val pendingPagingState = accessorState.use {
- it.getPendingRefresh()
- }
+ isolationRunner.runInIsolation(priority = PRIORITY_REFRESH) {
+ val pendingPagingState = accessorState.use { it.getPendingRefresh() }
pendingPagingState?.let {
val loadResult = remoteMediator.load(LoadType.REFRESH, pendingPagingState)
- launchAppendPrepend = when (loadResult) {
- is MediatorResult.Success -> {
- accessorState.use {
- // First clear refresh from pending requests to update LoadState.
- // Note: Only clear refresh request, allowing potentially
- // out-of-date boundary requests as there's no guarantee that
- // refresh will trigger invalidation, and clearing boundary requests
- // here could prevent Paging from making progress.
- it.clearPendingRequest(LoadType.REFRESH)
+ launchAppendPrepend =
+ when (loadResult) {
+ is MediatorResult.Success -> {
+ accessorState.use {
+ // First clear refresh from pending requests to update
+ // LoadState.
+ // Note: Only clear refresh request, allowing potentially
+ // out-of-date boundary requests as there's no guarantee that
+ // refresh will trigger invalidation, and clearing boundary
+ // requests
+ // here could prevent Paging from making progress.
+ it.clearPendingRequest(LoadType.REFRESH)
- if (loadResult.endOfPaginationReached) {
- it.setBlockState(LoadType.REFRESH, COMPLETED)
- it.setBlockState(LoadType.PREPEND, COMPLETED)
- it.setBlockState(LoadType.APPEND, COMPLETED)
+ if (loadResult.endOfPaginationReached) {
+ it.setBlockState(LoadType.REFRESH, COMPLETED)
+ it.setBlockState(LoadType.PREPEND, COMPLETED)
+ it.setBlockState(LoadType.APPEND, COMPLETED)
- // Now that blockState is updated, which should block
- // new boundary requests, clear all requests since
- // endOfPaginationReached from refresh should prevent prepend
- // and append from triggering, even if they are queued up.
- it.clearPendingRequests()
- } else {
- // Update block state for boundary requests now that we can
- // handle them if they required refresh.
- it.setBlockState(LoadType.PREPEND, UNBLOCKED)
- it.setBlockState(LoadType.APPEND, UNBLOCKED)
+ // Now that blockState is updated, which should block
+ // new boundary requests, clear all requests since
+ // endOfPaginationReached from refresh should prevent
+ // prepend
+ // and append from triggering, even if they are queued up.
+ it.clearPendingRequests()
+ } else {
+ // Update block state for boundary requests now that we can
+ // handle them if they required refresh.
+ it.setBlockState(LoadType.PREPEND, UNBLOCKED)
+ it.setBlockState(LoadType.APPEND, UNBLOCKED)
+ }
+
+ // clean their errors
+ it.setError(LoadType.PREPEND, null)
+ it.setError(LoadType.APPEND, null)
+
+ // If there is a pending boundary, trigger its launch, allowing
+ // out-of-date requests in the case where queued requests were
+ // from previous generation. See b/176855944.
+ it.getPendingBoundary() != null
}
+ }
+ is MediatorResult.Error -> {
+ // if refresh failed, don't change append/prepend states so that if
+ // refresh is not required, they can run.
+ accessorState.use {
+ // only clear refresh. we can use append prepend
+ it.clearPendingRequest(LoadType.REFRESH)
+ it.setError(
+ LoadType.REFRESH,
+ LoadState.Error(loadResult.throwable)
+ )
- // clean their errors
- it.setError(LoadType.PREPEND, null)
- it.setError(LoadType.APPEND, null)
-
- // If there is a pending boundary, trigger its launch, allowing
- // out-of-date requests in the case where queued requests were
- // from previous generation. See b/176855944.
- it.getPendingBoundary() != null
+ // If there is a pending boundary, trigger its launch, allowing
+ // out-of-date requests in the case where queued requests were
+ // from previous generation. See b/176855944.
+ it.getPendingBoundary() != null
+ }
}
}
- is MediatorResult.Error -> {
- // if refresh failed, don't change append/prepend states so that if
- // refresh is not required, they can run.
- accessorState.use {
- // only clear refresh. we can use append prepend
- it.clearPendingRequest(LoadType.REFRESH)
- it.setError(LoadType.REFRESH, LoadState.Error(loadResult.throwable))
-
- // If there is a pending boundary, trigger its launch, allowing
- // out-of-date requests in the case where queued requests were
- // from previous generation. See b/176855944.
- it.getPendingBoundary() != null
- }
- }
- }
}
}
// launch this after we leave the restricted scope otherwise append / prepend won't
@@ -383,13 +360,10 @@
private fun launchBoundary() {
scope.launch {
- isolationRunner.runInIsolation(
- priority = PRIORITY_APPEND_PREPEND
- ) {
+ isolationRunner.runInIsolation(priority = PRIORITY_APPEND_PREPEND) {
while (true) {
- val (loadType, pendingPagingState) = accessorState.use {
- it.getPendingBoundary()
- } ?: break
+ val (loadType, pendingPagingState) =
+ accessorState.use { it.getPendingBoundary() } ?: break
when (val loadResult = remoteMediator.load(loadType, pendingPagingState)) {
is MediatorResult.Success -> {
accessorState.use {
@@ -436,9 +410,7 @@
}
}
- toBeStarted.forEach {
- requestLoad(it, pagingState)
- }
+ toBeStarted.forEach { requestLoad(it, pagingState) }
}
override suspend fun initialize(): RemoteMediator.InitializeAction {
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/Separators.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/Separators.kt
index 00f9e19..5452d3a 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/Separators.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/Separators.kt
@@ -39,8 +39,8 @@
*
* End of paginations occurs when [CombinedLoadStates] has set
* [LoadState.endOfPaginationReached] to `true` for both [CombinedLoadStates.source] and
- * [CombinedLoadStates.mediator] in the [PREPEND] direction for the header and in the
- * [APPEND] direction for the footer.
+ * [CombinedLoadStates.mediator] in the [PREPEND] direction for the header and in the [APPEND]
+ * direction for the footer.
*
* In cases where [RemoteMediator] isn't used, only [CombinedLoadStates.source] will be
* considered.
@@ -67,8 +67,7 @@
generator: suspend (T?, T?) -> R?
): TransformablePage<R> {
if (data.isEmpty()) {
- @Suppress("UNCHECKED_CAST")
- return this as TransformablePage<R>
+ @Suppress("UNCHECKED_CAST") return this as TransformablePage<R>
}
val initialCapacity = data.size + 4 // extra space to avoid bigger allocations
@@ -106,20 +105,19 @@
}
}
-/**
- * Create a [TransformablePage] with the given separator (or empty, if the separator is null)
- */
+/** Create a [TransformablePage] with the given separator (or empty, if the separator is null) */
internal fun <T : Any> separatorPage(
separator: T,
originalPageOffsets: IntArray,
hintOriginalPageOffset: Int,
hintOriginalIndex: Int
-): TransformablePage<T> = TransformablePage(
- originalPageOffsets = originalPageOffsets,
- data = listOf(separator),
- hintOriginalPageOffset = hintOriginalPageOffset,
- hintOriginalIndices = listOf(hintOriginalIndex)
-)
+): TransformablePage<T> =
+ TransformablePage(
+ originalPageOffsets = originalPageOffsets,
+ data = listOf(separator),
+ hintOriginalPageOffset = hintOriginalPageOffset,
+ hintOriginalIndices = listOf(hintOriginalIndex)
+ )
/**
* Create a [TransformablePage] with the given separator, and add it if [separator] is non-null
@@ -135,12 +133,13 @@
) {
if (separator == null) return
- val separatorPage = separatorPage(
- separator = separator,
- originalPageOffsets = originalPageOffsets,
- hintOriginalPageOffset = hintOriginalPageOffset,
- hintOriginalIndex = hintOriginalIndex
- )
+ val separatorPage =
+ separatorPage(
+ separator = separator,
+ originalPageOffsets = originalPageOffsets,
+ hintOriginalPageOffset = hintOriginalPageOffset,
+ hintOriginalIndex = hintOriginalIndex
+ )
add(separatorPage)
}
@@ -161,17 +160,19 @@
val afterOffsets = adjacentPageAfter?.originalPageOffsets
addSeparatorPage(
separator = separator,
- originalPageOffsets = when {
- beforeOffsets != null && afterOffsets != null -> {
- (beforeOffsets + afterOffsets).distinct().sorted().toIntArray()
- }
- beforeOffsets == null && afterOffsets != null -> afterOffsets
- beforeOffsets != null && afterOffsets == null -> beforeOffsets
- else -> throw IllegalArgumentException(
- "Separator page expected adjacentPageBefore or adjacentPageAfter, but both were" +
- " null."
- )
- },
+ originalPageOffsets =
+ when {
+ beforeOffsets != null && afterOffsets != null -> {
+ (beforeOffsets + afterOffsets).distinct().sorted().toIntArray()
+ }
+ beforeOffsets == null && afterOffsets != null -> afterOffsets
+ beforeOffsets != null && afterOffsets == null -> beforeOffsets
+ else ->
+ throw IllegalArgumentException(
+ "Separator page expected adjacentPageBefore or adjacentPageAfter, but both were" +
+ " null."
+ )
+ },
hintOriginalPageOffset = hintOriginalPageOffset,
hintOriginalIndex = hintOriginalIndex
)
@@ -184,9 +185,9 @@
/**
* Lookup table of previously emitted pages, that skips empty pages.
*
- * This table is used to keep track of originalPageOffsets for separators that would span
- * across empty pages. It includes a simplified version of loaded pages which only has the
- * first and last item in each page to reduce memory pressure.
+ * This table is used to keep track of originalPageOffsets for separators that would span across
+ * empty pages. It includes a simplified version of loaded pages which only has the first and
+ * last item in each page to reduce memory pressure.
*
* Note: [TransformablePage] added to this stash must always have
* [TransformablePage.originalPageOffsets] defined, since it needs to keep track of the
@@ -210,24 +211,24 @@
var headerAdded = false
@Suppress("UNCHECKED_CAST")
- suspend fun onEvent(event: PageEvent<T>): PageEvent<R> = when (event) {
- is Insert<T> -> onInsert(event)
- is Drop -> onDrop(event)
- is LoadStateUpdate -> onLoadStateUpdate(event)
- is StaticList -> onStaticList(event)
- }.also {
- // validate internal state after each modification
- if (endTerminalSeparatorDeferred) {
- check(pageStash.isEmpty()) { "deferred endTerm, page stash should be empty" }
+ suspend fun onEvent(event: PageEvent<T>): PageEvent<R> =
+ when (event) {
+ is Insert<T> -> onInsert(event)
+ is Drop -> onDrop(event)
+ is LoadStateUpdate -> onLoadStateUpdate(event)
+ is StaticList -> onStaticList(event)
+ }.also {
+ // validate internal state after each modification
+ if (endTerminalSeparatorDeferred) {
+ check(pageStash.isEmpty()) { "deferred endTerm, page stash should be empty" }
+ }
+ if (startTerminalSeparatorDeferred) {
+ check(pageStash.isEmpty()) { "deferred startTerm, page stash should be empty" }
+ }
}
- if (startTerminalSeparatorDeferred) {
- check(pageStash.isEmpty()) { "deferred startTerm, page stash should be empty" }
- }
- }
fun Insert<T>.asRType(): Insert<R> {
- @Suppress("UNCHECKED_CAST")
- return this as Insert<R>
+ @Suppress("UNCHECKED_CAST") return this as Insert<R>
}
fun <T : Any> Insert<T>.terminatesStart(terminalSeparatorType: TerminalSeparatorType): Boolean {
@@ -411,16 +412,19 @@
separator = separator,
adjacentPageBefore = pageBefore,
adjacentPageAfter = page,
- hintOriginalPageOffset = if (event.loadType == PREPEND) {
- pageBefore.hintOriginalPageOffset
- } else {
- page.hintOriginalPageOffset
- },
- hintOriginalIndex = if (event.loadType == PREPEND) {
- pageBefore.hintOriginalIndices?.last() ?: pageBefore.data.lastIndex
- } else {
- page.hintOriginalIndices?.first() ?: 0
- }
+ hintOriginalPageOffset =
+ if (event.loadType == PREPEND) {
+ pageBefore.hintOriginalPageOffset
+ } else {
+ page.hintOriginalPageOffset
+ },
+ hintOriginalIndex =
+ if (event.loadType == PREPEND) {
+ pageBefore.hintOriginalIndices?.last()
+ ?: pageBefore.data.lastIndex
+ } else {
+ page.hintOriginalIndices?.first() ?: 0
+ }
)
}
@@ -443,8 +447,9 @@
adjacentPageBefore = lastNonEmptyPage,
adjacentPageAfter = pageAfter,
hintOriginalPageOffset = lastNonEmptyPage.hintOriginalPageOffset,
- hintOriginalIndex = lastNonEmptyPage.hintOriginalIndices?.last()
- ?: lastNonEmptyPage.data.lastIndex
+ hintOriginalIndex =
+ lastNonEmptyPage.hintOriginalIndices?.last()
+ ?: lastNonEmptyPage.data.lastIndex
)
}
@@ -465,8 +470,8 @@
adjacentPageBefore = pageBefore,
adjacentPageAfter = null,
hintOriginalPageOffset = pageBefore.hintOriginalPageOffset,
- hintOriginalIndex = pageBefore.hintOriginalIndices?.last()
- ?: pageBefore.data.lastIndex
+ hintOriginalIndex =
+ pageBefore.hintOriginalIndices?.last() ?: pageBefore.data.lastIndex
)
}
@@ -481,9 +486,7 @@
return event.transformPages { outList }
}
- /**
- * Process a [Drop] event to update [pageStash] stage.
- */
+ /** Process a [Drop] event to update [pageStash] stage. */
fun onDrop(event: Drop<T>): Drop<R> {
sourceStates.set(type = event.loadType, state = NotLoading.Incomplete)
if (event.loadType == PREPEND) {
@@ -508,8 +511,7 @@
stash.originalPageOffsets.any { pageOffsetsToDrop.contains(it) }
}
- @Suppress("UNCHECKED_CAST")
- return event as Drop<R>
+ @Suppress("UNCHECKED_CAST") return event as Drop<R>
}
suspend fun onLoadStateUpdate(event: LoadStateUpdate<T>): PageEvent<R> {
@@ -517,8 +519,7 @@
// Check for redundant LoadStateUpdate events to avoid unnecessary mapping to empty inserts
// that might cause terminal separators to get added out of place.
if (sourceStates.snapshot() == event.source && prevMediator == event.mediator) {
- @Suppress("UNCHECKED_CAST")
- return event as PageEvent<R>
+ @Suppress("UNCHECKED_CAST") return event as PageEvent<R>
}
sourceStates.set(event.source)
@@ -530,29 +531,34 @@
// isn't possible to add a separator to. Note: Adding a separate insert event also
// doesn't work in the case where .insertSeparators() is called multiple times on the
// same page event stream - we have to transform the terminating LoadStateUpdate event.
- if (event.mediator != null && event.mediator.prepend.endOfPaginationReached &&
- prevMediator?.prepend != event.mediator.prepend
+ if (
+ event.mediator != null &&
+ event.mediator.prepend.endOfPaginationReached &&
+ prevMediator?.prepend != event.mediator.prepend
) {
- val prependTerminalInsert: Insert<T> = Insert.Prepend(
- pages = emptyList(),
- placeholdersBefore = placeholdersBefore,
- sourceLoadStates = event.source,
- mediatorLoadStates = event.mediator,
- )
+ val prependTerminalInsert: Insert<T> =
+ Insert.Prepend(
+ pages = emptyList(),
+ placeholdersBefore = placeholdersBefore,
+ sourceLoadStates = event.source,
+ mediatorLoadStates = event.mediator,
+ )
return onInsert(prependTerminalInsert)
- } else if (event.mediator != null && event.mediator.append.endOfPaginationReached &&
- prevMediator?.append != event.mediator.append
+ } else if (
+ event.mediator != null &&
+ event.mediator.append.endOfPaginationReached &&
+ prevMediator?.append != event.mediator.append
) {
- val appendTerminalInsert: Insert<T> = Insert.Append(
- pages = emptyList(),
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = event.source,
- mediatorLoadStates = event.mediator,
- )
+ val appendTerminalInsert: Insert<T> =
+ Insert.Append(
+ pages = emptyList(),
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = event.source,
+ mediatorLoadStates = event.mediator,
+ )
return onInsert(appendTerminalInsert)
}
- @Suppress("UNCHECKED_CAST")
- return event as PageEvent<R>
+ @Suppress("UNCHECKED_CAST") return event as PageEvent<R>
}
suspend fun onStaticList(event: StaticList<T>): PageEvent<R> {
@@ -584,24 +590,24 @@
originalPageOffsets = originalPage.originalPageOffsets,
data = listOf(originalPage.data.first(), originalPage.data.last()),
hintOriginalPageOffset = originalPage.hintOriginalPageOffset,
- hintOriginalIndices = listOf(
- originalPage.hintOriginalIndices?.first() ?: 0,
- originalPage.hintOriginalIndices?.last() ?: originalPage.data.lastIndex
- )
+ hintOriginalIndices =
+ listOf(
+ originalPage.hintOriginalIndices?.first() ?: 0,
+ originalPage.hintOriginalIndices?.last() ?: originalPage.data.lastIndex
+ )
)
}
}
/**
- * This is intentionally not named insertSeparators to avoid creating a clashing import
- * with PagingData.insertSeparators, which is public
+ * This is intentionally not named insertSeparators to avoid creating a clashing import with
+ * PagingData.insertSeparators, which is public
*/
internal fun <T : R, R : Any> Flow<PageEvent<T>>.insertEventSeparators(
terminalSeparatorType: TerminalSeparatorType,
generator: suspend (T?, T?) -> R?
): Flow<PageEvent<R>> {
- val separatorState = SeparatorState(terminalSeparatorType) { before: T?, after: T? ->
- generator(before, after)
- }
+ val separatorState =
+ SeparatorState(terminalSeparatorType) { before: T?, after: T? -> generator(before, after) }
return map { separatorState.onEvent(it) }
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SimpleChannelFlow.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SimpleChannelFlow.kt
index 509cf455..63e1a29 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SimpleChannelFlow.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SimpleChannelFlow.kt
@@ -33,48 +33,48 @@
import kotlinx.coroutines.suspendCancellableCoroutine
/**
- * This is a simplified channelFlow implementation as a temporary measure until channel flow
- * leaves experimental state.
+ * This is a simplified channelFlow implementation as a temporary measure until channel flow leaves
+ * experimental state.
*
- * The exact same implementation is not possible due to [FusibleFlow] being an internal API. To
- * get close to that implementation, internally we use a [Channel.RENDEZVOUS] channel and use a
- * [buffer] ([Channel.BUFFERED]) operator on the resulting Flow. This gives us a close behavior
- * where the default is buffered and any followup buffer operation will result in +1 value being
- * produced.
+ * The exact same implementation is not possible due to [FusibleFlow] being an internal API. To get
+ * close to that implementation, internally we use a [Channel.RENDEZVOUS] channel and use a [buffer]
+ * ([Channel.BUFFERED]) operator on the resulting Flow. This gives us a close behavior where the
+ * default is buffered and any followup buffer operation will result in +1 value being produced.
*/
-internal fun <T> simpleChannelFlow(
- block: suspend SimpleProducerScope<T>.() -> Unit
-): Flow<T> {
+internal fun <T> simpleChannelFlow(block: suspend SimpleProducerScope<T>.() -> Unit): Flow<T> {
return flow {
- coroutineScope {
- val channel = Channel<T>(capacity = Channel.RENDEZVOUS)
- val producer = launch {
- try {
- // run producer in a separate inner scope to ensure we wait for its children
- // to finish, in case it does more launches inside.
- coroutineScope {
- val producerScopeImpl = SimpleProducerScopeImpl(
- scope = this,
- channel = channel,
- )
- producerScopeImpl.block()
+ coroutineScope {
+ val channel = Channel<T>(capacity = Channel.RENDEZVOUS)
+ val producer = launch {
+ try {
+ // run producer in a separate inner scope to ensure we wait for its children
+ // to finish, in case it does more launches inside.
+ coroutineScope {
+ val producerScopeImpl =
+ SimpleProducerScopeImpl(
+ scope = this,
+ channel = channel,
+ )
+ producerScopeImpl.block()
+ }
+ channel.close()
+ } catch (t: Throwable) {
+ channel.close(t)
}
- channel.close()
- } catch (t: Throwable) {
- channel.close(t)
}
+ for (item in channel) {
+ emit(item)
+ }
+ // in case channel closed before producer completes, cancel the producer.
+ producer.cancel()
}
- for (item in channel) {
- emit(item)
- }
- // in case channel closed before producer completes, cancel the producer.
- producer.cancel()
}
- }.buffer(Channel.BUFFERED)
+ .buffer(Channel.BUFFERED)
}
internal interface SimpleProducerScope<T> : CoroutineScope, SendChannel<T> {
val channel: SendChannel<T>
+
suspend fun awaitClose(block: () -> Unit)
}
@@ -84,13 +84,10 @@
) : SimpleProducerScope<T>, CoroutineScope by scope, SendChannel<T> by channel {
override suspend fun awaitClose(block: () -> Unit) {
try {
- val job = checkNotNull(coroutineContext[Job]) {
- "Internal error, context should have a job."
- }
+ val job =
+ checkNotNull(coroutineContext[Job]) { "Internal error, context should have a job." }
suspendCancellableCoroutine<Unit> { cont ->
- job.invokeOnCompletion {
- cont.resume(Unit)
- }
+ job.invokeOnCompletion { cont.resume(Unit) }
}
} finally {
block()
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SingleRunner.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SingleRunner.kt
index e411de3..de49597 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SingleRunner.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SingleRunner.kt
@@ -29,31 +29,23 @@
*
* When priorities are used, if the currently running block has a higher priority, the new one is
* cancelled. If the currently running block has lower priority, currently running block is
- * cancelled.
- * If they have equal priority:
- * * if cancelPreviousInEqualPriority == true, existing block is cancelled
- * * if cancelPreviousInEqualPriority == false, new block is cancelled
+ * cancelled. If they have equal priority:
+ * * if cancelPreviousInEqualPriority == true, existing block is cancelled
+ * * if cancelPreviousInEqualPriority == false, new block is cancelled
*
* Note: When a block is cancelled, the outer scope (which called runInIsolation) is NOT cancelled.
*/
-internal class SingleRunner(
- cancelPreviousInEqualPriority: Boolean = true
-) {
+internal class SingleRunner(cancelPreviousInEqualPriority: Boolean = true) {
private val holder = Holder(this, cancelPreviousInEqualPriority)
- suspend fun runInIsolation(
- priority: Int = DEFAULT_PRIORITY,
- block: suspend () -> Unit
- ) {
+ suspend fun runInIsolation(priority: Int = DEFAULT_PRIORITY, block: suspend () -> Unit) {
try {
coroutineScope {
- val myJob = checkNotNull(coroutineContext[Job]) {
- "Internal error. coroutineScope should've created a job."
- }
- val run = holder.tryEnqueue(
- priority = priority,
- job = myJob
- )
+ val myJob =
+ checkNotNull(coroutineContext[Job]) {
+ "Internal error. coroutineScope should've created a job."
+ }
+ val run = holder.tryEnqueue(priority = priority, job = myJob)
if (run) {
try {
block()
@@ -71,13 +63,12 @@
}
/**
- * Internal exception which is used to cancel previous instance of an isolated runner.
- * We use this special class so that we can still support regular cancelation coming from the
- * `block` but don't cancel its coroutine just to cancel the block.
+ * Internal exception which is used to cancel previous instance of an isolated runner. We use
+ * this special class so that we can still support regular cancelation coming from the `block`
+ * but don't cancel its coroutine just to cancel the block.
*/
- private class CancelIsolatedRunnerException(
- val runner: SingleRunner
- ) : CancellationException("Cancelled isolated runner")
+ private class CancelIsolatedRunnerException(val runner: SingleRunner) :
+ CancellationException("Cancelled isolated runner")
private class Holder(
private val singleRunner: SingleRunner,
@@ -87,16 +78,14 @@
private var previous: Job? = null
private var previousPriority: Int = 0
- suspend fun tryEnqueue(
- priority: Int,
- job: Job
- ): Boolean {
+ suspend fun tryEnqueue(priority: Int, job: Job): Boolean {
mutex.withLock {
val prev = previous
- return if (prev == null ||
- !prev.isActive ||
- previousPriority < priority ||
- (previousPriority == priority && cancelPreviousInEqualPriority)
+ return if (
+ prev == null ||
+ !prev.isActive ||
+ previousPriority < priority ||
+ (previousPriority == priority && cancelPreviousInEqualPriority)
) {
prev?.cancel(CancelIsolatedRunnerException(singleRunner))
prev?.join()
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt
index 1b15542f..2ff9c96 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt
@@ -22,9 +22,8 @@
/**
* Utility class to convert the paging source factory to a suspend one.
*
- * This is internal because it is only necessary for the legacy paging source implementation
- * where the data source must be created on the given thread pool for API guarantees.
- * see: b/173029013
+ * This is internal because it is only necessary for the legacy paging source implementation where
+ * the data source must be created on the given thread pool for API guarantees. see: b/173029013
* see: b/168061354
*/
internal class SuspendingPagingSourceFactory<Key : Any, Value : Any>(
@@ -32,9 +31,7 @@
private val delegate: () -> PagingSource<Key, Value>
) : () -> PagingSource<Key, Value> {
suspend fun create(): PagingSource<Key, Value> {
- return withContext(dispatcher) {
- delegate()
- }
+ return withContext(dispatcher) { delegate() }
}
override fun invoke(): PagingSource<Key, Value> {
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/TransformablePage.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/TransformablePage.kt
index ded4c66..89da0db 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/TransformablePage.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/TransformablePage.kt
@@ -25,9 +25,7 @@
*/
val originalPageOffsets: IntArray,
- /**
- * Data to present (post-transformation)
- */
+ /** Data to present (post-transformation) */
val data: List<T>,
/**
@@ -75,17 +73,20 @@
presentedItemsAfter: Int,
originalPageOffsetFirst: Int,
originalPageOffsetLast: Int
- ) = ViewportHint.Access(
- pageOffset = hintOriginalPageOffset,
- indexInPage = when {
- hintOriginalIndices?.indices?.contains(index) == true -> hintOriginalIndices[index]
- else -> index
- },
- presentedItemsBefore = presentedItemsBefore,
- presentedItemsAfter = presentedItemsAfter,
- originalPageOffsetFirst = originalPageOffsetFirst,
- originalPageOffsetLast = originalPageOffsetLast
- )
+ ) =
+ ViewportHint.Access(
+ pageOffset = hintOriginalPageOffset,
+ indexInPage =
+ when {
+ hintOriginalIndices?.indices?.contains(index) == true ->
+ hintOriginalIndices[index]
+ else -> index
+ },
+ presentedItemsBefore = presentedItemsBefore,
+ presentedItemsAfter = presentedItemsAfter,
+ originalPageOffsetFirst = originalPageOffsetFirst,
+ originalPageOffsetLast = originalPageOffsetLast
+ )
// Do not edit. Implementation generated by Studio, since data class uses referential equality
// for IntArray.
@@ -116,6 +117,7 @@
companion object {
@Suppress("UNCHECKED_CAST")
fun <T : Any> empty() = EMPTY_INITIAL_PAGE as TransformablePage<T>
+
val EMPTY_INITIAL_PAGE: TransformablePage<Any> = TransformablePage(0, emptyList())
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/UiReceiver.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/UiReceiver.kt
index 3175ced..82174fc 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/UiReceiver.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/UiReceiver.kt
@@ -21,5 +21,6 @@
*/
internal interface UiReceiver {
fun retry()
+
fun refresh()
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ViewportHint.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ViewportHint.kt
index faf472c..43b34ac 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/ViewportHint.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/ViewportHint.kt
@@ -18,31 +18,24 @@
import androidx.paging.PagingSource.LoadResult.Page
-/**
- * Load access information blob, containing information from presenter.
- */
+/** Load access information blob, containing information from presenter. */
internal sealed class ViewportHint(
/**
- * Number of loaded items presented before this hint. Calculated as the distance from this
- * hint to first loaded item being presented: `anchorPosition - firstLoadedItemPosition`
+ * Number of loaded items presented before this hint. Calculated as the distance from this hint
+ * to first loaded item being presented: `anchorPosition - firstLoadedItemPosition`
*
- * Zero indicates access at boundary
- * Positive -> Within loaded range or in placeholders after presented items if greater
- * than the size of all presented items.
- * Negative -> placeholder access before first loaded item.
- *
+ * Zero indicates access at boundary Positive -> Within loaded range or in placeholders after
+ * presented items if greater than the size of all presented items. Negative -> placeholder
+ * access before first loaded item.
*/
val presentedItemsBefore: Int,
/**
- * Number of loaded items presented after this hint. Calculated as the distance from this
- * hint to last loaded item being presented:
- * `presenterSize - anchorPosition - placeholdersAfter - 1`
+ * Number of loaded items presented after this hint. Calculated as the distance from this hint
+ * to last loaded item being presented: `presenterSize - anchorPosition - placeholdersAfter - 1`
*
- * Zero indicates access at boundary
- * Positive -> Within loaded range or in placeholders before presented items if greater
- * than the size of all presented items.
- * Negative -> placeholder access after last loaded item.
- *
+ * Zero indicates access at boundary Positive -> Within loaded range or in placeholders before
+ * presented items if greater than the size of all presented items. Negative -> placeholder
+ * access after last loaded item.
*/
val presentedItemsAfter: Int,
/**
@@ -68,45 +61,49 @@
/**
* @return Count of presented items between this hint, and either:
- * * the beginning of the list if [loadType] == PREPEND
- * * the end of the list if loadType == APPEND
+ * * the beginning of the list if [loadType] == PREPEND
+ * * the end of the list if loadType == APPEND
*/
- internal fun presentedItemsBeyondAnchor(loadType: LoadType): Int = when (loadType) {
- LoadType.REFRESH -> throw IllegalArgumentException(
- "Cannot get presentedItems for loadType: REFRESH"
- )
- LoadType.PREPEND -> presentedItemsBefore
- LoadType.APPEND -> presentedItemsAfter
- }
+ internal fun presentedItemsBeyondAnchor(loadType: LoadType): Int =
+ when (loadType) {
+ LoadType.REFRESH ->
+ throw IllegalArgumentException("Cannot get presentedItems for loadType: REFRESH")
+ LoadType.PREPEND -> presentedItemsBefore
+ LoadType.APPEND -> presentedItemsAfter
+ }
override fun hashCode(): Int {
- return presentedItemsBefore.hashCode() + presentedItemsAfter.hashCode() +
- originalPageOffsetFirst.hashCode() + originalPageOffsetLast.hashCode()
+ return presentedItemsBefore.hashCode() +
+ presentedItemsAfter.hashCode() +
+ originalPageOffsetFirst.hashCode() +
+ originalPageOffsetLast.hashCode()
}
/**
* [ViewportHint] reporting presenter state after receiving initial page. An [Initial] hint
- * should never take precedence over an [Access] hint and is only used to inform
- * [PageFetcher] how many items from the initial page load were presented by [PagingDataPresenter]
+ * should never take precedence over an [Access] hint and is only used to inform [PageFetcher]
+ * how many items from the initial page load were presented by [PagingDataPresenter]
*/
class Initial(
presentedItemsBefore: Int,
presentedItemsAfter: Int,
originalPageOffsetFirst: Int,
originalPageOffsetLast: Int
- ) : ViewportHint(
- presentedItemsBefore = presentedItemsBefore,
- presentedItemsAfter = presentedItemsAfter,
- originalPageOffsetFirst = originalPageOffsetFirst,
- originalPageOffsetLast = originalPageOffsetLast,
- ) {
+ ) :
+ ViewportHint(
+ presentedItemsBefore = presentedItemsBefore,
+ presentedItemsAfter = presentedItemsAfter,
+ originalPageOffsetFirst = originalPageOffsetFirst,
+ originalPageOffsetLast = originalPageOffsetLast,
+ ) {
override fun toString(): String {
return """ViewportHint.Initial(
| presentedItemsBefore=$presentedItemsBefore,
| presentedItemsAfter=$presentedItemsAfter,
| originalPageOffsetFirst=$originalPageOffsetFirst,
| originalPageOffsetLast=$originalPageOffsetLast,
- |)""".trimMargin()
+ |)"""
+ .trimMargin()
}
}
@@ -115,30 +112,29 @@
* prefetch distance.
*/
class Access(
- /**
- * Page index offset from initial load
- */
+ /** Page index offset from initial load */
val pageOffset: Int,
/**
* Original index of item in the [Page] with [pageOffset].
*
* Three cases to consider:
- * - [indexInPage] in Page.data.indices -> Hint references original item directly
- * - [indexInPage] > Page.data.indices -> Hint references a placeholder after the last
- * presented item.
- * - [indexInPage] < 0 -> Hint references a placeholder before the first presented item.
+ * - [indexInPage] in Page.data.indices -> Hint references original item directly
+ * - [indexInPage] > Page.data.indices -> Hint references a placeholder after the last
+ * presented item.
+ * - [indexInPage] < 0 -> Hint references a placeholder before the first presented item.
*/
val indexInPage: Int,
presentedItemsBefore: Int,
presentedItemsAfter: Int,
originalPageOffsetFirst: Int,
originalPageOffsetLast: Int
- ) : ViewportHint(
- presentedItemsBefore = presentedItemsBefore,
- presentedItemsAfter = presentedItemsAfter,
- originalPageOffsetFirst = originalPageOffsetFirst,
- originalPageOffsetLast = originalPageOffsetLast,
- ) {
+ ) :
+ ViewportHint(
+ presentedItemsBefore = presentedItemsBefore,
+ presentedItemsAfter = presentedItemsAfter,
+ originalPageOffsetFirst = originalPageOffsetFirst,
+ originalPageOffsetLast = originalPageOffsetLast,
+ ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Access) return false
@@ -163,7 +159,8 @@
| presentedItemsAfter=$presentedItemsAfter,
| originalPageOffsetFirst=$originalPageOffsetFirst,
| originalPageOffsetLast=$originalPageOffsetLast,
- |)""".trimMargin()
+ |)"""
+ .trimMargin()
}
}
}
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/internal/Atomics.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/internal/Atomics.kt
index f4e790b..a1cc02b 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/internal/Atomics.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/internal/Atomics.kt
@@ -22,12 +22,15 @@
internal expect class CopyOnWriteArrayList<T>() : Iterable<T> {
fun add(value: T): Boolean
+
fun remove(value: T): Boolean
+
override fun iterator(): Iterator<T>
}
internal expect class ReentrantLock constructor() {
fun lock()
+
fun unlock()
}
@@ -35,8 +38,11 @@
constructor(initialValue: Int)
fun getAndIncrement(): Int
+
fun incrementAndGet(): Int
+
fun decrementAndGet(): Int
+
fun get(): Int
}
@@ -44,7 +50,9 @@
constructor(initialValue: Boolean)
fun get(): Boolean
+
fun set(value: Boolean)
+
fun compareAndSet(expect: Boolean, update: Boolean): Boolean
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachedPageEventFlowTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachedPageEventFlowTest.kt
index 40550f8..b118031 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachedPageEventFlowTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachedPageEventFlowTest.kt
@@ -38,316 +38,234 @@
class CachedPageEventFlowTest {
private val testScope = TestScope(UnconfinedTestDispatcher())
- @Test
- fun slowFastCollectors_CloseUpstream() = slowFastCollectors(TerminationType.CloseUpstream)
+ @Test fun slowFastCollectors_CloseUpstream() = slowFastCollectors(TerminationType.CloseUpstream)
@Test
fun slowFastCollectors_CloseCachedEventFlow() =
slowFastCollectors(TerminationType.CloseCachedEventFlow)
- private fun slowFastCollectors(terminationType: TerminationType) = testScope.runTest {
- val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
- val subject = CachedPageEventFlow(
- src = upstream.consumeAsFlow(),
- scope = testScope
- )
- val fastCollector = PageCollector(subject.downstreamFlow)
- fastCollector.collectIn(testScope)
- val slowCollector = PageCollector(
- subject.downstreamFlow.onEach {
- delay(1_000)
- }
- )
- slowCollector.collectIn(testScope)
- val refreshEvent = localRefresh(
- listOf(
- TransformablePage(
- listOf("a", "b", "c")
+ private fun slowFastCollectors(terminationType: TerminationType) =
+ testScope.runTest {
+ val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
+ val subject = CachedPageEventFlow(src = upstream.consumeAsFlow(), scope = testScope)
+ val fastCollector = PageCollector(subject.downstreamFlow)
+ fastCollector.collectIn(testScope)
+ val slowCollector = PageCollector(subject.downstreamFlow.onEach { delay(1_000) })
+ slowCollector.collectIn(testScope)
+ val refreshEvent =
+ localRefresh(
+ listOf(TransformablePage(listOf("a", "b", "c"))),
)
- ),
- )
- upstream.send(refreshEvent)
- runCurrent()
- assertThat(fastCollector.items()).containsExactly(
- refreshEvent
- )
- assertThat(slowCollector.items()).isEmpty()
+ upstream.send(refreshEvent)
+ runCurrent()
+ assertThat(fastCollector.items()).containsExactly(refreshEvent)
+ assertThat(slowCollector.items()).isEmpty()
- val appendEvent = localAppend(
- listOf(
- TransformablePage(
- listOf("d", "e")
+ val appendEvent =
+ localAppend(
+ listOf(TransformablePage(listOf("d", "e"))),
)
- ),
- )
- upstream.send(appendEvent)
- runCurrent()
- assertThat(fastCollector.items()).containsExactly(
- refreshEvent,
- appendEvent
- )
- assertThat(slowCollector.items()).isEmpty()
- advanceTimeBy(3_000)
- assertThat(slowCollector.items()).containsExactly(
- refreshEvent,
- appendEvent
- )
- val manyNewAppendEvents = (0 until 100).map {
- localAppend(
- listOf(
- TransformablePage(
- listOf("f", "g")
+ upstream.send(appendEvent)
+ runCurrent()
+ assertThat(fastCollector.items()).containsExactly(refreshEvent, appendEvent)
+ assertThat(slowCollector.items()).isEmpty()
+ advanceTimeBy(3_000)
+ assertThat(slowCollector.items()).containsExactly(refreshEvent, appendEvent)
+ val manyNewAppendEvents =
+ (0 until 100).map {
+ localAppend(
+ listOf(TransformablePage(listOf("f", "g"))),
)
- ),
- )
- }
- manyNewAppendEvents.forEach {
- upstream.send(it)
- }
- val lateSlowCollector = PageCollector(subject.downstreamFlow.onEach { delay(1_000) })
- lateSlowCollector.collectIn(testScope)
- val finalAppendEvent = localAppend(
- listOf(
- TransformablePage(
- listOf("d", "e")
+ }
+ manyNewAppendEvents.forEach { upstream.send(it) }
+ val lateSlowCollector = PageCollector(subject.downstreamFlow.onEach { delay(1_000) })
+ lateSlowCollector.collectIn(testScope)
+ val finalAppendEvent =
+ localAppend(
+ listOf(TransformablePage(listOf("d", "e"))),
)
- ),
- )
- upstream.send(finalAppendEvent)
- when (terminationType) {
- TerminationType.CloseUpstream -> upstream.close()
- TerminationType.CloseCachedEventFlow -> subject.close()
+ upstream.send(finalAppendEvent)
+ when (terminationType) {
+ TerminationType.CloseUpstream -> upstream.close()
+ TerminationType.CloseCachedEventFlow -> subject.close()
+ }
+ val fullList =
+ listOf(refreshEvent, appendEvent) + manyNewAppendEvents + finalAppendEvent
+ runCurrent()
+ assertThat(fastCollector.items()).containsExactlyElementsIn(fullList).inOrder()
+ assertThat(fastCollector.isActive()).isFalse()
+ assertThat(slowCollector.isActive()).isTrue()
+ assertThat(lateSlowCollector.isActive()).isTrue()
+ advanceUntilIdle()
+ assertThat(slowCollector.items()).containsExactlyElementsIn(fullList).inOrder()
+ assertThat(slowCollector.isActive()).isFalse()
+
+ val lateCollectorState =
+ localRefresh(
+ pages =
+ (listOf(refreshEvent, appendEvent) + manyNewAppendEvents).flatMap {
+ it.pages
+ },
+ )
+ assertThat(lateSlowCollector.items())
+ .containsExactly(lateCollectorState, finalAppendEvent)
+ .inOrder()
+ assertThat(lateSlowCollector.isActive()).isFalse()
+
+ upstream.close()
}
- val fullList = listOf(
- refreshEvent,
- appendEvent
- ) + manyNewAppendEvents + finalAppendEvent
- runCurrent()
- assertThat(fastCollector.items()).containsExactlyElementsIn(fullList).inOrder()
- assertThat(fastCollector.isActive()).isFalse()
- assertThat(slowCollector.isActive()).isTrue()
- assertThat(lateSlowCollector.isActive()).isTrue()
- advanceUntilIdle()
- assertThat(slowCollector.items()).containsExactlyElementsIn(fullList).inOrder()
- assertThat(slowCollector.isActive()).isFalse()
- val lateCollectorState = localRefresh(
- pages = (listOf(refreshEvent, appendEvent) + manyNewAppendEvents).flatMap {
- it.pages
- },
- )
- assertThat(lateSlowCollector.items()).containsExactly(
- lateCollectorState, finalAppendEvent
- ).inOrder()
- assertThat(lateSlowCollector.isActive()).isFalse()
-
- upstream.close()
- }
+ @Test fun ensureSharing_CloseUpstream() = ensureSharing(TerminationType.CloseUpstream)
@Test
- fun ensureSharing_CloseUpstream() = ensureSharing(TerminationType.CloseUpstream)
+ fun ensureSharing_CloseCachedEventFlow() = ensureSharing(TerminationType.CloseCachedEventFlow)
- @Test
- fun ensureSharing_CloseCachedEventFlow() =
- ensureSharing(TerminationType.CloseCachedEventFlow)
+ private fun ensureSharing(terminationType: TerminationType) =
+ testScope.runTest {
+ val refreshEvent =
+ localRefresh(
+ listOf(TransformablePage(listOf("a", "b", "c"))),
+ )
+ val appendEvent =
+ localAppend(
+ listOf(TransformablePage(listOf("d", "e"))),
+ )
+ val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
+ val subject = CachedPageEventFlow(src = upstream.consumeAsFlow(), scope = testScope)
- private fun ensureSharing(terminationType: TerminationType) = testScope.runTest {
- val refreshEvent = localRefresh(
- listOf(
- TransformablePage(
- listOf("a", "b", "c")
+ val collector1 = PageCollector(subject.downstreamFlow)
+ upstream.send(refreshEvent)
+ upstream.send(appendEvent)
+ collector1.collectIn(testScope)
+ runCurrent()
+ assertThat(collector1.items()).isEqualTo(listOf(refreshEvent, appendEvent))
+ val collector2 = PageCollector(subject.downstreamFlow)
+ collector2.collectIn(testScope)
+ runCurrent()
+ val firstSnapshotRefreshEvent =
+ localRefresh(
+ listOf(
+ TransformablePage(listOf("a", "b", "c")),
+ TransformablePage(listOf("d", "e"))
+ ),
)
- ),
- )
- val appendEvent = localAppend(
- listOf(
- TransformablePage(
- listOf("d", "e")
+ assertThat(collector2.items()).containsExactly(firstSnapshotRefreshEvent)
+ val prependEvent =
+ localPrepend(
+ listOf(
+ TransformablePage(listOf("a0", "a1")),
+ TransformablePage(listOf("a2", "a3"))
+ ),
)
- ),
- )
- val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
- val subject = CachedPageEventFlow(
- src = upstream.consumeAsFlow(),
- scope = testScope
- )
-
- val collector1 = PageCollector(subject.downstreamFlow)
- upstream.send(refreshEvent)
- upstream.send(appendEvent)
- collector1.collectIn(testScope)
- runCurrent()
- assertThat(collector1.items()).isEqualTo(
- listOf(refreshEvent, appendEvent)
- )
- val collector2 = PageCollector(subject.downstreamFlow)
- collector2.collectIn(testScope)
- runCurrent()
- val firstSnapshotRefreshEvent = localRefresh(
- listOf(
- TransformablePage(
- listOf("a", "b", "c")
- ),
- TransformablePage(
- listOf("d", "e")
+ upstream.send(prependEvent)
+ assertThat(collector1.items())
+ .isEqualTo(listOf(refreshEvent, appendEvent, prependEvent))
+ assertThat(collector2.items())
+ .isEqualTo(listOf(firstSnapshotRefreshEvent, prependEvent))
+ val collector3 = PageCollector(subject.downstreamFlow)
+ collector3.collectIn(testScope)
+ val finalState =
+ localRefresh(
+ listOf(
+ TransformablePage(listOf("a0", "a1")),
+ TransformablePage(listOf("a2", "a3")),
+ TransformablePage(listOf("a", "b", "c")),
+ TransformablePage(listOf("d", "e"))
+ ),
)
- ),
- )
- assertThat(collector2.items()).containsExactly(firstSnapshotRefreshEvent)
- val prependEvent = localPrepend(
- listOf(
- TransformablePage(
- listOf("a0", "a1")
- ),
- TransformablePage(
- listOf("a2", "a3")
- )
- ),
- )
- upstream.send(prependEvent)
- assertThat(collector1.items()).isEqualTo(
- listOf(refreshEvent, appendEvent, prependEvent)
- )
- assertThat(collector2.items()).isEqualTo(
- listOf(firstSnapshotRefreshEvent, prependEvent)
- )
- val collector3 = PageCollector(subject.downstreamFlow)
- collector3.collectIn(testScope)
- val finalState = localRefresh(
- listOf(
- TransformablePage(
- listOf("a0", "a1")
- ),
- TransformablePage(
- listOf("a2", "a3")
- ),
- TransformablePage(
- listOf("a", "b", "c")
- ),
- TransformablePage(
- listOf("d", "e")
- )
- ),
- )
- assertThat(collector3.items()).containsExactly(
- finalState
- )
- assertThat(collector1.isActive()).isTrue()
- assertThat(collector2.isActive()).isTrue()
- assertThat(collector3.isActive()).isTrue()
- when (terminationType) {
- TerminationType.CloseUpstream -> upstream.close()
- TerminationType.CloseCachedEventFlow -> subject.close()
+ assertThat(collector3.items()).containsExactly(finalState)
+ assertThat(collector1.isActive()).isTrue()
+ assertThat(collector2.isActive()).isTrue()
+ assertThat(collector3.isActive()).isTrue()
+ when (terminationType) {
+ TerminationType.CloseUpstream -> upstream.close()
+ TerminationType.CloseCachedEventFlow -> subject.close()
+ }
+ runCurrent()
+ assertThat(collector1.isActive()).isFalse()
+ assertThat(collector2.isActive()).isFalse()
+ assertThat(collector3.isActive()).isFalse()
+ val collector4 = PageCollector(subject.downstreamFlow).also { it.collectIn(testScope) }
+ runCurrent()
+ // since upstream is closed, this should just close
+ assertThat(collector4.isActive()).isFalse()
+ assertThat(collector4.items()).containsExactly(finalState)
}
- runCurrent()
- assertThat(collector1.isActive()).isFalse()
- assertThat(collector2.isActive()).isFalse()
- assertThat(collector3.isActive()).isFalse()
- val collector4 = PageCollector(subject.downstreamFlow).also {
- it.collectIn(testScope)
+
+ @Test
+ fun emptyPage_singlelocalLoadStateUpdate() =
+ testScope.runTest {
+ val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
+ val subject = CachedPageEventFlow(src = upstream.consumeAsFlow(), scope = testScope)
+
+ // creating two collectors and collecting right away to assert that all collectors
+ val collector = PageCollector(subject.downstreamFlow)
+ collector.collectIn(testScope)
+
+ val collector2 = PageCollector(subject.downstreamFlow)
+ collector2.collectIn(testScope)
+
+ runCurrent()
+
+ // until upstream sends events, collectors shouldn't receive any events
+ assertThat(collector.items()).isEmpty()
+ assertThat(collector2.items()).isEmpty()
+
+ // now send refresh event
+ val refreshEvent =
+ localRefresh(
+ listOf(TransformablePage(listOf("a", "b", "c"))),
+ )
+ upstream.send(refreshEvent)
+ runCurrent()
+
+ assertThat(collector.items()).containsExactly(refreshEvent)
+
+ assertThat(collector2.items()).containsExactly(refreshEvent)
+
+ upstream.close()
}
- runCurrent()
- // since upstream is closed, this should just close
- assertThat(collector4.isActive()).isFalse()
- assertThat(collector4.items()).containsExactly(
- finalState
- )
- }
@Test
- fun emptyPage_singlelocalLoadStateUpdate() = testScope.runTest {
- val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
- val subject = CachedPageEventFlow(
- src = upstream.consumeAsFlow(),
- scope = testScope
- )
+ fun idleStateUpdate_collectedBySingleCollector() =
+ testScope.runTest {
+ val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
+ val subject = CachedPageEventFlow(src = upstream.consumeAsFlow(), scope = testScope)
- // creating two collectors and collecting right away to assert that all collectors
- val collector = PageCollector(subject.downstreamFlow)
- collector.collectIn(testScope)
-
- val collector2 = PageCollector(subject.downstreamFlow)
- collector2.collectIn(testScope)
-
- runCurrent()
-
- // until upstream sends events, collectors shouldn't receive any events
- assertThat(collector.items()).isEmpty()
- assertThat(collector2.items()).isEmpty()
-
- // now send refresh event
- val refreshEvent = localRefresh(
- listOf(
- TransformablePage(
- listOf("a", "b", "c")
+ val refreshEvent =
+ localRefresh(
+ listOf(TransformablePage(listOf("a", "b", "c"))),
)
- ),
- )
- upstream.send(refreshEvent)
- runCurrent()
+ upstream.send(refreshEvent)
+ runCurrent()
- assertThat(collector.items()).containsExactly(
- refreshEvent
- )
+ val collector = PageCollector(subject.downstreamFlow)
+ collector.collectIn(testScope)
- assertThat(collector2.items()).containsExactly(
- refreshEvent
- )
+ runCurrent()
- upstream.close()
- }
+ // collector shouldn't receive any idle events before the refresh
+ assertThat(collector.items()).containsExactly(refreshEvent)
- @Test
- fun idleStateUpdate_collectedBySingleCollector() = testScope.runTest {
- val upstream = Channel<PageEvent<String>>(Channel.UNLIMITED)
- val subject = CachedPageEventFlow(
- src = upstream.consumeAsFlow(),
- scope = testScope
- )
+ val delayedCollector = PageCollector(subject.downstreamFlow)
+ delayedCollector.collectIn(testScope)
- val refreshEvent = localRefresh(
- listOf(
- TransformablePage(
- listOf("a", "b", "c")
- )
- ),
- )
- upstream.send(refreshEvent)
- runCurrent()
+ // delayed collector shouldn't receive any idle events since we already have refresh
+ assertThat(delayedCollector.items()).containsExactly(refreshEvent)
- val collector = PageCollector(subject.downstreamFlow)
- collector.collectIn(testScope)
-
- runCurrent()
-
- // collector shouldn't receive any idle events before the refresh
- assertThat(collector.items()).containsExactly(
- refreshEvent
- )
-
- val delayedCollector = PageCollector(subject.downstreamFlow)
- delayedCollector.collectIn(testScope)
-
- // delayed collector shouldn't receive any idle events since we already have refresh
- assertThat(delayedCollector.items()).containsExactly(
- refreshEvent
- )
-
- upstream.close()
- }
+ upstream.close()
+ }
private class PageCollector<T : Any>(val src: Flow<T>) {
private val items = mutableListOf<T>()
private var job: Job? = null
+
fun collectIn(scope: CoroutineScope) {
- job = scope.launch {
- src.collect {
- items.add(it)
- }
- }
+ job = scope.launch { src.collect { items.add(it) } }
}
fun isActive() = job?.isActive ?: false
+
fun items() = items.toList()
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachingTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachingTest.kt
index e7112cb..ffdd971 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachingTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/CachingTest.kt
@@ -50,570 +50,439 @@
private val testScope = TestScope(UnconfinedTestDispatcher())
@Test
- fun noSharing() = testScope.runTest {
- val pageFlow = buildPageFlow()
- val firstCollect = pageFlow.collectItemsUntilSize(6)
- val secondCollect = pageFlow.collectItemsUntilSize(9)
- assertThat(firstCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- )
- )
+ fun noSharing() =
+ testScope.runTest {
+ val pageFlow = buildPageFlow()
+ val firstCollect = pageFlow.collectItemsUntilSize(6)
+ val secondCollect = pageFlow.collectItemsUntilSize(9)
+ assertThat(firstCollect)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 6))
- assertThat(secondCollect).isEqualTo(
- buildItems(
- version = 1,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
- }
-
- @Test
- fun cached() = testScope.runTest {
- val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
- val firstCollect = pageFlow.collectItemsUntilSize(6)
- val secondCollect = pageFlow.collectItemsUntilSize(9)
- assertThat(firstCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- )
- )
-
- assertThat(secondCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
-
- @Test
- fun cachedData() = testScope.runTest {
- val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
- assertThat(pageFlow).isInstanceOf<SharedFlow<PagingData<Item>>>()
- assertThat((pageFlow as SharedFlow<PagingData<Item>>).replayCache).isEmpty()
-
- pageFlow.collectItemsUntilSize(6)
- val firstCachedData = pageFlow.cachedData()
- assertThat(firstCachedData).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- )
- )
-
- pageFlow.collectItemsUntilSize(9)
- val secondCachedEvent = pageFlow.cachedData()
- assertThat(secondCachedEvent).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
-
- @Test
- fun cached_afterMapping() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
- }.cachedIn(backgroundScope, tracker)
- val firstCollect = pageFlow.collectItemsUntilSize(6)
- val secondCollect = pageFlow.collectItemsUntilSize(9)
- assertThat(firstCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- ) {
- it.copy(metadata = "0")
- }
- )
-
- assertThat(secondCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- ) {
- it.copy(metadata = "0")
- }
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
-
- @Test
- fun cachedData_afterMapping() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
- }.cachedIn(backgroundScope, tracker)
-
- pageFlow.collectItemsUntilSize(6)
- val firstCachedData = pageFlow.cachedData()
- assertThat(firstCachedData).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- ) {
- it.copy(metadata = "0")
- }
- )
-
- pageFlow.collectItemsUntilSize(9)
- val secondCachedData = pageFlow.cachedData()
- assertThat(secondCachedData).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- ) {
- it.copy(metadata = "0")
- }
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
-
- @Test
- fun cached_beforeMapping() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker).map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
+ assertThat(secondCollect)
+ .isEqualTo(buildItems(version = 1, generation = 0, start = 0, size = 9))
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
}
- val firstCollect = pageFlow.collectItemsUntilSize(6)
- val secondCollect = pageFlow.collectItemsUntilSize(9)
- assertThat(firstCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- ) {
- it.copy(metadata = "0")
- }
- )
-
- assertThat(secondCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- ) {
- it.copy(metadata = "1")
- }
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
@Test
- fun cachedData_beforeMapping() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
- val mappedFlow = pageFlow.map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
+ fun cached() =
+ testScope.runTest {
+ val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
+ val firstCollect = pageFlow.collectItemsUntilSize(6)
+ val secondCollect = pageFlow.collectItemsUntilSize(9)
+ assertThat(firstCollect)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 6))
+
+ assertThat(secondCollect)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 9))
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
}
- // Mapping converts SharedFlow to Flow and thereby blocks access to cachedIn's
- // replayCache. You can still access latest cachedData directly from pre-mapped flow.
- mappedFlow.collectItemsUntilSize(6)
- val firstCachedData = pageFlow.cachedData()
- assertThat(firstCachedData).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6,
- modifier = null // before mapping
- )
- )
-
- mappedFlow.collectItemsUntilSize(9)
- val secondCachedEvent = pageFlow.cachedData()
- assertThat(secondCachedEvent).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9,
- modifier = null // before mapping
- )
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
@Test
- fun cached_afterMapping_withMoreMappingAfterwards() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
- }.cachedIn(backgroundScope, tracker).map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = "${it.metadata}_$mappingIndex")
- }
+ fun cachedData() =
+ testScope.runTest {
+ val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
+ assertThat(pageFlow).isInstanceOf<SharedFlow<PagingData<Item>>>()
+ assertThat((pageFlow as SharedFlow<PagingData<Item>>).replayCache).isEmpty()
+
+ pageFlow.collectItemsUntilSize(6)
+ val firstCachedData = pageFlow.cachedData()
+ assertThat(firstCachedData)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 6))
+
+ pageFlow.collectItemsUntilSize(9)
+ val secondCachedEvent = pageFlow.cachedData()
+ assertThat(secondCachedEvent)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 9))
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
}
- val firstCollect = pageFlow.collectItemsUntilSize(6)
- val secondCollect = pageFlow.collectItemsUntilSize(9)
- assertThat(firstCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- ) {
- it.copy(metadata = "0_1")
- }
- )
-
- assertThat(secondCollect).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- ) {
- it.copy(metadata = "0_2")
- }
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
@Test
- fun cachedData_afterMapping_withMoreMappingAfterwards() = testScope.runTest {
- var mappingCnt = 0
- val pageFlow = buildPageFlow().map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = mappingIndex.toString())
- }
- }.cachedIn(backgroundScope, tracker)
- val mappedFlow = pageFlow.map { pagingData ->
- val mappingIndex = mappingCnt++
- pagingData.map {
- it.copy(metadata = "${it.metadata}_$mappingIndex")
- }
+ fun cached_afterMapping() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow =
+ buildPageFlow()
+ .map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ .cachedIn(backgroundScope, tracker)
+ val firstCollect = pageFlow.collectItemsUntilSize(6)
+ val secondCollect = pageFlow.collectItemsUntilSize(9)
+ assertThat(firstCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 6) {
+ it.copy(metadata = "0")
+ }
+ )
+
+ assertThat(secondCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 9) {
+ it.copy(metadata = "0")
+ }
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
}
- // Mapping converts SharedFlow to Flow and thereby blocks access to cachedIn's
- // replayCache. You can still access latest cachedData directly from pre-mapped flow.
- mappedFlow.collectItemsUntilSize(6)
- val firstCachedData = pageFlow.cachedData()
- assertThat(firstCachedData).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 6
- ) {
- it.copy(metadata = "0") // with mapping before cache
- }
- )
-
- mappedFlow.collectItemsUntilSize(9)
- val secondCachedEvent = pageFlow.cachedData()
- assertThat(secondCachedEvent).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- ) {
- it.copy(metadata = "0") // with mapping before cache
- }
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- }
@Test
- fun pagesAreClosedProperty() = testScope.runTest {
- val job = SupervisorJob()
- val subScope = CoroutineScope(job + Dispatchers.Default)
- val pageFlow = buildPageFlow().cachedIn(subScope, tracker)
- assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
- assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
- val items = pageFlow.collectItemsUntilSize(9)
- val firstList = buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- )
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- val items2 = pageFlow.collectItemsUntilSize(21)
- assertThat(items2).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 21
- )
- )
- assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
- assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
- assertThat(items).isEqualTo(firstList)
- job.cancelAndJoin()
- assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
- assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
- }
+ fun cachedData_afterMapping() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow =
+ buildPageFlow()
+ .map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ .cachedIn(backgroundScope, tracker)
+
+ pageFlow.collectItemsUntilSize(6)
+ val firstCachedData = pageFlow.cachedData()
+ assertThat(firstCachedData)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 6) {
+ it.copy(metadata = "0")
+ }
+ )
+
+ pageFlow.collectItemsUntilSize(9)
+ val secondCachedData = pageFlow.cachedData()
+ assertThat(secondCachedData)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 9) {
+ it.copy(metadata = "0")
+ }
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ }
@Test
- fun cachedWithPassiveCollector() = testScope.runTest {
- val flow = buildPageFlow().cachedIn(backgroundScope, tracker)
- val passive = ItemCollector(flow)
- passive.collectPassivelyIn(backgroundScope)
- testScope.runCurrent()
- // collecting on the paged source will trigger initial page
- assertThat(passive.items()).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 3
- )
- )
- val firstList = buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 9
- )
- // another collector is causing more items to be loaded, they should be reflected in the
- // passive one
- assertThat(flow.collectItemsUntilSize(9)).isEqualTo(firstList)
- assertThat(passive.items()).isEqualTo(firstList)
- val passive2 = ItemCollector(flow)
- passive2.collectPassivelyIn(backgroundScope)
- testScope.runCurrent()
- // a new passive one should receive all existing items immediately
- assertThat(passive2.items()).isEqualTo(firstList)
+ fun cached_beforeMapping() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow =
+ buildPageFlow().cachedIn(backgroundScope, tracker).map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ val firstCollect = pageFlow.collectItemsUntilSize(6)
+ val secondCollect = pageFlow.collectItemsUntilSize(9)
+ assertThat(firstCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 6) {
+ it.copy(metadata = "0")
+ }
+ )
- // now we get another collector that'll fetch more pages, it should reflect in passives
- val secondList = buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 12
- )
- // another collector is causing more items to be loaded, they should be reflected in the
- // passive one
- assertThat(flow.collectItemsUntilSize(12)).isEqualTo(secondList)
- assertThat(passive.items()).isEqualTo(secondList)
- assertThat(passive2.items()).isEqualTo(secondList)
- }
+ assertThat(secondCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 9) {
+ it.copy(metadata = "1")
+ }
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ }
+
+ @Test
+ fun cachedData_beforeMapping() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
+ val mappedFlow =
+ pageFlow.map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ // Mapping converts SharedFlow to Flow and thereby blocks access to cachedIn's
+ // replayCache. You can still access latest cachedData directly from pre-mapped flow.
+ mappedFlow.collectItemsUntilSize(6)
+ val firstCachedData = pageFlow.cachedData()
+ assertThat(firstCachedData)
+ .isEqualTo(
+ buildItems(
+ version = 0,
+ generation = 0,
+ start = 0,
+ size = 6,
+ modifier = null // before mapping
+ )
+ )
+
+ mappedFlow.collectItemsUntilSize(9)
+ val secondCachedEvent = pageFlow.cachedData()
+ assertThat(secondCachedEvent)
+ .isEqualTo(
+ buildItems(
+ version = 0,
+ generation = 0,
+ start = 0,
+ size = 9,
+ modifier = null // before mapping
+ )
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ }
+
+ @Test
+ fun cached_afterMapping_withMoreMappingAfterwards() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow =
+ buildPageFlow()
+ .map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ .cachedIn(backgroundScope, tracker)
+ .map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = "${it.metadata}_$mappingIndex") }
+ }
+ val firstCollect = pageFlow.collectItemsUntilSize(6)
+ val secondCollect = pageFlow.collectItemsUntilSize(9)
+ assertThat(firstCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 6) {
+ it.copy(metadata = "0_1")
+ }
+ )
+
+ assertThat(secondCollect)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 9) {
+ it.copy(metadata = "0_2")
+ }
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ }
+
+ @Test
+ fun cachedData_afterMapping_withMoreMappingAfterwards() =
+ testScope.runTest {
+ var mappingCnt = 0
+ val pageFlow =
+ buildPageFlow()
+ .map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = mappingIndex.toString()) }
+ }
+ .cachedIn(backgroundScope, tracker)
+ val mappedFlow =
+ pageFlow.map { pagingData ->
+ val mappingIndex = mappingCnt++
+ pagingData.map { it.copy(metadata = "${it.metadata}_$mappingIndex") }
+ }
+ // Mapping converts SharedFlow to Flow and thereby blocks access to cachedIn's
+ // replayCache. You can still access latest cachedData directly from pre-mapped flow.
+ mappedFlow.collectItemsUntilSize(6)
+ val firstCachedData = pageFlow.cachedData()
+ assertThat(firstCachedData)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 6) {
+ it.copy(metadata = "0") // with mapping before cache
+ }
+ )
+
+ mappedFlow.collectItemsUntilSize(9)
+ val secondCachedEvent = pageFlow.cachedData()
+ assertThat(secondCachedEvent)
+ .isEqualTo(
+ buildItems(version = 0, generation = 0, start = 0, size = 9) {
+ it.copy(metadata = "0") // with mapping before cache
+ }
+ )
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ }
+
+ @Test
+ fun pagesAreClosedProperty() =
+ testScope.runTest {
+ val job = SupervisorJob()
+ val subScope = CoroutineScope(job + Dispatchers.Default)
+ val pageFlow = buildPageFlow().cachedIn(subScope, tracker)
+ assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
+ val items = pageFlow.collectItemsUntilSize(9)
+ val firstList = buildItems(version = 0, generation = 0, start = 0, size = 9)
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ val items2 = pageFlow.collectItemsUntilSize(21)
+ assertThat(items2)
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 21))
+ assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(1)
+ assertThat(items).isEqualTo(firstList)
+ job.cancelAndJoin()
+ assertThat(tracker.pageEventFlowCount()).isEqualTo(0)
+ assertThat(tracker.pageDataFlowCount()).isEqualTo(0)
+ }
+
+ @Test
+ fun cachedWithPassiveCollector() =
+ testScope.runTest {
+ val flow = buildPageFlow().cachedIn(backgroundScope, tracker)
+ val passive = ItemCollector(flow)
+ passive.collectPassivelyIn(backgroundScope)
+ testScope.runCurrent()
+ // collecting on the paged source will trigger initial page
+ assertThat(passive.items())
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 3))
+ val firstList = buildItems(version = 0, generation = 0, start = 0, size = 9)
+ // another collector is causing more items to be loaded, they should be reflected in the
+ // passive one
+ assertThat(flow.collectItemsUntilSize(9)).isEqualTo(firstList)
+ assertThat(passive.items()).isEqualTo(firstList)
+ val passive2 = ItemCollector(flow)
+ passive2.collectPassivelyIn(backgroundScope)
+ testScope.runCurrent()
+ // a new passive one should receive all existing items immediately
+ assertThat(passive2.items()).isEqualTo(firstList)
+
+ // now we get another collector that'll fetch more pages, it should reflect in passives
+ val secondList = buildItems(version = 0, generation = 0, start = 0, size = 12)
+ // another collector is causing more items to be loaded, they should be reflected in the
+ // passive one
+ assertThat(flow.collectItemsUntilSize(12)).isEqualTo(secondList)
+ assertThat(passive.items()).isEqualTo(secondList)
+ assertThat(passive2.items()).isEqualTo(secondList)
+ }
/**
* Test that, when cache is active but there is no active downstream collectors, intermediate
* invalidations create new PagingData BUT a new collector only sees the latest one.
*/
@Test
- public fun unusedPagingDataIsNeverCollectedByNewDownstream() = testScope.runTest {
- val factory = StringPagingSource.VersionedFactory()
- val flow = buildPageFlow(factory).cachedIn(backgroundScope, tracker)
- val collector = ItemCollector(flow)
- val job = SupervisorJob()
- val subScope = CoroutineScope(coroutineContext + job)
- collector.collectPassivelyIn(subScope)
- testScope.runCurrent()
- assertThat(collector.items()).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 3
- )
- )
- // finish that collector
- job.cancelAndJoin()
- assertThat(factory.nextVersion).isEqualTo(1)
- repeat(10) {
- factory.invalidateLatest()
+ public fun unusedPagingDataIsNeverCollectedByNewDownstream() =
+ testScope.runTest {
+ val factory = StringPagingSource.VersionedFactory()
+ val flow = buildPageFlow(factory).cachedIn(backgroundScope, tracker)
+ val collector = ItemCollector(flow)
+ val job = SupervisorJob()
+ val subScope = CoroutineScope(coroutineContext + job)
+ collector.collectPassivelyIn(subScope)
testScope.runCurrent()
- }
- runCurrent()
- // next version is 11, the last paged data we've created has version 10
- assertThat(factory.nextVersion).isEqualTo(11)
+ assertThat(collector.items())
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 3))
+ // finish that collector
+ job.cancelAndJoin()
+ assertThat(factory.nextVersion).isEqualTo(1)
+ repeat(10) {
+ factory.invalidateLatest()
+ testScope.runCurrent()
+ }
+ runCurrent()
+ // next version is 11, the last paged data we've created has version 10
+ assertThat(factory.nextVersion).isEqualTo(11)
- // create another collector from shared, should only receive 1 paging data and that
- // should be the latest because previous PagingData is invalidated
- val collector2 = ItemCollector(flow)
- collector2.collectPassivelyIn(backgroundScope)
- testScope.runCurrent()
- assertThat(collector2.items()).isEqualTo(
- buildItems(
- version = 10,
- generation = 0,
- start = 0,
- size = 3
- )
- )
- assertThat(collector2.receivedPagingDataCount).isEqualTo(1)
- testScope.runCurrent()
- assertThat(factory.nextVersion).isEqualTo(11)
- val activeCollection = flow.collectItemsUntilSize(9)
- assertThat(activeCollection).isEqualTo(
- buildItems(
- version = 10,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- testScope.runCurrent()
- // make sure passive collector received those items as well
- assertThat(collector2.items()).isEqualTo(
- buildItems(
- version = 10,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- }
+ // create another collector from shared, should only receive 1 paging data and that
+ // should be the latest because previous PagingData is invalidated
+ val collector2 = ItemCollector(flow)
+ collector2.collectPassivelyIn(backgroundScope)
+ testScope.runCurrent()
+ assertThat(collector2.items())
+ .isEqualTo(buildItems(version = 10, generation = 0, start = 0, size = 3))
+ assertThat(collector2.receivedPagingDataCount).isEqualTo(1)
+ testScope.runCurrent()
+ assertThat(factory.nextVersion).isEqualTo(11)
+ val activeCollection = flow.collectItemsUntilSize(9)
+ assertThat(activeCollection)
+ .isEqualTo(buildItems(version = 10, generation = 0, start = 0, size = 9))
+ testScope.runCurrent()
+ // make sure passive collector received those items as well
+ assertThat(collector2.items())
+ .isEqualTo(buildItems(version = 10, generation = 0, start = 0, size = 9))
+ }
@Test
- public fun unusedPagingDataIsNeverCached() = testScope.runTest {
- val factory = StringPagingSource.VersionedFactory()
- val flow = buildPageFlow(factory).cachedIn(backgroundScope, tracker)
- val collector = ItemCollector(flow)
- val job = SupervisorJob()
- val subScope = CoroutineScope(coroutineContext + job)
- collector.collectPassivelyIn(subScope)
- testScope.runCurrent()
- // check that cachedData contains data from passive collector
- assertThat(flow.cachedData()).isEqualTo(
- buildItems(
- version = 0,
- generation = 0,
- start = 0,
- size = 3
- )
- )
- // finish that collector
- job.cancelAndJoin()
- assertThat(factory.nextVersion).isEqualTo(1)
- repeat(10) {
- factory.invalidateLatest()
+ public fun unusedPagingDataIsNeverCached() =
+ testScope.runTest {
+ val factory = StringPagingSource.VersionedFactory()
+ val flow = buildPageFlow(factory).cachedIn(backgroundScope, tracker)
+ val collector = ItemCollector(flow)
+ val job = SupervisorJob()
+ val subScope = CoroutineScope(coroutineContext + job)
+ collector.collectPassivelyIn(subScope)
testScope.runCurrent()
+ // check that cachedData contains data from passive collector
+ assertThat(flow.cachedData())
+ .isEqualTo(buildItems(version = 0, generation = 0, start = 0, size = 3))
+ // finish that collector
+ job.cancelAndJoin()
+ assertThat(factory.nextVersion).isEqualTo(1)
+ repeat(10) {
+ factory.invalidateLatest()
+ testScope.runCurrent()
+ }
+ runCurrent()
+ // next version is 11, the last paged data we've created has version 10
+ assertThat(factory.nextVersion).isEqualTo(11)
+
+ // the replayCache has paged data version 10 but no collection on this pagingData yet
+ // so it has no cachedEvent.
+ val cachedPagingData = (flow as SharedFlow<PagingData<Item>>).replayCache.first()
+ assertThat(cachedPagingData.cachedEvent()).isNull()
+
+ // create another collector from shared, should only receive 1 paging data and that
+ // should be the latest because previous PagingData is invalidated
+ val collector2 = ItemCollector(flow)
+ collector2.collectPassivelyIn(backgroundScope)
+ testScope.runCurrent()
+ // now this PagingData has cachedEvents from version 10
+ assertThat(flow.cachedData())
+ .isEqualTo(buildItems(version = 10, generation = 0, start = 0, size = 3))
+ assertThat(factory.nextVersion).isEqualTo(11)
+ // collect some more and ensure cachedData is still up-to-date
+ flow.collectItemsUntilSize(9)
+ assertThat(flow.cachedData())
+ .isEqualTo(buildItems(version = 10, generation = 0, start = 0, size = 9))
}
- runCurrent()
- // next version is 11, the last paged data we've created has version 10
- assertThat(factory.nextVersion).isEqualTo(11)
-
- // the replayCache has paged data version 10 but no collection on this pagingData yet
- // so it has no cachedEvent.
- val cachedPagingData = (flow as SharedFlow<PagingData<Item>>).replayCache.first()
- assertThat(cachedPagingData.cachedEvent()).isNull()
-
- // create another collector from shared, should only receive 1 paging data and that
- // should be the latest because previous PagingData is invalidated
- val collector2 = ItemCollector(flow)
- collector2.collectPassivelyIn(backgroundScope)
- testScope.runCurrent()
- // now this PagingData has cachedEvents from version 10
- assertThat(flow.cachedData()).isEqualTo(
- buildItems(
- version = 10,
- generation = 0,
- start = 0,
- size = 3
- )
- )
- assertThat(factory.nextVersion).isEqualTo(11)
- // collect some more and ensure cachedData is still up-to-date
- flow.collectItemsUntilSize(9)
- assertThat(flow.cachedData()).isEqualTo(
- buildItems(
- version = 10,
- generation = 0,
- start = 0,
- size = 9
- )
- )
- }
private fun buildPageFlow(
factory: StringPagingSource.VersionedFactory = StringPagingSource.VersionedFactory()
): Flow<PagingData<Item>> {
return Pager(
- pagingSourceFactory = factory::create,
- config = PagingConfig(
- pageSize = 3,
- prefetchDistance = 1,
- enablePlaceholders = false,
- initialLoadSize = 3,
- maxSize = 1000
+ pagingSourceFactory = factory::create,
+ config =
+ PagingConfig(
+ pageSize = 3,
+ prefetchDistance = 1,
+ enablePlaceholders = false,
+ initialLoadSize = 3,
+ maxSize = 1000
+ )
)
- ).flow
+ .flow
}
/**
- * Used for assertions internally to ensure we don't get some data with wrong generation
- * during collection. This shouldn't happen but happened during development so it is best to
- * add assertions for it.
+ * Used for assertions internally to ensure we don't get some data with wrong generation during
+ * collection. This shouldn't happen but happened during development so it is best to add
+ * assertions for it.
*/
private val PagingData<Item>.version
get(): Int {
- return (
- (hintReceiver as PageFetcher<*, *>.PagerHintReceiver<*, *>)
- .pageFetcherSnapshot.pagingSource as StringPagingSource
- ).version
+ return ((hintReceiver as PageFetcher<*, *>.PagerHintReceiver<*, *>)
+ .pageFetcherSnapshot
+ .pagingSource as StringPagingSource)
+ .version
}
private suspend fun Flow<PagingData<Item>>.collectItemsUntilSize(
expectedSize: Int,
): List<Item> {
- return this
- .mapLatest { pagingData ->
+ return this.mapLatest { pagingData ->
val expectedVersion = pagingData.version
val items = mutableListOf<Item>()
yield() // this yield helps w/ cancellation wrt mapLatest
val receiver = pagingData.hintReceiver
var loadedPageCount = 0
- pagingData.flow.filterIsInstance<PageEvent.Insert<Item>>()
+ pagingData.flow
+ .filterIsInstance<PageEvent.Insert<Item>>()
.onEach {
items.addAll(
it.pages.flatMap {
- assertThat(
- it.data.map { it.pagingSourceId }.toSet()
- ).containsExactly(
- expectedVersion
- )
+ assertThat(it.data.map { it.pagingSourceId }.toSet())
+ .containsExactly(expectedVersion)
it.data
}
)
@@ -634,14 +503,16 @@
} else {
throw AbortCollectionException()
}
- }.catch { ex ->
+ }
+ .catch { ex ->
if (ex !is AbortCollectionException) {
throw ex
}
}
.toList()
items
- }.first()
+ }
+ .first()
}
private fun Flow<PagingData<Item>>.cachedData(): List<Item> {
@@ -658,12 +529,8 @@
return (event as PageEvent.Insert<Item>).pages.flatMap { it.data }
}
- /**
- * Paged list collector that does not call any hints but always collects
- */
- private class ItemCollector(
- val source: Flow<PagingData<Item>>
- ) {
+ /** Paged list collector that does not call any hints but always collects */
+ private class ItemCollector(val source: Flow<PagingData<Item>>) {
private var items: List<Item> = emptyList()
private var job: Job? = null
var receivedPagingDataCount = 0
@@ -673,12 +540,8 @@
* Collect w/o calling any UI hints so it more like observing the stream w/o affecting it.
*/
fun collectPassivelyIn(scope: CoroutineScope) {
- check(job == null) {
- "don't call collect twice"
- }
- job = scope.launch {
- collectPassively()
- }
+ check(job == null) { "don't call collect twice" }
+ job = scope.launch { collectPassively() }
}
private suspend fun collectPassively() {
@@ -688,9 +551,7 @@
val list = mutableListOf<Item>()
items = list
it.flow.filterIsInstance<PageEvent.Insert<Item>>().collect {
- it.pages.forEach {
- list.addAll(it.data)
- }
+ it.pages.forEach { list.addAll(it.data) }
}
}
}
@@ -698,9 +559,7 @@
fun items() = items.toList()
}
- private class StringPagingSource(
- val version: Int
- ) : PagingSource<Int, Item>() {
+ private class StringPagingSource(val version: Int) : PagingSource<Int, Item>() {
private var generation = -1
override val keyReuseSupported: Boolean
@@ -710,40 +569,29 @@
when (params) {
is LoadParams.Refresh -> {
generation++
- return doLoad(
- position = params.key ?: 0,
- size = params.loadSize
- )
+ return doLoad(position = params.key ?: 0, size = params.loadSize)
}
is LoadParams.Prepend -> {
val loadSize = minOf(params.key, params.loadSize)
- return doLoad(
- position = params.key - params.loadSize,
- size = loadSize
- )
+ return doLoad(position = params.key - params.loadSize, size = loadSize)
}
is LoadParams.Append -> {
- return doLoad(
- position = params.key,
- size = params.loadSize
- )
+ return doLoad(position = params.key, size = params.loadSize)
}
}
}
override fun getRefreshKey(state: PagingState<Int, Item>): Int? = null
- private fun doLoad(
- position: Int,
- size: Int
- ): LoadResult<Int, Item> {
+ private fun doLoad(position: Int, size: Int): LoadResult<Int, Item> {
return LoadResult.Page(
- data = buildItems(
- version = version,
- generation = generation,
- start = position,
- size = size
- ),
+ data =
+ buildItems(
+ version = version,
+ generation = generation,
+ start = position,
+ size = size
+ ),
prevKey = if (position == 0) null else position,
nextKey = position + size
)
@@ -752,10 +600,11 @@
class VersionedFactory {
var nextVersion = 0
private set
+
private var latestSource: StringPagingSource? = null
- fun create() = StringPagingSource(nextVersion++).also {
- latestSource = it
- }
+
+ fun create() = StringPagingSource(nextVersion++).also { latestSource = it }
+
fun invalidateLatest() = latestSource?.invalidate()
}
}
@@ -769,11 +618,7 @@
modifier: ((Item) -> Item)? = null
): List<Item> {
return (start until start + size).map { id ->
- Item(
- pagingSourceId = version,
- generation = generation,
- value = id
- ).let {
+ Item(pagingSourceId = version, generation = generation, value = id).let {
modifier?.invoke(it) ?: it
}
}
@@ -781,33 +626,22 @@
}
private data class Item(
- /**
- * which paged source generated this item
- */
+ /** which paged source generated this item */
val pagingSourceId: Int,
- /**
- * # of refresh counts in the paged source
- */
+ /** # of refresh counts in the paged source */
val generation: Int,
- /**
- * Item unique identifier
- */
+ /** Item unique identifier */
val value: Int,
- /**
- * Any additional data by transformations etc
- */
+ /** Any additional data by transformations etc */
val metadata: String? = null
)
private class ActiveFlowTrackerImpl : ActiveFlowTracker {
- private val counters = mapOf(
- PAGED_DATA_FLOW to AtomicInt(0),
- PAGE_EVENT_FLOW to AtomicInt(0)
- )
+ private val counters =
+ mapOf(PAGED_DATA_FLOW to AtomicInt(0), PAGE_EVENT_FLOW to AtomicInt(0))
- override fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>) {
- }
+ override fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>) {}
override suspend fun onStart(flowType: FlowType) {
(counters[flowType] ?: error("invalid type $flowType")).incrementAndGet()
@@ -818,6 +652,7 @@
}
fun pageDataFlowCount() = (counters[PAGED_DATA_FLOW] ?: error("unexpected")).get()
+
fun pageEventFlowCount() = (counters[PAGE_EVENT_FLOW] ?: error("unexpected")).get()
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/ConflatedEventBusTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/ConflatedEventBusTest.kt
index f5e8c7b..63b908d 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/ConflatedEventBusTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/ConflatedEventBusTest.kt
@@ -31,52 +31,34 @@
@Test
fun noInitialValue() {
val bus = ConflatedEventBus<Unit>(null)
- val collector = bus.createCollector().also {
- it.start()
- }
+ val collector = bus.createCollector().also { it.start() }
testScope.runCurrent()
- assertThat(
- collector.values
- ).isEmpty()
+ assertThat(collector.values).isEmpty()
bus.send(Unit)
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(Unit)
+ assertThat(collector.values).containsExactly(Unit)
}
@Test
fun withInitialValue() {
val bus = ConflatedEventBus<Int>(1)
- val collector = bus.createCollector().also {
- it.start()
- }
+ val collector = bus.createCollector().also { it.start() }
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(1)
+ assertThat(collector.values).containsExactly(1)
bus.send(2)
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(1, 2)
+ assertThat(collector.values).containsExactly(1, 2)
}
@Test
fun allowDuplicateValues() {
val bus = ConflatedEventBus<Int>(1)
- val collector = bus.createCollector().also {
- it.start()
- }
+ val collector = bus.createCollector().also { it.start() }
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(1)
+ assertThat(collector.values).containsExactly(1)
bus.send(1)
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(1, 1)
+ assertThat(collector.values).containsExactly(1, 1)
}
@Test
@@ -87,28 +69,20 @@
bus.send(2)
collector.start()
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(2)
+ assertThat(collector.values).containsExactly(2)
bus.send(3)
testScope.runCurrent()
- assertThat(
- collector.values
- ).containsExactly(2, 3)
+ assertThat(collector.values).containsExactly(2, 3)
}
@Test
fun multipleCollectors() {
val bus = ConflatedEventBus(1)
- val c1 = bus.createCollector().also {
- it.start()
- }
+ val c1 = bus.createCollector().also { it.start() }
testScope.runCurrent()
bus.send(2)
testScope.runCurrent()
- val c2 = bus.createCollector().also {
- it.start()
- }
+ val c2 = bus.createCollector().also { it.start() }
testScope.runCurrent()
assertThat(c1.values).containsExactly(1, 2)
assertThat(c2.values).containsExactly(2)
@@ -133,11 +107,7 @@
get() = _values
fun start() {
- scope.launch {
- bus.flow.collect {
- _values.add(it)
- }
- }
+ scope.launch { bus.flow.collect { _values.add(it) } }
}
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FailDispatcher.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FailDispatcher.kt
index 0642f31..7d33643 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FailDispatcher.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FailDispatcher.kt
@@ -21,9 +21,8 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Runnable
-class FailDispatcher(
- val string: String = "Executor expected to be unused"
-) : CoroutineDispatcher() {
+class FailDispatcher(val string: String = "Executor expected to be unused") :
+ CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
fail(string)
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlattenedPageEventStorageTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlattenedPageEventStorageTest.kt
index 02bf478..b801dde 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlattenedPageEventStorageTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlattenedPageEventStorageTest.kt
@@ -40,129 +40,123 @@
fun refresh() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c"))
- ),
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c"),
- placeholdersBefore = 3,
- placeholdersAfter = 5
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 5
+ )
)
- )
}
@Test
fun refresh_thenPrepend() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c"))
- ),
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
list.add(
localPrepend(
- pages = listOf(
- TransformablePage(data = listOf("x1")),
- TransformablePage(data = listOf("x2"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("x1")),
+ TransformablePage(data = listOf("x2"))
+ ),
placeholdersBefore = 1,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("x1", "x2", "a", "b", "c"),
- placeholdersBefore = 1,
- placeholdersAfter = 5
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("x1", "x2", "a", "b", "c"),
+ placeholdersBefore = 1,
+ placeholdersAfter = 5
+ )
)
- )
}
@Test
fun refresh_thenAppend() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c"))
- ),
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
list.add(
localAppend(
- pages = listOf(
- TransformablePage(data = listOf("x1")),
- TransformablePage(data = listOf("x2")),
- TransformablePage(data = listOf("x3"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("x1")),
+ TransformablePage(data = listOf("x2")),
+ TransformablePage(data = listOf("x3"))
+ ),
placeholdersAfter = 2,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c", "x1", "x2", "x3"),
- placeholdersBefore = 3,
- placeholdersAfter = 2
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c", "x1", "x2", "x3"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 2
+ )
)
- )
}
@Test
fun refresh_refreshAgain() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c"))
- ),
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("x", "y"))
- ),
+ pages = listOf(TransformablePage(data = listOf("x", "y"))),
placeholdersBefore = 2,
placeholdersAfter = 4,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("x", "y"),
- placeholdersBefore = 2,
- placeholdersAfter = 4
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(items = listOf("x", "y"), placeholdersBefore = 2, placeholdersAfter = 4)
)
- )
}
@Test
fun drop_fromStart() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c", "d", "e"),
- placeholdersBefore = 3,
- placeholdersAfter = 5
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c", "d", "e"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 5
+ )
)
- )
list.add(
Drop(
loadType = PREPEND,
@@ -171,80 +165,78 @@
placeholdersRemaining = 6
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("d", "e"),
- placeholdersBefore = 6,
- placeholdersAfter = 5
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(items = listOf("d", "e"), placeholdersBefore = 6, placeholdersAfter = 5)
)
- )
}
@Test
fun drop_fromEnd() {
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c", "d", "e"),
- placeholdersBefore = 3,
- placeholdersAfter = 5
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c", "d", "e"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 5
+ )
)
- )
list.add(
- Drop(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 7
- )
+ Drop(loadType = APPEND, minPageOffset = 1, maxPageOffset = 1, placeholdersRemaining = 7)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c"),
- placeholdersBefore = 3,
- placeholdersAfter = 7
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 7
+ )
)
- )
}
@Test
fun staticList_initWithoutLoadStates() {
list.add(StaticList(listOf("a", "b", "c")))
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("a", "b", "c"),
- sourceLoadStates = LoadStates.IDLE,
- mediatorLoadStates = null,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("a", "b", "c"),
+ sourceLoadStates = LoadStates.IDLE,
+ mediatorLoadStates = null,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- localRefresh(
- pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = LoadStates.IDLE,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ localRefresh(
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = LoadStates.IDLE,
+ )
)
- )
}
@Test
fun staticList_initWithLoadStates() {
- val nonDefaultloadStates = loadStates(
- refresh = Error(TEST_EXCEPTION),
- prepend = Error(TEST_EXCEPTION),
- append = Error(TEST_EXCEPTION),
- )
+ val nonDefaultloadStates =
+ loadStates(
+ refresh = Error(TEST_EXCEPTION),
+ prepend = Error(TEST_EXCEPTION),
+ append = Error(TEST_EXCEPTION),
+ )
list.add(
StaticList(
data = listOf("a", "b", "c"),
@@ -252,44 +244,49 @@
mediatorLoadStates = nonDefaultloadStates,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("a", "b", "c"),
- sourceLoadStates = nonDefaultloadStates,
- mediatorLoadStates = nonDefaultloadStates,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("a", "b", "c"),
+ sourceLoadStates = nonDefaultloadStates,
+ mediatorLoadStates = nonDefaultloadStates,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = nonDefaultloadStates,
- mediator = nonDefaultloadStates,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf("a", "b", "c"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = nonDefaultloadStates,
+ mediator = nonDefaultloadStates,
+ )
)
- )
}
@Test
fun staticList_afterInsertOverridesStates() {
- val initialLoadStates = loadStates(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- )
- val overridenloadStates = loadStates(
- refresh = Error(TEST_EXCEPTION),
- prepend = Error(TEST_EXCEPTION),
- append = Error(TEST_EXCEPTION),
- )
+ val initialLoadStates =
+ loadStates(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ )
+ val overridenloadStates =
+ loadStates(
+ refresh = Error(TEST_EXCEPTION),
+ prepend = Error(TEST_EXCEPTION),
+ append = Error(TEST_EXCEPTION),
+ )
list.add(
remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
source = initialLoadStates,
@@ -303,44 +300,49 @@
mediatorLoadStates = overridenloadStates,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("x", "y", "z"),
- sourceLoadStates = overridenloadStates,
- mediatorLoadStates = overridenloadStates,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("x", "y", "z"),
+ sourceLoadStates = overridenloadStates,
+ mediatorLoadStates = overridenloadStates,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = overridenloadStates,
- mediator = overridenloadStates,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = overridenloadStates,
+ mediator = overridenloadStates,
+ )
)
- )
}
@Test
fun staticList_afterInsertOverridesOnlySourceStates() {
- val initialLoadStates = loadStates(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- )
- val overridenloadStates = loadStates(
- refresh = Error(TEST_EXCEPTION),
- prepend = Error(TEST_EXCEPTION),
- append = Error(TEST_EXCEPTION),
- )
+ val initialLoadStates =
+ loadStates(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ )
+ val overridenloadStates =
+ loadStates(
+ refresh = Error(TEST_EXCEPTION),
+ prepend = Error(TEST_EXCEPTION),
+ append = Error(TEST_EXCEPTION),
+ )
list.add(
remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
source = initialLoadStates,
@@ -354,44 +356,49 @@
mediatorLoadStates = null,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("x", "y", "z"),
- sourceLoadStates = overridenloadStates,
- mediatorLoadStates = initialLoadStates,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("x", "y", "z"),
+ sourceLoadStates = overridenloadStates,
+ mediatorLoadStates = initialLoadStates,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = overridenloadStates,
- mediator = initialLoadStates,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = overridenloadStates,
+ mediator = initialLoadStates,
+ )
)
- )
}
@Test
fun staticList_afterInsertOverridesOnlyMediatorStates() {
- val initialLoadStates = loadStates(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- )
- val overridenloadStates = loadStates(
- refresh = Error(TEST_EXCEPTION),
- prepend = Error(TEST_EXCEPTION),
- append = Error(TEST_EXCEPTION),
- )
+ val initialLoadStates =
+ loadStates(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ )
+ val overridenloadStates =
+ loadStates(
+ refresh = Error(TEST_EXCEPTION),
+ prepend = Error(TEST_EXCEPTION),
+ append = Error(TEST_EXCEPTION),
+ )
list.add(
remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
source = initialLoadStates,
@@ -405,39 +412,43 @@
mediatorLoadStates = overridenloadStates,
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("x", "y", "z"),
- sourceLoadStates = initialLoadStates,
- mediatorLoadStates = overridenloadStates,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("x", "y", "z"),
+ sourceLoadStates = initialLoadStates,
+ mediatorLoadStates = overridenloadStates,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = initialLoadStates,
- mediator = overridenloadStates,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = initialLoadStates,
+ mediator = overridenloadStates,
+ )
)
- )
}
@Test
fun staticList_afterInsertPreservesStates() {
- val nonDefaultloadStates = loadStates(
- refresh = Error(TEST_EXCEPTION),
- prepend = Error(TEST_EXCEPTION),
- append = Error(TEST_EXCEPTION),
- )
+ val nonDefaultloadStates =
+ loadStates(
+ refresh = Error(TEST_EXCEPTION),
+ prepend = Error(TEST_EXCEPTION),
+ append = Error(TEST_EXCEPTION),
+ )
list.add(
remoteRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
source = nonDefaultloadStates,
@@ -445,24 +456,26 @@
)
)
list.add(StaticList(listOf("x", "y", "z")))
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- items = listOf("x", "y", "z"),
- sourceLoadStates = nonDefaultloadStates,
- mediatorLoadStates = nonDefaultloadStates,
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ items = listOf("x", "y", "z"),
+ sourceLoadStates = nonDefaultloadStates,
+ mediatorLoadStates = nonDefaultloadStates,
+ )
)
- )
- assertThat(list.getAsEvents()).containsExactly(
- remoteRefresh(
- pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- source = nonDefaultloadStates,
- mediator = nonDefaultloadStates,
+ assertThat(list.getAsEvents())
+ .containsExactly(
+ remoteRefresh(
+ pages = listOf(TransformablePage(data = listOf("x", "y", "z"))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ source = nonDefaultloadStates,
+ mediator = nonDefaultloadStates,
+ )
)
- )
}
@Test
@@ -470,36 +483,37 @@
val error = Error(RuntimeException("?"))
list.add(
localRefresh(
- pages = listOf(
- TransformablePage(data = listOf("a", "b", "c")),
- TransformablePage(data = listOf("d", "e"))
- ),
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a", "b", "c")),
+ TransformablePage(data = listOf("d", "e"))
+ ),
placeholdersBefore = 3,
placeholdersAfter = 5,
source = loadStates(prepend = Loading, append = error)
)
)
- assertThat(list.snapshot()).isEqualTo(
- Snapshot(
- items = listOf("a", "b", "c", "d", "e"),
- placeholdersBefore = 3,
- placeholdersAfter = 5,
- sourceLoadStates = loadStates(
- refresh = NotLoading.Incomplete,
- prepend = Loading,
- append = error
+ assertThat(list.snapshot())
+ .isEqualTo(
+ Snapshot(
+ items = listOf("a", "b", "c", "d", "e"),
+ placeholdersBefore = 3,
+ placeholdersAfter = 5,
+ sourceLoadStates =
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ prepend = Loading,
+ append = error
+ )
)
)
- )
}
private fun <T : Any> FlattenedPageEventStorage<T>.snapshot(): Snapshot<T> {
return this.getAsEvents().fold(Snapshot()) { snapshot, event ->
when (event) {
is PageEvent.Insert -> {
- check(event.loadType == REFRESH) {
- "should only send refresh event"
- }
+ check(event.loadType == REFRESH) { "should only send refresh event" }
snapshot.copy(
items = snapshot.items + event.pages.flatMap { it.data },
placeholdersBefore = event.placeholdersBefore,
@@ -520,8 +534,8 @@
placeholdersBefore = 0,
placeholdersAfter = 0,
sourceLoadStates = event.sourceLoadStates ?: snapshot.sourceLoadStates,
- mediatorLoadStates = event.mediatorLoadStates
- ?: snapshot.mediatorLoadStates,
+ mediatorLoadStates =
+ event.mediatorLoadStates ?: snapshot.mediatorLoadStates,
)
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlowExtTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlowExtTest.kt
index 2beb9e0..ae6bb09 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlowExtTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/FlowExtTest.kt
@@ -46,254 +46,242 @@
val testScope = TestScope()
@Test
- fun scan_basic() = testScope.runTest {
- val arguments = mutableListOf<Pair<Int, Int>>()
- assertThat(
- flowOf(1, 2, 3).simpleScan(0) { acc, value ->
- arguments.add(acc to value)
- value + acc
- }.toList()
- ).containsExactly(
- 0, 1, 3, 6
- ).inOrder()
- assertThat(arguments).containsExactly(
- 0 to 1,
- 1 to 2,
- 3 to 3
- ).inOrder()
- }
-
- @Test
- fun scan_initialValue() = testScope.runTest {
- assertThat(
- emptyFlow<Int>().simpleScan("x") { _, value ->
- "$value"
- }.toList()
- ).containsExactly("x")
- }
-
- @Test
- fun runningReduce_basic() = testScope.runTest {
- assertThat(
- flowOf(1, 2, 3, 4).simpleRunningReduce { acc, value ->
- acc + value
- }.toList()
- ).containsExactly(1, 3, 6, 10)
- }
-
- @Test
- fun runningReduce_empty() = testScope.runTest {
- assertThat(
- emptyFlow<Int>().simpleRunningReduce { acc, value ->
- acc + value
- }.toList()
- ).isEmpty()
- }
-
- @Test
- fun mapLatest() = testScope.runTest {
- assertThat(
- flowOf(1, 2, 3, 4)
- .onEach {
- delay(1)
- }
- .simpleMapLatest { value ->
- delay(value.toLong())
- "$value-$value"
- }.toList()
- ).containsExactly(
- "1-1", "4-4"
- ).inOrder()
- }
-
- @Test
- fun mapLatest_empty() = testScope.runTest {
- assertThat(
- emptyFlow<Int>().simpleMapLatest { value ->
- "$value-$value"
- }.toList()
- ).isEmpty()
- }
-
- @Test
- fun flatMapLatest() = testScope.runTest {
- assertThat(
- flowOf(1, 2, 3, 4)
- .onEach {
- delay(1)
- }
- .simpleFlatMapLatest { value ->
- flow {
- repeat(value) {
- emit(value)
+ fun scan_basic() =
+ testScope.runTest {
+ val arguments = mutableListOf<Pair<Int, Int>>()
+ assertThat(
+ flowOf(1, 2, 3)
+ .simpleScan(0) { acc, value ->
+ arguments.add(acc to value)
+ value + acc
}
+ .toList()
+ )
+ .containsExactly(0, 1, 3, 6)
+ .inOrder()
+ assertThat(arguments).containsExactly(0 to 1, 1 to 2, 3 to 3).inOrder()
+ }
+
+ @Test
+ fun scan_initialValue() =
+ testScope.runTest {
+ assertThat(emptyFlow<Int>().simpleScan("x") { _, value -> "$value" }.toList())
+ .containsExactly("x")
+ }
+
+ @Test
+ fun runningReduce_basic() =
+ testScope.runTest {
+ assertThat(
+ flowOf(1, 2, 3, 4).simpleRunningReduce { acc, value -> acc + value }.toList()
+ )
+ .containsExactly(1, 3, 6, 10)
+ }
+
+ @Test
+ fun runningReduce_empty() =
+ testScope.runTest {
+ assertThat(emptyFlow<Int>().simpleRunningReduce { acc, value -> acc + value }.toList())
+ .isEmpty()
+ }
+
+ @Test
+ fun mapLatest() =
+ testScope.runTest {
+ assertThat(
+ flowOf(1, 2, 3, 4)
+ .onEach { delay(1) }
+ .simpleMapLatest { value ->
+ delay(value.toLong())
+ "$value-$value"
+ }
+ .toList()
+ )
+ .containsExactly("1-1", "4-4")
+ .inOrder()
+ }
+
+ @Test
+ fun mapLatest_empty() =
+ testScope.runTest {
+ assertThat(emptyFlow<Int>().simpleMapLatest { value -> "$value-$value" }.toList())
+ .isEmpty()
+ }
+
+ @Test
+ fun flatMapLatest() =
+ testScope.runTest {
+ assertThat(
+ flowOf(1, 2, 3, 4)
+ .onEach { delay(1) }
+ .simpleFlatMapLatest { value -> flow { repeat(value) { emit(value) } } }
+ .toList()
+ )
+ .containsExactly(1, 2, 2, 3, 3, 3, 4, 4, 4, 4)
+ .inOrder()
+ }
+
+ @Test
+ fun flatMapLatest_empty() =
+ testScope.runTest {
+ assertThat(emptyFlow<Int>().simpleFlatMapLatest { flowOf(it) }.toList()).isEmpty()
+ }
+
+ @Test
+ fun combineWithoutBatching_buffersEmissions() =
+ testScope.runTest {
+ val flow1 = Channel<Int>(BUFFERED)
+ val flow2 = Channel<String>(BUFFERED)
+
+ val result = mutableListOf<String>()
+ launch {
+ flow1
+ .consumeAsFlow()
+ .combineWithoutBatching(flow2.consumeAsFlow()) { first, second, _ ->
+ "$first$second"
}
- }.toList()
- ).containsExactly(
- 1, 2, 2, 3, 3, 3, 4, 4, 4, 4
- ).inOrder()
- }
-
- @Test
- fun flatMapLatest_empty() = testScope.runTest {
- assertThat(
- emptyFlow<Int>()
- .simpleFlatMapLatest {
- flowOf(it)
- }.toList()
- ).isEmpty()
- }
-
- @Test
- fun combineWithoutBatching_buffersEmissions() = testScope.runTest {
- val flow1 = Channel<Int>(BUFFERED)
- val flow2 = Channel<String>(BUFFERED)
-
- val result = mutableListOf<String>()
- launch {
- flow1.consumeAsFlow()
- .combineWithoutBatching(flow2.consumeAsFlow()) { first, second, _ ->
- "$first$second"
- }
- .collect(result::add)
- }
-
- flow1.send(1)
- advanceUntilIdle()
- assertThat(result).isEmpty()
-
- flow1.send(2)
- advanceUntilIdle()
- assertThat(result).isEmpty()
-
- flow2.send("A")
- advanceUntilIdle()
- assertThat(result).containsExactly("1A", "2A")
-
- // This should automatically propagate cancellation to the launched collector.
- flow1.close()
- flow2.close()
- }
-
- @Test
- fun combineWithoutBatching_doesNotBatchOnSlowTransform() = testScope.runTest {
- val flow1 = flowOf(1, 2, 3)
- val flow2 = flowOf("A", "B", "C")
- val slowTransform: suspend (Int, String) -> String = { num: Int, letter: String ->
- delay(10)
- "$num$letter"
- }
-
- val batchedCombine = flow1
- .combine(flow2, slowTransform)
- .toList()
- advanceUntilIdle()
- assertThat(batchedCombine).containsExactly("1A", "3B", "3C")
-
- val unbatchedCombine = flow1
- .combineWithoutBatching(flow2) { num, letter, _ -> slowTransform(num, letter) }
- .toList()
- advanceUntilIdle()
- assertThat(unbatchedCombine).containsExactly("1A", "2A", "2B", "3B", "3C")
- }
-
- @Test
- fun combineWithoutBatching_updateFrom() = testScope.runTest {
- val flow1 = Channel<Int>(BUFFERED)
- val flow2 = Channel<Int>(BUFFERED)
-
- val result = mutableListOf<CombineSource>()
- launch {
- flow1.consumeAsFlow()
- .combineWithoutBatching(flow2.consumeAsFlow()) { _, _, updateFrom ->
- result.add(updateFrom)
- }
- .collect { }
- }
-
- flow1.send(1)
- advanceUntilIdle()
- assertThat(result).isEmpty()
-
- flow1.send(1)
- advanceUntilIdle()
- assertThat(result).isEmpty()
-
- flow2.send(2)
- advanceUntilIdle()
- assertThat(result).containsExactly(INITIAL, RECEIVER)
-
- flow1.send(1)
- flow2.send(2)
- advanceUntilIdle()
- assertThat(result).containsExactly(INITIAL, RECEIVER, RECEIVER, OTHER)
-
- // This should automatically propagate cancellation to the launched collector.
- flow1.close()
- flow2.close()
- }
-
- @Test
- fun combineWithoutBatching_collectorCancellationPropagates() = testScope.runTest {
- val flow1Emissions = mutableListOf<Int>()
- val flow1 = flowOf(1, 2, 3).onEach(flow1Emissions::add)
- val flow2Emissions = mutableListOf<String>()
- val flow2 = flowOf("A", "B", "C").onEach(flow2Emissions::add)
- val result = mutableListOf<Unit>()
-
- flow1
- .combineWithoutBatching(flow2) { _, _, _ ->
- result.add(Unit)
+ .collect(result::add)
}
- .first()
- advanceUntilIdle()
+ flow1.send(1)
+ advanceUntilIdle()
+ assertThat(result).isEmpty()
- // We can't guarantee whether cancellation will propagate before or after the second item
- // is emitted, but we should never get the third.
- assertThat(flow1Emissions.size).isIn(1..2)
- assertThat(flow2Emissions.size).isIn(1..2)
- assertThat(result.size).isIn(1..2)
- }
+ flow1.send(2)
+ advanceUntilIdle()
+ assertThat(result).isEmpty()
+
+ flow2.send("A")
+ advanceUntilIdle()
+ assertThat(result).containsExactly("1A", "2A")
+
+ // This should automatically propagate cancellation to the launched collector.
+ flow1.close()
+ flow2.close()
+ }
+
+ @Test
+ fun combineWithoutBatching_doesNotBatchOnSlowTransform() =
+ testScope.runTest {
+ val flow1 = flowOf(1, 2, 3)
+ val flow2 = flowOf("A", "B", "C")
+ val slowTransform: suspend (Int, String) -> String = { num: Int, letter: String ->
+ delay(10)
+ "$num$letter"
+ }
+
+ val batchedCombine = flow1.combine(flow2, slowTransform).toList()
+ advanceUntilIdle()
+ assertThat(batchedCombine).containsExactly("1A", "3B", "3C")
+
+ val unbatchedCombine =
+ flow1
+ .combineWithoutBatching(flow2) { num, letter, _ -> slowTransform(num, letter) }
+ .toList()
+ advanceUntilIdle()
+ assertThat(unbatchedCombine).containsExactly("1A", "2A", "2B", "3B", "3C")
+ }
+
+ @Test
+ fun combineWithoutBatching_updateFrom() =
+ testScope.runTest {
+ val flow1 = Channel<Int>(BUFFERED)
+ val flow2 = Channel<Int>(BUFFERED)
+
+ val result = mutableListOf<CombineSource>()
+ launch {
+ flow1
+ .consumeAsFlow()
+ .combineWithoutBatching(flow2.consumeAsFlow()) { _, _, updateFrom ->
+ result.add(updateFrom)
+ }
+ .collect {}
+ }
+
+ flow1.send(1)
+ advanceUntilIdle()
+ assertThat(result).isEmpty()
+
+ flow1.send(1)
+ advanceUntilIdle()
+ assertThat(result).isEmpty()
+
+ flow2.send(2)
+ advanceUntilIdle()
+ assertThat(result).containsExactly(INITIAL, RECEIVER)
+
+ flow1.send(1)
+ flow2.send(2)
+ advanceUntilIdle()
+ assertThat(result).containsExactly(INITIAL, RECEIVER, RECEIVER, OTHER)
+
+ // This should automatically propagate cancellation to the launched collector.
+ flow1.close()
+ flow2.close()
+ }
+
+ @Test
+ fun combineWithoutBatching_collectorCancellationPropagates() =
+ testScope.runTest {
+ val flow1Emissions = mutableListOf<Int>()
+ val flow1 = flowOf(1, 2, 3).onEach(flow1Emissions::add)
+ val flow2Emissions = mutableListOf<String>()
+ val flow2 = flowOf("A", "B", "C").onEach(flow2Emissions::add)
+ val result = mutableListOf<Unit>()
+
+ flow1.combineWithoutBatching(flow2) { _, _, _ -> result.add(Unit) }.first()
+
+ advanceUntilIdle()
+
+ // We can't guarantee whether cancellation will propagate before or after the second
+ // item
+ // is emitted, but we should never get the third.
+ assertThat(flow1Emissions.size).isIn(1..2)
+ assertThat(flow2Emissions.size).isIn(1..2)
+ assertThat(result.size).isIn(1..2)
+ }
@Ignore // b/329157121
@Test
- fun combineWithoutBatching_stressTest() = testScope.runTest {
- val flow1 = flow {
- repeat(1000) {
- if (Random.nextBoolean()) {
- delay(1)
+ fun combineWithoutBatching_stressTest() =
+ testScope.runTest {
+ val flow1 = flow {
+ repeat(1000) {
+ if (Random.nextBoolean()) {
+ delay(1)
+ }
+ emit(it)
}
- emit(it)
}
- }
- val flow2 = flow {
- repeat(1000) {
- if (Random.nextBoolean()) {
- delay(1)
+ val flow2 = flow {
+ repeat(1000) {
+ if (Random.nextBoolean()) {
+ delay(1)
+ }
+ emit(it)
}
- emit(it)
- }
- }
-
- repeat(10) {
- val result = flow1.combineWithoutBatching(flow2) { first, second, _ -> first to second }
- .toList()
-
- // Never emit the same values twice.
- assertThat(result).containsNoDuplicates()
-
- // Assert order of emissions
- result.scan(0 to 0) { acc, next ->
- assertThat(next.first).isAtLeast(acc.first)
- assertThat(next.second).isAtLeast(acc.second)
- next
}
- // Check we don't miss any emissions
- assertThat(result).hasSize(1999)
+ repeat(10) {
+ val result =
+ flow1
+ .combineWithoutBatching(flow2) { first, second, _ -> first to second }
+ .toList()
+
+ // Never emit the same values twice.
+ assertThat(result).containsNoDuplicates()
+
+ // Assert order of emissions
+ result.scan(0 to 0) { acc, next ->
+ assertThat(next.first).isAtLeast(acc.first)
+ assertThat(next.second).isAtLeast(acc.second)
+ next
+ }
+
+ // Check we don't miss any emissions
+ assertThat(result).hasSize(1999)
+ }
}
- }
class UnbatchedFlowCombinerTest {
private data class SendResult<T1, T2>(
@@ -305,15 +293,12 @@
@Test
fun onNext_receiverBuffers() = runTest {
val result = mutableListOf<SendResult<Int, Int>>()
- val combiner = UnbatchedFlowCombiner<Int, Int> { a, b, c ->
- result.add(SendResult(a, b, c))
- }
+ val combiner =
+ UnbatchedFlowCombiner<Int, Int> { a, b, c -> result.add(SendResult(a, b, c)) }
combiner.onNext(index = 0, value = 0)
val job = launch {
- repeat(9) { receiverValue ->
- combiner.onNext(index = 0, value = receiverValue + 1)
- }
+ repeat(9) { receiverValue -> combiner.onNext(index = 0, value = receiverValue + 1) }
}
// Ensure subsequent calls to onNext from receiver suspends forever until onNext
@@ -327,32 +312,30 @@
advanceUntilIdle()
assertThat(job.isCompleted).isTrue()
- assertThat(result).containsExactly(
- SendResult(0, 0, INITIAL),
- SendResult(1, 0, RECEIVER),
- SendResult(2, 0, RECEIVER),
- SendResult(3, 0, RECEIVER),
- SendResult(4, 0, RECEIVER),
- SendResult(5, 0, RECEIVER),
- SendResult(6, 0, RECEIVER),
- SendResult(7, 0, RECEIVER),
- SendResult(8, 0, RECEIVER),
- SendResult(9, 0, RECEIVER),
- )
+ assertThat(result)
+ .containsExactly(
+ SendResult(0, 0, INITIAL),
+ SendResult(1, 0, RECEIVER),
+ SendResult(2, 0, RECEIVER),
+ SendResult(3, 0, RECEIVER),
+ SendResult(4, 0, RECEIVER),
+ SendResult(5, 0, RECEIVER),
+ SendResult(6, 0, RECEIVER),
+ SendResult(7, 0, RECEIVER),
+ SendResult(8, 0, RECEIVER),
+ SendResult(9, 0, RECEIVER),
+ )
}
@Test
fun onNext_otherBuffers() = runTest {
val result = mutableListOf<SendResult<Int, Int>>()
- val combiner = UnbatchedFlowCombiner<Int, Int> { a, b, c ->
- result.add(SendResult(a, b, c))
- }
+ val combiner =
+ UnbatchedFlowCombiner<Int, Int> { a, b, c -> result.add(SendResult(a, b, c)) }
combiner.onNext(index = 1, value = 0)
val job = launch {
- repeat(9) { receiverValue ->
- combiner.onNext(index = 1, value = receiverValue + 1)
- }
+ repeat(9) { receiverValue -> combiner.onNext(index = 1, value = receiverValue + 1) }
}
// Ensure subsequent calls to onNext from receiver suspends forever until onNext
@@ -366,45 +349,40 @@
advanceUntilIdle()
assertThat(job.isCompleted).isTrue()
- assertThat(result).containsExactly(
- SendResult(0, 0, INITIAL),
- SendResult(0, 1, OTHER),
- SendResult(0, 2, OTHER),
- SendResult(0, 3, OTHER),
- SendResult(0, 4, OTHER),
- SendResult(0, 5, OTHER),
- SendResult(0, 6, OTHER),
- SendResult(0, 7, OTHER),
- SendResult(0, 8, OTHER),
- SendResult(0, 9, OTHER),
- )
+ assertThat(result)
+ .containsExactly(
+ SendResult(0, 0, INITIAL),
+ SendResult(0, 1, OTHER),
+ SendResult(0, 2, OTHER),
+ SendResult(0, 3, OTHER),
+ SendResult(0, 4, OTHER),
+ SendResult(0, 5, OTHER),
+ SendResult(0, 6, OTHER),
+ SendResult(0, 7, OTHER),
+ SendResult(0, 8, OTHER),
+ SendResult(0, 9, OTHER),
+ )
}
@Test
fun onNext_initialDispatchesFirst() = runTest {
val result = mutableListOf<SendResult<Int, Int>>()
- val combiner = UnbatchedFlowCombiner<Int, Int> { a, b, c ->
- // Give a chance for other calls to onNext to run.
- yield()
- result.add(SendResult(a, b, c))
- }
-
- launch {
- repeat(1000) { value ->
- combiner.onNext(index = 0, value = value)
+ val combiner =
+ UnbatchedFlowCombiner<Int, Int> { a, b, c ->
+ // Give a chance for other calls to onNext to run.
+ yield()
+ result.add(SendResult(a, b, c))
}
- }
- repeat(1) { value ->
- launch {
- combiner.onNext(index = 1, value = value)
- }
- }
+ launch { repeat(1000) { value -> combiner.onNext(index = 0, value = value) } }
+
+ repeat(1) { value -> launch { combiner.onNext(index = 1, value = value) } }
advanceUntilIdle()
- assertThat(result.first()).isEqualTo(
- SendResult(0, 0, INITIAL),
- )
+ assertThat(result.first())
+ .isEqualTo(
+ SendResult(0, 0, INITIAL),
+ )
}
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/HeaderFooterTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/HeaderFooterTest.kt
index f05f7f8..a450923 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/HeaderFooterTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/HeaderFooterTest.kt
@@ -26,208 +26,200 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
-/**
- * Prepend and append are both Done, so that headers will appear
- */
-private val fullLoadStates = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete
-)
+/** Prepend and append are both Done, so that headers will appear */
+private val fullLoadStates = loadStates(prepend = NotLoading.Complete, append = NotLoading.Complete)
@OptIn(ExperimentalCoroutinesApi::class)
class HeaderFooterTest {
- private fun <T : Any> PageEvent<T>.toPagingData() = PagingData(
- flowOf(this),
- PagingData.NOOP_UI_RECEIVER,
- PagingData.NOOP_HINT_RECEIVER
- )
+ private fun <T : Any> PageEvent<T>.toPagingData() =
+ PagingData(flowOf(this), PagingData.NOOP_UI_RECEIVER, PagingData.NOOP_HINT_RECEIVER)
- private suspend fun <T : Any> PageEvent<T>.insertHeaderItem(item: T) = toPagingData()
- .insertHeaderItem(item = item)
- .flow
- .single()
+ private suspend fun <T : Any> PageEvent<T>.insertHeaderItem(item: T) =
+ toPagingData().insertHeaderItem(item = item).flow.single()
- private suspend fun <T : Any> PageEvent<T>.insertFooterItem(item: T) = toPagingData()
- .insertFooterItem(item = item)
- .flow
- .single()
+ private suspend fun <T : Any> PageEvent<T>.insertFooterItem(item: T) =
+ toPagingData().insertFooterItem(item = item).flow.single()
@Test
- fun insertHeader_prepend() = runTest(UnconfinedTestDispatcher()) {
- val actual = localPrepend(
- pages = listOf(
- TransformablePage(
- data = listOf(0),
- originalPageOffset = -1
- )
- ),
- source = fullLoadStates
- ).insertHeaderItem(-1)
+ fun insertHeader_prepend() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localPrepend(
+ pages =
+ listOf(TransformablePage(data = listOf(0), originalPageOffset = -1)),
+ source = fullLoadStates
+ )
+ .insertHeaderItem(-1)
- val expected = localPrepend(
- pages = listOf(
- TransformablePage(
- data = listOf(-1),
- originalPageOffsets = intArrayOf(-1),
- hintOriginalPageOffset = -1,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- data = listOf(0),
- originalPageOffset = -1
+ val expected =
+ localPrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ data = listOf(-1),
+ originalPageOffsets = intArrayOf(-1),
+ hintOriginalPageOffset = -1,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(data = listOf(0), originalPageOffset = -1)
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertThat(actual).isEqualTo(expected)
- }
+ assertThat(actual).isEqualTo(expected)
+ }
@Test
- fun insertHeader_refresh() = runTest(UnconfinedTestDispatcher()) {
- val actual = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("a"),
- originalPageOffset = 0
- )
- ),
- source = fullLoadStates
- ).insertHeaderItem("HEADER")
+ fun insertHeader_refresh() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localRefresh(
+ pages =
+ listOf(TransformablePage(data = listOf("a"), originalPageOffset = 0)),
+ source = fullLoadStates
+ )
+ .insertHeaderItem("HEADER")
- val expected = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("HEADER"),
- originalPageOffsets = intArrayOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- data = listOf("a"),
- originalPageOffset = 0
+ val expected =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ data = listOf("HEADER"),
+ originalPageOffsets = intArrayOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(data = listOf("a"), originalPageOffset = 0)
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertThat(actual).isEqualTo(expected)
- }
+ assertThat(actual).isEqualTo(expected)
+ }
@Test
- fun insertHeader_empty() = runTest(UnconfinedTestDispatcher()) {
- val actual = localRefresh(
- pages = listOf(
- TransformablePage(
- data = emptyList<String>(),
- originalPageOffset = 0
- )
- ),
- source = fullLoadStates
- ).insertHeaderItem("HEADER")
+ fun insertHeader_empty() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ data = emptyList<String>(),
+ originalPageOffset = 0
+ )
+ ),
+ source = fullLoadStates
+ )
+ .insertHeaderItem("HEADER")
- val expected = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("HEADER"),
- originalPageOffsets = intArrayOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
+ val expected =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ data = listOf("HEADER"),
+ originalPageOffsets = intArrayOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ )
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertEquals(expected, actual)
- }
+ assertEquals(expected, actual)
+ }
@Test
- fun insertFooter_append() = runTest(UnconfinedTestDispatcher()) {
- val actual = localAppend(
- pages = listOf(
- TransformablePage(
- data = listOf("b"),
- originalPageOffset = 0
- )
- ),
- source = fullLoadStates
- ).insertFooterItem("FOOTER")
+ fun insertFooter_append() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localAppend(
+ pages =
+ listOf(TransformablePage(data = listOf("b"), originalPageOffset = 0)),
+ source = fullLoadStates
+ )
+ .insertFooterItem("FOOTER")
- val expected = localAppend(
- pages = listOf(
- TransformablePage(
- data = listOf("b"),
- originalPageOffset = 0
- ),
- TransformablePage(
- data = listOf("FOOTER"),
- originalPageOffsets = intArrayOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
+ val expected =
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(data = listOf("b"), originalPageOffset = 0),
+ TransformablePage(
+ data = listOf("FOOTER"),
+ originalPageOffsets = intArrayOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ )
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertEquals(expected, actual)
- }
+ assertEquals(expected, actual)
+ }
@Test
- fun insertFooter_refresh() = runTest(UnconfinedTestDispatcher()) {
- val actual = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("a"),
- originalPageOffset = 0
- )
- ),
- source = fullLoadStates
- ).insertFooterItem("FOOTER")
+ fun insertFooter_refresh() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localRefresh(
+ pages =
+ listOf(TransformablePage(data = listOf("a"), originalPageOffset = 0)),
+ source = fullLoadStates
+ )
+ .insertFooterItem("FOOTER")
- val expected = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("a"),
- originalPageOffset = 0
- ),
- TransformablePage(
- data = listOf("FOOTER"),
- originalPageOffsets = intArrayOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
+ val expected =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(data = listOf("a"), originalPageOffset = 0),
+ TransformablePage(
+ data = listOf("FOOTER"),
+ originalPageOffsets = intArrayOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ )
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertThat(actual).isEqualTo(expected)
- }
+ assertThat(actual).isEqualTo(expected)
+ }
@Test
- fun insertFooter_empty() = runTest(UnconfinedTestDispatcher()) {
- val actual = localRefresh(
- pages = listOf(
- TransformablePage(
- data = emptyList<String>(),
- originalPageOffset = 0
- )
- ),
- source = fullLoadStates
- ).insertFooterItem("FOOTER")
+ fun insertFooter_empty() =
+ runTest(UnconfinedTestDispatcher()) {
+ val actual =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ data = emptyList<String>(),
+ originalPageOffset = 0
+ )
+ ),
+ source = fullLoadStates
+ )
+ .insertFooterItem("FOOTER")
- val expected = localRefresh(
- pages = listOf(
- TransformablePage(
- data = listOf("FOOTER"),
- originalPageOffsets = intArrayOf(0),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
+ val expected =
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ data = listOf("FOOTER"),
+ originalPageOffsets = intArrayOf(0),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ )
+ ),
+ source = fullLoadStates
)
- ),
- source = fullLoadStates
- )
- assertThat(actual).isEqualTo(expected)
- }
+ assertThat(actual).isEqualTo(expected)
+ }
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/HintHandlerTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/HintHandlerTest.kt
index 7e2df94..58b92bc 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/HintHandlerTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/HintHandlerTest.kt
@@ -44,36 +44,35 @@
}
@Test
- fun noStateForRefresh() = runTest(UnconfinedTestDispatcher()) {
- val refreshHints = kotlin.runCatching {
- hintHandler.hintFor(REFRESH)
+ fun noStateForRefresh() =
+ runTest(UnconfinedTestDispatcher()) {
+ val refreshHints = kotlin.runCatching { hintHandler.hintFor(REFRESH) }
+ assertThat(refreshHints.exceptionOrNull()).isInstanceOf<IllegalArgumentException>()
}
- assertThat(refreshHints.exceptionOrNull()).isInstanceOf<IllegalArgumentException>()
- }
@Test
fun expandChecks() {
- val initialHint = ViewportHint.Initial(
- presentedItemsAfter = 0,
- presentedItemsBefore = 0,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- ).also(hintHandler::processHint)
- hintHandler.assertValues(
- prepend = initialHint,
- append = initialHint,
- lastAccessHint = null
- )
+ val initialHint =
+ ViewportHint.Initial(
+ presentedItemsAfter = 0,
+ presentedItemsBefore = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ .also(hintHandler::processHint)
+ hintHandler.assertValues(prepend = initialHint, append = initialHint, lastAccessHint = null)
// both hints get updated w/ access hint
- val accessHint1 = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsBefore = 100,
- presentedItemsAfter = 100,
- originalPageOffsetLast = 0,
- originalPageOffsetFirst = 0
- ).also(hintHandler::processHint)
+ val accessHint1 =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 100,
+ presentedItemsAfter = 100,
+ originalPageOffsetLast = 0,
+ originalPageOffsetFirst = 0
+ )
+ .also(hintHandler::processHint)
hintHandler.assertValues(
prepend = accessHint1,
@@ -82,10 +81,10 @@
)
// new access that only affects prepend
- val accessHintPrepend = accessHint1.copy(
- presentedItemsBefore = 70,
- presentedItemsAfter = 110
- ).also(hintHandler::processHint)
+ val accessHintPrepend =
+ accessHint1
+ .copy(presentedItemsBefore = 70, presentedItemsAfter = 110)
+ .also(hintHandler::processHint)
hintHandler.assertValues(
prepend = accessHintPrepend,
append = accessHint1,
@@ -93,18 +92,24 @@
)
// new access hints that should be ignored
- val ignoredPrependHint = accessHintPrepend.copy(
- presentedItemsBefore = 90,
- ).also(hintHandler::processHint)
+ val ignoredPrependHint =
+ accessHintPrepend
+ .copy(
+ presentedItemsBefore = 90,
+ )
+ .also(hintHandler::processHint)
hintHandler.assertValues(
prepend = accessHintPrepend,
append = accessHint1,
lastAccessHint = ignoredPrependHint
)
- val accessHintAppend = accessHintPrepend.copy(
- presentedItemsAfter = 80,
- ).also(hintHandler::processHint)
+ val accessHintAppend =
+ accessHintPrepend
+ .copy(
+ presentedItemsAfter = 80,
+ )
+ .also(hintHandler::processHint)
hintHandler.assertValues(
prepend = accessHintPrepend,
append = accessHintAppend,
@@ -112,17 +117,13 @@
)
// more ignored access hints
- hintHandler.processHint(
- accessHint1
- )
+ hintHandler.processHint(accessHint1)
hintHandler.assertValues(
prepend = accessHintPrepend,
append = accessHintAppend,
lastAccessHint = accessHint1
)
- hintHandler.processHint(
- initialHint
- )
+ hintHandler.processHint(initialHint)
hintHandler.assertValues(
prepend = accessHintPrepend,
append = accessHintAppend,
@@ -130,17 +131,15 @@
)
// try changing original page offsets
- val newFirstOffset = accessHintPrepend.copy(
- originalPageOffsetFirst = 2
- ).also(hintHandler::processHint)
+ val newFirstOffset =
+ accessHintPrepend.copy(originalPageOffsetFirst = 2).also(hintHandler::processHint)
hintHandler.assertValues(
prepend = newFirstOffset,
append = newFirstOffset,
lastAccessHint = newFirstOffset
)
- val newLastOffset = newFirstOffset.copy(
- originalPageOffsetLast = 5
- ).also(hintHandler::processHint)
+ val newLastOffset =
+ newFirstOffset.copy(originalPageOffsetLast = 5).also(hintHandler::processHint)
hintHandler.assertValues(
prepend = newLastOffset,
append = newLastOffset,
@@ -150,38 +149,23 @@
@Test
fun reset() {
- val initial = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 3,
- presentedItemsBefore = 10,
- presentedItemsAfter = 10,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0
- )
+ val initial =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 3,
+ presentedItemsBefore = 10,
+ presentedItemsAfter = 10,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
hintHandler.processHint(initial)
- val appendReset = initial.copy(
- presentedItemsBefore = 20,
- presentedItemsAfter = 5
- )
- hintHandler.forceSetHint(
- APPEND,
- appendReset
- )
- hintHandler.assertValues(
- prepend = initial,
- append = appendReset,
- lastAccessHint = initial
- )
+ val appendReset = initial.copy(presentedItemsBefore = 20, presentedItemsAfter = 5)
+ hintHandler.forceSetHint(APPEND, appendReset)
+ hintHandler.assertValues(prepend = initial, append = appendReset, lastAccessHint = initial)
- val prependReset = initial.copy(
- presentedItemsBefore = 4,
- presentedItemsAfter = 19
- )
- hintHandler.forceSetHint(
- PREPEND,
- prependReset
- )
+ val prependReset = initial.copy(presentedItemsBefore = 4, presentedItemsAfter = 19)
+ hintHandler.forceSetHint(PREPEND, prependReset)
hintHandler.assertValues(
prepend = prependReset,
append = appendReset,
@@ -190,45 +174,43 @@
}
@Test
- fun resetCanReSendSameValues() = runTest(UnconfinedTestDispatcher()) {
- val hint = ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 1,
- presentedItemsAfter = 10,
- presentedItemsBefore = 10,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- val prependHints = collectAsync(
- hintHandler.hintFor(PREPEND)
- )
- val appendHints = collectAsync(
- hintHandler.hintFor(APPEND)
- )
- runCurrent()
- assertThat(prependHints.values).isEmpty()
- assertThat(appendHints.values).isEmpty()
- hintHandler.processHint(hint)
- runCurrent()
- assertThat(prependHints.values).containsExactly(hint)
- assertThat(appendHints.values).containsExactly(hint)
+ fun resetCanReSendSameValues() =
+ runTest(UnconfinedTestDispatcher()) {
+ val hint =
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsAfter = 10,
+ presentedItemsBefore = 10,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ val prependHints = collectAsync(hintHandler.hintFor(PREPEND))
+ val appendHints = collectAsync(hintHandler.hintFor(APPEND))
+ runCurrent()
+ assertThat(prependHints.values).isEmpty()
+ assertThat(appendHints.values).isEmpty()
+ hintHandler.processHint(hint)
+ runCurrent()
+ assertThat(prependHints.values).containsExactly(hint)
+ assertThat(appendHints.values).containsExactly(hint)
- // send same hint twice, it should not get dispatched
- hintHandler.processHint(hint.copy())
- runCurrent()
- assertThat(prependHints.values).containsExactly(hint)
- assertThat(appendHints.values).containsExactly(hint)
+ // send same hint twice, it should not get dispatched
+ hintHandler.processHint(hint.copy())
+ runCurrent()
+ assertThat(prependHints.values).containsExactly(hint)
+ assertThat(appendHints.values).containsExactly(hint)
- // how send that hint as reset, now it should get dispatched
- hintHandler.forceSetHint(PREPEND, hint)
- runCurrent()
- assertThat(prependHints.values).containsExactly(hint, hint)
- assertThat(appendHints.values).containsExactly(hint)
- hintHandler.forceSetHint(APPEND, hint)
- runCurrent()
- assertThat(prependHints.values).containsExactly(hint, hint)
- assertThat(appendHints.values).containsExactly(hint, hint)
- }
+ // how send that hint as reset, now it should get dispatched
+ hintHandler.forceSetHint(PREPEND, hint)
+ runCurrent()
+ assertThat(prependHints.values).containsExactly(hint, hint)
+ assertThat(appendHints.values).containsExactly(hint)
+ hintHandler.forceSetHint(APPEND, hint)
+ runCurrent()
+ assertThat(prependHints.values).containsExactly(hint, hint)
+ assertThat(appendHints.values).containsExactly(hint, hint)
+ }
private fun HintHandler.assertValues(
prepend: ViewportHint,
@@ -240,37 +222,27 @@
assertThat(hintHandler.lastAccessHint).isEqualTo(lastAccessHint)
}
- private fun HintHandler.currentValue(
- loadType: LoadType
- ): ViewportHint? {
+ private fun HintHandler.currentValue(loadType: LoadType): ViewportHint? {
var value: ViewportHint? = null
runTest(UnconfinedTestDispatcher()) {
- val job = launch {
- [email protected](loadType).take(1).collect {
- value = it
- }
- }
+ val job = launch { [email protected](loadType).take(1).collect { value = it } }
runCurrent()
job.cancel()
}
return value
}
- private suspend fun CoroutineScope.collectAsync(
- flow: Flow<ViewportHint>
- ): TestCollection {
+ private suspend fun CoroutineScope.collectAsync(flow: Flow<ViewportHint>): TestCollection {
val impl = TestCollectionImpl()
- async(context = impl.job) {
- flow.collect {
- impl.values.add(it)
- }
- }
+ async(context = impl.job) { flow.collect { impl.values.add(it) } }
return impl
}
private interface TestCollection {
val values: List<ViewportHint>
+
fun stop()
+
val latest: ViewportHint?
get() = values.lastOrNull()
}
@@ -278,6 +250,7 @@
private class TestCollectionImpl : TestCollection {
val job = Job()
override val values = mutableListOf<ViewportHint>()
+
override fun stop() {
job.cancel()
}
@@ -290,12 +263,13 @@
presentedItemsAfter: Int = [email protected],
originalPageOffsetFirst: Int = [email protected],
originalPageOffsetLast: Int = [email protected]
- ) = ViewportHint.Access(
- pageOffset = pageOffset,
- indexInPage = indexInPage,
- presentedItemsBefore = presentedItemsBefore,
- presentedItemsAfter = presentedItemsAfter,
- originalPageOffsetFirst = originalPageOffsetFirst,
- originalPageOffsetLast = originalPageOffsetLast
- )
+ ) =
+ ViewportHint.Access(
+ pageOffset = pageOffset,
+ indexInPage = indexInPage,
+ presentedItemsBefore = presentedItemsBefore,
+ presentedItemsAfter = presentedItemsAfter,
+ originalPageOffsetFirst = originalPageOffsetFirst,
+ originalPageOffsetLast = originalPageOffsetLast
+ )
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/InvalidatingPagingSourceFactoryTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/InvalidatingPagingSourceFactoryTest.kt
index 647d7c8..85a0f6d 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/InvalidatingPagingSourceFactoryTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/InvalidatingPagingSourceFactoryTest.kt
@@ -49,9 +49,7 @@
val testFactory = InvalidatingPagingSourceFactory { TestPagingSource() }
repeat(4) { testFactory() }
testFactory.pagingSources().forEachIndexed { index, pagingSource ->
- pagingSource.registerInvalidatedCallback {
- invalidateCalls[index] = true
- }
+ pagingSource.registerInvalidatedCallback { invalidateCalls[index] = true }
}
testFactory.invalidate()
assertTrue { invalidateCalls.all { it } }
@@ -69,11 +67,7 @@
var invalidateCount = 0
- testFactory.pagingSources().forEach {
- it.registerInvalidatedCallback {
- invalidateCount++
- }
- }
+ testFactory.pagingSources().forEach { it.registerInvalidatedCallback { invalidateCount++ } }
testFactory.invalidate()
@@ -96,15 +90,16 @@
}
@Test
- fun invalidate_threadSafe() = runBlocking<Unit> {
- val factory = InvalidatingPagingSourceFactory { TestPagingSource() }
- (0 until 100).map {
- async(Dispatchers.Default) {
- factory().registerInvalidatedCallback {
- factory().invalidate()
+ fun invalidate_threadSafe() =
+ runBlocking<Unit> {
+ val factory = InvalidatingPagingSourceFactory { TestPagingSource() }
+ (0 until 100)
+ .map {
+ async(Dispatchers.Default) {
+ factory().registerInvalidatedCallback { factory().invalidate() }
+ factory.invalidate()
+ }
}
- factory.invalidate()
- }
- }.awaitAll()
- }
+ .awaitAll()
+ }
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageEventTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageEventTest.kt
index 79581ce..cc26a7f 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageEventTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageEventTest.kt
@@ -33,27 +33,18 @@
page: List<T>,
originalPageOffset: Int,
placeholdersRemaining: Int
-) = if (isPrepend) {
- localPrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = originalPageOffset,
- data = page
- )
- ),
- placeholdersBefore = placeholdersRemaining,
- )
-} else {
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = originalPageOffset,
- data = page
- )
- ),
- placeholdersAfter = placeholdersRemaining,
- )
-}
+) =
+ if (isPrepend) {
+ localPrepend(
+ pages = listOf(TransformablePage(originalPageOffset = originalPageOffset, data = page)),
+ placeholdersBefore = placeholdersRemaining,
+ )
+ } else {
+ localAppend(
+ pages = listOf(TransformablePage(originalPageOffset = originalPageOffset, data = page)),
+ placeholdersAfter = placeholdersRemaining,
+ )
+ }
@OptIn(ExperimentalCoroutinesApi::class)
class PageEventTest {
@@ -112,229 +103,234 @@
}
@Test
- fun dropTransform() = runTest(UnconfinedTestDispatcher()) {
- val drop = Drop<Char>(
- loadType = PREPEND,
- minPageOffset = 0,
- maxPageOffset = 0,
- placeholdersRemaining = 0
- )
+ fun dropTransform() =
+ runTest(UnconfinedTestDispatcher()) {
+ val drop =
+ Drop<Char>(
+ loadType = PREPEND,
+ minPageOffset = 0,
+ maxPageOffset = 0,
+ placeholdersRemaining = 0
+ )
- assertSame(drop, drop.map { it + 1 })
- assertSame(drop, drop.flatMap { listOf(it, it) })
- assertSame(drop, drop.filter { false })
- }
-
- @Test
- fun stateTransform() = runTest(UnconfinedTestDispatcher()) {
- val state = localLoadStateUpdate<Char>(
- refreshLocal = LoadState.Loading
- )
-
- assertSame(state, state.map { it + 1 })
- assertSame(state, state.flatMap { listOf(it, it) })
- assertSame(state, state.filter { false })
- }
-
- @Test
- fun insertMap() = runTest(UnconfinedTestDispatcher()) {
- val insert = localAppend(
- pages = listOf(TransformablePage(listOf('a', 'b'))),
- placeholdersAfter = 4,
- )
- assertEquals(
- localAppend(
- pages = listOf(TransformablePage(listOf("a", "b"))),
- placeholdersAfter = 4,
- ),
- insert.map { it.toString() }
- )
-
- assertEquals(
- insert,
- insert.map { it.toString() }.map { it[0] }
- )
- }
-
- @Test
- fun insertMapTransformed() = runTest(UnconfinedTestDispatcher()) {
- assertEquals(
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("a", "b"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 2)
- )
- ),
- placeholdersAfter = 4,
- ),
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("a", "b"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 2)
- )
- ),
- placeholdersAfter = 4,
- ).map { it.toString() }
- )
- }
-
- @Test
- fun insertFilter() = runTest(UnconfinedTestDispatcher()) {
- val insert = localAppend(
- pages = listOf(TransformablePage(listOf('a', 'b', 'c', 'd'))),
- placeholdersAfter = 4,
- )
-
- // filter out C
- val insertNoC = insert.filter { it != 'c' }
-
- assertEquals(
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf('a', 'b', 'd'),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 1, 3)
- )
- ),
- placeholdersAfter = 4,
- ),
- insertNoC
- )
-
- // now filter out A, to validate filtration when lookup present
- assertEquals(
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf('b', 'd'),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(1, 3)
- )
- ),
- placeholdersAfter = 4,
- ),
- insertNoC.filter { it != 'a' }
- )
- }
-
- @Test
- fun insertFlatMap() = runTest(UnconfinedTestDispatcher()) {
- val insert = localAppend(
- pages = listOf(TransformablePage(listOf('a', 'b'))),
- placeholdersAfter = 4,
- )
-
- val flatMapped = insert.flatMap {
- listOf("${it}1", "${it}2")
+ assertSame(drop, drop.map { it + 1 })
+ assertSame(drop, drop.flatMap { listOf(it, it) })
+ assertSame(drop, drop.filter { false })
}
- assertEquals(
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("a1", "a2", "b1", "b2"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 0, 1, 1)
- )
- ),
- placeholdersAfter = 4,
- ),
- flatMapped
- )
+ @Test
+ fun stateTransform() =
+ runTest(UnconfinedTestDispatcher()) {
+ val state = localLoadStateUpdate<Char>(refreshLocal = LoadState.Loading)
- val flatMappedAgain = flatMapped.flatMap {
- listOf(it, "-")
+ assertSame(state, state.map { it + 1 })
+ assertSame(state, state.flatMap { listOf(it, it) })
+ assertSame(state, state.filter { false })
}
- assertEquals(
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("a1", "-", "a2", "-", "b1", "-", "b2", "-"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 0, 0, 0, 1, 1, 1, 1)
- )
+ @Test
+ fun insertMap() =
+ runTest(UnconfinedTestDispatcher()) {
+ val insert =
+ localAppend(
+ pages = listOf(TransformablePage(listOf('a', 'b'))),
+ placeholdersAfter = 4,
+ )
+ assertEquals(
+ localAppend(
+ pages = listOf(TransformablePage(listOf("a", "b"))),
+ placeholdersAfter = 4,
),
- placeholdersAfter = 4,
- ),
- flatMappedAgain
- )
- }
+ insert.map { it.toString() }
+ )
+
+ assertEquals(insert, insert.map { it.toString() }.map { it[0] })
+ }
+
+ @Test
+ fun insertMapTransformed() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertEquals(
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("a", "b"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 2)
+ )
+ ),
+ placeholdersAfter = 4,
+ ),
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("a", "b"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 2)
+ )
+ ),
+ placeholdersAfter = 4,
+ )
+ .map { it.toString() }
+ )
+ }
+
+ @Test
+ fun insertFilter() =
+ runTest(UnconfinedTestDispatcher()) {
+ val insert =
+ localAppend(
+ pages = listOf(TransformablePage(listOf('a', 'b', 'c', 'd'))),
+ placeholdersAfter = 4,
+ )
+
+ // filter out C
+ val insertNoC = insert.filter { it != 'c' }
+
+ assertEquals(
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf('a', 'b', 'd'),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 1, 3)
+ )
+ ),
+ placeholdersAfter = 4,
+ ),
+ insertNoC
+ )
+
+ // now filter out A, to validate filtration when lookup present
+ assertEquals(
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf('b', 'd'),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(1, 3)
+ )
+ ),
+ placeholdersAfter = 4,
+ ),
+ insertNoC.filter { it != 'a' }
+ )
+ }
+
+ @Test
+ fun insertFlatMap() =
+ runTest(UnconfinedTestDispatcher()) {
+ val insert =
+ localAppend(
+ pages = listOf(TransformablePage(listOf('a', 'b'))),
+ placeholdersAfter = 4,
+ )
+
+ val flatMapped = insert.flatMap { listOf("${it}1", "${it}2") }
+
+ assertEquals(
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("a1", "a2", "b1", "b2"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 0, 1, 1)
+ )
+ ),
+ placeholdersAfter = 4,
+ ),
+ flatMapped
+ )
+
+ val flatMappedAgain = flatMapped.flatMap { listOf(it, "-") }
+
+ assertEquals(
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("a1", "-", "a2", "-", "b1", "-", "b2", "-"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 0, 0, 0, 1, 1, 1, 1)
+ )
+ ),
+ placeholdersAfter = 4,
+ ),
+ flatMappedAgain
+ )
+ }
class StaticPagingData {
companion object {
- fun initParameters() = listOf(
- listOf("a", "b", "c"),
- emptyList(),
- )
+ fun initParameters() =
+ listOf(
+ listOf("a", "b", "c"),
+ emptyList(),
+ )
}
private val presenter = TestPagingDataPresenter<String>(EmptyCoroutineContext)
- @Test
- fun map_nonEmpty() = map(PagingData.from(listOf("a", "b", "c")))
+ @Test fun map_nonEmpty() = map(PagingData.from(listOf("a", "b", "c")))
- @Test
- fun map_empty() = map(PagingData.empty())
+ @Test fun map_empty() = map(PagingData.empty())
- private fun map(pagingData: PagingData<String>) = runTest(UnconfinedTestDispatcher()) {
- val transform = { it: String -> it + it }
- presenter.collectFrom(pagingData)
- val originalItems = presenter.snapshot().items
- val expectedItems = originalItems.map(transform)
- val transformedPagingData = pagingData.map { transform(it) }
- presenter.collectFrom(transformedPagingData)
- assertEquals(expectedItems, presenter.snapshot().items)
- }
+ private fun map(pagingData: PagingData<String>) =
+ runTest(UnconfinedTestDispatcher()) {
+ val transform = { it: String -> it + it }
+ presenter.collectFrom(pagingData)
+ val originalItems = presenter.snapshot().items
+ val expectedItems = originalItems.map(transform)
+ val transformedPagingData = pagingData.map { transform(it) }
+ presenter.collectFrom(transformedPagingData)
+ assertEquals(expectedItems, presenter.snapshot().items)
+ }
- @Test
- fun flatMap_nonEmpty() = flatMap(PagingData.from(listOf("a", "b", "c")))
+ @Test fun flatMap_nonEmpty() = flatMap(PagingData.from(listOf("a", "b", "c")))
- @Test
- fun flatMap_empty() = flatMap(PagingData.empty())
+ @Test fun flatMap_empty() = flatMap(PagingData.empty())
- private fun flatMap(pagingData: PagingData<String>) = runTest(UnconfinedTestDispatcher()) {
- val transform = { it: String -> listOf(it, it) }
- presenter.collectFrom(pagingData)
- val originalItems = presenter.snapshot().items
- val expectedItems = originalItems.flatMap(transform)
- val transformedPagingData = pagingData.flatMap { transform(it) }
- presenter.collectFrom(transformedPagingData)
- assertEquals(expectedItems, presenter.snapshot().items)
- }
+ private fun flatMap(pagingData: PagingData<String>) =
+ runTest(UnconfinedTestDispatcher()) {
+ val transform = { it: String -> listOf(it, it) }
+ presenter.collectFrom(pagingData)
+ val originalItems = presenter.snapshot().items
+ val expectedItems = originalItems.flatMap(transform)
+ val transformedPagingData = pagingData.flatMap { transform(it) }
+ presenter.collectFrom(transformedPagingData)
+ assertEquals(expectedItems, presenter.snapshot().items)
+ }
- @Test
- fun filter_nonEmpty() = filter(PagingData.from(listOf("a", "b", "c")))
+ @Test fun filter_nonEmpty() = filter(PagingData.from(listOf("a", "b", "c")))
- @Test
- fun filter_empty() = filter(PagingData.empty())
+ @Test fun filter_empty() = filter(PagingData.empty())
- private fun filter(pagingData: PagingData<String>) = runTest(UnconfinedTestDispatcher()) {
- val predicate = { it: String -> it != "b" }
- presenter.collectFrom(pagingData)
- val originalItems = presenter.snapshot().items
- val expectedItems = originalItems.filter(predicate)
- val transformedPagingData = pagingData.filter { predicate(it) }
- presenter.collectFrom(transformedPagingData)
- assertEquals(expectedItems, presenter.snapshot().items)
- }
+ private fun filter(pagingData: PagingData<String>) =
+ runTest(UnconfinedTestDispatcher()) {
+ val predicate = { it: String -> it != "b" }
+ presenter.collectFrom(pagingData)
+ val originalItems = presenter.snapshot().items
+ val expectedItems = originalItems.filter(predicate)
+ val transformedPagingData = pagingData.filter { predicate(it) }
+ presenter.collectFrom(transformedPagingData)
+ assertEquals(expectedItems, presenter.snapshot().items)
+ }
@Test
fun insertSeparators_nonEmpty() = insertSeparators(PagingData.from(listOf("a", "b", "c")))
- @Test
- fun insertSeparators_empty() = insertSeparators(PagingData.empty())
+ @Test fun insertSeparators_empty() = insertSeparators(PagingData.empty())
private fun insertSeparators(pagingData: PagingData<String>) =
runTest(UnconfinedTestDispatcher()) {
@@ -343,21 +339,21 @@
}
presenter.collectFrom(pagingData)
val originalItems = presenter.snapshot().items
- val expectedItems = originalItems.flatMapIndexed { index, s ->
- val result = mutableListOf<String>()
- if (index == 0) {
- transform(null, s)?.let(result::add)
+ val expectedItems =
+ originalItems.flatMapIndexed { index, s ->
+ val result = mutableListOf<String>()
+ if (index == 0) {
+ transform(null, s)?.let(result::add)
+ }
+ result.add(s)
+ transform(s, originalItems.getOrNull(index + 1))?.let(result::add)
+ if (index == originalItems.lastIndex) {
+ transform(s, null)?.let(result::add)
+ }
+ result
}
- result.add(s)
- transform(s, originalItems.getOrNull(index + 1))?.let(result::add)
- if (index == originalItems.lastIndex) {
- transform(s, null)?.let(result::add)
- }
- result
- }
- val transformedPagingData = pagingData.insertSeparators { left, right ->
- transform(left, right)
- }
+ val transformedPagingData =
+ pagingData.insertSeparators { left, right -> transform(left, right) }
presenter.collectFrom(transformedPagingData)
assertEquals(expectedItems, presenter.snapshot().items)
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageFetcherSnapshotStateTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageFetcherSnapshotStateTest.kt
index fc38034..32826ca 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageFetcherSnapshotStateTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageFetcherSnapshotStateTest.kt
@@ -35,472 +35,467 @@
val testScope = TestScope()
@Test
- fun placeholders_uncounted() = testScope.runTest {
- val pagerState = PageFetcherSnapshotState.Holder<Int, Int>(
- config = PagingConfig(2, enablePlaceholders = false)
- ).withLock { it }
+ fun placeholders_uncounted() =
+ testScope.runTest {
+ val pagerState =
+ PageFetcherSnapshotState.Holder<Int, Int>(
+ config = PagingConfig(2, enablePlaceholders = false)
+ )
+ .withLock { it }
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
- pagerState.insert(
- loadId = 0, loadType = REFRESH,
- page = Page(
- data = listOf(),
- prevKey = -1,
- nextKey = 1,
- itemsBefore = 50,
- itemsAfter = 50
+ pagerState.insert(
+ loadId = 0,
+ loadType = REFRESH,
+ page =
+ Page(
+ data = listOf(),
+ prevKey = -1,
+ nextKey = 1,
+ itemsBefore = 50,
+ itemsAfter = 50
+ )
)
- )
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
- pagerState.insert(
- loadId = 0, loadType = PREPEND,
- page = Page(
- data = listOf(),
- prevKey = -2,
- nextKey = 0,
- itemsBefore = 25
- )
- )
- pagerState.insert(
- loadId = 0, loadType = APPEND,
- page = Page(
- data = listOf(),
- prevKey = 0,
- nextKey = 2,
- itemsBefore = 25
- )
- )
-
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
-
- // Should automatically decrement remaining placeholders when counted.
- pagerState.insert(
- loadId = 0,
- loadType = PREPEND,
- page = Page(
- data = listOf(0),
- prevKey = -3,
- nextKey = 0,
- itemsBefore = COUNT_UNDEFINED,
- itemsAfter = COUNT_UNDEFINED
- )
- )
- pagerState.insert(
- loadId = 0,
- loadType = APPEND,
- page = Page(
- data = listOf(0),
- prevKey = 0,
- nextKey = 3,
- itemsBefore = COUNT_UNDEFINED,
- itemsAfter = COUNT_UNDEFINED
- )
- )
-
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
-
- pagerState.drop(
- event = PageEvent.Drop(
+ pagerState.insert(
+ loadId = 0,
loadType = PREPEND,
- minPageOffset = -2,
- maxPageOffset = -2,
- placeholdersRemaining = 100
+ page = Page(data = listOf(), prevKey = -2, nextKey = 0, itemsBefore = 25)
)
- )
- pagerState.drop(
- event = PageEvent.Drop(
+ pagerState.insert(
+ loadId = 0,
loadType = APPEND,
- minPageOffset = 2,
- maxPageOffset = 2,
- placeholdersRemaining = 100
+ page = Page(data = listOf(), prevKey = 0, nextKey = 2, itemsBefore = 25)
)
- )
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
- }
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
- @Test
- fun placeholders_counted() = testScope.runTest {
- val pagerState = PageFetcherSnapshotState.Holder<Int, Int>(
- config = PagingConfig(2, enablePlaceholders = true)
- ).withLock { it }
-
- assertEquals(0, pagerState.placeholdersBefore)
- assertEquals(0, pagerState.placeholdersAfter)
-
- pagerState.insert(
- loadId = 0,
- loadType = REFRESH,
- page = Page(
- data = listOf(),
- prevKey = -1,
- nextKey = 1,
- itemsBefore = 50,
- itemsAfter = 50
- )
- )
-
- assertEquals(50, pagerState.placeholdersBefore)
- assertEquals(50, pagerState.placeholdersAfter)
-
- pagerState.insert(
- loadId = 0,
- loadType = PREPEND,
- page = Page(
- data = listOf(),
- prevKey = -2,
- nextKey = 0,
- itemsBefore = 25,
- itemsAfter = COUNT_UNDEFINED
- )
- )
- pagerState.insert(
- loadId = 0,
- loadType = APPEND,
- page = Page(
- data = listOf(),
- prevKey = 0,
- nextKey = 2,
- itemsBefore = COUNT_UNDEFINED,
- itemsAfter = 25
- )
- )
-
- assertEquals(25, pagerState.placeholdersBefore)
- assertEquals(25, pagerState.placeholdersAfter)
-
- // Should automatically decrement remaining placeholders when counted.
- pagerState.insert(
- loadId = 0,
- loadType = PREPEND,
- page = Page(
- data = listOf(0),
- prevKey = -3,
- nextKey = 0,
- itemsBefore = COUNT_UNDEFINED,
- itemsAfter = COUNT_UNDEFINED
- )
- )
- pagerState.insert(
- loadId = 0,
- loadType = APPEND,
- page = Page(
- data = listOf(0),
- prevKey = 0,
- nextKey = 3,
- itemsBefore = COUNT_UNDEFINED,
- itemsAfter = COUNT_UNDEFINED
- )
- )
-
- assertEquals(24, pagerState.placeholdersBefore)
- assertEquals(24, pagerState.placeholdersAfter)
-
- pagerState.drop(
- event = PageEvent.Drop(
+ // Should automatically decrement remaining placeholders when counted.
+ pagerState.insert(
+ loadId = 0,
loadType = PREPEND,
- minPageOffset = -2,
- maxPageOffset = -2,
- placeholdersRemaining = 100
+ page =
+ Page(
+ data = listOf(0),
+ prevKey = -3,
+ nextKey = 0,
+ itemsBefore = COUNT_UNDEFINED,
+ itemsAfter = COUNT_UNDEFINED
+ )
)
- )
- pagerState.drop(
- event = PageEvent.Drop(
+ pagerState.insert(
+ loadId = 0,
loadType = APPEND,
- minPageOffset = 2,
- maxPageOffset = 2,
- placeholdersRemaining = 100
+ page =
+ Page(
+ data = listOf(0),
+ prevKey = 0,
+ nextKey = 3,
+ itemsBefore = COUNT_UNDEFINED,
+ itemsAfter = COUNT_UNDEFINED
+ )
)
- )
- assertEquals(100, pagerState.placeholdersBefore)
- assertEquals(100, pagerState.placeholdersAfter)
- }
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
+
+ pagerState.drop(
+ event =
+ PageEvent.Drop(
+ loadType = PREPEND,
+ minPageOffset = -2,
+ maxPageOffset = -2,
+ placeholdersRemaining = 100
+ )
+ )
+ pagerState.drop(
+ event =
+ PageEvent.Drop(
+ loadType = APPEND,
+ minPageOffset = 2,
+ maxPageOffset = 2,
+ placeholdersRemaining = 100
+ )
+ )
+
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
+ }
@Test
- fun currentPagingState() = testScope.runTest {
- val config = PagingConfig(pageSize = 2)
- val state = PageFetcherSnapshotState.Holder<Int, Int>(config = config).withLock { it }
+ fun placeholders_counted() =
+ testScope.runTest {
+ val pagerState =
+ PageFetcherSnapshotState.Holder<Int, Int>(
+ config = PagingConfig(2, enablePlaceholders = true)
+ )
+ .withLock { it }
- val pages = listOf(
- Page(
- data = listOf(2, 3),
- prevKey = 1,
- nextKey = 4
- ),
- Page(
- data = listOf(4, 5),
- prevKey = 3,
- nextKey = 6,
- itemsBefore = 4,
- itemsAfter = 4
- ),
- Page(
- data = listOf(6, 7),
- prevKey = 5,
- nextKey = 8
- )
- )
+ assertEquals(0, pagerState.placeholdersBefore)
+ assertEquals(0, pagerState.placeholdersAfter)
- state.insert(0, REFRESH, pages[1])
- state.insert(0, PREPEND, pages[0])
- state.insert(0, APPEND, pages[2])
-
- val storage = pages.toPageStore(1)
- val storageMissingPrepend = pages.drop(1).toPageStore(0)
- val storageMissingAppend = pages.dropLast(1).toPageStore(1)
- val storageExtraPrepend = pages.toMutableList().apply {
- add(
- 0,
- Page(
- data = listOf(0, 1),
- prevKey = null,
- nextKey = 2
- )
- )
- }.toPageStore(2)
- val storageExtraAppend = pages.toMutableList().apply {
- add(
- Page(
- data = listOf(8, 9),
- prevKey = 7,
- nextKey = null
- )
- )
- }.toPageStore(1)
-
- // Hint in loaded items, fetcher state == storage state.
- assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(4)))
- .isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 4,
- config = config,
- leadingPlaceholderCount = 2
- )
+ pagerState.insert(
+ loadId = 0,
+ loadType = REFRESH,
+ page =
+ Page(
+ data = listOf(),
+ prevKey = -1,
+ nextKey = 1,
+ itemsBefore = 50,
+ itemsAfter = 50
+ )
)
- // Hint in placeholders before, fetcher state == storage state.
- assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(0)))
- .isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 2
- )
+ assertEquals(50, pagerState.placeholdersBefore)
+ assertEquals(50, pagerState.placeholdersAfter)
+
+ pagerState.insert(
+ loadId = 0,
+ loadType = PREPEND,
+ page =
+ Page(
+ data = listOf(),
+ prevKey = -2,
+ nextKey = 0,
+ itemsBefore = 25,
+ itemsAfter = COUNT_UNDEFINED
+ )
+ )
+ pagerState.insert(
+ loadId = 0,
+ loadType = APPEND,
+ page =
+ Page(
+ data = listOf(),
+ prevKey = 0,
+ nextKey = 2,
+ itemsBefore = COUNT_UNDEFINED,
+ itemsAfter = 25
+ )
)
- // Hint in placeholders after, fetcher state == storage state.
- assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(9)))
- .isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 9,
- config = config,
- leadingPlaceholderCount = 2
- )
+ assertEquals(25, pagerState.placeholdersBefore)
+ assertEquals(25, pagerState.placeholdersAfter)
+
+ // Should automatically decrement remaining placeholders when counted.
+ pagerState.insert(
+ loadId = 0,
+ loadType = PREPEND,
+ page =
+ Page(
+ data = listOf(0),
+ prevKey = -3,
+ nextKey = 0,
+ itemsBefore = COUNT_UNDEFINED,
+ itemsAfter = COUNT_UNDEFINED
+ )
+ )
+ pagerState.insert(
+ loadId = 0,
+ loadType = APPEND,
+ page =
+ Page(
+ data = listOf(0),
+ prevKey = 0,
+ nextKey = 3,
+ itemsBefore = COUNT_UNDEFINED,
+ itemsAfter = COUNT_UNDEFINED
+ )
)
- // Hint in loaded items, fetcher state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(4))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 4,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
+ assertEquals(24, pagerState.placeholdersBefore)
+ assertEquals(24, pagerState.placeholdersAfter)
- // Hint in placeholders before, fetcher state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(0))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 2
+ pagerState.drop(
+ event =
+ PageEvent.Drop(
+ loadType = PREPEND,
+ minPageOffset = -2,
+ maxPageOffset = -2,
+ placeholdersRemaining = 100
+ )
)
- )
+ pagerState.drop(
+ event =
+ PageEvent.Drop(
+ loadType = APPEND,
+ minPageOffset = 2,
+ maxPageOffset = 2,
+ placeholdersRemaining = 100
+ )
+ )
- // Hint in placeholders after, fetcher state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(9))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 9,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in loaded items, fetcher state has an extra appended page.
- assertThat(
- state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(4))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 4,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders before, fetcher state has an extra appended page.
- assertThat(
- state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(0))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders after, fetcher state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(9))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 9,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in loaded items, storage state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(4))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 4,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders before, storage state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(0))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders after, storage state has an extra prepended page.
- assertThat(
- state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(9))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 9,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in loaded items, storage state has an extra appended page.
- assertThat(
- state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(4))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 4,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders before, storage state has an extra appended page.
- assertThat(
- state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(0))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 0,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
-
- // Hint in placeholders after, fetcher state has an extra appended page.
- assertThat(
- state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(9))
- ).isEqualTo(
- PagingState(
- pages = pages,
- anchorPosition = 9,
- config = config,
- leadingPlaceholderCount = 2
- )
- )
- }
+ assertEquals(100, pagerState.placeholdersBefore)
+ assertEquals(100, pagerState.placeholdersAfter)
+ }
@Test
- fun loadStates() = testScope.runTest {
- val config = PagingConfig(pageSize = 2)
- val state = PageFetcherSnapshotState.Holder<Int, Int>(config = config).withLock { it }
+ fun currentPagingState() =
+ testScope.runTest {
+ val config = PagingConfig(pageSize = 2)
+ val state = PageFetcherSnapshotState.Holder<Int, Int>(config = config).withLock { it }
- // assert initial state
- assertThat(state.sourceLoadStates.snapshot()).isEqualTo(loadStates(refresh = Loading))
+ val pages =
+ listOf(
+ Page(data = listOf(2, 3), prevKey = 1, nextKey = 4),
+ Page(
+ data = listOf(4, 5),
+ prevKey = 3,
+ nextKey = 6,
+ itemsBefore = 4,
+ itemsAfter = 4
+ ),
+ Page(data = listOf(6, 7), prevKey = 5, nextKey = 8)
+ )
- // assert APPEND state is updated
- state.sourceLoadStates.set(APPEND, NotLoading.Complete)
- assertThat(state.sourceLoadStates.snapshot()).isEqualTo(
- loadStates(
- refresh = Loading,
- append = NotLoading.Complete
- )
- )
+ state.insert(0, REFRESH, pages[1])
+ state.insert(0, PREPEND, pages[0])
+ state.insert(0, APPEND, pages[2])
- // assert REFRESH state is incrementally updated
- state.sourceLoadStates.set(REFRESH, NotLoading.Incomplete)
- assertThat(state.sourceLoadStates.snapshot()).isEqualTo(
- loadStates(
- refresh = NotLoading.Incomplete,
- append = NotLoading.Complete,
- )
- )
- assertThat(state.sourceLoadStates.get(APPEND)).isEqualTo(NotLoading.Complete)
- }
+ val storage = pages.toPageStore(1)
+ val storageMissingPrepend = pages.drop(1).toPageStore(0)
+ val storageMissingAppend = pages.dropLast(1).toPageStore(1)
+ val storageExtraPrepend =
+ pages
+ .toMutableList()
+ .apply { add(0, Page(data = listOf(0, 1), prevKey = null, nextKey = 2)) }
+ .toPageStore(2)
+ val storageExtraAppend =
+ pages
+ .toMutableList()
+ .apply { add(Page(data = listOf(8, 9), prevKey = 7, nextKey = null)) }
+ .toPageStore(1)
+
+ // Hint in loaded items, fetcher state == storage state.
+ assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(4)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 4,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders before, fetcher state == storage state.
+ assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(0)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders after, fetcher state == storage state.
+ assertThat(state.currentPagingState(storage.accessHintForPresenterIndex(9)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 9,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in loaded items, fetcher state has an extra prepended page.
+ assertThat(
+ state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(4))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 4,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders before, fetcher state has an extra prepended page.
+ assertThat(
+ state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(0))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders after, fetcher state has an extra prepended page.
+ assertThat(
+ state.currentPagingState(storageMissingPrepend.accessHintForPresenterIndex(9))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 9,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in loaded items, fetcher state has an extra appended page.
+ assertThat(
+ state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(4))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 4,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders before, fetcher state has an extra appended page.
+ assertThat(
+ state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(0))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders after, fetcher state has an extra prepended page.
+ assertThat(
+ state.currentPagingState(storageMissingAppend.accessHintForPresenterIndex(9))
+ )
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 9,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in loaded items, storage state has an extra prepended page.
+ assertThat(state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(4)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 4,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders before, storage state has an extra prepended page.
+ assertThat(state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(0)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders after, storage state has an extra prepended page.
+ assertThat(state.currentPagingState(storageExtraPrepend.accessHintForPresenterIndex(9)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 9,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in loaded items, storage state has an extra appended page.
+ assertThat(state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(4)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 4,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders before, storage state has an extra appended page.
+ assertThat(state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(0)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+
+ // Hint in placeholders after, fetcher state has an extra appended page.
+ assertThat(state.currentPagingState(storageExtraAppend.accessHintForPresenterIndex(9)))
+ .isEqualTo(
+ PagingState(
+ pages = pages,
+ anchorPosition = 9,
+ config = config,
+ leadingPlaceholderCount = 2
+ )
+ )
+ }
+
+ @Test
+ fun loadStates() =
+ testScope.runTest {
+ val config = PagingConfig(pageSize = 2)
+ val state = PageFetcherSnapshotState.Holder<Int, Int>(config = config).withLock { it }
+
+ // assert initial state
+ assertThat(state.sourceLoadStates.snapshot()).isEqualTo(loadStates(refresh = Loading))
+
+ // assert APPEND state is updated
+ state.sourceLoadStates.set(APPEND, NotLoading.Complete)
+ assertThat(state.sourceLoadStates.snapshot())
+ .isEqualTo(loadStates(refresh = Loading, append = NotLoading.Complete))
+
+ // assert REFRESH state is incrementally updated
+ state.sourceLoadStates.set(REFRESH, NotLoading.Incomplete)
+ assertThat(state.sourceLoadStates.snapshot())
+ .isEqualTo(
+ loadStates(
+ refresh = NotLoading.Incomplete,
+ append = NotLoading.Complete,
+ )
+ )
+ assertThat(state.sourceLoadStates.get(APPEND)).isEqualTo(NotLoading.Complete)
+ }
private fun List<Page<Int, Int>>.toPageStore(initialPageIndex: Int): PageStore<Int> {
val pageSize = 2
val initialPage = get(initialPageIndex)
- val storage = PageStore(
- insertEvent = localRefresh(
- pages = listOf(TransformablePage(initialPage.data)),
- placeholdersBefore = initialPage.itemsBefore,
- placeholdersAfter = initialPage.itemsAfter,
+ val storage =
+ PageStore(
+ insertEvent =
+ localRefresh(
+ pages = listOf(TransformablePage(initialPage.data)),
+ placeholdersBefore = initialPage.itemsBefore,
+ placeholdersAfter = initialPage.itemsAfter,
+ )
)
- )
for (i in 0 until initialPageIndex) {
val offset = i + 1
storage.processEvent(
localPrepend(
- pages = listOf(
- TransformablePage(originalPageOffset = -offset, data = get(i).data)
- ),
+ pages =
+ listOf(TransformablePage(originalPageOffset = -offset, data = get(i).data)),
placeholdersBefore = initialPage.itemsBefore - (offset * pageSize),
)
)
@@ -510,9 +505,8 @@
val offset = i - initialPageIndex
storage.processEvent(
localAppend(
- pages = listOf(
- TransformablePage(originalPageOffset = offset, data = get(i).data)
- ),
+ pages =
+ listOf(TransformablePage(originalPageOffset = offset, data = get(i).data)),
placeholdersAfter = initialPage.itemsAfter - (offset * pageSize),
)
)
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageStoreTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageStoreTest.kt
index 76207ce..2c0ffce 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageStoreTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PageStoreTest.kt
@@ -30,45 +30,46 @@
leadingNullCount: Int = COUNT_UNDEFINED,
trailingNullCount: Int = COUNT_UNDEFINED,
indexOfInitialPage: Int = 0
-) = PageStore(
- localRefresh(
- pages = pages.mapIndexed { index, list ->
- TransformablePage(
- originalPageOffset = index - indexOfInitialPage,
- data = list
- )
- },
- placeholdersBefore = leadingNullCount,
- placeholdersAfter = trailingNullCount,
+) =
+ PageStore(
+ localRefresh(
+ pages =
+ pages.mapIndexed { index, list ->
+ TransformablePage(originalPageOffset = index - indexOfInitialPage, data = list)
+ },
+ placeholdersBefore = leadingNullCount,
+ placeholdersAfter = trailingNullCount,
+ )
)
-)
internal fun <T : Any> PageStore<T>.insertPage(
isPrepend: Boolean,
page: List<T>,
placeholdersRemaining: Int,
-) = processEvent(
- adjacentInsertEvent(
- isPrepend = isPrepend,
- page = page,
- originalPageOffset = 0,
- placeholdersRemaining = placeholdersRemaining
+) =
+ processEvent(
+ adjacentInsertEvent(
+ isPrepend = isPrepend,
+ page = page,
+ originalPageOffset = 0,
+ placeholdersRemaining = placeholdersRemaining
+ )
)
-)
internal fun <T : Any> PageStore<T>.dropPages(
isPrepend: Boolean,
minPageOffset: Int,
maxPageOffset: Int,
placeholdersRemaining: Int,
-) = processEvent(
- PageEvent.Drop(
- loadType = if (isPrepend) PREPEND else APPEND,
- minPageOffset = minPageOffset,
- maxPageOffset = maxPageOffset,
- placeholdersRemaining = placeholdersRemaining
+) =
+ processEvent(
+ PageEvent.Drop(
+ loadType = if (isPrepend) PREPEND else APPEND,
+ minPageOffset = minPageOffset,
+ maxPageOffset = maxPageOffset,
+ placeholdersRemaining = placeholdersRemaining
+ )
)
-)
internal fun <T : Any> PageStore<T>.asList() = List(size) { get(it) }
@@ -80,28 +81,30 @@
newItems: Int,
newNulls: Int = COUNT_UNDEFINED,
) {
- val data = PageStore(
- pages = mutableListOf(List(initialItems) { 'a' + it }),
- leadingNullCount = 0,
- trailingNullCount = initialNulls,
- indexOfInitialPage = 0
- )
+ val data =
+ PageStore(
+ pages = mutableListOf(List(initialItems) { 'a' + it }),
+ leadingNullCount = 0,
+ trailingNullCount = initialNulls,
+ indexOfInitialPage = 0
+ )
val inserted = List(newItems) { 'a' + it + initialItems }
- val event = data.insertPage(
- isPrepend = false,
- page = inserted,
- placeholdersRemaining = newNulls,
- )
+ val event =
+ data.insertPage(
+ isPrepend = false,
+ page = inserted,
+ placeholdersRemaining = newNulls,
+ )
// Assert list contents first (since this shows more obvious errors)...
- val expectedNulls = if (newNulls != COUNT_UNDEFINED) {
- newNulls
- } else {
- maxOf(initialNulls - newItems, 0)
- }
- val expectedData =
- List(initialItems + newItems) { 'a' + it } + List(expectedNulls) { null }
+ val expectedNulls =
+ if (newNulls != COUNT_UNDEFINED) {
+ newNulls
+ } else {
+ maxOf(initialNulls - newItems, 0)
+ }
+ val expectedData = List(initialItems + newItems) { 'a' + it } + List(expectedNulls) { null }
assertEquals(expectedData, data.asList())
// Then assert we got the right PagingDataEvent
@@ -122,27 +125,30 @@
newItems: Int,
newNulls: Int,
) {
- val data = PageStore(
- pages = mutableListOf(List(initialItems) { 'z' + it - initialItems - 1 }),
- leadingNullCount = initialNulls,
- trailingNullCount = 0,
- indexOfInitialPage = 0
- )
+ val data =
+ PageStore(
+ pages = mutableListOf(List(initialItems) { 'z' + it - initialItems - 1 }),
+ leadingNullCount = initialNulls,
+ trailingNullCount = 0,
+ indexOfInitialPage = 0
+ )
val endItemCount = newItems + initialItems
val inserted = List(newItems) { 'z' + it - endItemCount - 1 }
- val event = data.insertPage(
- isPrepend = true,
- page = inserted,
- placeholdersRemaining = newNulls,
- )
+ val event =
+ data.insertPage(
+ isPrepend = true,
+ page = inserted,
+ placeholdersRemaining = newNulls,
+ )
// Assert list contents first (since this shows more obvious errors)...
- val expectedNulls = if (newNulls != COUNT_UNDEFINED) {
- newNulls
- } else {
- maxOf(initialNulls - newItems, 0)
- }
+ val expectedNulls =
+ if (newNulls != COUNT_UNDEFINED) {
+ newNulls
+ } else {
+ maxOf(initialNulls - newItems, 0)
+ }
val expectedData =
List(expectedNulls) { null } + List(endItemCount) { 'z' + it - endItemCount - 1 }
assertEquals(expectedData, data.asList())
@@ -169,68 +175,76 @@
}
@Test
- fun insertPageEmpty() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 0,
- newItems = 0,
- newNulls = 0,
- )
+ fun insertPageEmpty() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 0,
+ newNulls = 0,
+ )
@Test
- fun insertPageSimple() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 0,
- newItems = 2,
- newNulls = 0,
- )
+ fun insertPageSimple() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 2,
+ newNulls = 0,
+ )
@Test
- fun insertPageSimplePlaceholders() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 4,
- newItems = 2,
- newNulls = 2,
- )
+ fun insertPageSimplePlaceholders() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 4,
+ newItems = 2,
+ newNulls = 2,
+ )
@Test
- fun insertPageInitPlaceholders() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 0,
- newItems = 2,
- newNulls = 3,
- )
+ fun insertPageInitPlaceholders() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 2,
+ newNulls = 3,
+ )
@Test
- fun insertPageInitJustPlaceholders() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 0,
- newItems = 0,
- newNulls = 3,
- )
+ fun insertPageInitJustPlaceholders() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 0,
+ newNulls = 3,
+ )
@Test
- fun insertPageInsertNulls() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 3,
- newItems = 2,
- newNulls = 2,
- )
+ fun insertPageInsertNulls() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 3,
+ newItems = 2,
+ newNulls = 2,
+ )
@Test
- fun insertPageRemoveNulls() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 7,
- newItems = 2,
- newNulls = 0,
- )
+ fun insertPageRemoveNulls() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 7,
+ newItems = 2,
+ newNulls = 0,
+ )
@Test
- fun insertPageReduceNulls() = verifyPrependAppend(
- initialItems = 2,
- initialNulls = 10,
- newItems = 3,
- newNulls = 4,
- )
+ fun insertPageReduceNulls() =
+ verifyPrependAppend(
+ initialItems = 2,
+ initialNulls = 10,
+ newItems = 3,
+ newNulls = 4,
+ )
private fun verifyDropEnd(
initialPages: List<List<Char>>,
@@ -243,21 +257,23 @@
fail("require at least 2 pages")
}
- val data = PageStore(
- pages = initialPages.toMutableList(),
- leadingNullCount = 0,
- trailingNullCount = initialNulls,
- indexOfInitialPage = 0
- )
+ val data =
+ PageStore(
+ pages = initialPages.toMutableList(),
+ leadingNullCount = 0,
+ trailingNullCount = initialNulls,
+ indexOfInitialPage = 0
+ )
assertEquals(initialPages.flatten() + List<Char?>(initialNulls) { null }, data.asList())
- val event = data.dropPages(
- isPrepend = false,
- minPageOffset = initialPages.lastIndex - (pagesToDrop - 1),
- maxPageOffset = initialPages.lastIndex,
- placeholdersRemaining = newNulls,
- )
+ val event =
+ data.dropPages(
+ isPrepend = false,
+ minPageOffset = initialPages.lastIndex - (pagesToDrop - 1),
+ maxPageOffset = initialPages.lastIndex,
+ placeholdersRemaining = newNulls,
+ )
// assert PagingDataEvent and final list state
assertEquals(
@@ -284,24 +300,26 @@
fail("require at least 2 pages")
}
- val data = PageStore(
- pages = initialPages.reversed().toMutableList(),
- leadingNullCount = initialNulls,
- trailingNullCount = 0,
- indexOfInitialPage = 0
- )
+ val data =
+ PageStore(
+ pages = initialPages.reversed().toMutableList(),
+ leadingNullCount = initialNulls,
+ trailingNullCount = 0,
+ indexOfInitialPage = 0
+ )
assertEquals(
List<Char?>(initialNulls) { null } + initialPages.reversed().flatten(),
data.asList()
)
- val event = data.dropPages(
- isPrepend = true,
- minPageOffset = 0,
- maxPageOffset = pagesToDrop - 1,
- placeholdersRemaining = newNulls,
- )
+ val event =
+ data.dropPages(
+ isPrepend = true,
+ minPageOffset = 0,
+ maxPageOffset = pagesToDrop - 1,
+ placeholdersRemaining = newNulls,
+ )
// assert PagingDataEvent and final list state
assertEquals(
@@ -328,115 +346,98 @@
}
@Test
- fun dropPageMulti() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 0,
- newNulls = 0,
- pagesToDrop = 2,
- )
+ fun dropPageMulti() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 0,
+ newNulls = 0,
+ pagesToDrop = 2,
+ )
@Test
- fun dropPageReturnNulls() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 1,
- newNulls = 4,
- pagesToDrop = 2,
- )
+ fun dropPageReturnNulls() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 1,
+ newNulls = 4,
+ pagesToDrop = 2,
+ )
@Test
- fun dropPageFromNoNullsToHavingNulls() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 0,
- newNulls = 3,
- pagesToDrop = 2,
- )
+ fun dropPageFromNoNullsToHavingNulls() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 0,
+ newNulls = 3,
+ pagesToDrop = 2,
+ )
@Test
- fun dropPageChangeRemovePlaceholders() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 2,
- newNulls = 4,
- pagesToDrop = 2,
- )
+ fun dropPageChangeRemovePlaceholders() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 2,
+ newNulls = 4,
+ pagesToDrop = 2,
+ )
@Test
- fun dropPageChangeRemoveItems() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 0,
- newNulls = 1,
- pagesToDrop = 2,
- )
+ fun dropPageChangeRemoveItems() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 0,
+ newNulls = 1,
+ pagesToDrop = 2,
+ )
@Test
- fun dropPageChangeDoubleRemove() = verifyDrop(
- initialPages = listOf(
- listOf('a', 'b'),
- listOf('c', 'd'),
- listOf('e')
- ),
- initialNulls = 3,
- newNulls = 1,
- pagesToDrop = 2,
- )
+ fun dropPageChangeDoubleRemove() =
+ verifyDrop(
+ initialPages = listOf(listOf('a', 'b'), listOf('c', 'd'), listOf('e')),
+ initialNulls = 3,
+ newNulls = 1,
+ pagesToDrop = 2,
+ )
@Test
fun getOutOfBounds() {
- val storage = PageStore(
- pages = mutableListOf(listOf('a')),
- leadingNullCount = 1,
- trailingNullCount = 1,
- indexOfInitialPage = 0
- )
- assertFailsWith<IndexOutOfBoundsException> {
- storage.get(-1)
- }
- assertFailsWith<IndexOutOfBoundsException> {
- storage.get(4)
- }
+ val storage =
+ PageStore(
+ pages = mutableListOf(listOf('a')),
+ leadingNullCount = 1,
+ trailingNullCount = 1,
+ indexOfInitialPage = 0
+ )
+ assertFailsWith<IndexOutOfBoundsException> { storage.get(-1) }
+ assertFailsWith<IndexOutOfBoundsException> { storage.get(4) }
}
// TODO storageCount test
@Test
fun snapshot_uncounted() {
- val pageStore = PageStore(
- insertEvent = localRefresh(
- pages = listOf(TransformablePage(listOf('a'))),
+ val pageStore =
+ PageStore(
+ insertEvent =
+ localRefresh(
+ pages = listOf(TransformablePage(listOf('a'))),
+ )
)
- )
assertEquals<List<Char?>>(listOf('a'), pageStore.snapshot())
}
@Test
fun snapshot_counted() {
- val pageStore = PageStore(
- insertEvent = localRefresh(
- pages = listOf(TransformablePage(listOf('a'))),
- placeholdersBefore = 1,
- placeholdersAfter = 3,
+ val pageStore =
+ PageStore(
+ insertEvent =
+ localRefresh(
+ pages = listOf(TransformablePage(listOf('a'))),
+ placeholdersBefore = 1,
+ placeholdersAfter = 3,
+ )
)
- )
assertEquals(listOf(null, 'a', null, null, null), pageStore.snapshot())
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingConfigTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingConfigTest.kt
index d5ad204..ddbd749 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingConfigTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingConfigTest.kt
@@ -35,22 +35,14 @@
@Test
fun requirePlaceholdersOrPrefetch() {
assertFailsWith<IllegalArgumentException> {
- PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- prefetchDistance = 0
- )
+ PagingConfig(pageSize = 10, enablePlaceholders = false, prefetchDistance = 0)
}
}
@Test
fun prefetchWindowMustFitInMaxSize() {
assertFailsWith<IllegalArgumentException> {
- PagingConfig(
- pageSize = 3,
- prefetchDistance = 4,
- maxSize = 10
- )
+ PagingConfig(pageSize = 3, prefetchDistance = 4, maxSize = 10)
}
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt
index 96166ac..d20f8c9 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingDataPresenterTest.kt
@@ -52,1407 +52,1423 @@
import kotlinx.coroutines.test.runTest
/**
- * run some tests with cached-in to ensure caching does not change behavior in the single
- * consumer cases.
+ * run some tests with cached-in to ensure caching does not change behavior in the single consumer
+ * cases.
*/
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
class PagingDataPresenterTest {
private val testScope = TestScope(UnconfinedTestDispatcher())
@Test
- fun collectFrom_static() = testScope.runTest {
- val presenter = SimplePresenter()
- val receiver = UiReceiverFake()
+ fun collectFrom_static() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val receiver = UiReceiverFake()
- val job1 = launch {
- presenter.collectFrom(infinitelySuspendingPagingData(receiver))
+ val job1 = launch { presenter.collectFrom(infinitelySuspendingPagingData(receiver)) }
+ advanceUntilIdle()
+ job1.cancel()
+
+ val job2 = launch { presenter.collectFrom(PagingData.empty()) }
+ advanceUntilIdle()
+ job2.cancel()
+
+ // Static replacement should also replace the UiReceiver from previous generation.
+ presenter.retry()
+ presenter.refresh()
+ advanceUntilIdle()
+
+ assertFalse { receiver.retryEvents.isNotEmpty() }
+ assertFalse { receiver.refreshEvents.isNotEmpty() }
}
- advanceUntilIdle()
- job1.cancel()
-
- val job2 = launch {
- presenter.collectFrom(PagingData.empty())
- }
- advanceUntilIdle()
- job2.cancel()
-
- // Static replacement should also replace the UiReceiver from previous generation.
- presenter.retry()
- presenter.refresh()
- advanceUntilIdle()
-
- assertFalse { receiver.retryEvents.isNotEmpty() }
- assertFalse { receiver.refreshEvents.isNotEmpty() }
- }
@Test
- fun collectFrom_twice() = testScope.runTest {
- val presenter = SimplePresenter()
+ fun collectFrom_twice() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- launch { presenter.collectFrom(infinitelySuspendingPagingData()) }
- .cancel()
- launch { presenter.collectFrom(infinitelySuspendingPagingData()) }
- .cancel()
- }
+ launch { presenter.collectFrom(infinitelySuspendingPagingData()) }.cancel()
+ launch { presenter.collectFrom(infinitelySuspendingPagingData()) }.cancel()
+ }
@Test
- fun collectFrom_twiceConcurrently() = testScope.runTest {
- val presenter = SimplePresenter()
+ fun collectFrom_twiceConcurrently() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- val job1 = launch {
- presenter.collectFrom(infinitelySuspendingPagingData())
+ val job1 = launch { presenter.collectFrom(infinitelySuspendingPagingData()) }
+
+ // Ensure job1 is running.
+ assertTrue { job1.isActive }
+
+ val job2 = launch { presenter.collectFrom(infinitelySuspendingPagingData()) }
+
+ // job2 collection should complete job1 but not cancel.
+ assertFalse { job1.isCancelled }
+ assertTrue { job1.isCompleted }
+ job2.cancel()
}
- // Ensure job1 is running.
- assertTrue { job1.isActive }
-
- val job2 = launch {
- presenter.collectFrom(infinitelySuspendingPagingData())
- }
-
- // job2 collection should complete job1 but not cancel.
- assertFalse { job1.isCancelled }
- assertTrue { job1.isCompleted }
- job2.cancel()
- }
-
@Test
- fun retry() = testScope.runTest {
- val presenter = SimplePresenter()
- val receiver = UiReceiverFake()
+ fun retry() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val receiver = UiReceiverFake()
- val job = launch {
- presenter.collectFrom(infinitelySuspendingPagingData(receiver))
+ val job = launch { presenter.collectFrom(infinitelySuspendingPagingData(receiver)) }
+
+ presenter.retry()
+ assertEquals(1, receiver.retryEvents.size)
+
+ job.cancel()
}
- presenter.retry()
- assertEquals(1, receiver.retryEvents.size)
-
- job.cancel()
- }
-
@Test
- fun refresh() = testScope.runTest {
- val presenter = SimplePresenter()
- val receiver = UiReceiverFake()
+ fun refresh() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val receiver = UiReceiverFake()
- val job = launch {
- presenter.collectFrom(infinitelySuspendingPagingData(receiver))
+ val job = launch { presenter.collectFrom(infinitelySuspendingPagingData(receiver)) }
+
+ presenter.refresh()
+
+ assertEquals(1, receiver.refreshEvents.size)
+
+ job.cancel()
}
- presenter.refresh()
-
- assertEquals(1, receiver.refreshEvents.size)
-
- job.cancel()
- }
-
@Test
- fun uiReceiverSetImmediately() = testScope.runTest {
- val presenter = SimplePresenter()
- val receiver = UiReceiverFake()
- val pagingData1 = infinitelySuspendingPagingData(uiReceiver = receiver)
+ fun uiReceiverSetImmediately() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val receiver = UiReceiverFake()
+ val pagingData1 = infinitelySuspendingPagingData(uiReceiver = receiver)
- val job1 = launch {
- presenter.collectFrom(pagingData1)
+ val job1 = launch { presenter.collectFrom(pagingData1) }
+ assertTrue(job1.isActive) // ensure job started
+
+ assertThat(receiver.refreshEvents).hasSize(0)
+
+ presenter.refresh()
+ // double check that the pagingdata's receiver was registered and had received refresh
+ // call
+ // before any PageEvent is collected/presented
+ assertThat(receiver.refreshEvents).hasSize(1)
+
+ job1.cancel()
}
- assertTrue(job1.isActive) // ensure job started
-
- assertThat(receiver.refreshEvents).hasSize(0)
-
- presenter.refresh()
- // double check that the pagingdata's receiver was registered and had received refresh call
- // before any PageEvent is collected/presented
- assertThat(receiver.refreshEvents).hasSize(1)
-
- job1.cancel()
- }
@Test
- fun hintReceiverSetAfterNewListPresented() = testScope.runTest {
- val presenter = SimplePresenter()
+ fun hintReceiverSetAfterNewListPresented() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- // first generation, load something so next gen can access index to trigger hint
- val hintReceiver1 = HintReceiverFake()
- val flow = flowOf(
- localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
- )
+ // first generation, load something so next gen can access index to trigger hint
+ val hintReceiver1 = HintReceiverFake()
+ val flow =
+ flowOf(
+ localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
+ )
- val job1 = launch {
- presenter.collectFrom(PagingData(flow, dummyUiReceiver, hintReceiver1))
- }
+ val job1 = launch {
+ presenter.collectFrom(PagingData(flow, dummyUiReceiver, hintReceiver1))
+ }
- // access any loaded item to make sure hint is sent
- presenter[3]
- assertThat(hintReceiver1.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 3,
- presentedItemsBefore = 3,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
+ // access any loaded item to make sure hint is sent
+ presenter[3]
+ assertThat(hintReceiver1.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 3,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+
+ // trigger second generation
+ presenter.refresh()
+
+ // second generation
+ val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ val hintReceiver2 = HintReceiverFake()
+ val job2 = launch {
+ presenter.collectFrom(
+ PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver2)
+ )
+ }
+
+ // we send the initial load state. this should NOT cause second gen hint receiver
+ // to register
+ pageEventCh.trySend(localLoadStateUpdate(refreshLocal = Loading))
+ assertThat(presenter.nonNullLoadStateFlow.first())
+ .isEqualTo(localLoadStatesOf(refreshLocal = Loading))
+
+ // ensure both hint receivers are idle before sending a hint
+ assertThat(hintReceiver1.hints).isEmpty()
+ assertThat(hintReceiver2.hints).isEmpty()
+
+ // try sending a hint, should be sent to first receiver
+ presenter[4]
+ assertThat(hintReceiver1.hints).hasSize(1)
+ assertThat(hintReceiver2.hints).isEmpty()
+
+ // now we send actual refresh load and make sure its presented
+ pageEventCh.trySend(
+ localRefresh(
+ pages = listOf(TransformablePage(listOf(20, 21, 22, 23, 24))),
+ placeholdersBefore = 20,
+ placeholdersAfter = 75
+ ),
)
- )
+ assertThat(presenter.snapshot().items).containsExactlyElementsIn(20 until 25)
- // trigger second generation
- presenter.refresh()
+ // access any loaded item to make sure hint is sent to proper receiver
+ presenter[3]
+ // second receiver was registered and received the initial viewport hint
+ assertThat(hintReceiver1.hints).isEmpty()
+ assertThat(hintReceiver2.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -17,
+ presentedItemsBefore = -17,
+ presentedItemsAfter = 21,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
- // second generation
- val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- val hintReceiver2 = HintReceiverFake()
- val job2 = launch {
- presenter.collectFrom(
- PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver2)
- )
+ job2.cancel()
+ job1.cancel()
}
- // we send the initial load state. this should NOT cause second gen hint receiver
- // to register
- pageEventCh.trySend(
- localLoadStateUpdate(refreshLocal = Loading)
- )
- assertThat(presenter.nonNullLoadStateFlow.first()).isEqualTo(
- localLoadStatesOf(refreshLocal = Loading)
- )
-
- // ensure both hint receivers are idle before sending a hint
- assertThat(hintReceiver1.hints).isEmpty()
- assertThat(hintReceiver2.hints).isEmpty()
-
- // try sending a hint, should be sent to first receiver
- presenter[4]
- assertThat(hintReceiver1.hints).hasSize(1)
- assertThat(hintReceiver2.hints).isEmpty()
-
- // now we send actual refresh load and make sure its presented
- pageEventCh.trySend(
- localRefresh(
- pages = listOf(TransformablePage(listOf(20, 21, 22, 23, 24))),
- placeholdersBefore = 20,
- placeholdersAfter = 75
- ),
- )
- assertThat(presenter.snapshot().items).containsExactlyElementsIn(20 until 25)
-
- // access any loaded item to make sure hint is sent to proper receiver
- presenter[3]
- // second receiver was registered and received the initial viewport hint
- assertThat(hintReceiver1.hints).isEmpty()
- assertThat(hintReceiver2.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -17,
- presentedItemsBefore = -17,
- presentedItemsAfter = 21,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
-
- job2.cancel()
- job1.cancel()
- }
-
- @Test
- fun refreshOnLatestGenerationReceiver() = refreshOnLatestGenerationReceiver(false)
+ @Test fun refreshOnLatestGenerationReceiver() = refreshOnLatestGenerationReceiver(false)
@Test
fun refreshOnLatestGenerationReceiver_collectWithCachedIn() =
refreshOnLatestGenerationReceiver(true)
private fun refreshOnLatestGenerationReceiver(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn) { presenter, _,
- uiReceivers, hintReceivers ->
- // first gen
- advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ runTest(collectWithCachedIn) { presenter, _, uiReceivers, hintReceivers ->
+ // first gen
+ advanceUntilIdle()
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // append a page so we can cache an anchorPosition of [8]
- presenter[8]
- advanceUntilIdle()
+ // append a page so we can cache an anchorPosition of [8]
+ presenter[8]
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- // trigger gen 2, the refresh signal should to sent to gen 1
- presenter.refresh()
- assertThat(uiReceivers[0].refreshEvents).hasSize(1)
- assertThat(uiReceivers[1].refreshEvents).hasSize(0)
+ // trigger gen 2, the refresh signal should to sent to gen 1
+ presenter.refresh()
+ assertThat(uiReceivers[0].refreshEvents).hasSize(1)
+ assertThat(uiReceivers[1].refreshEvents).hasSize(0)
- // trigger gen 3, refresh signal should be sent to gen 2
- presenter.refresh()
- assertThat(uiReceivers[0].refreshEvents).hasSize(1)
- assertThat(uiReceivers[1].refreshEvents).hasSize(1)
- advanceUntilIdle()
+ // trigger gen 3, refresh signal should be sent to gen 2
+ presenter.refresh()
+ assertThat(uiReceivers[0].refreshEvents).hasSize(1)
+ assertThat(uiReceivers[1].refreshEvents).hasSize(1)
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(8 until 17)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(8 until 17)
- // access any item to make sure gen 3 receiver is recipient of the hint
- presenter[0]
- assertThat(hintReceivers[2].hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 8,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
- }
+ // access any item to make sure gen 3 receiver is recipient of the hint
+ presenter[0]
+ assertThat(hintReceivers[2].hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 8,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+ }
- @Test
- fun retryOnLatestGenerationReceiver() = retryOnLatestGenerationReceiver(false)
+ @Test fun retryOnLatestGenerationReceiver() = retryOnLatestGenerationReceiver(false)
@Test
fun retryOnLatestGenerationReceiver_collectWithCachedIn() =
retryOnLatestGenerationReceiver(true)
private fun retryOnLatestGenerationReceiver(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn) { presenter, pagingSources,
- uiReceivers, hintReceivers ->
+ runTest(collectWithCachedIn) { presenter, pagingSources, uiReceivers, hintReceivers ->
- // first gen
- advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ // first gen
+ advanceUntilIdle()
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // append a page so we can cache an anchorPosition of [8]
- presenter[8]
- advanceUntilIdle()
+ // append a page so we can cache an anchorPosition of [8]
+ presenter[8]
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- // trigger gen 2, the refresh signal should be sent to gen 1
- presenter.refresh()
- assertThat(uiReceivers[0].refreshEvents).hasSize(1)
- assertThat(uiReceivers[1].refreshEvents).hasSize(0)
+ // trigger gen 2, the refresh signal should be sent to gen 1
+ presenter.refresh()
+ assertThat(uiReceivers[0].refreshEvents).hasSize(1)
+ assertThat(uiReceivers[1].refreshEvents).hasSize(0)
- // to recreate a real use-case of retry based on load error
- pagingSources[1].errorNextLoad = true
- advanceUntilIdle()
- // presenter should still have first gen presenter
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
+ // to recreate a real use-case of retry based on load error
+ pagingSources[1].errorNextLoad = true
+ advanceUntilIdle()
+ // presenter should still have first gen presenter
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- // retry should be sent to gen 2 even though it wasn't presented
- presenter.retry()
- assertThat(uiReceivers[0].retryEvents).hasSize(0)
- assertThat(uiReceivers[1].retryEvents).hasSize(1)
- advanceUntilIdle()
+ // retry should be sent to gen 2 even though it wasn't presented
+ presenter.retry()
+ assertThat(uiReceivers[0].retryEvents).hasSize(0)
+ assertThat(uiReceivers[1].retryEvents).hasSize(1)
+ advanceUntilIdle()
- // will retry with the correct cached hint
- assertThat(presenter.snapshot()).containsExactlyElementsIn(8 until 17)
+ // will retry with the correct cached hint
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(8 until 17)
- // access any item to ensure gen 2 receiver was recipient of the initial hint
- presenter[0]
- assertThat(hintReceivers[1].hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 0,
- presentedItemsBefore = 0,
- presentedItemsAfter = 8,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
- }
-
- @Test
- fun refreshAfterStaticList() = testScope.runTest {
- val presenter = SimplePresenter()
-
- val pagingData1 = PagingData.from(listOf(1, 2, 3))
- val job1 = launch { presenter.collectFrom(pagingData1) }
- assertTrue(job1.isCompleted)
- assertThat(presenter.snapshot()).containsAtLeastElementsIn(listOf(1, 2, 3))
-
- val uiReceiver = UiReceiverFake()
- val pagingData2 = infinitelySuspendingPagingData(uiReceiver = uiReceiver)
- val job2 = launch { presenter.collectFrom(pagingData2) }
- assertTrue(job2.isActive)
-
- // even though the second paging data never presented, it should be receiver of the refresh
- presenter.refresh()
- assertThat(uiReceiver.refreshEvents).hasSize(1)
-
- job2.cancel()
- }
-
- @Test
- fun retryAfterStaticList() = testScope.runTest {
- val presenter = SimplePresenter()
-
- val pagingData1 = PagingData.from(listOf(1, 2, 3))
- val job1 = launch { presenter.collectFrom(pagingData1) }
- assertTrue(job1.isCompleted)
- assertThat(presenter.snapshot()).containsAtLeastElementsIn(listOf(1, 2, 3))
-
- val uiReceiver = UiReceiverFake()
- val pagingData2 = infinitelySuspendingPagingData(uiReceiver = uiReceiver)
- val job2 = launch { presenter.collectFrom(pagingData2) }
- assertTrue(job2.isActive)
-
- // even though the second paging data never presented, it should be receiver of the retry
- presenter.retry()
- assertThat(uiReceiver.retryEvents).hasSize(1)
-
- job2.cancel()
- }
-
- @Test
- fun hintCalculationBasedOnCurrentGeneration() = testScope.runTest {
- val presenter = SimplePresenter()
-
- // first generation
- val hintReceiver1 = HintReceiverFake()
- val uiReceiver1 = UiReceiverFake()
- val flow = flowOf(
- localRefresh(
- pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4))),
- placeholdersBefore = 0,
- placeholdersAfter = 95
- )
- )
-
- val job1 = launch {
- presenter.collectFrom(PagingData(flow, uiReceiver1, hintReceiver1))
- }
- // access any item make sure hint is sent
- presenter[3]
- assertThat(hintReceiver1.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 3,
- presentedItemsBefore = 3,
- presentedItemsAfter = 1,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
-
- // jump to another position, triggers invalidation
- presenter[20]
- assertThat(hintReceiver1.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 20,
- presentedItemsBefore = 20,
- presentedItemsAfter = -16,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- ),
- )
- )
-
- // jump invalidation happens
- presenter.refresh()
- assertThat(uiReceiver1.refreshEvents).hasSize(1)
-
- // second generation
- val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- val hintReceiver2 = HintReceiverFake()
- val job2 = launch {
- presenter.collectFrom(
- PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver2)
- )
+ // access any item to ensure gen 2 receiver was recipient of the initial hint
+ presenter[0]
+ assertThat(hintReceivers[1].hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 8,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
}
- // jump to another position while second gen is loading. It should be sent to first gen.
- presenter[40]
- assertThat(hintReceiver1.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 40,
- presentedItemsBefore = 40,
- presentedItemsAfter = -36,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- ),
- )
- )
- assertThat(hintReceiver2.hints).isEmpty()
-
- // gen 2 initial load
- pageEventCh.trySend(
- localRefresh(
- pages = listOf(TransformablePage(listOf(20, 21, 22, 23, 24))),
- placeholdersBefore = 20,
- placeholdersAfter = 75
- ),
- )
- // access any item make sure hint is sent
- presenter[3]
- assertThat(hintReceiver2.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = -17,
- presentedItemsBefore = -17,
- presentedItemsAfter = 21,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- )
- )
-
- // jumping to index 50. Hint.indexInPage should be adjusted accordingly based on
- // the placeholdersBefore of new presenter. It should be
- // (index - placeholdersBefore) = 50 - 20 = 30
- presenter[50]
- assertThat(hintReceiver2.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = 0,
- indexInPage = 30,
- presentedItemsBefore = 30,
- presentedItemsAfter = -26,
- originalPageOffsetFirst = 0,
- originalPageOffsetLast = 0,
- ),
- )
- )
-
- job2.cancel()
- job1.cancel()
- }
-
@Test
- fun fetch_loadHintResentWhenUnfulfilled() = testScope.runTest {
- val presenter = SimplePresenter()
+ fun refreshAfterStaticList() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- pageEventCh.trySend(
- localRefresh(
- pages = listOf(TransformablePage(0, listOf(0, 1))),
- placeholdersBefore = 4,
- placeholdersAfter = 4,
- )
- )
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-1, listOf(-1, -2))),
- placeholdersBefore = 2,
- )
- )
- pageEventCh.trySend(
- localAppend(
- pages = listOf(TransformablePage(1, listOf(2, 3))),
- placeholdersAfter = 2,
- )
- )
+ val pagingData1 = PagingData.from(listOf(1, 2, 3))
+ val job1 = launch { presenter.collectFrom(pagingData1) }
+ assertTrue(job1.isCompleted)
+ assertThat(presenter.snapshot()).containsAtLeastElementsIn(listOf(1, 2, 3))
- val hintReceiver = HintReceiverFake()
- val job = launch {
- presenter.collectFrom(
- // Filter the original list of 10 items to 5, removing even numbers.
- PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver)
- .filter { it % 2 != 0 }
- )
+ val uiReceiver = UiReceiverFake()
+ val pagingData2 = infinitelySuspendingPagingData(uiReceiver = uiReceiver)
+ val job2 = launch { presenter.collectFrom(pagingData2) }
+ assertTrue(job2.isActive)
+
+ // even though the second paging data never presented, it should be receiver of the
+ // refresh
+ presenter.refresh()
+ assertThat(uiReceiver.refreshEvents).hasSize(1)
+
+ job2.cancel()
}
- // Initial state:
- // [null, null, [-1], [1], [3], null, null]
- assertNull(presenter[0])
- assertThat(hintReceiver.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = -1,
- indexInPage = -2,
- presentedItemsBefore = -2,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 1
+ @Test
+ fun retryAfterStaticList() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ val pagingData1 = PagingData.from(listOf(1, 2, 3))
+ val job1 = launch { presenter.collectFrom(pagingData1) }
+ assertTrue(job1.isCompleted)
+ assertThat(presenter.snapshot()).containsAtLeastElementsIn(listOf(1, 2, 3))
+
+ val uiReceiver = UiReceiverFake()
+ val pagingData2 = infinitelySuspendingPagingData(uiReceiver = uiReceiver)
+ val job2 = launch { presenter.collectFrom(pagingData2) }
+ assertTrue(job2.isActive)
+
+ // even though the second paging data never presented, it should be receiver of the
+ // retry
+ presenter.retry()
+ assertThat(uiReceiver.retryEvents).hasSize(1)
+
+ job2.cancel()
+ }
+
+ @Test
+ fun hintCalculationBasedOnCurrentGeneration() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ // first generation
+ val hintReceiver1 = HintReceiverFake()
+ val uiReceiver1 = UiReceiverFake()
+ val flow =
+ flowOf(
+ localRefresh(
+ pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4))),
+ placeholdersBefore = 0,
+ placeholdersAfter = 95
+ )
+ )
+
+ val job1 = launch {
+ presenter.collectFrom(PagingData(flow, uiReceiver1, hintReceiver1))
+ }
+ // access any item make sure hint is sent
+ presenter[3]
+ assertThat(hintReceiver1.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 3,
+ presentedItemsBefore = 3,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+
+ // jump to another position, triggers invalidation
+ presenter[20]
+ assertThat(hintReceiver1.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 20,
+ presentedItemsBefore = 20,
+ presentedItemsAfter = -16,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ ),
+ )
+ )
+
+ // jump invalidation happens
+ presenter.refresh()
+ assertThat(uiReceiver1.refreshEvents).hasSize(1)
+
+ // second generation
+ val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ val hintReceiver2 = HintReceiverFake()
+ val job2 = launch {
+ presenter.collectFrom(
+ PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver2)
+ )
+ }
+
+ // jump to another position while second gen is loading. It should be sent to first gen.
+ presenter[40]
+ assertThat(hintReceiver1.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 40,
+ presentedItemsBefore = 40,
+ presentedItemsAfter = -36,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ ),
+ )
+ )
+ assertThat(hintReceiver2.hints).isEmpty()
+
+ // gen 2 initial load
+ pageEventCh.trySend(
+ localRefresh(
+ pages = listOf(TransformablePage(listOf(20, 21, 22, 23, 24))),
+ placeholdersBefore = 20,
+ placeholdersAfter = 75
),
)
- )
+ // access any item make sure hint is sent
+ presenter[3]
+ assertThat(hintReceiver2.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = -17,
+ presentedItemsBefore = -17,
+ presentedItemsAfter = 21,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
- // Insert a new page, PagingDataPresenter should try to resend hint since index 0 still points
- // to a placeholder:
- // [null, null, [], [-1], [1], [3], null, null]
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-2, listOf())),
- placeholdersBefore = 2,
- )
- )
- assertThat(hintReceiver.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = -2,
- indexInPage = -2,
- presentedItemsBefore = -2,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = -2,
- originalPageOffsetLast = 1
+ // jumping to index 50. Hint.indexInPage should be adjusted accordingly based on
+ // the placeholdersBefore of new presenter. It should be
+ // (index - placeholdersBefore) = 50 - 20 = 30
+ presenter[50]
+ assertThat(hintReceiver2.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 30,
+ presentedItemsBefore = 30,
+ presentedItemsAfter = -26,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ ),
+ )
+ )
+
+ job2.cancel()
+ job1.cancel()
+ }
+
+ @Test
+ fun fetch_loadHintResentWhenUnfulfilled() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ pageEventCh.trySend(
+ localRefresh(
+ pages = listOf(TransformablePage(0, listOf(0, 1))),
+ placeholdersBefore = 4,
+ placeholdersAfter = 4,
)
)
- )
-
- // Now index 0 has been loaded:
- // [[-3], [], [-1], [1], [3], null, null]
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-3, listOf(-3, -4))),
- placeholdersBefore = 0,
- source = loadStates(prepend = NotLoading.Complete)
- )
- )
- assertThat(hintReceiver.hints).isEmpty()
-
- // This index points to a valid placeholder that ends up removed by filter().
- assertNull(presenter[5])
- assertThat(hintReceiver.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = 1,
- indexInPage = 2,
- presentedItemsBefore = 5,
- presentedItemsAfter = -2,
- originalPageOffsetFirst = -3,
- originalPageOffsetLast = 1
- )
- )
-
- // Should only resend the hint for index 5, since index 0 has already been loaded:
- // [[-3], [], [-1], [1], [3], [], null, null]
- pageEventCh.trySend(
- localAppend(
- pages = listOf(TransformablePage(2, listOf())),
- placeholdersAfter = 2,
- source = loadStates(prepend = NotLoading.Complete)
- )
- )
- assertThat(hintReceiver.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = 2,
- indexInPage = 1,
- presentedItemsBefore = 5,
- presentedItemsAfter = -2,
- originalPageOffsetFirst = -3,
- originalPageOffsetLast = 2
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-1, listOf(-1, -2))),
+ placeholdersBefore = 2,
)
)
- )
-
- // Index 5 hasn't loaded, but we are at the end of the list:
- // [[-3], [], [-1], [1], [3], [], [5]]
- pageEventCh.trySend(
- localAppend(
- pages = listOf(TransformablePage(3, listOf(4, 5))),
- placeholdersAfter = 0,
- source = loadStates(prepend = NotLoading.Complete, append = NotLoading.Complete)
- )
- )
- assertThat(hintReceiver.hints).isEmpty()
-
- job.cancel()
- }
-
- @Test
- fun fetch_loadHintResentUnlessPageDropped() = testScope.runTest {
- val presenter = SimplePresenter()
-
- val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- pageEventCh.trySend(
- localRefresh(
- pages = listOf(TransformablePage(0, listOf(0, 1))),
- placeholdersBefore = 4,
- placeholdersAfter = 4,
- )
- )
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-1, listOf(-1, -2))),
- placeholdersBefore = 2,
- )
- )
- pageEventCh.trySend(
- localAppend(
- pages = listOf(TransformablePage(1, listOf(2, 3))),
- placeholdersAfter = 2,
- )
- )
-
- val hintReceiver = HintReceiverFake()
- val job = launch {
- presenter.collectFrom(
- // Filter the original list of 10 items to 5, removing even numbers.
- PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver)
- .filter { it % 2 != 0 }
- )
- }
-
- // Initial state:
- // [null, null, [-1], [1], [3], null, null]
- assertNull(presenter[0])
- assertThat(hintReceiver.hints).containsExactly(
- ViewportHint.Access(
- pageOffset = -1,
- indexInPage = -2,
- presentedItemsBefore = -2,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = -1,
- originalPageOffsetLast = 1
- )
- )
-
- // Insert a new page, PagingDataPresenter should try to resend hint since index 0 still points
- // to a placeholder:
- // [null, null, [], [-1], [1], [3], null, null]
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-2, listOf())),
- placeholdersBefore = 2,
- )
- )
- assertThat(hintReceiver.hints).isEqualTo(
- listOf(
- ViewportHint.Access(
- pageOffset = -2,
- indexInPage = -2,
- presentedItemsBefore = -2,
- presentedItemsAfter = 4,
- originalPageOffsetFirst = -2,
- originalPageOffsetLast = 1
+ pageEventCh.trySend(
+ localAppend(
+ pages = listOf(TransformablePage(1, listOf(2, 3))),
+ placeholdersAfter = 2,
)
)
- )
- // Drop the previous page, which reset resendable index state in the PREPEND direction.
- // [null, null, [-1], [1], [3], null, null]
- pageEventCh.trySend(
- Drop(
- loadType = PREPEND,
- minPageOffset = -2,
- maxPageOffset = -2,
- placeholdersRemaining = 2
+ val hintReceiver = HintReceiverFake()
+ val job = launch {
+ presenter.collectFrom(
+ // Filter the original list of 10 items to 5, removing even numbers.
+ PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver).filter {
+ it % 2 != 0
+ }
+ )
+ }
+
+ // Initial state:
+ // [null, null, [-1], [1], [3], null, null]
+ assertNull(presenter[0])
+ assertThat(hintReceiver.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = -1,
+ indexInPage = -2,
+ presentedItemsBefore = -2,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 1
+ ),
+ )
+ )
+
+ // Insert a new page, PagingDataPresenter should try to resend hint since index 0 still
+ // points
+ // to a placeholder:
+ // [null, null, [], [-1], [1], [3], null, null]
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-2, listOf())),
+ placeholdersBefore = 2,
+ )
)
- )
+ assertThat(hintReceiver.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = -2,
+ indexInPage = -2,
+ presentedItemsBefore = -2,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = -2,
+ originalPageOffsetLast = 1
+ )
+ )
+ )
- // Re-insert the previous page, which should not trigger resending the index due to
- // previous page drop:
- // [[-3], [], [-1], [1], [3], null, null]
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-2, listOf())),
- placeholdersBefore = 2,
+ // Now index 0 has been loaded:
+ // [[-3], [], [-1], [1], [3], null, null]
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-3, listOf(-3, -4))),
+ placeholdersBefore = 0,
+ source = loadStates(prepend = NotLoading.Complete)
+ )
)
- )
+ assertThat(hintReceiver.hints).isEmpty()
- job.cancel()
- }
+ // This index points to a valid placeholder that ends up removed by filter().
+ assertNull(presenter[5])
+ assertThat(hintReceiver.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = 1,
+ indexInPage = 2,
+ presentedItemsBefore = 5,
+ presentedItemsAfter = -2,
+ originalPageOffsetFirst = -3,
+ originalPageOffsetLast = 1
+ )
+ )
+
+ // Should only resend the hint for index 5, since index 0 has already been loaded:
+ // [[-3], [], [-1], [1], [3], [], null, null]
+ pageEventCh.trySend(
+ localAppend(
+ pages = listOf(TransformablePage(2, listOf())),
+ placeholdersAfter = 2,
+ source = loadStates(prepend = NotLoading.Complete)
+ )
+ )
+ assertThat(hintReceiver.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = 2,
+ indexInPage = 1,
+ presentedItemsBefore = 5,
+ presentedItemsAfter = -2,
+ originalPageOffsetFirst = -3,
+ originalPageOffsetLast = 2
+ )
+ )
+ )
+
+ // Index 5 hasn't loaded, but we are at the end of the list:
+ // [[-3], [], [-1], [1], [3], [], [5]]
+ pageEventCh.trySend(
+ localAppend(
+ pages = listOf(TransformablePage(3, listOf(4, 5))),
+ placeholdersAfter = 0,
+ source = loadStates(prepend = NotLoading.Complete, append = NotLoading.Complete)
+ )
+ )
+ assertThat(hintReceiver.hints).isEmpty()
+
+ job.cancel()
+ }
@Test
- fun peek() = testScope.runTest {
- val presenter = SimplePresenter()
- val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- pageEventCh.trySend(
- localRefresh(
- pages = listOf(TransformablePage(0, listOf(0, 1))),
- placeholdersBefore = 4,
- placeholdersAfter = 4,
- )
- )
- pageEventCh.trySend(
- localPrepend(
- pages = listOf(TransformablePage(-1, listOf(-1, -2))),
- placeholdersBefore = 2,
- )
- )
- pageEventCh.trySend(
- localAppend(
- pages = listOf(TransformablePage(1, listOf(2, 3))),
- placeholdersAfter = 2,
- )
- )
+ fun fetch_loadHintResentUnlessPageDropped() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- val hintReceiver = HintReceiverFake()
- val job = launch {
+ val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ pageEventCh.trySend(
+ localRefresh(
+ pages = listOf(TransformablePage(0, listOf(0, 1))),
+ placeholdersBefore = 4,
+ placeholdersAfter = 4,
+ )
+ )
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-1, listOf(-1, -2))),
+ placeholdersBefore = 2,
+ )
+ )
+ pageEventCh.trySend(
+ localAppend(
+ pages = listOf(TransformablePage(1, listOf(2, 3))),
+ placeholdersAfter = 2,
+ )
+ )
+
+ val hintReceiver = HintReceiverFake()
+ val job = launch {
+ presenter.collectFrom(
+ // Filter the original list of 10 items to 5, removing even numbers.
+ PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver).filter {
+ it % 2 != 0
+ }
+ )
+ }
+
+ // Initial state:
+ // [null, null, [-1], [1], [3], null, null]
+ assertNull(presenter[0])
+ assertThat(hintReceiver.hints)
+ .containsExactly(
+ ViewportHint.Access(
+ pageOffset = -1,
+ indexInPage = -2,
+ presentedItemsBefore = -2,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = -1,
+ originalPageOffsetLast = 1
+ )
+ )
+
+ // Insert a new page, PagingDataPresenter should try to resend hint since index 0 still
+ // points
+ // to a placeholder:
+ // [null, null, [], [-1], [1], [3], null, null]
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-2, listOf())),
+ placeholdersBefore = 2,
+ )
+ )
+ assertThat(hintReceiver.hints)
+ .isEqualTo(
+ listOf(
+ ViewportHint.Access(
+ pageOffset = -2,
+ indexInPage = -2,
+ presentedItemsBefore = -2,
+ presentedItemsAfter = 4,
+ originalPageOffsetFirst = -2,
+ originalPageOffsetLast = 1
+ )
+ )
+ )
+
+ // Drop the previous page, which reset resendable index state in the PREPEND direction.
+ // [null, null, [-1], [1], [3], null, null]
+ pageEventCh.trySend(
+ Drop(
+ loadType = PREPEND,
+ minPageOffset = -2,
+ maxPageOffset = -2,
+ placeholdersRemaining = 2
+ )
+ )
+
+ // Re-insert the previous page, which should not trigger resending the index due to
+ // previous page drop:
+ // [[-3], [], [-1], [1], [3], null, null]
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-2, listOf())),
+ placeholdersBefore = 2,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun peek() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val pageEventCh = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ pageEventCh.trySend(
+ localRefresh(
+ pages = listOf(TransformablePage(0, listOf(0, 1))),
+ placeholdersBefore = 4,
+ placeholdersAfter = 4,
+ )
+ )
+ pageEventCh.trySend(
+ localPrepend(
+ pages = listOf(TransformablePage(-1, listOf(-1, -2))),
+ placeholdersBefore = 2,
+ )
+ )
+ pageEventCh.trySend(
+ localAppend(
+ pages = listOf(TransformablePage(1, listOf(2, 3))),
+ placeholdersAfter = 2,
+ )
+ )
+
+ val hintReceiver = HintReceiverFake()
+ val job = launch {
+ presenter.collectFrom(
+ // Filter the original list of 10 items to 5, removing even numbers.
+ PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver)
+ )
+ }
+
+ // Check that peek fetches the correct placeholder
+ assertThat(presenter.peek(4)).isEqualTo(0)
+
+ // Check that peek fetches the correct placeholder
+ assertNull(presenter.peek(0))
+
+ // Check that peek does not trigger page fetch.
+ assertThat(hintReceiver.hints).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun onPagingDataPresentedListener_empty() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val listenerEvents = mutableListOf<Unit>()
+ presenter.addOnPagesUpdatedListener { listenerEvents.add(Unit) }
+
+ presenter.collectFrom(PagingData.empty())
+ assertThat(listenerEvents.size).isEqualTo(1)
+
+ // No change to LoadState or presented list should still trigger the listener.
+ presenter.collectFrom(PagingData.empty())
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
+ val job = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Should wait for new generation to load and apply it first.
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun onPagingDataPresentedListener_insertDrop() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val listenerEvents = mutableListOf<Unit>()
+ presenter.addOnPagesUpdatedListener { listenerEvents.add(Unit) }
+
+ val pager =
+ Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
+ TestPagingSource()
+ }
+ val job = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Should wait for new generation to load and apply it first.
+ assertThat(listenerEvents.size).isEqualTo(0)
+
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(1)
+
+ // Trigger PREPEND.
+ presenter[50]
+ assertThat(listenerEvents.size).isEqualTo(1)
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ // Trigger APPEND + Drop
+ presenter[52]
+ assertThat(listenerEvents.size).isEqualTo(2)
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun onPagingDataPresentedFlow_empty() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val listenerEvents = mutableListOf<Unit>()
+ val job1 = launch { presenter.onPagesUpdatedFlow.collect { listenerEvents.add(Unit) } }
+
+ presenter.collectFrom(PagingData.empty())
+ assertThat(listenerEvents.size).isEqualTo(1)
+
+ // No change to LoadState or presented list should still trigger the listener.
+ presenter.collectFrom(PagingData.empty())
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
+ val job2 = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Should wait for new generation to load and apply it first.
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(3)
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun onPagingDataPresentedFlow_insertDrop() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val listenerEvents = mutableListOf<Unit>()
+ val job1 = launch { presenter.onPagesUpdatedFlow.collect { listenerEvents.add(Unit) } }
+
+ val pager =
+ Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
+ TestPagingSource()
+ }
+ val job2 = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Should wait for new generation to load and apply it first.
+ assertThat(listenerEvents.size).isEqualTo(0)
+
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(1)
+
+ // Trigger PREPEND.
+ presenter[50]
+ assertThat(listenerEvents.size).isEqualTo(1)
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(2)
+
+ // Trigger APPEND + Drop
+ presenter[52]
+ assertThat(listenerEvents.size).isEqualTo(2)
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(4)
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun onPagingDataPresentedFlow_buffer() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val listenerEvents = mutableListOf<Unit>()
+
+ // Trigger update, which should get ignored due to onPagesUpdatedFlow being hot.
+ presenter.collectFrom(PagingData.empty())
+
+ val job = launch {
+ presenter.onPagesUpdatedFlow.collect {
+ listenerEvents.add(Unit)
+ // Await advanceUntilIdle() before accepting another event.
+ delay(100)
+ }
+ }
+
+ // Previous update before collection happened should be ignored.
+ assertThat(listenerEvents.size).isEqualTo(0)
+
+ // Trigger update; should get immediately received.
+ presenter.collectFrom(PagingData.empty())
+ assertThat(listenerEvents.size).isEqualTo(1)
+
+ // Trigger 64 update while collector is still processing; should all get buffered.
+ repeat(64) { presenter.collectFrom(PagingData.empty()) }
+
+ // Trigger another update while collector is still processing; should cause event to
+ // drop.
+ presenter.collectFrom(PagingData.empty())
+
+ // Await all; we should now receive the buffered event.
+ advanceUntilIdle()
+ assertThat(listenerEvents.size).isEqualTo(65)
+
+ job.cancel()
+ }
+
+ @Test
+ fun loadStateFlow_synchronouslyUpdates() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ var combinedLoadStates: CombinedLoadStates? = null
+ var itemCount = -1
+ val loadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect {
+ combinedLoadStates = it
+ itemCount = presenter.size
+ }
+ }
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ initialLoadSize = 10,
+ prefetchDistance = 1
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
+ }
+ val job = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Initial refresh
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(10, itemCount)
+ assertEquals(10, presenter.size)
+
+ // Append
+ presenter[9]
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(20, itemCount)
+ assertEquals(20, presenter.size)
+
+ // Prepend
+ presenter[0]
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(30, itemCount)
+ assertEquals(30, presenter.size)
+
+ job.cancel()
+ loadStateJob.cancel()
+ }
+
+ @Test
+ fun loadStateFlow_hasNoInitialValue() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ // Should not immediately emit without a real value to a new collector.
+ val combinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val loadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { combinedLoadStates.add(it) }
+ }
+ assertThat(combinedLoadStates).isEmpty()
+
+ // Add a real value and now we should emit to collector.
presenter.collectFrom(
- // Filter the original list of 10 items to 5, removing even numbers.
- PagingData(pageEventCh.consumeAsFlow(), dummyUiReceiver, hintReceiver)
+ PagingData.empty(
+ sourceLoadStates =
+ loadStates(prepend = NotLoading.Complete, append = NotLoading.Complete)
+ )
)
- }
+ assertThat(combinedLoadStates)
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ )
+ )
- // Check that peek fetches the correct placeholder
- assertThat(presenter.peek(4)).isEqualTo(0)
-
- // Check that peek fetches the correct placeholder
- assertNull(presenter.peek(0))
-
- // Check that peek does not trigger page fetch.
- assertThat(hintReceiver.hints).isEmpty()
-
- job.cancel()
- }
-
- @Test
- fun onPagingDataPresentedListener_empty() = testScope.runTest {
- val presenter = SimplePresenter()
- val listenerEvents = mutableListOf<Unit>()
- presenter.addOnPagesUpdatedListener {
- listenerEvents.add(Unit)
- }
-
- presenter.collectFrom(PagingData.empty())
- assertThat(listenerEvents.size).isEqualTo(1)
-
- // No change to LoadState or presented list should still trigger the listener.
- presenter.collectFrom(PagingData.empty())
- assertThat(listenerEvents.size).isEqualTo(2)
-
- val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
- val job = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
-
- // Should wait for new generation to load and apply it first.
- assertThat(listenerEvents.size).isEqualTo(2)
-
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(3)
-
- job.cancel()
- }
-
- @Test
- fun onPagingDataPresentedListener_insertDrop() = testScope.runTest {
- val presenter = SimplePresenter()
- val listenerEvents = mutableListOf<Unit>()
- presenter.addOnPagesUpdatedListener {
- listenerEvents.add(Unit)
- }
-
- val pager = Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
- TestPagingSource()
- }
- val job = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
-
- // Should wait for new generation to load and apply it first.
- assertThat(listenerEvents.size).isEqualTo(0)
-
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(1)
-
- // Trigger PREPEND.
- presenter[50]
- assertThat(listenerEvents.size).isEqualTo(1)
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(2)
-
- // Trigger APPEND + Drop
- presenter[52]
- assertThat(listenerEvents.size).isEqualTo(2)
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(4)
-
- job.cancel()
- }
-
- @Test
- fun onPagingDataPresentedFlow_empty() = testScope.runTest {
- val presenter = SimplePresenter()
- val listenerEvents = mutableListOf<Unit>()
- val job1 = launch {
- presenter.onPagesUpdatedFlow.collect {
- listenerEvents.add(Unit)
+ // Should emit real values to new collectors immediately
+ val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val newLoadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { newCombinedLoadStates.add(it) }
}
+ assertThat(newCombinedLoadStates)
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ )
+ )
+
+ loadStateJob.cancel()
+ newLoadStateJob.cancel()
}
- presenter.collectFrom(PagingData.empty())
- assertThat(listenerEvents.size).isEqualTo(1)
-
- // No change to LoadState or presented list should still trigger the listener.
- presenter.collectFrom(PagingData.empty())
- assertThat(listenerEvents.size).isEqualTo(2)
-
- val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
- val job2 = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
-
- // Should wait for new generation to load and apply it first.
- assertThat(listenerEvents.size).isEqualTo(2)
-
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(3)
-
- job1.cancel()
- job2.cancel()
- }
-
@Test
- fun onPagingDataPresentedFlow_insertDrop() = testScope.runTest {
- val presenter = SimplePresenter()
- val listenerEvents = mutableListOf<Unit>()
- val job1 = launch {
- presenter.onPagesUpdatedFlow.collect {
- listenerEvents.add(Unit)
+ fun loadStateFlow_preservesLoadStatesOnEmptyList() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ // Should not immediately emit without a real value to a new collector.
+ val combinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val loadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { combinedLoadStates.add(it) }
}
- }
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- val pager = Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
- TestPagingSource()
- }
- val job2 = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
+ // Send a static list without load states, which should not send anything.
+ presenter.collectFrom(PagingData.empty())
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- // Should wait for new generation to load and apply it first.
- assertThat(listenerEvents.size).isEqualTo(0)
+ // Send a real LoadStateUpdate.
+ presenter.collectFrom(
+ PagingData(
+ flow =
+ flowOf(
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ ),
+ uiReceiver = PagingData.NOOP_UI_RECEIVER,
+ hintReceiver = PagingData.NOOP_HINT_RECEIVER
+ )
+ )
+ assertThat(combinedLoadStates.getAllAndClear())
+ .containsExactly(
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ )
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(1)
-
- // Trigger PREPEND.
- presenter[50]
- assertThat(listenerEvents.size).isEqualTo(1)
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(2)
-
- // Trigger APPEND + Drop
- presenter[52]
- assertThat(listenerEvents.size).isEqualTo(2)
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(4)
-
- job1.cancel()
- job2.cancel()
- }
-
- @Test
- fun onPagingDataPresentedFlow_buffer() = testScope.runTest {
- val presenter = SimplePresenter()
- val listenerEvents = mutableListOf<Unit>()
-
- // Trigger update, which should get ignored due to onPagesUpdatedFlow being hot.
- presenter.collectFrom(PagingData.empty())
-
- val job = launch {
- presenter.onPagesUpdatedFlow.collect {
- listenerEvents.add(Unit)
- // Await advanceUntilIdle() before accepting another event.
- delay(100)
+ // Send a static list without load states, which should preserve the previous state.
+ presenter.collectFrom(PagingData.empty())
+ // Existing observers should not receive any updates
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
+ // New observers should receive the previous state.
+ val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val newLoadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { newCombinedLoadStates.add(it) }
}
+ assertThat(newCombinedLoadStates.getAllAndClear())
+ .containsExactly(
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ )
+
+ loadStateJob.cancel()
+ newLoadStateJob.cancel()
}
- // Previous update before collection happened should be ignored.
- assertThat(listenerEvents.size).isEqualTo(0)
+ @Test
+ fun loadStateFlow_preservesLoadStatesOnStaticList() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
- // Trigger update; should get immediately received.
- presenter.collectFrom(PagingData.empty())
- assertThat(listenerEvents.size).isEqualTo(1)
+ // Should not immediately emit without a real value to a new collector.
+ val combinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val loadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { combinedLoadStates.add(it) }
+ }
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- // Trigger 64 update while collector is still processing; should all get buffered.
- repeat(64) { presenter.collectFrom(PagingData.empty()) }
+ // Send a static list without load states, which should not send anything.
+ presenter.collectFrom(PagingData.from(listOf(1)))
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- // Trigger another update while collector is still processing; should cause event to drop.
- presenter.collectFrom(PagingData.empty())
+ // Send a real LoadStateUpdate.
+ presenter.collectFrom(
+ PagingData(
+ flow =
+ flowOf(
+ remoteLoadStateUpdate(
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ ),
+ uiReceiver = PagingData.NOOP_UI_RECEIVER,
+ hintReceiver = PagingData.NOOP_HINT_RECEIVER
+ )
+ )
+ assertThat(combinedLoadStates.getAllAndClear())
+ .containsExactly(
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ )
- // Await all; we should now receive the buffered event.
- advanceUntilIdle()
- assertThat(listenerEvents.size).isEqualTo(65)
+ // Send a static list without load states, which should preserve the previous state.
+ presenter.collectFrom(PagingData.from(listOf(1)))
+ // Existing observers should not receive any updates
+ assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
+ // New observers should receive the previous state.
+ val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
+ val newLoadStateJob = launch {
+ presenter.nonNullLoadStateFlow.collect { newCombinedLoadStates.add(it) }
+ }
+ assertThat(newCombinedLoadStates.getAllAndClear())
+ .containsExactly(
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prepend = Loading,
+ append = Loading,
+ refreshLocal = Loading,
+ prependLocal = Loading,
+ appendLocal = Loading,
+ refreshRemote = Loading,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ )
+ )
- job.cancel()
- }
+ loadStateJob.cancel()
+ newLoadStateJob.cancel()
+ }
@Test
- fun loadStateFlow_synchronouslyUpdates() = testScope.runTest {
- val presenter = SimplePresenter()
- var combinedLoadStates: CombinedLoadStates? = null
- var itemCount = -1
- val loadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
+ fun loadStateFlow_deduplicate() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+
+ val combinedLoadStates = mutableListOf<CombinedLoadStates>()
+ backgroundScope.launch {
+ presenter.nonNullLoadStateFlow.collect { combinedLoadStates.add(it) }
+ }
+
+ presenter.collectFrom(
+ PagingData(
+ flow =
+ flowOf(
+ remoteLoadStateUpdate(
+ prependLocal = Loading,
+ appendLocal = Loading,
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = Loading,
+ ),
+ // duplicate update
+ remoteLoadStateUpdate(
+ appendLocal = Loading,
+ ),
+ ),
+ uiReceiver = PagingData.NOOP_UI_RECEIVER,
+ hintReceiver = PagingData.NOOP_HINT_RECEIVER
+ )
+ )
+ advanceUntilIdle()
+ assertThat(combinedLoadStates)
+ .containsExactly(
+ remoteLoadStatesOf(
+ prependLocal = Loading,
+ appendLocal = Loading,
+ ),
+ remoteLoadStatesOf(
+ appendLocal = Loading,
+ )
+ )
+ }
+
+ @Test
+ fun loadStateFlowListeners_deduplicate() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val combinedLoadStates = mutableListOf<CombinedLoadStates>()
+
+ presenter.addLoadStateListener { combinedLoadStates.add(it) }
+
+ presenter.collectFrom(
+ PagingData(
+ flow =
+ flowOf(
+ remoteLoadStateUpdate(
+ prependLocal = Loading,
+ appendLocal = Loading,
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = Loading,
+ ),
+ // duplicate update
+ remoteLoadStateUpdate(
+ appendLocal = Loading,
+ ),
+ ),
+ uiReceiver = PagingData.NOOP_UI_RECEIVER,
+ hintReceiver = PagingData.NOOP_HINT_RECEIVER
+ )
+ )
+ advanceUntilIdle()
+ assertThat(combinedLoadStates)
+ .containsExactly(
+ remoteLoadStatesOf(
+ prependLocal = Loading,
+ appendLocal = Loading,
+ ),
+ remoteLoadStatesOf(
+ appendLocal = Loading,
+ )
+ )
+ }
+
+ @Test
+ fun addLoadStateListener_SynchronouslyUpdates() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ var combinedLoadStates: CombinedLoadStates? = null
+ var itemCount = -1
+ presenter.addLoadStateListener {
combinedLoadStates = it
itemCount = presenter.size
}
+
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ initialLoadSize = 10,
+ prefetchDistance = 1
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
+ }
+ val job = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+
+ // Initial refresh
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(10, itemCount)
+ assertEquals(10, presenter.size)
+
+ // Append
+ presenter[9]
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(20, itemCount)
+ assertEquals(20, presenter.size)
+
+ // Prepend
+ presenter[0]
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(30, itemCount)
+ assertEquals(30, presenter.size)
+
+ job.cancel()
}
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- initialLoadSize = 10,
- prefetchDistance = 1
- ),
- initialKey = 50
- ) { TestPagingSource() }
- val job = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
-
- // Initial refresh
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(10, itemCount)
- assertEquals(10, presenter.size)
-
- // Append
- presenter[9]
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(20, itemCount)
- assertEquals(20, presenter.size)
-
- // Prepend
- presenter[0]
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(30, itemCount)
- assertEquals(30, presenter.size)
-
- job.cancel()
- loadStateJob.cancel()
- }
-
@Test
- fun loadStateFlow_hasNoInitialValue() = testScope.runTest {
- val presenter = SimplePresenter()
+ fun addLoadStateListener_hasNoInitialValue() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val combinedLoadStateCapture = CombinedLoadStatesCapture()
- // Should not immediately emit without a real value to a new collector.
- val combinedLoadStates = mutableListOf<CombinedLoadStates>()
- val loadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- combinedLoadStates.add(it)
- }
- }
- assertThat(combinedLoadStates).isEmpty()
+ // Adding a new listener without a real value should not trigger it.
+ presenter.addLoadStateListener(combinedLoadStateCapture)
+ assertThat(combinedLoadStateCapture.newEvents()).isEmpty()
- // Add a real value and now we should emit to collector.
- presenter.collectFrom(
- PagingData.empty(
- sourceLoadStates = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete
+ // Add a real value and now the listener should trigger.
+ presenter.collectFrom(
+ PagingData.empty(
+ sourceLoadStates =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ )
)
)
- )
- assertThat(combinedLoadStates).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- )
- )
-
- // Should emit real values to new collectors immediately
- val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
- val newLoadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- newCombinedLoadStates.add(it)
- }
- }
- assertThat(newCombinedLoadStates).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- )
- )
-
- loadStateJob.cancel()
- newLoadStateJob.cancel()
- }
-
- @Test
- fun loadStateFlow_preservesLoadStatesOnEmptyList() = testScope.runTest {
- val presenter = SimplePresenter()
-
- // Should not immediately emit without a real value to a new collector.
- val combinedLoadStates = mutableListOf<CombinedLoadStates>()
- val loadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- combinedLoadStates.add(it)
- }
- }
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
-
- // Send a static list without load states, which should not send anything.
- presenter.collectFrom(PagingData.empty())
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
-
- // Send a real LoadStateUpdate.
- presenter.collectFrom(
- PagingData(
- flow = flowOf(
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
+ assertThat(combinedLoadStateCapture.newEvents())
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
)
- ),
- uiReceiver = PagingData.NOOP_UI_RECEIVER,
- hintReceiver = PagingData.NOOP_HINT_RECEIVER
- )
- )
- assertThat(combinedLoadStates.getAllAndClear()).containsExactly(
- remoteLoadStatesOf(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
- )
- )
-
- // Send a static list without load states, which should preserve the previous state.
- presenter.collectFrom(PagingData.empty())
- // Existing observers should not receive any updates
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- // New observers should receive the previous state.
- val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
- val newLoadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- newCombinedLoadStates.add(it)
- }
- }
- assertThat(newCombinedLoadStates.getAllAndClear()).containsExactly(
- remoteLoadStatesOf(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
- )
- )
-
- loadStateJob.cancel()
- newLoadStateJob.cancel()
- }
-
- @Test
- fun loadStateFlow_preservesLoadStatesOnStaticList() = testScope.runTest {
- val presenter = SimplePresenter()
-
- // Should not immediately emit without a real value to a new collector.
- val combinedLoadStates = mutableListOf<CombinedLoadStates>()
- val loadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- combinedLoadStates.add(it)
- }
- }
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
-
- // Send a static list without load states, which should not send anything.
- presenter.collectFrom(PagingData.from(listOf(1)))
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
-
- // Send a real LoadStateUpdate.
- presenter.collectFrom(
- PagingData(
- flow = flowOf(
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
- )
- ),
- uiReceiver = PagingData.NOOP_UI_RECEIVER,
- hintReceiver = PagingData.NOOP_HINT_RECEIVER
- )
- )
- assertThat(combinedLoadStates.getAllAndClear()).containsExactly(
- remoteLoadStatesOf(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
- )
- )
-
- // Send a static list without load states, which should preserve the previous state.
- presenter.collectFrom(PagingData.from(listOf(1)))
- // Existing observers should not receive any updates
- assertThat(combinedLoadStates.getAllAndClear()).isEmpty()
- // New observers should receive the previous state.
- val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
- val newLoadStateJob = launch {
- presenter.nonNullLoadStateFlow.collect {
- newCombinedLoadStates.add(it)
- }
- }
- assertThat(newCombinedLoadStates.getAllAndClear()).containsExactly(
- remoteLoadStatesOf(
- refresh = Loading,
- prepend = Loading,
- append = Loading,
- refreshLocal = Loading,
- prependLocal = Loading,
- appendLocal = Loading,
- refreshRemote = Loading,
- prependRemote = Loading,
- appendRemote = Loading,
- )
- )
-
- loadStateJob.cancel()
- newLoadStateJob.cancel()
- }
-
- @Test
- fun loadStateFlow_deduplicate() = testScope.runTest {
- val presenter = SimplePresenter()
-
- val combinedLoadStates = mutableListOf<CombinedLoadStates>()
- backgroundScope.launch {
- presenter.nonNullLoadStateFlow.collect {
- combinedLoadStates.add(it)
- }
- }
-
- presenter.collectFrom(
- PagingData(
- flow = flowOf(
- remoteLoadStateUpdate(
- prependLocal = Loading,
- appendLocal = Loading,
- ),
- remoteLoadStateUpdate(
- appendLocal = Loading,
- ),
- // duplicate update
- remoteLoadStateUpdate(
- appendLocal = Loading,
- ),
- ),
- uiReceiver = PagingData.NOOP_UI_RECEIVER,
- hintReceiver = PagingData.NOOP_HINT_RECEIVER
- )
- )
- advanceUntilIdle()
- assertThat(combinedLoadStates).containsExactly(
- remoteLoadStatesOf(
- prependLocal = Loading,
- appendLocal = Loading,
- ),
- remoteLoadStatesOf(
- appendLocal = Loading,
- )
- )
- }
-
- @Test
- fun loadStateFlowListeners_deduplicate() = testScope.runTest {
- val presenter = SimplePresenter()
- val combinedLoadStates = mutableListOf<CombinedLoadStates>()
-
- presenter.addLoadStateListener {
- combinedLoadStates.add(it)
- }
-
- presenter.collectFrom(
- PagingData(
- flow = flowOf(
- remoteLoadStateUpdate(
- prependLocal = Loading,
- appendLocal = Loading,
- ),
- remoteLoadStateUpdate(
- appendLocal = Loading,
- ),
- // duplicate update
- remoteLoadStateUpdate(
- appendLocal = Loading,
- ),
- ),
- uiReceiver = PagingData.NOOP_UI_RECEIVER,
- hintReceiver = PagingData.NOOP_HINT_RECEIVER
- )
- )
- advanceUntilIdle()
- assertThat(combinedLoadStates).containsExactly(
- remoteLoadStatesOf(
- prependLocal = Loading,
- appendLocal = Loading,
- ),
- remoteLoadStatesOf(
- appendLocal = Loading,
- )
- )
- }
-
- @Test
- fun addLoadStateListener_SynchronouslyUpdates() = testScope.runTest {
- val presenter = SimplePresenter()
- var combinedLoadStates: CombinedLoadStates? = null
- var itemCount = -1
- presenter.addLoadStateListener {
- combinedLoadStates = it
- itemCount = presenter.size
- }
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- initialLoadSize = 10,
- prefetchDistance = 1
- ),
- initialKey = 50
- ) { TestPagingSource() }
- val job = launch {
- pager.flow.collectLatest { presenter.collectFrom(it) }
- }
-
- // Initial refresh
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(10, itemCount)
- assertEquals(10, presenter.size)
-
- // Append
- presenter[9]
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(20, itemCount)
- assertEquals(20, presenter.size)
-
- // Prepend
- presenter[0]
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(30, itemCount)
- assertEquals(30, presenter.size)
-
- job.cancel()
- }
-
- @Test
- fun addLoadStateListener_hasNoInitialValue() = testScope.runTest {
- val presenter = SimplePresenter()
- val combinedLoadStateCapture = CombinedLoadStatesCapture()
-
- // Adding a new listener without a real value should not trigger it.
- presenter.addLoadStateListener(combinedLoadStateCapture)
- assertThat(combinedLoadStateCapture.newEvents()).isEmpty()
-
- // Add a real value and now the listener should trigger.
- presenter.collectFrom(
- PagingData.empty(
- sourceLoadStates = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
)
- )
- )
- assertThat(combinedLoadStateCapture.newEvents()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- )
- )
- // Should emit real values to new listeners immediately
- val newCombinedLoadStateCapture = CombinedLoadStatesCapture()
- presenter.addLoadStateListener(newCombinedLoadStateCapture)
- assertThat(newCombinedLoadStateCapture.newEvents()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- )
- )
- }
+ // Should emit real values to new listeners immediately
+ val newCombinedLoadStateCapture = CombinedLoadStatesCapture()
+ presenter.addLoadStateListener(newCombinedLoadStateCapture)
+ assertThat(newCombinedLoadStateCapture.newEvents())
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ )
+ )
+ }
@Test
- fun uncaughtException() = testScope.runTest {
- val presenter = SimplePresenter()
- val pager = Pager(
- PagingConfig(1),
- ) {
- object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- throw IllegalStateException()
+ fun uncaughtException() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val pager =
+ Pager(
+ PagingConfig(1),
+ ) {
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ throw IllegalStateException()
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
}
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
- }
+ val pagingData = pager.flow.first()
+ val deferred = async(Job()) { presenter.collectFrom(pagingData) }
- val pagingData = pager.flow.first()
- val deferred = async(Job()) {
- presenter.collectFrom(pagingData)
+ advanceUntilIdle()
+ assertFailsWith<IllegalStateException> { deferred.await() }
}
- advanceUntilIdle()
- assertFailsWith<IllegalStateException> { deferred.await() }
- }
-
@Test
- fun handledLoadResultInvalid() = testScope.runTest {
- val presenter = SimplePresenter()
- var generation = 0
- val pager = Pager(
- PagingConfig(1),
- ) {
- TestPagingSource().also {
- if (generation == 0) {
- it.nextLoadResult = LoadResult.Invalid()
+ fun handledLoadResultInvalid() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ var generation = 0
+ val pager =
+ Pager(
+ PagingConfig(1),
+ ) {
+ TestPagingSource().also {
+ if (generation == 0) {
+ it.nextLoadResult = LoadResult.Invalid()
+ }
+ generation++
+ }
}
- generation++
+
+ val pagingData = pager.flow.first()
+ val deferred = async {
+ // only returns if flow is closed, or work canclled, or exception thrown
+ // in this case it should cancel due LoadResult.Invalid causing collectFrom to
+ // return
+ presenter.collectFrom(pagingData)
}
+
+ advanceUntilIdle()
+ // this will return only if presenter.collectFrom returns
+ deferred.await()
}
- val pagingData = pager.flow.first()
- val deferred = async {
- // only returns if flow is closed, or work canclled, or exception thrown
- // in this case it should cancel due LoadResult.Invalid causing collectFrom to return
- presenter.collectFrom(pagingData)
- }
+ @Test fun refresh_pagingDataEvent() = refresh_pagingDataEvent(false)
- advanceUntilIdle()
- // this will return only if presenter.collectFrom returns
- deferred.await()
- }
-
- @Test
- fun refresh_pagingDataEvent() = refresh_pagingDataEvent(false)
-
- @Test
- fun refresh_pagingDataEvent_collectWithCachedIn() = refresh_pagingDataEvent(true)
+ @Test fun refresh_pagingDataEvent_collectWithCachedIn() = refresh_pagingDataEvent(true)
private fun refresh_pagingDataEvent(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn, initialKey = 50) { presenter, _, _, _ ->
// execute queued initial REFRESH
advanceUntilIdle()
- val event = PageStore(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf(50, 51, 52, 53, 54, 55, 56, 57, 58),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = null,
- )
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- ) as PlaceholderPaddedList<Int>
+ val event =
+ PageStore(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf(50, 51, 52, 53, 54, 55, 56, 57, 58),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = null,
+ )
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
+ )
+ as PlaceholderPaddedList<Int>
assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- assertThat(presenter.newEvents()).containsExactly(
- PagingDataEvent.Refresh(
- previousList = PageStore.initial<Int>(null) as PlaceholderPaddedList<Int>,
- newList = event
+ assertThat(presenter.newEvents())
+ .containsExactly(
+ PagingDataEvent.Refresh(
+ previousList = PageStore.initial<Int>(null) as PlaceholderPaddedList<Int>,
+ newList = event
+ )
)
- )
presenter.refresh()
@@ -1461,30 +1477,32 @@
// // second refresh loads from initialKey = 0 because anchorPosition/refreshKey is null
assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newEvents()).containsExactly(
- PagingDataEvent.Refresh(
- previousList = event,
- newList = PageStore(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = null,
+ assertThat(presenter.newEvents())
+ .containsExactly(
+ PagingDataEvent.Refresh(
+ previousList = event,
+ newList =
+ PageStore(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = null,
+ )
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 0,
)
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 0,
- ) as PlaceholderPaddedList<Int>
+ as PlaceholderPaddedList<Int>
+ )
)
- )
}
- @Test
- fun append_pagingDataEvent() = append_pagingDataEvent(false)
+ @Test fun append_pagingDataEvent() = append_pagingDataEvent(false)
- @Test
- fun append_pagingDataEvent_collectWithCachedIn() = append_pagingDataEvent(true)
+ @Test fun append_pagingDataEvent_collectWithCachedIn() = append_pagingDataEvent(true)
private fun append_pagingDataEvent(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn) { presenter, _, _, _ ->
@@ -1499,31 +1517,26 @@
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- assertThat(presenter.newEvents().last()).isEqualTo(
- PagingDataEvent.Append(
- startIndex = 9,
- inserted = listOf(9, 10, 11),
- newPlaceholdersAfter = 0,
- oldPlaceholdersAfter = 0
+ assertThat(presenter.newEvents().last())
+ .isEqualTo(
+ PagingDataEvent.Append(
+ startIndex = 9,
+ inserted = listOf(9, 10, 11),
+ newPlaceholdersAfter = 0,
+ oldPlaceholdersAfter = 0
+ )
)
- )
}
- @Test
- fun appendDrop_pagingDataEvent() = appendDrop_pagingDataEvent(false)
+ @Test fun appendDrop_pagingDataEvent() = appendDrop_pagingDataEvent(false)
- @Test
- fun appendDrop_pagingDataEvent_collectWithCachedIn() = appendDrop_pagingDataEvent(true)
+ @Test fun appendDrop_pagingDataEvent_collectWithCachedIn() = appendDrop_pagingDataEvent(true)
private fun appendDrop_pagingDataEvent(collectWithCachedIn: Boolean) =
runTest(
collectWithCachedIn,
initialKey = 96,
- config = PagingConfig(
- pageSize = 1,
- maxSize = 4,
- enablePlaceholders = false
- )
+ config = PagingConfig(pageSize = 1, maxSize = 4, enablePlaceholders = false)
) { presenter, _, _, _ ->
// initial REFRESH
advanceUntilIdle()
@@ -1535,35 +1548,35 @@
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(96 until 100)
- assertThat(presenter.newEvents().last()).isEqualTo(
- PagingDataEvent.Append(
- startIndex = 3,
- inserted = listOf(99),
- newPlaceholdersAfter = 0,
- oldPlaceholdersAfter = 0
+ assertThat(presenter.newEvents().last())
+ .isEqualTo(
+ PagingDataEvent.Append(
+ startIndex = 3,
+ inserted = listOf(99),
+ newPlaceholdersAfter = 0,
+ oldPlaceholdersAfter = 0
+ )
)
- )
// trigger prepend and drop from append direction
presenter[0]
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(95 until 99)
// drop is processed before inserts
- assertThat(presenter.newEvents().first()).isEqualTo(
- PagingDataEvent.DropAppend<Int>(
- startIndex = 3,
- dropCount = 1,
- newPlaceholdersAfter = 0,
- oldPlaceholdersAfter = 0
+ assertThat(presenter.newEvents().first())
+ .isEqualTo(
+ PagingDataEvent.DropAppend<Int>(
+ startIndex = 3,
+ dropCount = 1,
+ newPlaceholdersAfter = 0,
+ oldPlaceholdersAfter = 0
+ )
)
- )
}
- @Test
- fun prepend_pagingDataEvent() = prepend_pagingDataEvent(false)
+ @Test fun prepend_pagingDataEvent() = prepend_pagingDataEvent(false)
- @Test
- fun prepend_pagingDataEvent_collectWithCachedIn() = prepend_pagingDataEvent(true)
+ @Test fun prepend_pagingDataEvent_collectWithCachedIn() = prepend_pagingDataEvent(true)
private fun prepend_pagingDataEvent(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn, initialKey = 50) { presenter, _, _, _ ->
@@ -1578,30 +1591,25 @@
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(47 until 59)
- assertThat(presenter.newEvents().last()).isEqualTo(
- PagingDataEvent.Prepend(
- inserted = listOf(47, 48, 49),
- newPlaceholdersBefore = 0,
- oldPlaceholdersBefore = 0
+ assertThat(presenter.newEvents().last())
+ .isEqualTo(
+ PagingDataEvent.Prepend(
+ inserted = listOf(47, 48, 49),
+ newPlaceholdersBefore = 0,
+ oldPlaceholdersBefore = 0
+ )
)
- )
}
- @Test
- fun prependDrop_pagingDataEvent() = prependDrop_pagingDataEvent(false)
+ @Test fun prependDrop_pagingDataEvent() = prependDrop_pagingDataEvent(false)
- @Test
- fun prependDrop_pagingDataEvent_collectWithCachedIn() = prependDrop_pagingDataEvent(true)
+ @Test fun prependDrop_pagingDataEvent_collectWithCachedIn() = prependDrop_pagingDataEvent(true)
private fun prependDrop_pagingDataEvent(collectWithCachedIn: Boolean) =
runTest(
collectWithCachedIn,
initialKey = 1,
- config = PagingConfig(
- pageSize = 1,
- maxSize = 4,
- enablePlaceholders = false
- )
+ config = PagingConfig(pageSize = 1, maxSize = 4, enablePlaceholders = false)
) { presenter, _, _, _ ->
// initial REFRESH
advanceUntilIdle()
@@ -1613,13 +1621,14 @@
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 4)
- assertThat(presenter.newEvents().last()).isEqualTo(
- PagingDataEvent.Prepend(
- inserted = listOf(0),
- newPlaceholdersBefore = 0,
- oldPlaceholdersBefore = 0
+ assertThat(presenter.newEvents().last())
+ .isEqualTo(
+ PagingDataEvent.Prepend(
+ inserted = listOf(0),
+ newPlaceholdersBefore = 0,
+ oldPlaceholdersBefore = 0
+ )
)
- )
// trigger append and drop from prepend direction
presenter[3]
@@ -1627,50 +1636,50 @@
assertThat(presenter.snapshot()).containsExactlyElementsIn(1 until 5)
// drop is processed before insert
- assertThat(presenter.newEvents().first()).isEqualTo(
- PagingDataEvent.DropPrepend<Int>(
- dropCount = 1,
- newPlaceholdersBefore = 0,
- oldPlaceholdersBefore = 0
+ assertThat(presenter.newEvents().first())
+ .isEqualTo(
+ PagingDataEvent.DropPrepend<Int>(
+ dropCount = 1,
+ newPlaceholdersBefore = 0,
+ oldPlaceholdersBefore = 0
+ )
)
- )
}
- @Test
- fun refresh_loadStates() = refresh_loadStates(false)
+ @Test fun refresh_loadStates() = refresh_loadStates(false)
- @Test
- fun refresh_loadStates_collectWithCachedIn() = refresh_loadStates(true)
+ @Test fun refresh_loadStates_collectWithCachedIn() = refresh_loadStates(true)
private fun refresh_loadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn, initialKey = 50) { presenter,
- pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn, initialKey = 50) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // execute queued initial REFRESH
- advanceUntilIdle()
+ // execute queued initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(),
+ )
- presenter.refresh()
+ presenter.refresh()
- // execute second REFRESH load
- advanceUntilIdle()
+ // execute second REFRESH load
+ advanceUntilIdle()
- // second refresh loads from initialKey = 0 because anchorPosition/refreshKey is null
- assertThat(pagingSources.size).isEqualTo(2)
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete)
- )
+ // second refresh loads from initialKey = 0 because anchorPosition/refreshKey is null
+ assertThat(pagingSources.size).isEqualTo(2)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete)
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
@Test
fun refresh_loadStates_afterEndOfPagination() = refresh_loadStates_afterEndOfPagination(false)
@@ -1681,249 +1690,245 @@
private fun refresh_loadStates_afterEndOfPagination(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn) { presenter, _, _, _ ->
- val loadStateCallbacks = mutableListOf<CombinedLoadStates>()
- presenter.addLoadStateListener {
- loadStateCallbacks.add(it)
+ val loadStateCallbacks = mutableListOf<CombinedLoadStates>()
+ presenter.addLoadStateListener { loadStateCallbacks.add(it) }
+ val collectLoadStates = launch { presenter.collectLoadStates() }
+ // execute initial refresh
+ advanceUntilIdle()
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(
+ refreshLocal = NotLoading(endOfPaginationReached = false),
+ prependLocal = NotLoading(endOfPaginationReached = true)
+ )
+ )
+ loadStateCallbacks.clear()
+ presenter.refresh()
+ // after a refresh, make sure the loading event comes in 1 piece w/ the end of
+ // pagination
+ // reset
+ advanceUntilIdle()
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(
+ refreshLocal = Loading,
+ prependLocal = NotLoading(endOfPaginationReached = false)
+ ),
+ localLoadStatesOf(
+ refreshLocal = NotLoading(endOfPaginationReached = false),
+ prependLocal = NotLoading(endOfPaginationReached = true)
+ ),
+ )
+ assertThat(loadStateCallbacks)
+ .containsExactly(
+ localLoadStatesOf(
+ refreshLocal = Loading,
+ prependLocal = NotLoading(endOfPaginationReached = false)
+ ),
+ localLoadStatesOf(
+ refreshLocal = NotLoading(endOfPaginationReached = false),
+ prependLocal = NotLoading(endOfPaginationReached = true)
+ ),
+ )
+ collectLoadStates.cancel()
}
- val collectLoadStates = launch { presenter.collectLoadStates() }
- // execute initial refresh
- advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- refreshLocal = Loading
- ),
- localLoadStatesOf(
- refreshLocal = NotLoading(endOfPaginationReached = false),
- prependLocal = NotLoading(endOfPaginationReached = true)
- )
- )
- loadStateCallbacks.clear()
- presenter.refresh()
- // after a refresh, make sure the loading event comes in 1 piece w/ the end of pagination
- // reset
- advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- refreshLocal = Loading,
- prependLocal = NotLoading(endOfPaginationReached = false)
- ),
- localLoadStatesOf(
- refreshLocal = NotLoading(endOfPaginationReached = false),
- prependLocal = NotLoading(endOfPaginationReached = true)
- ),
- )
- assertThat(loadStateCallbacks).containsExactly(
- localLoadStatesOf(
- refreshLocal = Loading,
- prependLocal = NotLoading(endOfPaginationReached = false)
- ),
- localLoadStatesOf(
- refreshLocal = NotLoading(endOfPaginationReached = false),
- prependLocal = NotLoading(endOfPaginationReached = true)
- ),
- )
- collectLoadStates.cancel()
- }
// TODO(b/195028524) the tests from here on checks the state after Invalid/Error results.
// Upon changes due to b/195028524, the asserts on these tests should see a new resetting
// LoadStateUpdate event
- @Test
- fun appendInvalid_loadStates() = appendInvalid_loadStates(false)
+ @Test fun appendInvalid_loadStates() = appendInvalid_loadStates(false)
- @Test
- fun appendInvalid_loadStates_collectWithCachedIn() = appendInvalid_loadStates(true)
+ @Test fun appendInvalid_loadStates_collectWithCachedIn() = appendInvalid_loadStates(true)
private fun appendInvalid_loadStates(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn) { presenter, pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- // normal append
- presenter[8]
+ // normal append
+ presenter[8]
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = Loading
- ),
- localLoadStatesOf(prependLocal = NotLoading.Complete)
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete)
+ )
- // do invalid append which will return LoadResult.Invalid
- presenter[11]
- pagingSources[0].nextLoadResult = LoadResult.Invalid()
+ // do invalid append which will return LoadResult.Invalid
+ presenter[11]
+ pagingSources[0].nextLoadResult = LoadResult.Invalid()
- // using advanceTimeBy instead of advanceUntilIdle, otherwise this invalid APPEND + subsequent
- // REFRESH will auto run consecutively and we won't be able to assert them incrementally
- advanceTimeBy(1001)
+ // using advanceTimeBy instead of advanceUntilIdle, otherwise this invalid APPEND +
+ // subsequent
+ // REFRESH will auto run consecutively and we won't be able to assert them incrementally
+ advanceTimeBy(1001)
- assertThat(pagingSources.size).isEqualTo(2)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // the invalid append
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = Loading
- ),
- // REFRESH on new paging source. Append/Prepend local states is reset because the
- // LoadStateUpdate from refresh sends the full map of a local LoadStates which was
- // initialized as IDLE upon new Snapshot.
- localLoadStatesOf(
- refreshLocal = Loading,
- ),
- )
+ assertThat(pagingSources.size).isEqualTo(2)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // the invalid append
+ localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
+ // REFRESH on new paging source. Append/Prepend local states is reset because
+ // the
+ // LoadStateUpdate from refresh sends the full map of a local LoadStates which
+ // was
+ // initialized as IDLE upon new Snapshot.
+ localLoadStatesOf(
+ refreshLocal = Loading,
+ ),
+ )
- // the LoadResult.Invalid from failed APPEND triggers new pagingSource + initial REFRESH
- advanceUntilIdle()
+ // the LoadResult.Invalid from failed APPEND triggers new pagingSource + initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(11 until 20)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(11 until 20)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun appendDrop_loadStates() = appendDrop_loadStates(false)
+ @Test fun appendDrop_loadStates() = appendDrop_loadStates(false)
- @Test
- fun appendDrop_loadStates_collectWithCachedIn() = appendDrop_loadStates(true)
+ @Test fun appendDrop_loadStates_collectWithCachedIn() = appendDrop_loadStates(true)
private fun appendDrop_loadStates(collectWithCachedIn: Boolean) =
runTest(
collectWithCachedIn,
initialKey = 96,
- config = PagingConfig(
- pageSize = 1,
- maxSize = 4,
- enablePlaceholders = false
- )
+ config = PagingConfig(pageSize = 1, maxSize = 4, enablePlaceholders = false)
) { presenter, _, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(96 until 99)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- // ensure append has reached end of pagination
- localLoadStatesOf(),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(96 until 99)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ // ensure append has reached end of pagination
+ localLoadStatesOf(),
+ )
- // trigger append to reach max page size
- presenter[2]
- advanceUntilIdle()
+ // trigger append to reach max page size
+ presenter[2]
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(96 until 100)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(appendLocal = Loading),
- localLoadStatesOf(appendLocal = NotLoading.Complete),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(96 until 100)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(appendLocal = Loading),
+ localLoadStatesOf(appendLocal = NotLoading.Complete),
+ )
- // trigger prepend and drop from append direction
- presenter[0]
- advanceUntilIdle()
+ // trigger prepend and drop from append direction
+ presenter[0]
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(95 until 99)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading, appendLocal = NotLoading.Complete),
- // page from the end is dropped so now appendLocal should be NotLoading.Incomplete
- localLoadStatesOf(prependLocal = Loading),
- localLoadStatesOf(),
- )
- collectLoadStates.cancel()
- }
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(95 until 99)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading, appendLocal = NotLoading.Complete),
+ // page from the end is dropped so now appendLocal should be
+ // NotLoading.Incomplete
+ localLoadStatesOf(prependLocal = Loading),
+ localLoadStatesOf(),
+ )
+ collectLoadStates.cancel()
+ }
- @Test
- fun prependInvalid_loadStates() = prependInvalid_loadStates(false)
+ @Test fun prependInvalid_loadStates() = prependInvalid_loadStates(false)
- @Test
- fun prependInvalid_loadStates_collectWithCachedIn() = prependInvalid_loadStates(true)
+ @Test fun prependInvalid_loadStates_collectWithCachedIn() = prependInvalid_loadStates(true)
private fun prependInvalid_loadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn, initialKey = 50) { presenter,
- pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn, initialKey = 50) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- // all local states NotLoading.Incomplete
- localLoadStatesOf(),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ // all local states NotLoading.Incomplete
+ localLoadStatesOf(),
+ )
- // normal prepend to ensure LoadStates for Page returns remains the same
- presenter[0]
+ // normal prepend to ensure LoadStates for Page returns remains the same
+ presenter[0]
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(47 until 59)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading),
- // all local states NotLoading.Incomplete
- localLoadStatesOf(),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(47 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading),
+ // all local states NotLoading.Incomplete
+ localLoadStatesOf(),
+ )
- // do an invalid prepend which will return LoadResult.Invalid
- presenter[0]
- pagingSources[0].nextLoadResult = LoadResult.Invalid()
- advanceTimeBy(1001)
+ // do an invalid prepend which will return LoadResult.Invalid
+ presenter[0]
+ pagingSources[0].nextLoadResult = LoadResult.Invalid()
+ advanceTimeBy(1001)
- assertThat(pagingSources.size).isEqualTo(2)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // the invalid prepend
- localLoadStatesOf(prependLocal = Loading),
- // REFRESH on new paging source. Append/Prepend local states is reset because the
- // LoadStateUpdate from refresh sends the full map of a local LoadStates which was
- // initialized as IDLE upon new Snapshot.
- localLoadStatesOf(refreshLocal = Loading),
- )
+ assertThat(pagingSources.size).isEqualTo(2)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // the invalid prepend
+ localLoadStatesOf(prependLocal = Loading),
+ // REFRESH on new paging source. Append/Prepend local states is reset because
+ // the
+ // LoadStateUpdate from refresh sends the full map of a local LoadStates which
+ // was
+ // initialized as IDLE upon new Snapshot.
+ localLoadStatesOf(refreshLocal = Loading),
+ )
- // the LoadResult.Invalid from failed PREPEND triggers new pagingSource + initial REFRESH
- advanceUntilIdle()
+ // the LoadResult.Invalid from failed PREPEND triggers new pagingSource + initial
+ // REFRESH
+ advanceUntilIdle()
- // load starts from 0 again because the provided initialKey = 50 is not multi-generational
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ // load starts from 0 again because the provided initialKey = 50 is not
+ // multi-generational
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun prependDrop_loadStates() = prependDrop_loadStates(false)
+ @Test fun prependDrop_loadStates() = prependDrop_loadStates(false)
- @Test
- fun prependDrop_loadStates_collectWithCachedIn() = prependDrop_loadStates(true)
+ @Test fun prependDrop_loadStates_collectWithCachedIn() = prependDrop_loadStates(true)
private fun prependDrop_loadStates(collectWithCachedIn: Boolean) =
runTest(
collectWithCachedIn,
initialKey = 1,
- config = PagingConfig(
- pageSize = 1,
- maxSize = 4,
- enablePlaceholders = false
- )
+ config = PagingConfig(pageSize = 1, maxSize = 4, enablePlaceholders = false)
) { presenter, _, _, _ ->
val collectLoadStates = launch { presenter.collectLoadStates() }
@@ -1931,447 +1936,445 @@
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(1 until 4)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- // ensure append has reached end of pagination
- localLoadStatesOf(),
- )
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ // ensure append has reached end of pagination
+ localLoadStatesOf(),
+ )
// trigger prepend to reach max page size
presenter[0]
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 4)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
// trigger append and drop from prepend direction
presenter[3]
advanceUntilIdle()
assertThat(presenter.snapshot()).containsExactlyElementsIn(1 until 5)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
- // first page is dropped so now prependLocal should be NotLoading.Incomplete
- localLoadStatesOf(appendLocal = Loading),
- localLoadStatesOf(),
- )
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
+ // first page is dropped so now prependLocal should be NotLoading.Incomplete
+ localLoadStatesOf(appendLocal = Loading),
+ localLoadStatesOf(),
+ )
collectLoadStates.cancel()
}
- @Test
- fun refreshInvalid_loadStates() = refreshInvalid_loadStates(false)
+ @Test fun refreshInvalid_loadStates() = refreshInvalid_loadStates(false)
- @Test
- fun refreshInvalid_loadStates_collectWithCachedIn() = refreshInvalid_loadStates(true)
+ @Test fun refreshInvalid_loadStates_collectWithCachedIn() = refreshInvalid_loadStates(true)
private fun refreshInvalid_loadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn, initialKey = 50) { presenter,
- pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn, initialKey = 50) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // execute queued initial REFRESH load which will return LoadResult.Invalid()
- pagingSources[0].nextLoadResult = LoadResult.Invalid()
- advanceTimeBy(1001)
+ // execute queued initial REFRESH load which will return LoadResult.Invalid()
+ pagingSources[0].nextLoadResult = LoadResult.Invalid()
+ advanceTimeBy(1001)
- assertThat(presenter.snapshot()).isEmpty()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // invalid first refresh. The second refresh state update that follows is identical to
- // this LoadStates so it gets de-duped
- localLoadStatesOf(refreshLocal = Loading),
- )
+ assertThat(presenter.snapshot()).isEmpty()
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // invalid first refresh. The second refresh state update that follows is
+ // identical to
+ // this LoadStates so it gets de-duped
+ localLoadStatesOf(refreshLocal = Loading),
+ )
- // execute second REFRESH load
- advanceUntilIdle()
+ // execute second REFRESH load
+ advanceUntilIdle()
- // second refresh still loads from initialKey = 50 because anchorPosition/refreshKey is null
- assertThat(pagingSources.size).isEqualTo(2)
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- )
- )
+ // second refresh still loads from initialKey = 50 because anchorPosition/refreshKey is
+ // null
+ assertThat(pagingSources.size).isEqualTo(2)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ )
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun appendError_retryLoadStates() = appendError_retryLoadStates(false)
+ @Test fun appendError_retryLoadStates() = appendError_retryLoadStates(false)
- @Test
- fun appendError_retryLoadStates_collectWithCachedIn() = appendError_retryLoadStates(true)
+ @Test fun appendError_retryLoadStates_collectWithCachedIn() = appendError_retryLoadStates(true)
private fun appendError_retryLoadStates(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn) { presenter, pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // append returns LoadResult.Error
- presenter[8]
- val exception = Throwable()
- pagingSources[0].nextLoadResult = LoadResult.Error(exception)
+ // append returns LoadResult.Error
+ presenter[8]
+ val exception = Throwable()
+ pagingSources[0].nextLoadResult = LoadResult.Error(exception)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = Loading
- ),
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = LoadState.Error(exception)
- ),
- )
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
+ localLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = LoadState.Error(exception)
+ ),
+ )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // retry append
- presenter.retry()
- advanceUntilIdle()
+ // retry append
+ presenter.retry()
+ advanceUntilIdle()
- // make sure append success
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
- // no reset
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = Loading
- ),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ // make sure append success
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 12)
+ // no reset
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = NotLoading.Complete, appendLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun prependError_retryLoadStates() = prependError_retryLoadStates(false)
+ @Test fun prependError_retryLoadStates() = prependError_retryLoadStates(false)
@Test
fun prependError_retryLoadStates_collectWithCachedIn() = prependError_retryLoadStates(true)
private fun prependError_retryLoadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn, initialKey = 50) { presenter,
- pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn, initialKey = 50) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(),
- )
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(),
+ )
- assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- // prepend returns LoadResult.Error
- presenter[0]
- val exception = Throwable()
- pagingSources[0].nextLoadResult = LoadResult.Error(exception)
+ // prepend returns LoadResult.Error
+ presenter[0]
+ val exception = Throwable()
+ pagingSources[0].nextLoadResult = LoadResult.Error(exception)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading),
- localLoadStatesOf(prependLocal = LoadState.Error(exception)),
- )
- assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading),
+ localLoadStatesOf(prependLocal = LoadState.Error(exception)),
+ )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- // retry prepend
- presenter.retry()
+ // retry prepend
+ presenter.retry()
- advanceUntilIdle()
+ advanceUntilIdle()
- // make sure prepend success
- assertThat(presenter.snapshot()).containsExactlyElementsIn(47 until 59)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading),
- localLoadStatesOf(),
- )
+ // make sure prepend success
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(47 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading),
+ localLoadStatesOf(),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun refreshError_retryLoadStates() = refreshError_retryLoadStates(false)
+ @Test fun refreshError_retryLoadStates() = refreshError_retryLoadStates(false)
@Test
fun refreshError_retryLoadStates_collectWithCachedIn() = refreshError_retryLoadStates(true)
private fun refreshError_retryLoadStates(collectWithCachedIn: Boolean) =
runTest(collectWithCachedIn) { presenter, pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial load returns LoadResult.Error
- val exception = Throwable()
- pagingSources[0].nextLoadResult = LoadResult.Error(exception)
+ // initial load returns LoadResult.Error
+ val exception = Throwable()
+ pagingSources[0].nextLoadResult = LoadResult.Error(exception)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(refreshLocal = LoadState.Error(exception)),
- )
- assertThat(presenter.snapshot()).isEmpty()
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(refreshLocal = LoadState.Error(exception)),
+ )
+ assertThat(presenter.snapshot()).isEmpty()
- // retry refresh
- presenter.retry()
+ // retry refresh
+ presenter.retry()
- advanceUntilIdle()
+ advanceUntilIdle()
- // refresh retry does not trigger new gen
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // Goes directly from Error --> Loading without resetting refresh to NotLoading
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ // refresh retry does not trigger new gen
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ // Goes directly from Error --> Loading without resetting refresh to NotLoading
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun prependError_refreshLoadStates() = prependError_refreshLoadStates(false)
+ @Test fun prependError_refreshLoadStates() = prependError_refreshLoadStates(false)
@Test
fun prependError_refreshLoadStates_collectWithCachedIn() = prependError_refreshLoadStates(true)
private fun prependError_refreshLoadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn, initialKey = 50) { presenter,
- pagingSources, _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn, initialKey = 50) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // initial REFRESH
- advanceUntilIdle()
+ // initial REFRESH
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(),
- )
- assertThat(presenter.size).isEqualTo(9)
- assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(),
+ )
+ assertThat(presenter.size).isEqualTo(9)
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(50 until 59)
- // prepend returns LoadResult.Error
- presenter[0]
- val exception = Throwable()
- pagingSources[0].nextLoadResult = LoadResult.Error(exception)
+ // prepend returns LoadResult.Error
+ presenter[0]
+ val exception = Throwable()
+ pagingSources[0].nextLoadResult = LoadResult.Error(exception)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(prependLocal = Loading),
- localLoadStatesOf(prependLocal = LoadState.Error(exception)),
- )
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(prependLocal = Loading),
+ localLoadStatesOf(prependLocal = LoadState.Error(exception)),
+ )
- // refresh() should reset local LoadStates and trigger new REFRESH
- presenter.refresh()
- advanceUntilIdle()
+ // refresh() should reset local LoadStates and trigger new REFRESH
+ presenter.refresh()
+ advanceUntilIdle()
- // Initial load starts from 0 because initialKey is single gen.
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // second gen REFRESH load. The Error prepend state was automatically reset to
- // NotLoading.
- localLoadStatesOf(refreshLocal = Loading),
- // REFRESH complete
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ // Initial load starts from 0 because initialKey is single gen.
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // second gen REFRESH load. The Error prepend state was automatically reset to
+ // NotLoading.
+ localLoadStatesOf(refreshLocal = Loading),
+ // REFRESH complete
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
- @Test
- fun refreshError_refreshLoadStates() = refreshError_refreshLoadStates(false)
+ @Test fun refreshError_refreshLoadStates() = refreshError_refreshLoadStates(false)
@Test
fun refreshError_refreshLoadStates_collectWithCachedIn() = refreshError_refreshLoadStates(true)
private fun refreshError_refreshLoadStates(collectWithCachedIn: Boolean) =
- runTest(collectWithCachedIn) { presenter, pagingSources,
- _, _ ->
- val collectLoadStates = launch { presenter.collectLoadStates() }
+ runTest(collectWithCachedIn) { presenter, pagingSources, _, _ ->
+ val collectLoadStates = launch { presenter.collectLoadStates() }
- // the initial load will return LoadResult.Error
- val exception = Throwable()
- pagingSources[0].nextLoadResult = LoadResult.Error(exception)
+ // the initial load will return LoadResult.Error
+ val exception = Throwable()
+ pagingSources[0].nextLoadResult = LoadResult.Error(exception)
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(refreshLocal = LoadState.Error(exception)),
- )
- assertThat(presenter.snapshot()).isEmpty()
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(refreshLocal = LoadState.Error(exception)),
+ )
+ assertThat(presenter.snapshot()).isEmpty()
- // refresh should trigger new generation
- presenter.refresh()
+ // refresh should trigger new generation
+ presenter.refresh()
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
- // Goes directly from Error --> Loading without resetting refresh to NotLoading
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(prependLocal = NotLoading.Complete),
- )
+ assertThat(presenter.snapshot()).containsExactlyElementsIn(0 until 9)
+ // Goes directly from Error --> Loading without resetting refresh to NotLoading
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(prependLocal = NotLoading.Complete),
+ )
- collectLoadStates.cancel()
- }
+ collectLoadStates.cancel()
+ }
@Test
- fun remoteRefresh_refreshStatePersists() = testScope.runTest {
- val presenter = SimplePresenter()
- val remoteMediator = RemoteMediatorMock(loadDelay = 1500).apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ fun remoteRefresh_refreshStatePersists() =
+ testScope.runTest {
+ val presenter = SimplePresenter()
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 1500).apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
+ val pager =
+ Pager(
+ PagingConfig(pageSize = 3, enablePlaceholders = false),
+ remoteMediator = remoteMediator,
+ ) {
+ TestPagingSource(loadDelay = 500, items = emptyList())
+ }
+
+ val collectLoadStates = launch { presenter.collectLoadStates() }
+ val job = launch { pager.flow.collectLatest { presenter.collectFrom(it) } }
+ // allow local refresh to complete but not remote refresh
+ advanceTimeBy(600)
+
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // local starts loading
+ remoteLoadStatesOf(
+ refreshLocal = Loading,
+ ),
+ // remote starts loading
+ remoteLoadStatesOf(
+ refresh = Loading,
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ // local load returns with empty data, mediator is still loading
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ refreshRemote = Loading,
+ ),
+ )
+
+ // refresh triggers new generation & LoadState reset
+ presenter.refresh()
+
+ // allow local refresh to complete but not remote refresh
+ advanceTimeBy(600)
+
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // local starts second refresh while mediator continues remote refresh from
+ // before
+ remoteLoadStatesOf(
+ refresh = Loading,
+ refreshLocal = Loading,
+ refreshRemote = Loading,
+ ),
+ // local load returns empty data
+ remoteLoadStatesOf(
+ refresh = Loading,
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ refreshRemote = Loading,
+ ),
+ )
+
+ // allow remote refresh to complete
+ advanceTimeBy(600)
+
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // remote refresh returns empty and triggers remote append/prepend
+ remoteLoadStatesOf(
+ prepend = Loading,
+ append = Loading,
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ prependRemote = Loading,
+ appendRemote = Loading,
+ ),
+ )
+
+ // allow remote append and prepend to complete
+ advanceUntilIdle()
+
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(
+ // prepend completes first
+ remoteLoadStatesOf(
+ append = Loading,
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ appendRemote = Loading,
+ ),
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ appendLocal = NotLoading.Complete,
+ ),
+ )
+
+ job.cancel()
+ collectLoadStates.cancel()
}
- val pager = Pager(
- PagingConfig(pageSize = 3, enablePlaceholders = false),
- remoteMediator = remoteMediator,
- ) {
- TestPagingSource(loadDelay = 500, items = emptyList())
- }
-
- val collectLoadStates = launch { presenter.collectLoadStates() }
- val job = launch {
- pager.flow.collectLatest {
- presenter.collectFrom(it)
- }
- }
- // allow local refresh to complete but not remote refresh
- advanceTimeBy(600)
-
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // local starts loading
- remoteLoadStatesOf(
- refreshLocal = Loading,
- ),
- // remote starts loading
- remoteLoadStatesOf(
- refresh = Loading,
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- // local load returns with empty data, mediator is still loading
- remoteLoadStatesOf(
- refresh = Loading,
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- refreshRemote = Loading,
- ),
- )
-
- // refresh triggers new generation & LoadState reset
- presenter.refresh()
-
- // allow local refresh to complete but not remote refresh
- advanceTimeBy(600)
-
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // local starts second refresh while mediator continues remote refresh from before
- remoteLoadStatesOf(
- refresh = Loading,
- refreshLocal = Loading,
- refreshRemote = Loading,
- ),
- // local load returns empty data
- remoteLoadStatesOf(
- refresh = Loading,
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- refreshRemote = Loading,
- ),
- )
-
- // allow remote refresh to complete
- advanceTimeBy(600)
-
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // remote refresh returns empty and triggers remote append/prepend
- remoteLoadStatesOf(
- prepend = Loading,
- append = Loading,
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- prependRemote = Loading,
- appendRemote = Loading,
- ),
- )
-
- // allow remote append and prepend to complete
- advanceUntilIdle()
-
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- // prepend completes first
- remoteLoadStatesOf(
- append = Loading,
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- appendRemote = Loading,
- ),
- remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- appendLocal = NotLoading.Complete,
- ),
- )
-
- job.cancel()
- collectLoadStates.cancel()
- }
@Test
- fun recollectOnNewPresenter_initialLoadStates() = testScope.runTest {
- val pager = Pager(
- config = PagingConfig(pageSize = 3, enablePlaceholders = false),
- initialKey = 50,
- pagingSourceFactory = { TestPagingSource() }
- ).flow.cachedIn(this)
+ fun recollectOnNewPresenter_initialLoadStates() =
+ testScope.runTest {
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, enablePlaceholders = false),
+ initialKey = 50,
+ pagingSourceFactory = { TestPagingSource() }
+ )
+ .flow
+ .cachedIn(this)
- val presenter = SimplePresenter()
- backgroundScope.launch { presenter.collectLoadStates() }
+ val presenter = SimplePresenter()
+ backgroundScope.launch { presenter.collectLoadStates() }
- val job = launch {
- pager.collectLatest {
- presenter.collectFrom(it)
- }
+ val job = launch { pager.collectLatest { presenter.collectFrom(it) } }
+ advanceUntilIdle()
+
+ assertThat(presenter.newCombinedLoadStates())
+ .containsExactly(localLoadStatesOf(refreshLocal = Loading), localLoadStatesOf())
+
+ // we start a separate presenter to recollect on cached Pager.flow
+ val presenter2 = SimplePresenter()
+ backgroundScope.launch { presenter2.collectLoadStates() }
+
+ val job2 = launch { pager.collectLatest { presenter2.collectFrom(it) } }
+ advanceUntilIdle()
+
+ assertThat(presenter2.newCombinedLoadStates()).containsExactly(localLoadStatesOf())
+
+ job.cancel()
+ job2.cancel()
+ coroutineContext.cancelChildren()
}
- advanceUntilIdle()
-
- assertThat(presenter.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf()
- )
-
- // we start a separate presenter to recollect on cached Pager.flow
- val presenter2 = SimplePresenter()
- backgroundScope.launch { presenter2.collectLoadStates() }
-
- val job2 = launch {
- pager.collectLatest {
- presenter2.collectFrom(it)
- }
- }
- advanceUntilIdle()
-
- assertThat(presenter2.newCombinedLoadStates()).containsExactly(
- localLoadStatesOf()
- )
-
- job.cancel()
- job2.cancel()
- coroutineContext.cancelChildren()
- }
@Test
fun cachedData() {
@@ -2395,11 +2398,12 @@
val data = List(50) { it }
val localStates = loadStates(refresh = Loading)
val mediatorStates = loadStates()
- val cachedPagingData = createCachedPagingData(
- data = data,
- sourceLoadStates = localStates,
- mediatorLoadStates = mediatorStates
- )
+ val cachedPagingData =
+ createCachedPagingData(
+ data = data,
+ sourceLoadStates = localStates,
+ mediatorLoadStates = mediatorStates
+ )
val simplePresenter = SimplePresenter(cachedPagingData)
val expected = simplePresenter.loadStateFlow.value
assertThat(expected).isNotNull()
@@ -2408,183 +2412,201 @@
}
@Test
- fun cachedData_doesNotSetHintReceiver() = testScope.runTest {
- val data = List(50) { it }
- val hintReceiver = HintReceiverFake()
- val cachedPagingData = createCachedPagingData(
- data = data,
- sourceLoadStates = loadStates(refresh = Loading),
- mediatorLoadStates = null,
- hintReceiver = hintReceiver
- )
- val presenter = SimplePresenter(cachedPagingData)
+ fun cachedData_doesNotSetHintReceiver() =
+ testScope.runTest {
+ val data = List(50) { it }
+ val hintReceiver = HintReceiverFake()
+ val cachedPagingData =
+ createCachedPagingData(
+ data = data,
+ sourceLoadStates = loadStates(refresh = Loading),
+ mediatorLoadStates = null,
+ hintReceiver = hintReceiver
+ )
+ val presenter = SimplePresenter(cachedPagingData)
- // access item
- presenter[5]
- assertThat(hintReceiver.hints).hasSize(0)
+ // access item
+ presenter[5]
+ assertThat(hintReceiver.hints).hasSize(0)
- val flow = flowOf(
- localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
- )
- val hintReceiver2 = HintReceiverFake()
+ val flow =
+ flowOf(
+ localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
+ )
+ val hintReceiver2 = HintReceiverFake()
- val job1 = launch {
- presenter.collectFrom(PagingData(flow, dummyUiReceiver, hintReceiver2))
+ val job1 = launch {
+ presenter.collectFrom(PagingData(flow, dummyUiReceiver, hintReceiver2))
+ }
+
+ // access item, hint should be sent to the first uncached PagingData
+ presenter[3]
+ assertThat(hintReceiver.hints).hasSize(0)
+ assertThat(hintReceiver2.hints).hasSize(1)
+ job1.cancel()
}
- // access item, hint should be sent to the first uncached PagingData
- presenter[3]
- assertThat(hintReceiver.hints).hasSize(0)
- assertThat(hintReceiver2.hints).hasSize(1)
- job1.cancel()
- }
-
@Test
- fun cachedData_doesNotSetUiReceiver() = testScope.runTest {
- val data = List(50) { it }
- val uiReceiver = UiReceiverFake()
- val cachedPagingData = createCachedPagingData(
- data = data,
- sourceLoadStates = loadStates(refresh = Loading),
- mediatorLoadStates = null,
- uiReceiver = uiReceiver
- )
- val presenter = SimplePresenter(cachedPagingData)
- presenter.refresh()
- advanceUntilIdle()
- assertThat(uiReceiver.refreshEvents).hasSize(0)
+ fun cachedData_doesNotSetUiReceiver() =
+ testScope.runTest {
+ val data = List(50) { it }
+ val uiReceiver = UiReceiverFake()
+ val cachedPagingData =
+ createCachedPagingData(
+ data = data,
+ sourceLoadStates = loadStates(refresh = Loading),
+ mediatorLoadStates = null,
+ uiReceiver = uiReceiver
+ )
+ val presenter = SimplePresenter(cachedPagingData)
+ presenter.refresh()
+ advanceUntilIdle()
+ assertThat(uiReceiver.refreshEvents).hasSize(0)
- val flow = flowOf(
- localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
- )
- val uiReceiver2 = UiReceiverFake()
- val job1 = launch {
- presenter.collectFrom(PagingData(flow, uiReceiver2, dummyHintReceiver))
+ val flow =
+ flowOf(
+ localRefresh(pages = listOf(TransformablePage(listOf(0, 1, 2, 3, 4)))),
+ )
+ val uiReceiver2 = UiReceiverFake()
+ val job1 = launch {
+ presenter.collectFrom(PagingData(flow, uiReceiver2, dummyHintReceiver))
+ }
+ presenter.refresh()
+ assertThat(uiReceiver.refreshEvents).hasSize(0)
+ assertThat(uiReceiver2.refreshEvents).hasSize(1)
+ job1.cancel()
}
- presenter.refresh()
- assertThat(uiReceiver.refreshEvents).hasSize(0)
- assertThat(uiReceiver2.refreshEvents).hasSize(1)
- job1.cancel()
- }
@Test
- fun cachedData_thenRealData() = testScope.runTest {
- val data = List(2) { it }
- val cachedPagingData = createCachedPagingData(
- data = data,
- sourceLoadStates = loadStates(refresh = Loading),
- mediatorLoadStates = null,
- )
- val presenter = SimplePresenter(cachedPagingData)
- val data2 = List(10) { it }
- val flow = flowOf(
- localRefresh(pages = listOf(TransformablePage(data2))),
- )
- val job1 = launch {
- presenter.collectFrom(PagingData(flow, dummyUiReceiver, dummyHintReceiver))
- }
+ fun cachedData_thenRealData() =
+ testScope.runTest {
+ val data = List(2) { it }
+ val cachedPagingData =
+ createCachedPagingData(
+ data = data,
+ sourceLoadStates = loadStates(refresh = Loading),
+ mediatorLoadStates = null,
+ )
+ val presenter = SimplePresenter(cachedPagingData)
+ val data2 = List(10) { it }
+ val flow =
+ flowOf(
+ localRefresh(pages = listOf(TransformablePage(data2))),
+ )
+ val job1 = launch {
+ presenter.collectFrom(PagingData(flow, dummyUiReceiver, dummyHintReceiver))
+ }
- assertThat(presenter.snapshot()).isEqualTo(data2)
- job1.cancel()
- }
+ assertThat(presenter.snapshot()).isEqualTo(data2)
+ job1.cancel()
+ }
@Test
- fun cachedData_thenLoadError() = testScope.runTest {
- val data = List(3) { it }
- val cachedPagingData = createCachedPagingData(
- data = data,
- sourceLoadStates = loadStates(refresh = Loading),
- mediatorLoadStates = null,
- )
- val presenter = SimplePresenter(cachedPagingData)
+ fun cachedData_thenLoadError() =
+ testScope.runTest {
+ val data = List(3) { it }
+ val cachedPagingData =
+ createCachedPagingData(
+ data = data,
+ sourceLoadStates = loadStates(refresh = Loading),
+ mediatorLoadStates = null,
+ )
+ val presenter = SimplePresenter(cachedPagingData)
- val channel = Channel<PageEvent<Int>>(Channel.UNLIMITED)
- val hintReceiver = HintReceiverFake()
- val uiReceiver = UiReceiverFake()
- val job1 = launch {
- presenter.collectFrom(PagingData(channel.consumeAsFlow(), uiReceiver, hintReceiver))
+ val channel = Channel<PageEvent<Int>>(Channel.UNLIMITED)
+ val hintReceiver = HintReceiverFake()
+ val uiReceiver = UiReceiverFake()
+ val job1 = launch {
+ presenter.collectFrom(PagingData(channel.consumeAsFlow(), uiReceiver, hintReceiver))
+ }
+ val error = LoadState.Error(Exception())
+ channel.trySend(localLoadStateUpdate(refreshLocal = error))
+ assertThat(presenter.nonNullLoadStateFlow.first())
+ .isEqualTo(localLoadStatesOf(refreshLocal = error))
+
+ // ui receiver is set upon processing a LoadStateUpdate so we can still trigger
+ // refresh/retry
+ presenter.refresh()
+ assertThat(uiReceiver.refreshEvents).hasSize(1)
+ // but hint receiver is only set if presenter has presented a refresh from this
+ // PagingData
+ // which did not happen in this case
+ presenter[2]
+ assertThat(hintReceiver.hints).hasSize(0)
+ job1.cancel()
}
- val error = LoadState.Error(Exception())
- channel.trySend(
- localLoadStateUpdate(refreshLocal = error)
- )
- assertThat(presenter.nonNullLoadStateFlow.first()).isEqualTo(
- localLoadStatesOf(refreshLocal = error)
- )
-
- // ui receiver is set upon processing a LoadStateUpdate so we can still trigger
- // refresh/retry
- presenter.refresh()
- assertThat(uiReceiver.refreshEvents).hasSize(1)
- // but hint receiver is only set if presenter has presented a refresh from this PagingData
- // which did not happen in this case
- presenter[2]
- assertThat(hintReceiver.hints).hasSize(0)
- job1.cancel()
- }
private fun runTest(
collectWithCachedIn: Boolean,
initialKey: Int? = null,
config: PagingConfig = PagingConfig(pageSize = 3, enablePlaceholders = false),
- block: TestScope.(
- presenter: SimplePresenter,
- pagingSources: List<TestPagingSource>,
- uiReceivers: List<TrackableUiReceiverWrapper>,
- hintReceivers: List<TrackableHintReceiverWrapper>
- ) -> Unit
- ) = testScope.runTest {
- val pagingSources = mutableListOf<TestPagingSource>()
- val pager = Pager(
- config = config,
- initialKey = initialKey,
- pagingSourceFactory = {
- TestPagingSource(
- loadDelay = 1000,
- ).also { pagingSources.add(it) }
- }
- )
- val presenter = SimplePresenter()
- val uiReceivers = mutableListOf<TrackableUiReceiverWrapper>()
- val hintReceivers = mutableListOf<TrackableHintReceiverWrapper>()
-
- val collection = launch {
- pager.flow
- .map { pagingData ->
- PagingData(
- flow = pagingData.flow,
- uiReceiver = TrackableUiReceiverWrapper(pagingData.uiReceiver)
- .also { uiReceivers.add(it) },
- hintReceiver = TrackableHintReceiverWrapper(pagingData.hintReceiver)
- .also { hintReceivers.add(it) }
+ block:
+ TestScope.(
+ presenter: SimplePresenter,
+ pagingSources: List<TestPagingSource>,
+ uiReceivers: List<TrackableUiReceiverWrapper>,
+ hintReceivers: List<TrackableHintReceiverWrapper>
+ ) -> Unit
+ ) =
+ testScope.runTest {
+ val pagingSources = mutableListOf<TestPagingSource>()
+ val pager =
+ Pager(
+ config = config,
+ initialKey = initialKey,
+ pagingSourceFactory = {
+ TestPagingSource(
+ loadDelay = 1000,
+ )
+ .also { pagingSources.add(it) }
+ }
)
- }.let {
- if (collectWithCachedIn) {
- it.cachedIn(this)
- } else {
- it
- }
- }.collect {
- presenter.collectFrom(it)
+ val presenter = SimplePresenter()
+ val uiReceivers = mutableListOf<TrackableUiReceiverWrapper>()
+ val hintReceivers = mutableListOf<TrackableHintReceiverWrapper>()
+
+ val collection = launch {
+ pager.flow
+ .map { pagingData ->
+ PagingData(
+ flow = pagingData.flow,
+ uiReceiver =
+ TrackableUiReceiverWrapper(pagingData.uiReceiver).also {
+ uiReceivers.add(it)
+ },
+ hintReceiver =
+ TrackableHintReceiverWrapper(pagingData.hintReceiver).also {
+ hintReceivers.add(it)
+ }
+ )
+ }
+ .let {
+ if (collectWithCachedIn) {
+ it.cachedIn(this)
+ } else {
+ it
+ }
+ }
+ .collect { presenter.collectFrom(it) }
+ }
+
+ try {
+ block(presenter, pagingSources, uiReceivers, hintReceivers)
+ } finally {
+ collection.cancel()
}
}
-
- try {
- block(presenter, pagingSources, uiReceivers, hintReceivers)
- } finally {
- collection.cancel()
- }
- }
}
private fun infinitelySuspendingPagingData(
uiReceiver: UiReceiver = dummyUiReceiver,
hintReceiver: HintReceiver = dummyHintReceiver
-) = PagingData(
- flow { emit(suspendCancellableCoroutine<PageEvent<Int>> { }) },
- uiReceiver,
- hintReceiver
-)
+) =
+ PagingData(
+ flow { emit(suspendCancellableCoroutine<PageEvent<Int>> {}) },
+ uiReceiver,
+ hintReceiver
+ )
private fun createCachedPagingData(
data: List<Int>,
@@ -2628,8 +2650,7 @@
val hints: List<ViewportHint>
get() {
val result = _hints.toList()
- @OptIn(ExperimentalStdlibApi::class)
- repeat(result.size) { _hints.removeFirst() }
+ @OptIn(ExperimentalStdlibApi::class) repeat(result.size) { _hints.removeFirst() }
return result
}
@@ -2662,8 +2683,7 @@
val hints: List<ViewportHint>
get() {
val result = _hints.toList()
- @OptIn(ExperimentalStdlibApi::class)
- repeat(result.size) { _hints.removeFirst() }
+ @OptIn(ExperimentalStdlibApi::class) repeat(result.size) { _hints.removeFirst() }
return result
}
@@ -2675,10 +2695,11 @@
private class SimplePresenter(
cachedPagingData: PagingData<Int>? = null,
-) : PagingDataPresenter<Int>(
- mainContext = EmptyCoroutineContext,
- cachedPagingData = cachedPagingData
-) {
+) :
+ PagingDataPresenter<Int>(
+ mainContext = EmptyCoroutineContext,
+ cachedPagingData = cachedPagingData
+ ) {
private val _localLoadStates = mutableListOf<CombinedLoadStates>()
val nonNullLoadStateFlow = loadStateFlow.filterNotNull()
@@ -2708,11 +2729,14 @@
}
}
-internal val dummyUiReceiver = object : UiReceiver {
- override fun retry() {}
- override fun refresh() {}
-}
+internal val dummyUiReceiver =
+ object : UiReceiver {
+ override fun retry() {}
-internal val dummyHintReceiver = object : HintReceiver {
- override fun accessHint(viewportHint: ViewportHint) {}
-}
+ override fun refresh() {}
+ }
+
+internal val dummyHintReceiver =
+ object : HintReceiver {
+ override fun accessHint(viewportHint: ViewportHint) {}
+ }
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingSourceTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingSourceTest.kt
index 3e19c36..5f0400f 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingSourceTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingSourceTest.kt
@@ -61,9 +61,7 @@
val errorParams = LoadParams.Refresh(key, 10, false)
// Verify error is propagated correctly.
pagingSource.enqueueError()
- assertFailsWith<CustomException> {
- pagingSource.load(errorParams)
- }
+ assertFailsWith<CustomException> { pagingSource.load(errorParams) }
// Verify LoadResult.Invalid is returned
pagingSource.invalidateLoad()
assertTrue(pagingSource.load(errorParams) is LoadResult.Invalid)
@@ -201,9 +199,7 @@
val errorParams = LoadParams.Prepend(key, 5, false)
// Verify error is propagated correctly.
dataSource.enqueueError()
- assertFailsWith<CustomException> {
- dataSource.load(errorParams)
- }
+ assertFailsWith<CustomException> { dataSource.load(errorParams) }
// Verify LoadResult.Invalid is returned
dataSource.invalidateLoad()
assertTrue(dataSource.load(errorParams) is LoadResult.Invalid)
@@ -222,9 +218,7 @@
val errorParams = LoadParams.Append(key, 5, false)
// Verify error is propagated correctly.
dataSource.enqueueError()
- assertFailsWith<CustomException> {
- dataSource.load(errorParams)
- }
+ assertFailsWith<CustomException> { dataSource.load(errorParams) }
// Verify LoadResult.Invalid is returned
dataSource.invalidateLoad()
assertTrue(dataSource.load(errorParams) is LoadResult.Invalid)
@@ -277,26 +271,19 @@
}
// second page
- val params2 = LoadParams.Append(
- ITEMS_BY_NAME_ID[10].key(), 5, false
- )
+ val params2 = LoadParams.Append(ITEMS_BY_NAME_ID[10].key(), 5, false)
val page2 = dataSource.load(params2) as LoadResult.Page
pages.add(page2)
// iterate through list of pages
- assertThat(pages.flatten()).containsExactlyElementsIn(
- ITEMS_BY_NAME_ID.subList(6, 16)
- ).inOrder()
+ assertThat(pages.flatten())
+ .containsExactlyElementsIn(ITEMS_BY_NAME_ID.subList(6, 16))
+ .inOrder()
}
data class Key(val name: String, val id: Int)
- data class Item(
- val name: String,
- val id: Int,
- val balance: Double,
- val address: String
- )
+ data class Item(val name: String, val id: Int, val balance: Double, val address: String)
fun Item.key() = Key(name, id)
@@ -309,13 +296,14 @@
private fun List<Item>.asPage(
itemsBefore: Int = COUNT_UNDEFINED,
itemsAfter: Int = COUNT_UNDEFINED
- ): LoadResult.Page<Key, Item> = LoadResult.Page(
- data = this,
- prevKey = firstOrNull()?.key(),
- nextKey = lastOrNull()?.key(),
- itemsBefore = itemsBefore,
- itemsAfter = itemsAfter
- )
+ ): LoadResult.Page<Key, Item> =
+ LoadResult.Page(
+ data = this,
+ prevKey = firstOrNull()?.key(),
+ nextKey = lastOrNull()?.key(),
+ itemsBefore = itemsBefore,
+ itemsAfter = itemsAfter
+ )
private var error = false
@@ -387,9 +375,8 @@
}
private fun findFirstIndexAfter(key: Key): Int {
- return items.indices.firstOrNull {
- KEY_COMPARATOR.compare(key, items[it].key()) < 0
- } ?: items.size
+ return items.indices.firstOrNull { KEY_COMPARATOR.compare(key, items[it].key()) < 0 }
+ ?: items.size
}
private fun findFirstIndexBefore(key: Key): Int {
@@ -413,15 +400,17 @@
private val ITEM_COMPARATOR = compareBy<Item> { it.name }.thenByDescending { it.id }
private val KEY_COMPARATOR = compareBy<Key> { it.name }.thenByDescending { it.id }
- private val ITEMS_BY_NAME_ID = List(100) {
- val names = Array(10) { index -> "f" + ('a' + index) }
- Item(
- names[it % 10],
- it,
- Random.nextDouble(1000.0),
- Random.nextInt(200).toString() + " fake st."
- )
- }.sortedWith(ITEM_COMPARATOR)
+ private val ITEMS_BY_NAME_ID =
+ List(100) {
+ val names = Array(10) { index -> "f" + ('a' + index) }
+ Item(
+ names[it % 10],
+ it,
+ Random.nextDouble(1000.0),
+ Random.nextInt(200).toString() + " fake st."
+ )
+ }
+ .sortedWith(ITEM_COMPARATOR)
private val EXCEPTION = CustomException()
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingStateTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingStateTest.kt
index a88fad5..0d92de1 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingStateTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/PagingStateTest.kt
@@ -25,12 +25,13 @@
class PagingStateTest {
@Test
fun closestItemToPosition_withoutPlaceholders() {
- val pagingState = PagingState(
- pages = listOf(List(10) { it }).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 0
- )
+ val pagingState =
+ PagingState(
+ pages = listOf(List(10) { it }).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 0
+ )
assertEquals(0, pagingState.closestItemToPosition(-1))
assertEquals(5, pagingState.closestItemToPosition(5))
@@ -39,12 +40,13 @@
@Test
fun closestItemToPosition_withPlaceholders() {
- val pagingState = PagingState(
- pages = listOf(List(10) { it }).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf(List(10) { it }).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(0, pagingState.closestItemToPosition(5))
assertEquals(5, pagingState.closestItemToPosition(15))
@@ -53,16 +55,13 @@
@Test
fun closestItemToPosition_withEmptyPages() {
- val pagingState = PagingState(
- pages = listOf(
- listOf(),
- List(10) { it },
- listOf()
- ).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf(listOf(), List(10) { it }, listOf()).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(0, pagingState.closestItemToPosition(5))
assertEquals(5, pagingState.closestItemToPosition(15))
@@ -71,15 +70,13 @@
@Test
fun closestItemToPosition_onlyEmptyPages() {
- val pagingState = PagingState(
- pages = listOf<List<Int>>(
- listOf(),
- listOf()
- ).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf<List<Int>>(listOf(), listOf()).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(null, pagingState.closestItemToPosition(5))
assertEquals(null, pagingState.closestItemToPosition(25))
@@ -88,12 +85,13 @@
@Test
fun closestPageToPosition_withoutPlaceholders() {
val pages = List(10) { listOf(it) }.asPages()
- val pagingState = PagingState(
- pages = pages,
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 0
- )
+ val pagingState =
+ PagingState(
+ pages = pages,
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 0
+ )
assertEquals(pages.first(), pagingState.closestPageToPosition(-1))
assertEquals(pages[5], pagingState.closestPageToPosition(5))
@@ -103,12 +101,13 @@
@Test
fun closestPageToPosition_withPlaceholders() {
val pages = List(10) { listOf(it) }.asPages()
- val pagingState = PagingState(
- pages = pages,
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = pages,
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(pages.first(), pagingState.closestPageToPosition(5))
assertEquals(pages[5], pagingState.closestPageToPosition(15))
@@ -117,18 +116,21 @@
@Test
fun closestPageToPosition_withEmptyPages() {
- val pages = List(10) {
- when {
- it % 3 == 0 -> listOf()
- else -> listOf(it)
- }
- }.asPages()
- val pagingState = PagingState(
- pages = pages,
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pages =
+ List(10) {
+ when {
+ it % 3 == 0 -> listOf()
+ else -> listOf(it)
+ }
+ }
+ .asPages()
+ val pagingState =
+ PagingState(
+ pages = pages,
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(pages.first(), pagingState.closestPageToPosition(5))
assertEquals(pages[5], pagingState.closestPageToPosition(13)) // pages[5].data == [5]
@@ -137,15 +139,13 @@
@Test
fun closestPageToPosition_onlyEmptyPages() {
- val pagingState = PagingState(
- pages = listOf<List<Int>>(
- listOf(),
- listOf()
- ).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf<List<Int>>(listOf(), listOf()).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(null, pagingState.closestPageToPosition(5))
assertEquals(null, pagingState.closestPageToPosition(25))
@@ -153,12 +153,13 @@
@Test
fun itemOrNull_noPages() {
- val pagingState = PagingState(
- pages = listOf<Page<Int, Int>>(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf<Page<Int, Int>>(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(null, pagingState.firstItemOrNull())
assertEquals(null, pagingState.lastItemOrNull())
@@ -166,12 +167,13 @@
@Test
fun itemOrNull_emptyPages() {
- val pagingState = PagingState(
- pages = List(10) { listOf<Int>() }.asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = List(10) { listOf<Int>() }.asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(null, pagingState.firstItemOrNull())
assertEquals(null, pagingState.lastItemOrNull())
@@ -179,12 +181,13 @@
@Test
fun itemOrNull_emptyPagesAtEnds() {
- val pagingState = PagingState(
- pages = (listOf<List<Int>>() + List(10) { listOf(it) } + listOf()).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = (listOf<List<Int>>() + List(10) { listOf(it) } + listOf()).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertEquals(0, pagingState.firstItemOrNull())
assertEquals(9, pagingState.lastItemOrNull())
@@ -192,36 +195,39 @@
@Test
fun isEmpty_noPages() {
- val pagingState = PagingState(
- pages = listOf<Page<Int, Int>>(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = listOf<Page<Int, Int>>(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertTrue { pagingState.isEmpty() }
}
@Test
fun isEmpty_emptyPages() {
- val pagingState = PagingState(
- pages = List(10) { listOf<Int>() }.asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = List(10) { listOf<Int>() }.asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertTrue { pagingState.isEmpty() }
}
@Test
fun isEmpty_emptyPagesAtEnds() {
- val pagingState = PagingState(
- pages = (listOf<List<Int>>() + List(10) { listOf(it) } + listOf()).asPages(),
- anchorPosition = 10,
- config = PagingConfig(pageSize = 10),
- leadingPlaceholderCount = 10
- )
+ val pagingState =
+ PagingState(
+ pages = (listOf<List<Int>>() + List(10) { listOf(it) } + listOf()).asPages(),
+ anchorPosition = 10,
+ config = PagingConfig(pageSize = 10),
+ leadingPlaceholderCount = 10
+ )
assertFalse { pagingState.isEmpty() }
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
index 2c9ebf2..78c0fea 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
@@ -46,9 +46,7 @@
private var mockStateId = 0
// creates a unique state using the anchor position to be able to do equals check in assertions
- private fun createMockState(
- anchorPosition: Int? = mockStateId++
- ): PagingState<Int, Int> {
+ private fun createMockState(anchorPosition: Int? = mockStateId++): PagingState<Int, Int> {
return PagingState(
pages = listOf(),
anchorPosition = anchorPosition,
@@ -58,577 +56,593 @@
}
@Test
- fun requestLoadIfRefreshAllowed_noop() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
-
- remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).isEmpty()
- }
-
- @Test
- fun requestLoadIfRefreshAllowed_simple() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- }
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
-
- remoteMediatorAccessor.allowRefresh()
- remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- // allowRefresh should only allow one successful request to go through.
- remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).isEmpty()
- }
-
- @Test
- fun requestLoadIfRefreshAllowed_retry() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = LAUNCH_INITIAL_REFRESH
- }
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
-
- remoteMediator.loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Error(Exception())
- }
-
- remoteMediatorAccessor.allowRefresh()
- remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(loadType = REFRESH, state = pagingState)
- )
-
- remoteMediator.loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
- }
-
- remoteMediatorAccessor.retryFailed(pagingState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(loadType = REFRESH, state = pagingState)
- )
- }
-
- @Test
- fun requestLoad_queuesBoundaryBehindRefresh() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val firstState = createMockState()
- val secondState = createMockState()
-
- remoteMediatorAccessor.requestLoad(REFRESH, firstState)
- advanceTimeBy(50) // Start remote refresh, but do not let it finish.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, firstState)
- )
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete
- )
- )
-
- // Queue a boundary requests, but it should not launch since refresh is running.
- remoteMediatorAccessor.requestLoad(PREPEND, firstState)
- remoteMediatorAccessor.requestLoad(APPEND, firstState)
- assertThat(remoteMediator.newLoadEvents).isEmpty()
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.Loading,
- append = LoadState.Loading
- )
- )
-
- // Queue more boundary requests, but with an updated PagingState.
- remoteMediatorAccessor.requestLoad(PREPEND, secondState)
- remoteMediatorAccessor.requestLoad(APPEND, secondState)
- assertThat(remoteMediator.newLoadEvents).isEmpty()
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.Loading,
- append = LoadState.Loading
- )
- )
-
- // Now wait until all queued requests finish running
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, secondState),
- LoadEvent(APPEND, secondState),
- )
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete
- )
- )
- }
-
- @Test
- fun requestLoad_cancelledBoundaryRetriesAfterRefresh() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val firstState = createMockState()
-
- // Launch boundary calls, but do not let them finish.
- remoteMediatorAccessor.requestLoad(PREPEND, firstState)
- // Single runner should prevent append from triggering, but it should still be queued.
- remoteMediatorAccessor.requestLoad(APPEND, firstState)
- advanceTimeBy(50)
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, firstState),
- )
-
- // Launch refresh, which should cancel running boundary calls
- remoteMediatorAccessor.requestLoad(REFRESH, firstState)
- advanceTimeBy(50)
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, firstState)
- )
-
- // Let refresh finish, retrying cancelled boundary calls
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, firstState),
- LoadEvent(APPEND, firstState),
- )
- }
-
- @Test
- fun requestLoad_queuesBoundaryAfterRefreshFails() = testScope.runTest {
- val firstState = createMockState()
- val secondState = createMockState()
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- loadCallback = { loadType, state ->
- // Only error out on first refresh.
- if (loadType == REFRESH && state == firstState) {
- RemoteMediator.MediatorResult.Error(throwable = LOAD_ERROR)
- } else {
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
+ fun requestLoadIfRefreshAllowed_noop() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = SKIP_INITIAL_REFRESH
}
- }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
+
+ remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents).isEmpty()
}
- val remoteMediatorAccessor = createAccessor(remoteMediator)
-
- // Queue up some remote boundary calls, which will not run immediately because they
- // depend on refresh.
- remoteMediatorAccessor.requestLoad(PREPEND, firstState)
- remoteMediatorAccessor.requestLoad(APPEND, firstState)
-
- // Trigger refresh, letting it fail.
- remoteMediatorAccessor.requestLoad(REFRESH, firstState)
- advanceUntilIdle()
- // Boundary calls should be queued, but not started.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, firstState),
- )
- // Although boundary calls are queued, they should not trigger or update LoadState since
- // they are waiting for refresh to succeed.
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(LOAD_ERROR),
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete
- )
- )
-
- // Let refresh finish, triggering queued boundary calls.
- remoteMediatorAccessor.retryFailed(secondState)
- advanceUntilIdle()
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, secondState),
- LoadEvent(PREPEND, firstState),
- LoadEvent(APPEND, firstState),
- )
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete
- )
- )
- }
@Test
- fun requestLoad_refreshEndOfPaginationReachedClearsBoundaryCalls() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- loadCallback = { _, _ ->
+ fun requestLoadIfRefreshAllowed_simple() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
+
+ remoteMediatorAccessor.allowRefresh()
+ remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
+
+ // allowRefresh should only allow one successful request to go through.
+ remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents).isEmpty()
+ }
+
+ @Test
+ fun requestLoadIfRefreshAllowed_retry() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
+
+ remoteMediator.loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Error(Exception())
+ }
+
+ remoteMediatorAccessor.allowRefresh()
+ remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
+
+ remoteMediator.loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
+ }
+
+ remoteMediatorAccessor.retryFailed(pagingState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
+ }
+
+ @Test
+ fun requestLoad_queuesBoundaryBehindRefresh() =
+ testScope.runTest {
+ val remoteMediator = RemoteMediatorMock(loadDelay = 100)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val firstState = createMockState()
+ val secondState = createMockState()
+
+ remoteMediatorAccessor.requestLoad(REFRESH, firstState)
+ advanceTimeBy(50) // Start remote refresh, but do not let it finish.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete
+ )
+ )
+
+ // Queue a boundary requests, but it should not launch since refresh is running.
+ remoteMediatorAccessor.requestLoad(PREPEND, firstState)
+ remoteMediatorAccessor.requestLoad(APPEND, firstState)
+ assertThat(remoteMediator.newLoadEvents).isEmpty()
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.Loading,
+ append = LoadState.Loading
+ )
+ )
+
+ // Queue more boundary requests, but with an updated PagingState.
+ remoteMediatorAccessor.requestLoad(PREPEND, secondState)
+ remoteMediatorAccessor.requestLoad(APPEND, secondState)
+ assertThat(remoteMediator.newLoadEvents).isEmpty()
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.Loading,
+ append = LoadState.Loading
+ )
+ )
+
+ // Now wait until all queued requests finish running
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(PREPEND, secondState),
+ LoadEvent(APPEND, secondState),
+ )
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete
+ )
+ )
+ }
+
+ @Test
+ fun requestLoad_cancelledBoundaryRetriesAfterRefresh() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = SKIP_INITIAL_REFRESH
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val firstState = createMockState()
+
+ // Launch boundary calls, but do not let them finish.
+ remoteMediatorAccessor.requestLoad(PREPEND, firstState)
+ // Single runner should prevent append from triggering, but it should still be queued.
+ remoteMediatorAccessor.requestLoad(APPEND, firstState)
+ advanceTimeBy(50)
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(PREPEND, firstState),
+ )
+
+ // Launch refresh, which should cancel running boundary calls
+ remoteMediatorAccessor.requestLoad(REFRESH, firstState)
+ advanceTimeBy(50)
+ assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
+
+ // Let refresh finish, retrying cancelled boundary calls
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(PREPEND, firstState),
+ LoadEvent(APPEND, firstState),
+ )
+ }
+
+ @Test
+ fun requestLoad_queuesBoundaryAfterRefreshFails() =
+ testScope.runTest {
+ val firstState = createMockState()
+ val secondState = createMockState()
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { loadType, state ->
+ // Only error out on first refresh.
+ if (loadType == REFRESH && state == firstState) {
+ RemoteMediator.MediatorResult.Error(throwable = LOAD_ERROR)
+ } else {
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
+ }
+ }
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+ // Queue up some remote boundary calls, which will not run immediately because they
+ // depend on refresh.
+ remoteMediatorAccessor.requestLoad(PREPEND, firstState)
+ remoteMediatorAccessor.requestLoad(APPEND, firstState)
+
+ // Trigger refresh, letting it fail.
+ remoteMediatorAccessor.requestLoad(REFRESH, firstState)
+ advanceUntilIdle()
+ // Boundary calls should be queued, but not started.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(REFRESH, firstState),
+ )
+ // Although boundary calls are queued, they should not trigger or update LoadState since
+ // they are waiting for refresh to succeed.
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(LOAD_ERROR),
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete
+ )
+ )
+
+ // Let refresh finish, triggering queued boundary calls.
+ remoteMediatorAccessor.retryFailed(secondState)
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(REFRESH, secondState),
+ LoadEvent(PREPEND, firstState),
+ LoadEvent(APPEND, firstState),
+ )
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete
+ )
+ )
+ }
+
+ @Test
+ fun requestLoad_refreshEndOfPaginationReachedClearsBoundaryCalls() =
+ testScope.runTest {
+ val remoteMediator =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+ val firstState = createMockState()
+
+ // Queue up some remote boundary calls, which will not run immediately because they
+ // depend on refresh.
+ remoteMediatorAccessor.requestLoad(PREPEND, firstState)
+ remoteMediatorAccessor.requestLoad(APPEND, firstState)
+
+ // Trigger refresh and let it mark endOfPaginationReached
+ remoteMediatorAccessor.requestLoad(REFRESH, firstState)
+ advanceUntilIdle()
+
+ // Ensure boundary calls are not triggered since they should be cleared by
+ // endOfPaginationReached from refresh.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
+ // Although boundary calls are queued, they should not trigger or update LoadState since
+ // they are waiting for refresh.
+ assertThat(remoteMediatorAccessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Complete,
+ append = LoadState.NotLoading.Complete
+ )
+ )
+ }
+
+ @Test
+ fun load_reportsPrependLoadState() =
+ testScope.runTest {
+ val emptyState =
+ PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+ // Assert initial state is NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+ remoteMediatorAccessor.state.value,
+ )
+
+ // Start a PREPEND load.
+ remoteMediatorAccessor.requestLoad(
+ loadType = PREPEND,
+ pagingState = emptyState,
+ )
+
+ // Assert state is immediately set to Loading.
+ assertEquals(
+ LoadStates.IDLE.copy(prepend = LoadState.Loading),
+ remoteMediatorAccessor.state.value,
+ )
+
+ // Wait for load to finish.
+ advanceUntilIdle()
+
+ // Assert state is set to NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+ remoteMediatorAccessor.state.value,
+ )
+
+ // Start a PREPEND load which results in endOfPaginationReached = true.
+ remoteMediator.loadCallback = { _, _ ->
RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
}
- }
- val remoteMediatorAccessor = createAccessor(remoteMediator)
- val firstState = createMockState()
-
- // Queue up some remote boundary calls, which will not run immediately because they
- // depend on refresh.
- remoteMediatorAccessor.requestLoad(PREPEND, firstState)
- remoteMediatorAccessor.requestLoad(APPEND, firstState)
-
- // Trigger refresh and let it mark endOfPaginationReached
- remoteMediatorAccessor.requestLoad(REFRESH, firstState)
- advanceUntilIdle()
-
- // Ensure boundary calls are not triggered since they should be cleared by
- // endOfPaginationReached from refresh.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, firstState)
- )
- // Although boundary calls are queued, they should not trigger or update LoadState since
- // they are waiting for refresh.
- assertThat(remoteMediatorAccessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Complete,
- append = LoadState.NotLoading.Complete
+ remoteMediatorAccessor.requestLoad(
+ loadType = PREPEND,
+ pagingState = emptyState,
)
- )
- }
- @Test
- fun load_reportsPrependLoadState() = testScope.runTest {
- val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
+ // Wait for load to finish.
+ advanceUntilIdle()
- // Assert initial state is NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
- remoteMediatorAccessor.state.value,
- )
-
- // Start a PREPEND load.
- remoteMediatorAccessor.requestLoad(
- loadType = PREPEND,
- pagingState = emptyState,
- )
-
- // Assert state is immediately set to Loading.
- assertEquals(
- LoadStates.IDLE.copy(prepend = LoadState.Loading),
- remoteMediatorAccessor.state.value,
- )
-
- // Wait for load to finish.
- advanceUntilIdle()
-
- // Assert state is set to NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
- remoteMediatorAccessor.state.value,
- )
-
- // Start a PREPEND load which results in endOfPaginationReached = true.
- remoteMediator.loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ // Assert state is set to NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Complete),
+ remoteMediatorAccessor.state.value,
+ )
}
- remoteMediatorAccessor.requestLoad(
- loadType = PREPEND,
- pagingState = emptyState,
- )
-
- // Wait for load to finish.
- advanceUntilIdle()
-
- // Assert state is set to NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Complete),
- remoteMediatorAccessor.state.value,
- )
- }
@Test
- fun load_reportsAppendLoadState() = testScope.runTest {
- val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
+ fun load_reportsAppendLoadState() =
+ testScope.runTest {
+ val emptyState =
+ PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
- // Assert initial state is NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
- remoteMediatorAccessor.state.value,
- )
+ // Assert initial state is NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+ remoteMediatorAccessor.state.value,
+ )
- // Start a APPEND load.
- remoteMediatorAccessor.requestLoad(
- loadType = APPEND,
- pagingState = emptyState,
- )
+ // Start a APPEND load.
+ remoteMediatorAccessor.requestLoad(
+ loadType = APPEND,
+ pagingState = emptyState,
+ )
- // Assert state is immediately set to Loading.
- assertEquals(
- LoadStates.IDLE.copy(append = LoadState.Loading),
- remoteMediatorAccessor.state.value,
- )
+ // Assert state is immediately set to Loading.
+ assertEquals(
+ LoadStates.IDLE.copy(append = LoadState.Loading),
+ remoteMediatorAccessor.state.value,
+ )
- // Wait for load to finish.
- advanceUntilIdle()
+ // Wait for load to finish.
+ advanceUntilIdle()
- // Assert state is set to NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(append = LoadState.NotLoading.Incomplete),
- remoteMediatorAccessor.state.value,
- )
+ // Assert state is set to NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(append = LoadState.NotLoading.Incomplete),
+ remoteMediatorAccessor.state.value,
+ )
- // Start a APPEND load which results in endOfPaginationReached = true.
- remoteMediator.loadCallback = { _, _ ->
- RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
- }
- remoteMediatorAccessor.requestLoad(
- loadType = APPEND,
- pagingState = emptyState,
- )
-
- // Wait for load to finish.
- advanceUntilIdle()
-
- // Assert state is set to NotLoading.Incomplete.
- assertEquals(
- LoadStates.IDLE.copy(append = LoadState.NotLoading.Complete),
- remoteMediatorAccessor.state.value,
- )
- }
-
- @Test
- fun load_conflatesPrepend() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
-
- remoteMediatorAccessor.requestLoad(
- loadType = PREPEND,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- remoteMediatorAccessor.requestLoad(
- loadType = PREPEND,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- // Assert that exactly one load request was started.
- assertEquals(1, remoteMediator.newLoadEvents.size)
-
- // Fast-forward time until both load requests jobs complete.
- advanceUntilIdle()
-
- // Assert that the second load request was skipped since it was launched while the first
- // load request was still running.
- assertEquals(0, remoteMediator.newLoadEvents.size)
- }
-
- @Test
- fun load_conflatesAppend() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
-
- remoteMediatorAccessor.requestLoad(
- loadType = APPEND,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- remoteMediatorAccessor.requestLoad(
- loadType = APPEND,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- // Assert that exactly one load request was started.
- assertEquals(1, remoteMediator.newLoadEvents.size)
-
- // Fast-forward time until both load requests jobs complete.
- advanceUntilIdle()
-
- // Assert that the second load request was skipped since it was launched while the first
- // load request was still running.
- assertEquals(0, remoteMediator.newLoadEvents.size)
- }
-
- @Test
- fun load_conflatesRefresh() = testScope.runTest {
- val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
- val remoteMediatorAccessor = createAccessor(remoteMediator)
-
- remoteMediatorAccessor.requestLoad(
- loadType = REFRESH,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- remoteMediatorAccessor.requestLoad(
- loadType = REFRESH,
- pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- )
-
- // Assert that exactly one load request was started.
- assertEquals(1, remoteMediator.newLoadEvents.size)
-
- // Fast-forward time until both load requests jobs complete.
- advanceUntilIdle()
-
- // Assert that the second load request was skipped since it was launched while the first
- // load request was still running.
- assertEquals(0, remoteMediator.newLoadEvents.size)
- }
-
- @Test
- fun load_concurrentInitializeJobCancelsBoundaryJobs() = testScope.runTest {
- val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- val remoteMediator = object : RemoteMediatorMock(loadDelay = 1000) {
- var loading = AtomicBoolean(false)
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- if (!loading.compareAndSet(false, true)) fail("Concurrent load")
-
- return try {
- super.load(loadType, state)
- } finally {
- loading.set(false)
- }
+ // Start a APPEND load which results in endOfPaginationReached = true.
+ remoteMediator.loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
}
+ remoteMediatorAccessor.requestLoad(
+ loadType = APPEND,
+ pagingState = emptyState,
+ )
+
+ // Wait for load to finish.
+ advanceUntilIdle()
+
+ // Assert state is set to NotLoading.Incomplete.
+ assertEquals(
+ LoadStates.IDLE.copy(append = LoadState.NotLoading.Complete),
+ remoteMediatorAccessor.state.value,
+ )
}
- val remoteMediatorAccessor = createAccessor(remoteMediator)
-
- remoteMediatorAccessor.requestLoad(
- loadType = PREPEND,
- pagingState = emptyState
- )
-
- remoteMediatorAccessor.requestLoad(
- loadType = APPEND,
- pagingState = emptyState
- )
-
- // Start prependJob and appendJob, but do not let them finish.
- advanceTimeBy(500)
-
- // Assert that only the PREPEND RemoteMediator.load() call was made.
- assertEquals(
- listOf(LoadEvent(PREPEND, emptyState)),
- remoteMediator.newLoadEvents
- )
-
- // Start refreshJob
- remoteMediatorAccessor.requestLoad(
- loadType = REFRESH,
- pagingState = emptyState
- )
-
- // Give prependJob enough time to be cancelled and refresh started due to higher priority
- advanceTimeBy(500)
-
- assertEquals(
- listOf(LoadEvent(REFRESH, emptyState)),
- remoteMediator.newLoadEvents
- )
- // assert that all of them are in loading state as we don't know if refresh will succeed
- // if refresh fails, we would retry append / prepend
- assertEquals(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.Loading,
- prepend = LoadState.Loading
- ),
- remoteMediatorAccessor.state.value
- )
-
- // Wait for all outstanding / queued jobs to finish.
- advanceUntilIdle()
-
- // Assert all outstanding / queued jobs finished.
- assertEquals(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete
- ),
- remoteMediatorAccessor.state.value
- )
-
- // Queued boundary requests should be triggered, even though they are out-of-date.
- assertThat(remoteMediator.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, emptyState),
- LoadEvent(APPEND, emptyState),
- )
- }
@Test
- fun load_concurrentBoundaryJobsRunsSerially() = testScope.runTest {
- val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
- val remoteMediator = object : RemoteMediatorMock(loadDelay = 1000) {
- var loading = AtomicBoolean(false)
- override suspend fun load(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): MediatorResult {
- if (!loading.compareAndSet(false, true)) fail("Concurrent load")
+ fun load_conflatesPrepend() =
+ testScope.runTest {
+ val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
- return try {
- super.load(loadType, state)
- } finally {
- loading.set(false)
- }
- }
+ remoteMediatorAccessor.requestLoad(
+ loadType = PREPEND,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
+
+ remoteMediatorAccessor.requestLoad(
+ loadType = PREPEND,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
+
+ // Assert that exactly one load request was started.
+ assertEquals(1, remoteMediator.newLoadEvents.size)
+
+ // Fast-forward time until both load requests jobs complete.
+ advanceUntilIdle()
+
+ // Assert that the second load request was skipped since it was launched while the first
+ // load request was still running.
+ assertEquals(0, remoteMediator.newLoadEvents.size)
}
- val remoteMediatorAccessor = createAccessor(remoteMediator)
+ @Test
+ fun load_conflatesAppend() =
+ testScope.runTest {
+ val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
- remoteMediatorAccessor.requestLoad(loadType = PREPEND, pagingState = emptyState)
+ remoteMediatorAccessor.requestLoad(
+ loadType = APPEND,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
- remoteMediatorAccessor.requestLoad(loadType = APPEND, pagingState = emptyState)
+ remoteMediatorAccessor.requestLoad(
+ loadType = APPEND,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
- // Assert that only one job runs due to second job joining the first before starting.
- assertEquals(1, remoteMediator.newLoadEvents.size)
+ // Assert that exactly one load request was started.
+ assertEquals(1, remoteMediator.newLoadEvents.size)
- // Advance some time, but not enough to finish first load.
- advanceTimeBy(500)
- assertEquals(0, remoteMediator.newLoadEvents.size)
+ // Fast-forward time until both load requests jobs complete.
+ advanceUntilIdle()
- // Assert that second job starts after first finishes.
- advanceTimeBy(500)
- runCurrent()
- assertEquals(1, remoteMediator.newLoadEvents.size)
+ // Assert that the second load request was skipped since it was launched while the first
+ // load request was still running.
+ assertEquals(0, remoteMediator.newLoadEvents.size)
+ }
- // Allow second job to finish.
- advanceTimeBy(1000)
- }
+ @Test
+ fun load_conflatesRefresh() =
+ testScope.runTest {
+ val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+ remoteMediatorAccessor.requestLoad(
+ loadType = REFRESH,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
+
+ remoteMediatorAccessor.requestLoad(
+ loadType = REFRESH,
+ pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ )
+
+ // Assert that exactly one load request was started.
+ assertEquals(1, remoteMediator.newLoadEvents.size)
+
+ // Fast-forward time until both load requests jobs complete.
+ advanceUntilIdle()
+
+ // Assert that the second load request was skipped since it was launched while the first
+ // load request was still running.
+ assertEquals(0, remoteMediator.newLoadEvents.size)
+ }
+
+ @Test
+ fun load_concurrentInitializeJobCancelsBoundaryJobs() =
+ testScope.runTest {
+ val emptyState =
+ PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ val remoteMediator =
+ object : RemoteMediatorMock(loadDelay = 1000) {
+ var loading = AtomicBoolean(false)
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ if (!loading.compareAndSet(false, true)) fail("Concurrent load")
+
+ return try {
+ super.load(loadType, state)
+ } finally {
+ loading.set(false)
+ }
+ }
+ }
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+ remoteMediatorAccessor.requestLoad(loadType = PREPEND, pagingState = emptyState)
+
+ remoteMediatorAccessor.requestLoad(loadType = APPEND, pagingState = emptyState)
+
+ // Start prependJob and appendJob, but do not let them finish.
+ advanceTimeBy(500)
+
+ // Assert that only the PREPEND RemoteMediator.load() call was made.
+ assertEquals(listOf(LoadEvent(PREPEND, emptyState)), remoteMediator.newLoadEvents)
+
+ // Start refreshJob
+ remoteMediatorAccessor.requestLoad(loadType = REFRESH, pagingState = emptyState)
+
+ // Give prependJob enough time to be cancelled and refresh started due to higher
+ // priority
+ advanceTimeBy(500)
+
+ assertEquals(listOf(LoadEvent(REFRESH, emptyState)), remoteMediator.newLoadEvents)
+ // assert that all of them are in loading state as we don't know if refresh will succeed
+ // if refresh fails, we would retry append / prepend
+ assertEquals(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ ),
+ remoteMediatorAccessor.state.value
+ )
+
+ // Wait for all outstanding / queued jobs to finish.
+ advanceUntilIdle()
+
+ // Assert all outstanding / queued jobs finished.
+ assertEquals(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete
+ ),
+ remoteMediatorAccessor.state.value
+ )
+
+ // Queued boundary requests should be triggered, even though they are out-of-date.
+ assertThat(remoteMediator.newLoadEvents)
+ .containsExactly(
+ LoadEvent(PREPEND, emptyState),
+ LoadEvent(APPEND, emptyState),
+ )
+ }
+
+ @Test
+ fun load_concurrentBoundaryJobsRunsSerially() =
+ testScope.runTest {
+ val emptyState =
+ PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+ val remoteMediator =
+ object : RemoteMediatorMock(loadDelay = 1000) {
+ var loading = AtomicBoolean(false)
+
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): MediatorResult {
+ if (!loading.compareAndSet(false, true)) fail("Concurrent load")
+
+ return try {
+ super.load(loadType, state)
+ } finally {
+ loading.set(false)
+ }
+ }
+ }
+
+ val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+ remoteMediatorAccessor.requestLoad(loadType = PREPEND, pagingState = emptyState)
+
+ remoteMediatorAccessor.requestLoad(loadType = APPEND, pagingState = emptyState)
+
+ // Assert that only one job runs due to second job joining the first before starting.
+ assertEquals(1, remoteMediator.newLoadEvents.size)
+
+ // Advance some time, but not enough to finish first load.
+ advanceTimeBy(500)
+ assertEquals(0, remoteMediator.newLoadEvents.size)
+
+ // Assert that second job starts after first finishes.
+ advanceTimeBy(500)
+ runCurrent()
+ assertEquals(1, remoteMediator.newLoadEvents.size)
+
+ // Allow second job to finish.
+ advanceTimeBy(1000)
+ }
@Test
fun ignoreAppendPrependWhenRefreshIsRequired() {
- val remoteMediatorMock = RemoteMediatorMock().apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock().apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
val accessor = testScope.createAccessor(remoteMediatorMock)
accessor.requestLoad(APPEND, createMockState())
accessor.requestLoad(PREPEND, createMockState())
testScope.advanceUntilIdle()
- assertThat(
- remoteMediatorMock.loadEvents
- ).isEmpty()
+ assertThat(remoteMediatorMock.loadEvents).isEmpty()
}
@Test
fun allowAppendPrependWhenRefreshIsNotRequired() {
- val remoteMediatorMock = RemoteMediatorMock().apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock().apply { initializeResult = SKIP_INITIAL_REFRESH }
val accessor = testScope.createAccessor(remoteMediatorMock)
val appendState = createMockState(1)
@@ -636,19 +650,16 @@
accessor.requestLoad(APPEND, appendState)
accessor.requestLoad(PREPEND, prependState)
testScope.advanceUntilIdle()
- assertThat(
- remoteMediatorMock.loadEvents
- ).containsExactly(
- LoadEvent(APPEND, appendState),
- LoadEvent(PREPEND, prependState)
- )
+ assertThat(remoteMediatorMock.loadEvents)
+ .containsExactly(LoadEvent(APPEND, appendState), LoadEvent(PREPEND, prependState))
}
@Test
fun ignoreAppendPrependBeforeRefresh() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
val testScope = TestScope(StandardTestDispatcher())
val accessor = testScope.createAccessor(remoteMediatorMock)
@@ -670,17 +681,17 @@
testScope.advanceTimeBy(1)
accessor.requestLoad(PREPEND, prependState)
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, refreshState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(REFRESH, refreshState))
// now advance enough that we can accept append prepend
testScope.advanceUntilIdle()
// queued append/prepend should be executed afterwards.
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(APPEND, appendState),
- LoadEvent(PREPEND, prependState),
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(
+ LoadEvent(APPEND, appendState),
+ LoadEvent(PREPEND, prependState),
+ )
val otherPrependState = createMockState()
val otherAppendState = createMockState()
@@ -688,18 +699,16 @@
accessor.requestLoad(APPEND, otherAppendState)
testScope.advanceTimeBy(50)
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, otherPrependState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(PREPEND, otherPrependState))
// while prepend running, any more requests should be ignored
accessor.requestLoad(PREPEND, createMockState())
testScope.advanceTimeBy(10)
assertThat(remoteMediatorMock.newLoadEvents).isEmpty()
testScope.advanceTimeBy(41)
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(APPEND, otherAppendState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(APPEND, otherAppendState))
accessor.requestLoad(APPEND, createMockState())
// while append running, any more requests should be ignored
accessor.requestLoad(APPEND, createMockState())
@@ -710,47 +719,34 @@
val newAppendState = createMockState()
accessor.requestLoad(APPEND, newAppendState)
testScope.advanceUntilIdle()
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(APPEND, newAppendState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(APPEND, newAppendState))
}
@Test
fun dropAppendPrependIfRefreshIsTriggered() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
val accessor = testScope.createAccessor(remoteMediatorMock)
val initialAppend = createMockState()
accessor.requestLoad(APPEND, initialAppend)
testScope.advanceTimeBy(50)
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(APPEND, initialAppend)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(APPEND, initialAppend))
// now before that append finishes, trigger a refresh
val newRefresh = createMockState()
accessor.requestLoad(REFRESH, newRefresh)
testScope.advanceTimeBy(10)
// check that we immediately get the new refresh because we'll cancel the append
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(REFRESH, newRefresh)
- )
- assertThat(
- remoteMediatorMock.incompleteEvents
- ).containsExactly(
- LoadEvent(APPEND, initialAppend)
- )
+ assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(REFRESH, newRefresh))
+ assertThat(remoteMediatorMock.incompleteEvents)
+ .containsExactly(LoadEvent(APPEND, initialAppend))
}
@Test
fun loadEvents() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
val accessor = testScope.createAccessor(remoteMediatorMock)
// Initial state
@@ -760,42 +756,45 @@
val firstAppendState = createMockState()
accessor.requestLoad(APPEND, firstAppendState)
testScope.advanceTimeBy(40)
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.Loading
+ )
)
- )
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(APPEND, firstAppendState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(APPEND, firstAppendState))
// Trigger refresh, cancelling remote append
val firstRefreshState = createMockState()
accessor.requestLoad(REFRESH, firstRefreshState)
testScope.advanceTimeBy(1)
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.Loading
+ )
)
- )
// advance enough to complete refresh
testScope.advanceUntilIdle()
// assert that we receive refresh, and append is retried since it was cancelled
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, firstRefreshState),
- LoadEvent(APPEND, firstAppendState),
- )
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete,
- append = LoadState.NotLoading.Incomplete
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(
+ LoadEvent(REFRESH, firstRefreshState),
+ LoadEvent(APPEND, firstAppendState),
)
- )
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete,
+ append = LoadState.NotLoading.Incomplete
+ )
+ )
val appendState = createMockState()
accessor.requestLoad(APPEND, appendState)
@@ -803,35 +802,32 @@
accessor.requestLoad(PREPEND, prependState)
testScope.advanceTimeBy(50)
// both states should be set to loading even though prepend is not really running
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Loading,
- append = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Loading,
+ append = LoadState.Loading
+ )
)
- )
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(APPEND, appendState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(APPEND, appendState))
// advance enough to trigger prepend
testScope.advanceTimeBy(51)
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Loading,
- append = LoadState.NotLoading.Incomplete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Loading,
+ append = LoadState.NotLoading.Incomplete
+ )
)
- )
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(PREPEND, prependState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(PREPEND, prependState))
testScope.advanceUntilIdle()
val exception = Throwable()
remoteMediatorMock.loadCallback = { type, _ ->
if (type == PREPEND) {
- RemoteMediator.MediatorResult.Error(
- exception
- )
+ RemoteMediator.MediatorResult.Error(exception)
} else {
null
}
@@ -839,73 +835,69 @@
accessor.requestLoad(APPEND, createMockState())
accessor.requestLoad(PREPEND, createMockState())
testScope.advanceUntilIdle()
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Error(exception),
- append = LoadState.NotLoading.Incomplete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Error(exception),
+ append = LoadState.NotLoading.Incomplete
+ )
)
- )
// now complete append, a.k.a. endOfPaginationReached
remoteMediatorMock.loadCallback = { type, _ ->
if (type == APPEND) {
- RemoteMediator.MediatorResult.Success(
- endOfPaginationReached = true
- )
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
} else {
null
}
}
accessor.requestLoad(APPEND, createMockState())
testScope.advanceUntilIdle()
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Error(exception),
- append = LoadState.NotLoading.Complete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Error(exception),
+ append = LoadState.NotLoading.Complete
+ )
)
- )
// clear events
remoteMediatorMock.newLoadEvents
// another append request should just be ignored
accessor.requestLoad(APPEND, createMockState())
testScope.advanceUntilIdle()
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).isEmpty()
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Error(exception),
- append = LoadState.NotLoading.Complete
+ assertThat(remoteMediatorMock.newLoadEvents).isEmpty()
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Error(exception),
+ append = LoadState.NotLoading.Complete
+ )
)
- )
val refreshState = createMockState()
accessor.requestLoad(REFRESH, refreshState)
testScope.advanceTimeBy(50)
// prepend error state is still present
- assertThat(accessor.state.value).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.Error(exception),
- append = LoadState.NotLoading.Complete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.Error(exception),
+ append = LoadState.NotLoading.Complete
+ )
)
- )
testScope.advanceUntilIdle()
// if refresh succeeds, it will clear the error state for refresh
- assertThat(accessor.state.value).isEqualTo(
- LoadStates.IDLE
- )
- assertThat(remoteMediatorMock.newLoadEvents).containsExactly(
- LoadEvent(REFRESH, refreshState)
- )
+ assertThat(accessor.state.value).isEqualTo(LoadStates.IDLE)
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(REFRESH, refreshState))
}
@Test
fun retry_refresh() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
val exception = Exception()
val accessor = testScope.createAccessor(remoteMediatorMock)
remoteMediatorMock.loadCallback = { loadType, _ ->
@@ -918,28 +910,22 @@
}
val firstRefreshState = createMockState()
accessor.requestLoad(REFRESH, firstRefreshState)
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(LoadEvent(REFRESH, firstRefreshState))
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(REFRESH, firstRefreshState))
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value.refresh
- ).isEqualTo(
- LoadState.Error(exception)
- )
+ assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Error(exception))
val retryState = createMockState()
accessor.retryFailed(retryState)
testScope.advanceUntilIdle()
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(LoadEvent(REFRESH, retryState))
+ assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(REFRESH, retryState))
}
@Test
fun failedRefreshShouldNotAllowAppendPrependIfRefreshIsRequired() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ }
val exception = Exception()
val accessor = testScope.createAccessor(remoteMediatorMock)
remoteMediatorMock.loadCallback = { _, _ ->
@@ -952,30 +938,20 @@
// fail
// ensure that we didn't set append/prepend to loading when refresh is required
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates.IDLE.modifyState(REFRESH, LoadState.Loading)
- )
+ assertThat(accessor.state.value)
+ .isEqualTo(LoadStates.IDLE.modifyState(REFRESH, LoadState.Loading))
testScope.advanceUntilIdle()
// make sure only refresh has happened
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(REFRESH, initialState)
- )
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates.IDLE.modifyState(REFRESH, LoadState.Error(exception))
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(REFRESH, initialState))
+ assertThat(accessor.state.value)
+ .isEqualTo(LoadStates.IDLE.modifyState(REFRESH, LoadState.Error(exception)))
}
@Test
fun failedRefreshShouldAllowAppendPrependIfRefreshIsNotRequired() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
val exception = Exception()
val accessor = testScope.createAccessor(remoteMediatorMock)
remoteMediatorMock.loadCallback = { loadType, _ ->
@@ -991,41 +967,36 @@
accessor.requestLoad(PREPEND, initialState)
accessor.requestLoad(APPEND, initialState)
// make sure we optimistically updated append prepend states
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
testScope.advanceUntilIdle()
// make sure all requests did happen eventually
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(REFRESH, initialState),
- LoadEvent(PREPEND, initialState),
- LoadEvent(APPEND, initialState)
-
- )
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- append = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Error(exception)
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(
+ LoadEvent(REFRESH, initialState),
+ LoadEvent(PREPEND, initialState),
+ LoadEvent(APPEND, initialState)
)
- )
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ append = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Error(exception)
+ )
+ )
}
@Test
fun retry_retryBothAppendAndPrepend() {
- val remoteMediatorMock = RemoteMediatorMock(loadDelay = 100).apply {
- initializeResult = SKIP_INITIAL_REFRESH
- }
+ val remoteMediatorMock =
+ RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
val exception = Exception()
val accessor = testScope.createAccessor(remoteMediatorMock)
remoteMediatorMock.loadCallback = { _, _ ->
@@ -1037,52 +1008,43 @@
accessor.requestLoad(PREPEND, prependState)
accessor.requestLoad(APPEND, appendState)
testScope.advanceUntilIdle()
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(PREPEND, prependState),
- LoadEvent(APPEND, appendState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(PREPEND, prependState), LoadEvent(APPEND, appendState))
// now retry, ensure both runs
val retryState = createMockState()
accessor.retryFailed(retryState)
// make sure they both move to loading
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
testScope.advanceUntilIdle()
// ensure they both got called
- assertThat(
- remoteMediatorMock.newLoadEvents
- ).containsExactly(
- LoadEvent(PREPEND, retryState),
- LoadEvent(APPEND, retryState)
- )
+ assertThat(remoteMediatorMock.newLoadEvents)
+ .containsExactly(LoadEvent(PREPEND, retryState), LoadEvent(APPEND, retryState))
// make sure new loading states are correct
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- append = LoadState.Error(exception),
- prepend = LoadState.Error(exception)
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ append = LoadState.Error(exception),
+ prepend = LoadState.Error(exception)
+ )
)
- )
}
@Test
fun retry_multipleTriggersOnlyRefresh() {
- val remoteMediator = object : RemoteMediatorMock(100) {
- override suspend fun initialize(): InitializeAction {
- return SKIP_INITIAL_REFRESH
+ val remoteMediator =
+ object : RemoteMediatorMock(100) {
+ override suspend fun initialize(): InitializeAction {
+ return SKIP_INITIAL_REFRESH
+ }
}
- }
val exception = Exception()
remoteMediator.loadCallback = { _, _ ->
// fail all
@@ -1093,49 +1055,47 @@
accessor.requestLoad(REFRESH, createMockState())
accessor.requestLoad(APPEND, createMockState())
accessor.requestLoad(PREPEND, createMockState())
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
// let refresh start but don't let it finish
testScope.advanceUntilIdle()
// get all errors
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- append = LoadState.Error(exception),
- prepend = LoadState.Error(exception)
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ append = LoadState.Error(exception),
+ prepend = LoadState.Error(exception)
+ )
)
- )
// let requests succeed
remoteMediator.loadCallback = null
val retryState = createMockState()
accessor.retryFailed(retryState)
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete
+ )
)
- )
}
@Test
fun failingRefreshRetriesAppendPrepend_refreshNotRequired() {
- val remoteMediator = object : RemoteMediatorMock(100) {
- override suspend fun initialize(): InitializeAction {
- return SKIP_INITIAL_REFRESH
+ val remoteMediator =
+ object : RemoteMediatorMock(100) {
+ override suspend fun initialize(): InitializeAction {
+ return SKIP_INITIAL_REFRESH
+ }
}
- }
val exception = Exception()
remoteMediator.loadCallback = { type, _ ->
// only fail for refresh
@@ -1150,59 +1110,54 @@
accessor.requestLoad(REFRESH, createMockState())
accessor.requestLoad(APPEND, createMockState())
accessor.requestLoad(PREPEND, createMockState())
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
// let refresh start but don't let it finish
testScope.advanceTimeBy(50)
// make sure refresh does not revert the append / prepend states
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
// let refresh fail, it should retry append prepend
testScope.advanceTimeBy(20)
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- append = LoadState.Loading,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ append = LoadState.Loading,
+ prepend = LoadState.Loading
+ )
)
- )
// let the prepend retry start
testScope.advanceTimeBy(100)
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- append = LoadState.NotLoading.Incomplete,
- prepend = LoadState.Loading
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ append = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.Loading
+ )
)
- )
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- append = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ append = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete
+ )
)
- )
}
@Test
@@ -1217,33 +1172,17 @@
val accessor = testScope.createAccessor(remoteMediator)
val state1 = createMockState()
accessor.requestLoad(REFRESH, state1)
- assertThat(
- accessor.state.value.refresh
- ).isEqualTo(
- LoadState.Loading
- )
+ assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Loading)
// run to get the error
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value.refresh
- ).isEqualTo(
- LoadState.Error(exception)
- )
+ assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Error(exception))
// now send another load type refresh, should trigger another load
remoteMediator.loadCallback = null // let it succeed
val state2 = createMockState()
accessor.requestLoad(REFRESH, state2)
- assertThat(
- accessor.state.value.refresh
- ).isEqualTo(
- LoadState.Loading
- )
+ assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Loading)
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value.refresh
- ).isEqualTo(
- LoadState.NotLoading.Incomplete
- )
+ assertThat(accessor.state.value.refresh).isEqualTo(LoadState.NotLoading.Incomplete)
}
@Test
@@ -1262,38 +1201,35 @@
accessor.requestLoad(PREPEND, state1)
// run to get the error
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception),
- prepend = LoadState.Error(exception),
- append = LoadState.Error(exception),
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception),
+ prepend = LoadState.Error(exception),
+ append = LoadState.Error(exception),
+ )
)
- )
// now send another load type refresh, should trigger another load
remoteMediator.loadCallback = null // let it succeed
val state2 = createMockState()
accessor.requestLoad(REFRESH, state2)
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.Error(exception), // keep errors for these for now
- append = LoadState.Error(exception),
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.Error(exception), // keep errors for these for now
+ append = LoadState.Error(exception),
+ )
)
- )
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.NotLoading.Incomplete,
- prepend = LoadState.NotLoading.Incomplete, // clear errors
- append = LoadState.NotLoading.Incomplete,
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.NotLoading.Incomplete,
+ prepend = LoadState.NotLoading.Incomplete, // clear errors
+ append = LoadState.NotLoading.Incomplete,
+ )
)
- )
}
@Test
@@ -1312,15 +1248,14 @@
accessor.requestLoad(PREPEND, state1)
// run to get the error
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception1),
- prepend = LoadState.Error(exception1),
- append = LoadState.Error(exception1),
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception1),
+ prepend = LoadState.Error(exception1),
+ append = LoadState.Error(exception1),
+ )
)
- )
// now send another load type refresh, should trigger another load
val exception2 = Exception("2")
remoteMediator.loadCallback = { _, _ ->
@@ -1329,25 +1264,23 @@
}
val state2 = createMockState()
accessor.requestLoad(REFRESH, state2)
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.Error(exception1), // keep errors for these for now
- append = LoadState.Error(exception1),
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.Error(exception1), // keep errors for these for now
+ append = LoadState.Error(exception1),
+ )
)
- )
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value
- ).isEqualTo(
- LoadStates(
- refresh = LoadState.Error(exception2),
- prepend = LoadState.Error(exception1), // these keep their original exceptions
- append = LoadState.Error(exception1),
+ assertThat(accessor.state.value)
+ .isEqualTo(
+ LoadStates(
+ refresh = LoadState.Error(exception2),
+ prepend = LoadState.Error(exception1), // these keep their original exceptions
+ append = LoadState.Error(exception1),
+ )
)
- )
}
@Test
@@ -1371,69 +1304,32 @@
val accessor = testScope.createAccessor(remoteMediator)
val state1 = createMockState()
accessor.requestLoad(loadType, state1)
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.Loading
- )
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Loading)
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.Error(exception)
- )
- assertThat(
- remoteMediator.newLoadEvents
- ).containsExactly(
- LoadEvent(loadType, state1)
- )
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
+ assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(loadType, state1))
// subsequent add calls shouldn't do anything
accessor.requestLoad(loadType, createMockState())
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.Error(exception)
- )
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
testScope.advanceUntilIdle()
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.Error(exception)
- )
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
assertThat(remoteMediator.newLoadEvents).isEmpty()
// if we send a retry, then it will work
remoteMediator.loadCallback = null
val retryState = createMockState()
accessor.retryFailed(retryState)
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.Loading
- )
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Loading)
testScope.advanceUntilIdle()
- assertThat(
- remoteMediator.newLoadEvents
- ).containsExactly(
- LoadEvent(loadType, retryState)
- )
- assertThat(
- accessor.state.value.get(loadType)
- ).isEqualTo(
- LoadState.NotLoading.Incomplete
- )
+ assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(loadType, retryState))
+ assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.NotLoading.Incomplete)
}
private fun TestScope.createAccessor(
mediator: RemoteMediatorMock
): RemoteMediatorAccessor<Int, Int> {
- val accessor = RemoteMediatorAccessor(
- scope = this,
- delegate = mediator
- )
- TestScope().launch(coroutineContext) {
- accessor.initialize()
- }
+ val accessor = RemoteMediatorAccessor(scope = this, delegate = mediator)
+ TestScope().launch(coroutineContext) { accessor.initialize() }
return accessor
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsTest.kt
index d350843..854a536 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsTest.kt
@@ -38,18 +38,9 @@
}
}
-/**
- * Simpler assert which only concerns itself with page data, all other event types are ignored.
- */
-private fun <T : Any> assertInsertData(
- expected: List<PageEvent<T>>,
- actual: List<PageEvent<T>>
-) {
- @Suppress("UNCHECKED_CAST")
- assertEquals(
- expected.getItems(),
- actual.getItems()
- )
+/** Simpler assert which only concerns itself with page data, all other event types are ignored. */
+private fun <T : Any> assertInsertData(expected: List<PageEvent<T>>, actual: List<PageEvent<T>>) {
+ @Suppress("UNCHECKED_CAST") assertEquals(expected.getItems(), actual.getItems())
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,290 +48,275 @@
@Test
fun refreshFull() = runTest {
assertThat(
- flowOf(
- localRefresh(
- pages = listOf(
- listOf("a2", "b1"),
- listOf("c1", "c2")
- ).toTransformablePages(),
- placeholdersAfter = 1,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("a2", "B", "b1"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1", "c2")
+ flowOf(
+ localRefresh(
+ pages =
+ listOf(listOf("a2", "b1"), listOf("c1", "c2"))
+ .toTransformablePages(),
+ placeholdersAfter = 1,
)
- ),
- placeholdersAfter = 1,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("a2", "B", "b1"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(originalPageOffset = 1, data = listOf("c1", "c2"))
+ ),
+ placeholdersAfter = 1,
+ )
)
)
- )
}
@Test
fun refreshStartFull() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("c1")
- ).toTransformablePages(),
- placeholdersAfter = 1,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("c1")).toTransformablePages(),
+ placeholdersAfter = 1,
+ )
assertThat(
- flowOf(
- refresh,
- localPrepend(
- pages = listOf(
- listOf("a1", "b1"),
- listOf("b2", "b3")
- ).toTransformablePages(2),
- placeholdersBefore = 1,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localPrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(-2),
- data = listOf("a1", "B", "b1"),
- hintOriginalPageOffset = -2,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("b2", "b3")
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-1, 0),
- data = listOf("C"),
- hintOriginalPageOffset = -1,
- hintOriginalIndices = listOf(1)
+ flowOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(listOf("a1", "b1"), listOf("b2", "b3"))
+ .toTransformablePages(2),
+ placeholdersBefore = 1,
)
- ),
- placeholdersBefore = 1,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-2),
+ data = listOf("a1", "B", "b1"),
+ hintOriginalPageOffset = -2,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("b2", "b3")
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1, 0),
+ data = listOf("C"),
+ hintOriginalPageOffset = -1,
+ hintOriginalIndices = listOf(1)
+ )
+ ),
+ placeholdersBefore = 1,
+ )
)
)
- )
}
@Test
fun refreshEndFull() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("a1", "a2")
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("a1", "a2")).toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ )
assertThat(
- flowOf(
- refresh,
- localAppend(
- pages = listOf(
- listOf("c1", "d1"),
- listOf("d2", "d3")
- ).toTransformablePages(-1),
- placeholdersAfter = 1,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("c1", "D", "d1"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = 2,
- data = listOf("d2", "d3")
+ flowOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(listOf("c1", "d1"), listOf("d2", "d3"))
+ .toTransformablePages(-1),
+ placeholdersAfter = 1,
)
- ),
- placeholdersAfter = 1,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("c1", "D", "d1"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = 2, data = listOf("d2", "d3"))
+ ),
+ placeholdersAfter = 1,
+ )
)
)
- )
}
@Test
fun refreshDropFull() = runTest {
assertThat(
- flowOf(
- localRefresh(
- pages = listOf(
- listOf("a1"),
- listOf("a2")
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- ),
- Drop(APPEND, 1, 1, 4)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1")
+ flowOf(
+ localRefresh(
+ pages = listOf(listOf("a1"), listOf("a2")).toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("a2")
- )
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- ),
- Drop<String>(APPEND, 1, 1, 4)
+ Drop(APPEND, 1, 1, 4)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 0, data = listOf("a1")),
+ TransformablePage(originalPageOffset = 1, data = listOf("a2"))
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ ),
+ Drop<String>(APPEND, 1, 1, 4)
+ )
+ )
}
private fun refresh(
pages: List<String?>,
prepend: LoadState = NotLoading.Incomplete,
append: LoadState = NotLoading.Incomplete
- ) = localRefresh(
- pages = when {
- pages.isEmpty() -> listOf(TransformablePage.empty())
- else -> pages.map {
- if (it != null) listOf(it)
- else listOf()
- }.toTransformablePages()
- },
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- source = loadStates(prepend = prepend, append = append)
- )
+ ) =
+ localRefresh(
+ pages =
+ when {
+ pages.isEmpty() -> listOf(TransformablePage.empty())
+ else ->
+ pages
+ .map { if (it != null) listOf(it) else listOf() }
+ .toTransformablePages()
+ },
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ source = loadStates(prepend = prepend, append = append)
+ )
- private fun prepend(
- pages: List<String?>,
- prepend: LoadState = NotLoading.Incomplete
- ) = localPrepend(
- pages = pages.map {
- if (it != null) listOf(it) else listOf()
- }.toTransformablePages(),
- source = loadStates(prepend = prepend)
- )
+ private fun prepend(pages: List<String?>, prepend: LoadState = NotLoading.Incomplete) =
+ localPrepend(
+ pages = pages.map { if (it != null) listOf(it) else listOf() }.toTransformablePages(),
+ source = loadStates(prepend = prepend)
+ )
- private fun append(
- pages: List<String?>,
- append: LoadState = NotLoading.Incomplete
- ) = localAppend(
- pages = pages.map {
- if (it != null) listOf(it) else listOf()
- }.toTransformablePages(),
- source = loadStates(append = append)
- )
+ private fun append(pages: List<String?>, append: LoadState = NotLoading.Incomplete) =
+ localAppend(
+ pages = pages.map { if (it != null) listOf(it) else listOf() }.toTransformablePages(),
+ source = loadStates(append = append)
+ )
- private fun drop(loadType: LoadType, minPageOffset: Int, maxPageOffset: Int) = Drop<String>(
- loadType = loadType,
- minPageOffset = minPageOffset,
- maxPageOffset = maxPageOffset,
- placeholdersRemaining = 0
- )
+ private fun drop(loadType: LoadType, minPageOffset: Int, maxPageOffset: Int) =
+ Drop<String>(
+ loadType = loadType,
+ minPageOffset = minPageOffset,
+ maxPageOffset = maxPageOffset,
+ placeholdersRemaining = 0
+ )
@Test
fun refreshNoop() = runTest {
assertInsertData(
- listOf(
- refresh(pages = listOf("a1"))
- ),
- flowOf(
- refresh(pages = listOf("a1"))
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ listOf(refresh(pages = listOf("a1"))),
+ flowOf(refresh(pages = listOf("a1")))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@Test
fun refreshStartDone() = runTest {
assertInsertData(
- listOf(
- refresh(pages = listOf("A", "a1"))
- ),
- flowOf(
- refresh(pages = listOf("a1"), prepend = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ listOf(refresh(pages = listOf("A", "a1"))),
+ flowOf(refresh(pages = listOf("a1"), prepend = NotLoading.Complete))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@Test
fun refreshEndDone() = runTest {
assertInsertData(
- listOf(
- refresh(pages = listOf("a1", "END"))
- ),
- flowOf(
- refresh(pages = listOf("a1"), append = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ listOf(refresh(pages = listOf("a1", "END"))),
+ flowOf(refresh(pages = listOf("a1"), append = NotLoading.Complete))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@Test
fun refreshBothDone() = runTest {
assertInsertData(
- listOf(
- refresh(pages = listOf("A", "a1", "END"))
- ),
+ listOf(refresh(pages = listOf("A", "a1", "END"))),
flowOf(
- refresh(
- pages = listOf("a1"),
- prepend = NotLoading.Complete,
- append = NotLoading.Complete
+ refresh(
+ pages = listOf("a1"),
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete
+ )
)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -352,12 +328,12 @@
// both sides.
refresh(pages = listOf<String>())
),
- flowOf(
- refresh(pages = listOf())
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ flowOf(refresh(pages = listOf()))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -375,14 +351,16 @@
append(pages = listOf("END"))
),
flowOf(
- refresh(pages = listOf(), prepend = NotLoading.Complete),
- append(pages = listOf("a1")),
- append(pages = listOf()),
- append(pages = listOf(), append = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ refresh(pages = listOf(), prepend = NotLoading.Complete),
+ append(pages = listOf("a1")),
+ append(pages = listOf()),
+ append(pages = listOf(), append = NotLoading.Complete)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -400,14 +378,16 @@
prepend(pages = listOf("A"))
),
flowOf(
- refresh(pages = listOf(), append = NotLoading.Complete),
- prepend(pages = listOf("a1")),
- prepend(pages = listOf()),
- prepend(pages = listOf(), prepend = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ refresh(pages = listOf(), append = NotLoading.Complete),
+ prepend(pages = listOf("a1")),
+ prepend(pages = listOf()),
+ prepend(pages = listOf(), prepend = NotLoading.Complete)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -423,14 +403,16 @@
prepend(pages = listOf("A"))
),
flowOf(
- refresh(pages = listOf(), prepend = NotLoading.Complete),
- drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0),
- append(pages = listOf("a1")),
- prepend(pages = listOf(), prepend = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ refresh(pages = listOf(), prepend = NotLoading.Complete),
+ drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0),
+ append(pages = listOf("a1")),
+ prepend(pages = listOf(), prepend = NotLoading.Complete)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -446,14 +428,16 @@
append(pages = listOf("END"))
),
flowOf(
- refresh(pages = listOf(), append = NotLoading.Complete),
- drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0),
- prepend(pages = listOf("a1")),
- append(pages = listOf(), append = NotLoading.Complete)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ refresh(pages = listOf(), append = NotLoading.Complete),
+ drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0),
+ prepend(pages = listOf("a1")),
+ append(pages = listOf(), append = NotLoading.Complete)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -465,13 +449,12 @@
// not enough data to create separators yet
prepend(pages = listOf("a1"))
),
- flowOf(
- refresh(pages = listOf()),
- prepend(pages = listOf("a1"))
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ flowOf(refresh(pages = listOf()), prepend(pages = listOf("a1")))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -483,13 +466,12 @@
// not enough data to create separators yet
append(pages = listOf("a1"))
),
- flowOf(
- refresh(pages = listOf()),
- append(pages = listOf("a1"))
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
+ flowOf(refresh(pages = listOf()), append(pages = listOf("a1")))
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
}
@@ -497,95 +479,85 @@
fun refreshEmptyStartDropFull() = runTest {
// when start terminal separator is inserted, we need to drop count*2 + 1
assertThat(
- flowOf(
- refresh(
- pages = listOf("a1", "b1"),
- prepend = NotLoading.Complete
- ),
- drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalPageOffset = 0,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1")
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("B"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("b1")
- )
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- source = loadStates(prepend = NotLoading.Complete)
- ),
- drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0)
+ flowOf(
+ refresh(pages = listOf("a1", "b1"), prepend = NotLoading.Complete),
+ drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalPageOffset = 0,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(originalPageOffset = 0, data = listOf("a1")),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("B"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(originalPageOffset = 1, data = listOf("b1"))
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ source = loadStates(prepend = NotLoading.Complete)
+ ),
+ drop(loadType = PREPEND, minPageOffset = 0, maxPageOffset = 0)
+ )
+ )
}
@Test
fun refreshEmptyEndDropFull() = runTest {
// when end terminal separator is inserted, we need to drop count*2 + 1
assertThat(
- flowOf(
- refresh(
- pages = listOf("a1", "b1"),
- append = NotLoading.Complete
- ),
- drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0)
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1")
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("B"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("b1")
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("END"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0)
- )
- ),
- placeholdersAfter = 1,
- source = loadStates(append = NotLoading.Complete),
- ),
- drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0)
+ flowOf(
+ refresh(pages = listOf("a1", "b1"), append = NotLoading.Complete),
+ drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0)
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 0, data = listOf("a1")),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("B"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(originalPageOffset = 1, data = listOf("b1")),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("END"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0)
+ )
+ ),
+ placeholdersAfter = 1,
+ source = loadStates(append = NotLoading.Complete),
+ ),
+ drop(loadType = APPEND, minPageOffset = 0, maxPageOffset = 0)
+ )
+ )
}
@Test
@@ -597,1456 +569,1507 @@
assertInsertData(
listOf(
localRefresh(
- pages = listOf(
- listOf(
- PrimaryType("a1"),
- SeparatorType("B"),
- PrimaryType("b1")
- )
- ).toTransformablePages(),
+ pages =
+ listOf(listOf(PrimaryType("a1"), SeparatorType("B"), PrimaryType("b1")))
+ .toTransformablePages(),
placeholdersBefore = 0,
placeholdersAfter = 1,
)
),
flowOf(
- localRefresh(
- pages = listOf(listOf(PrimaryType("a1"), PrimaryType("b1")))
- .toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- )
- ).insertEventSeparators(terminalSeparatorType = FULLY_COMPLETE) { before, after ->
- return@insertEventSeparators (
- if (before != null && after != null) {
- SeparatorType("B")
- } else null
+ localRefresh(
+ pages =
+ listOf(listOf(PrimaryType("a1"), PrimaryType("b1")))
+ .toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
)
- }.toList()
+ )
+ .insertEventSeparators(terminalSeparatorType = FULLY_COMPLETE) { before, after ->
+ return@insertEventSeparators (if (before != null && after != null) {
+ SeparatorType("B")
+ } else null)
+ }
+ .toList()
)
}
@Test
fun refreshEmptyPagesExceptOne() = runTest {
assertThat(
- flowOf(
- localRefresh(
- pages = listOf(
- listOf(),
- listOf(),
- listOf("a2", "b1"),
- listOf(),
- listOf()
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(2),
- data = listOf("a2", "B", "b1"),
- hintOriginalPageOffset = 2,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = 3,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = 4,
- data = listOf()
+ flowOf(
+ localRefresh(
+ pages =
+ listOf(listOf(), listOf(), listOf("a2", "b1"), listOf(), listOf())
+ .toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
)
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 0, data = listOf()),
+ TransformablePage(originalPageOffset = 1, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(2),
+ data = listOf("a2", "B", "b1"),
+ hintOriginalPageOffset = 2,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = 3, data = listOf()),
+ TransformablePage(originalPageOffset = 4, data = listOf())
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ )
)
)
- )
}
@Test
fun refreshSparsePages() = runTest {
assertThat(
- flowOf(
- localRefresh(
- pages = listOf(
- listOf(),
- listOf("a2", "b1"),
- listOf(),
- listOf("c1", "c2"),
- listOf()
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- localRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("a2", "B", "b1"),
- hintOriginalPageOffset = 1,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = 2,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(1, 3),
- data = listOf("C"),
- hintOriginalPageOffset = 3,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 3,
- data = listOf("c1", "c2")
- ),
- TransformablePage(
- originalPageOffset = 4,
- data = listOf()
+ flowOf(
+ localRefresh(
+ pages =
+ listOf(
+ listOf(),
+ listOf("a2", "b1"),
+ listOf(),
+ listOf("c1", "c2"),
+ listOf()
+ )
+ .toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
)
- ),
- placeholdersBefore = 0,
- placeholdersAfter = 1,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ localRefresh(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 0, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("a2", "B", "b1"),
+ hintOriginalPageOffset = 1,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = 2, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1, 3),
+ data = listOf("C"),
+ hintOriginalPageOffset = 3,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(
+ originalPageOffset = 3,
+ data = listOf("c1", "c2")
+ ),
+ TransformablePage(originalPageOffset = 4, data = listOf())
+ ),
+ placeholdersBefore = 0,
+ placeholdersAfter = 1,
+ )
)
)
- )
}
@Test
fun prependEmptyPagesExceptOne() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("c1", "c2")
- ).toTransformablePages(),
- placeholdersBefore = 2,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("c1", "c2")).toTransformablePages(),
+ placeholdersBefore = 2,
+ )
assertThat(
- flowOf(
- refresh,
- localPrepend(
- pages = listOf(
- listOf(),
- listOf(),
- listOf("a1", "b1"),
- listOf(),
- listOf()
- ).toTransformablePages(5),
- placeholdersBefore = 0,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localPrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -5,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = -4,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-3),
- data = listOf("a1", "B", "b1"),
- hintOriginalPageOffset = -3,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-3, 0),
- data = listOf("C"),
- hintOriginalPageOffset = -3,
- hintOriginalIndices = listOf(1)
- ),
- TransformablePage(
- originalPageOffset = -2,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf()
+ flowOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(listOf(), listOf(), listOf("a1", "b1"), listOf(), listOf())
+ .toTransformablePages(5),
+ placeholdersBefore = 0,
)
- ),
- placeholdersBefore = 0,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = -5, data = listOf()),
+ TransformablePage(originalPageOffset = -4, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-3),
+ data = listOf("a1", "B", "b1"),
+ hintOriginalPageOffset = -3,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-3, 0),
+ data = listOf("C"),
+ hintOriginalPageOffset = -3,
+ hintOriginalIndices = listOf(1)
+ ),
+ TransformablePage(originalPageOffset = -2, data = listOf()),
+ TransformablePage(originalPageOffset = -1, data = listOf())
+ ),
+ placeholdersBefore = 0,
+ )
)
)
- )
}
@Test
fun prependSparsePages() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("d1", "d2")
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 4,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("d1", "d2")).toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 4,
+ )
assertThat(
- flowOf(
- refresh,
- localPrepend(
- pages = listOf(
- listOf(),
- listOf("a1", "b1"),
- listOf(),
- listOf("c1", "c2"),
- listOf()
- ).toTransformablePages(5),
- placeholdersBefore = 0,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localPrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -5,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-4),
- data = listOf("a1", "B", "b1"),
- hintOriginalPageOffset = -4,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = -3,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-4, -2),
- data = listOf("C"),
- hintOriginalPageOffset = -4,
- hintOriginalIndices = listOf(1)
- ),
- TransformablePage(
- originalPageOffset = -2,
- data = listOf("c1", "c2")
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-2, 0),
- data = listOf("D"),
- hintOriginalPageOffset = -2,
- hintOriginalIndices = listOf(1)
- ),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf()
+ flowOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(
+ listOf(),
+ listOf("a1", "b1"),
+ listOf(),
+ listOf("c1", "c2"),
+ listOf()
+ )
+ .toTransformablePages(5),
+ placeholdersBefore = 0,
)
- ),
- placeholdersBefore = 0,
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localPrepend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = -5, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-4),
+ data = listOf("a1", "B", "b1"),
+ hintOriginalPageOffset = -4,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = -3, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-4, -2),
+ data = listOf("C"),
+ hintOriginalPageOffset = -4,
+ hintOriginalIndices = listOf(1)
+ ),
+ TransformablePage(
+ originalPageOffset = -2,
+ data = listOf("c1", "c2")
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-2, 0),
+ data = listOf("D"),
+ hintOriginalPageOffset = -2,
+ hintOriginalIndices = listOf(1)
+ ),
+ TransformablePage(originalPageOffset = -1, data = listOf())
+ ),
+ placeholdersBefore = 0,
+ )
)
)
- )
}
@Test
fun appendEmptyPagesExceptOne() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("a1", "a2")
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 2,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("a1", "a2")).toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 2,
+ )
assertThat(
- flowOf(
- refresh,
- localAppend(
- pages = listOf(
- listOf(),
- listOf(),
- listOf("b1", "c1"),
- listOf(),
- listOf()
- ).toTransformablePages(-1),
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = 2,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 3),
- data = listOf("B"),
- hintOriginalPageOffset = 3,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(3),
- data = listOf("b1", "C", "c1"),
- hintOriginalPageOffset = 3,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = 4,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffset = 5,
- data = listOf()
+ flowOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(listOf(), listOf(), listOf("b1", "c1"), listOf(), listOf())
+ .toTransformablePages(-1),
)
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 1, data = listOf()),
+ TransformablePage(originalPageOffset = 2, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 3),
+ data = listOf("B"),
+ hintOriginalPageOffset = 3,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(3),
+ data = listOf("b1", "C", "c1"),
+ hintOriginalPageOffset = 3,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = 4, data = listOf()),
+ TransformablePage(originalPageOffset = 5, data = listOf())
+ ),
+ )
)
)
- )
}
@Test
fun appendSparsePages() = runTest {
- val refresh = localRefresh(
- pages = listOf(
- listOf("a1", "a2")
- ).toTransformablePages(),
- placeholdersBefore = 0,
- placeholdersAfter = 4,
- )
+ val refresh =
+ localRefresh(
+ pages = listOf(listOf("a1", "a2")).toTransformablePages(),
+ placeholdersBefore = 0,
+ placeholdersAfter = 4,
+ )
assertThat(
- flowOf(
- refresh,
- localAppend(
- pages = listOf(
- listOf(),
- listOf("b1", "c1"),
- listOf(),
- listOf("d1", "d2"),
- listOf()
- ).toTransformablePages(-1),
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- refresh,
- localAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 2),
- data = listOf("B"),
- hintOriginalPageOffset = 2,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(2),
- data = listOf("b1", "C", "c1"),
- hintOriginalPageOffset = 2,
- hintOriginalIndices = listOf(0, 1, 1)
- ),
- TransformablePage(
- originalPageOffset = 3,
- data = listOf()
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(2, 4),
- data = listOf("D"),
- hintOriginalPageOffset = 4,
- hintOriginalIndices = listOf(0)
- ),
- TransformablePage(
- originalPageOffset = 4,
- data = listOf("d1", "d2")
- ),
- TransformablePage(
- originalPageOffset = 5,
- data = listOf()
+ flowOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(
+ listOf(),
+ listOf("b1", "c1"),
+ listOf(),
+ listOf("d1", "d2"),
+ listOf()
+ )
+ .toTransformablePages(-1),
)
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
+ )
+ .isEqualTo(
+ listOf(
+ refresh,
+ localAppend(
+ pages =
+ listOf(
+ TransformablePage(originalPageOffset = 1, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 2),
+ data = listOf("B"),
+ hintOriginalPageOffset = 2,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(2),
+ data = listOf("b1", "C", "c1"),
+ hintOriginalPageOffset = 2,
+ hintOriginalIndices = listOf(0, 1, 1)
+ ),
+ TransformablePage(originalPageOffset = 3, data = listOf()),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(2, 4),
+ data = listOf("D"),
+ hintOriginalPageOffset = 4,
+ hintOriginalIndices = listOf(0)
+ ),
+ TransformablePage(
+ originalPageOffset = 4,
+ data = listOf("d1", "d2")
+ ),
+ TransformablePage(originalPageOffset = 5, data = listOf())
+ ),
+ )
)
)
- )
}
@Test
fun remoteRefreshEndOfPaginationReached_fullyComplete() = runTest {
assertThat(
- flowOf(
- remoteLoadStateUpdate(
- refreshRemote = Loading
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Loading
- ),
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(append = NotLoading.Complete, prepend = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete,
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteLoadStateUpdate(
- refreshRemote = Loading
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Loading
- ),
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(append = NotLoading.Complete, prepend = NotLoading.Complete)
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteLoadStateUpdate(refreshRemote = Loading),
+ remoteLoadStateUpdate(refreshLocal = Loading, refreshRemote = Loading),
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete
+ )
),
- ),
- placeholdersBefore = 1,
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete,
),
- ),
- placeholdersAfter = 1,
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Complete,
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteLoadStateUpdate(refreshRemote = Loading),
+ remoteLoadStateUpdate(refreshLocal = Loading, refreshRemote = Loading),
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(append = NotLoading.Complete, prepend = NotLoading.Complete)
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Complete,
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remoteRefreshEndOfPaginationReached_sourceComplete() = runTest {
assertThat(
- flowOf(
- remoteLoadStateUpdate(
- refreshRemote = Loading
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Loading
- ),
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- append = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- ).insertEventSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteLoadStateUpdate(
- refreshRemote = Loading
- ),
- remoteLoadStateUpdate(
- refreshLocal = Loading,
- refreshRemote = Loading
- ),
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteLoadStateUpdate(refreshRemote = Loading),
+ remoteLoadStateUpdate(refreshLocal = Loading, refreshRemote = Loading),
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ append = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1"),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete
),
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
),
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- refreshRemote = NotLoading.Complete
- ),
- remotePrepend(
- pages = listOf(),
- placeholdersBefore = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Complete,
- prepend = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(),
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- refresh = NotLoading.Complete,
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteLoadStateUpdate(refreshRemote = Loading),
+ remoteLoadStateUpdate(refreshLocal = Loading, refreshRemote = Loading),
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ refreshRemote = NotLoading.Complete
+ ),
+ remotePrepend(
+ pages = listOf(),
+ placeholdersBefore = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages = listOf(),
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ refresh = NotLoading.Complete,
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remotePrependEndOfPaginationReached_fullyComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote prepend is done triggers the header to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- )
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- placeholdersBefore = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
+ // Signalling that remote prepend is done triggers the header to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ )
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remotePrependEndOfPaginationReached_sourceComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote prepend is done triggers the header to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- )
- ).insertEventSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1"),
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
- ),
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(),
- placeholdersBefore = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
+ // Signalling that remote prepend is done triggers the header to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ )
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remotePrepend(
+ pages = listOf(),
+ placeholdersBefore = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remotePrependEndOfPaginationReachedWithDrops_fullyComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
- )
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote prepend is done triggers the header to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- // Drop the first page, header and separator between "b1" and "a1"
- Drop(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 1
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
- )
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffsets = intArrayOf(-1, 0),
- data = listOf("B"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ )
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(-1),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1,
+ // Signalling that remote prepend is done triggers the header to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
),
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
- Drop(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 1
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(-1),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1,
+ // Drop the first page, header and separator between "b1" and "a1"
+ Drop(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 1
),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ )
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffsets = intArrayOf(-1, 0),
- data = listOf("B"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1
- ),
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1, 0),
+ data = listOf("B"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1
+ ),
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1,
+ ),
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ Drop(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 1
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1,
+ ),
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1, 0),
+ data = listOf("B"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1
+ ),
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remotePrependEndOfPaginationReachedWithDrops_sourceComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(append = NotLoading.Complete),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
- )
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote prepend is done triggers the header to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- ),
- // Drop the first page, header and separator between "b1" and "a1"
- Drop(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 1
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
- )
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
- ).insertEventSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("b1"),
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source = loadStates(append = NotLoading.Complete),
),
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ )
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(append = NotLoading.Complete),
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(-1),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1,
+ // Signalling that remote prepend is done triggers the header to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
+ // Drop the first page, header and separator between "b1" and "a1"
+ Drop(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 1
),
- TransformablePage(
- originalPageOffsets = intArrayOf(-1, 0),
- data = listOf("B"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ )
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
),
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remotePrepend(
- pages = listOf(),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
- Drop(
- loadType = PREPEND,
- minPageOffset = -1,
- maxPageOffset = -1,
- placeholdersRemaining = 1
- ),
- remotePrepend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(-1),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1,
- ),
- TransformablePage(
- originalPageOffset = -1,
- data = listOf("a1"),
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(-1, 0),
- data = listOf("B"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = -1
- ),
- ),
- placeholdersBefore = 0,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf("b1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source = loadStates(append = NotLoading.Complete),
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1,
+ ),
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1, 0),
+ data = listOf("B"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1
+ ),
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remotePrepend(
+ pages = listOf(),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ Drop(
+ loadType = PREPEND,
+ minPageOffset = -1,
+ maxPageOffset = -1,
+ placeholdersRemaining = 1
+ ),
+ remotePrepend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1,
+ ),
+ TransformablePage(
+ originalPageOffset = -1,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(-1, 0),
+ data = listOf("B"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = -1
+ ),
+ ),
+ placeholdersBefore = 0,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remoteAppendEndOfPaginationReached_fullyComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote append is done triggers the footer to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
+ // Signalling that remote append is done triggers the footer to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remoteAppendEndOfPaginationReached_sourceComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("a1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote append is done triggers the footer to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- ).insertEventSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("A"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("a1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("a1"),
+ // Signalling that remote append is done triggers the footer to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
),
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0,
- ),
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(),
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("A"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf("a1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0,
+ ),
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages = listOf(),
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remoteAppendEndOfPaginationReachedWithDrops_fullyComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(
- prepend = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
- )
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote append is done triggers the footer to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- // Drop the last page, footer and separator between "b1" and "c1"
- Drop(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 1
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
- )
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
- ).insertEventSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(prepend = NotLoading.Complete)
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ )
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1,
+ // Signalling that remote append is done triggers the footer to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
),
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
- Drop(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 1
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
+ // Drop the last page, footer and separator between "b1" and "c1"
+ Drop(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 1
),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ )
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
- ),
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source = loadStates(prepend = NotLoading.Complete)
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ ),
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1,
+ ),
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ Drop(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 1
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
@Test
fun remoteAppendEndOfPaginationReachedWithDrops_sourceComplete() = runTest {
assertThat(
- flowOf(
- remoteRefresh(
- pages = listOf(listOf("b1")).toTransformablePages(),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(prepend = NotLoading.Complete),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
- )
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- // Signalling that remote append is done triggers the footer to resolve.
- remoteLoadStateUpdate(
- appendLocal = NotLoading.Complete,
- prependLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
- ),
- // Drop the last page, footer and separator between "b1" and "c1"
- Drop(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 1
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
- )
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
- ).insertEventSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- ).toList()
- ).isEqualTo(
- listOf(
- remoteRefresh(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0),
- data = listOf("B"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 0
+ flowOf(
+ remoteRefresh(
+ pages = listOf(listOf("b1")).toTransformablePages(),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source = loadStates(prepend = NotLoading.Complete),
),
- TransformablePage(
- originalPageOffset = 0,
- data = listOf("b1"),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ )
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
),
- ),
- placeholdersBefore = 1,
- placeholdersAfter = 1,
- source = loadStates(prepend = NotLoading.Complete),
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
+ // Signalling that remote append is done triggers the footer to resolve.
+ remoteLoadStateUpdate(
+ appendLocal = NotLoading.Complete,
+ prependLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
+ // Drop the last page, footer and separator between "b1" and "c1"
+ Drop(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 1
),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1,
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ )
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
),
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- ),
- remoteAppend(
- pages = listOf(),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
- Drop(
- loadType = APPEND,
- minPageOffset = 1,
- maxPageOffset = 1,
- placeholdersRemaining = 1
- ),
- remoteAppend(
- pages = listOf(
- TransformablePage(
- originalPageOffsets = intArrayOf(0, 1),
- data = listOf("C"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
- ),
- TransformablePage(
- originalPageOffset = 1,
- data = listOf("c1"),
- ),
- TransformablePage(
- originalPageOffsets = intArrayOf(1),
- data = listOf("END"),
- hintOriginalIndices = listOf(0),
- hintOriginalPageOffset = 1
- ),
- ),
- source = loadStates(
- prepend = NotLoading.Complete,
- append = NotLoading.Complete,
- ),
- mediator = loadStates(
- append = NotLoading.Complete,
- ),
- ),
+ )
+ .insertEventSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .toList()
)
- )
+ .isEqualTo(
+ listOf(
+ remoteRefresh(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0),
+ data = listOf("B"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 0
+ ),
+ TransformablePage(
+ originalPageOffset = 0,
+ data = listOf("b1"),
+ ),
+ ),
+ placeholdersBefore = 1,
+ placeholdersAfter = 1,
+ source = loadStates(prepend = NotLoading.Complete),
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1,
+ ),
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ ),
+ remoteAppend(
+ pages = listOf(),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ Drop(
+ loadType = APPEND,
+ minPageOffset = 1,
+ maxPageOffset = 1,
+ placeholdersRemaining = 1
+ ),
+ remoteAppend(
+ pages =
+ listOf(
+ TransformablePage(
+ originalPageOffsets = intArrayOf(0, 1),
+ data = listOf("C"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ TransformablePage(
+ originalPageOffset = 1,
+ data = listOf("c1"),
+ ),
+ TransformablePage(
+ originalPageOffsets = intArrayOf(1),
+ data = listOf("END"),
+ hintOriginalIndices = listOf(0),
+ hintOriginalPageOffset = 1
+ ),
+ ),
+ source =
+ loadStates(
+ prepend = NotLoading.Complete,
+ append = NotLoading.Complete,
+ ),
+ mediator =
+ loadStates(
+ append = NotLoading.Complete,
+ ),
+ ),
+ )
+ )
}
companion object {
/**
- * Creates an upper-case letter at the beginning of each section of strings that start
- * with the same letter, and the string "END" at the very end.
+ * Creates an upper-case letter at the beginning of each section of strings that start with
+ * the same letter, and the string "END" at the very end.
*/
val LETTER_SEPARATOR_GENERATOR: suspend (String?, String?) -> String? = { before, after ->
if (after == null) {
@@ -2059,16 +2082,11 @@
}
@Suppress("TestFunctionName")
-internal fun <T : Any> TransformablePage(data: List<T>) = TransformablePage(
- data = data,
- originalPageOffset = 0
-)
+internal fun <T : Any> TransformablePage(data: List<T>) =
+ TransformablePage(data = data, originalPageOffset = 0)
-internal fun <T : Any> List<List<T>>.toTransformablePages(
- indexOfInitialPage: Int = 0
-) = mapIndexed { index, list ->
- TransformablePage(
- data = list,
- originalPageOffset = index - indexOfInitialPage
- )
-}.toMutableList()
+internal fun <T : Any> List<List<T>>.toTransformablePages(indexOfInitialPage: Int = 0) =
+ mapIndexed { index, list ->
+ TransformablePage(data = list, originalPageOffset = index - indexOfInitialPage)
+ }
+ .toMutableList()
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsWithRemoteMediatorTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsWithRemoteMediatorTest.kt
index 2731e76..27c9ae9 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsWithRemoteMediatorTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SeparatorsWithRemoteMediatorTest.kt
@@ -32,24 +32,27 @@
class SeparatorsWithRemoteMediatorTest {
@Test
fun prependAfterPrependComplete() = runTest {
- val pageEventFlow = flowOf(
- generatePrepend(
- originalPageOffset = 0,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
- )
- ),
- generatePrepend(
- originalPageOffset = -1,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
+ val pageEventFlow =
+ flowOf(
+ generatePrepend(
+ originalPageOffset = 0,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ )
+ ),
+ generatePrepend(
+ originalPageOffset = -1,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ )
)
)
- )
assertFailsWith<IllegalArgumentException>(
"Prepend after endOfPaginationReached already true is invalid"
) {
@@ -58,30 +61,34 @@
terminalSeparatorType = FULLY_COMPLETE,
generator = LETTER_SEPARATOR_GENERATOR
)
- .flow.toList()
+ .flow
+ .toList()
}
}
@Test
fun appendAfterAppendComplete() = runTest {
- val pageEventFlow = flowOf(
- generateAppend(
- originalPageOffset = 0,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
+ val pageEventFlow =
+ flowOf(
+ generateAppend(
+ originalPageOffset = 0,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
),
- ),
- generateAppend(
- originalPageOffset = 1,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
+ generateAppend(
+ originalPageOffset = 1,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
),
- ),
- )
+ )
assertFailsWith<IllegalArgumentException>(
"Append after endOfPaginationReached already true is invalid"
) {
@@ -90,296 +97,334 @@
terminalSeparatorType = FULLY_COMPLETE,
generator = LETTER_SEPARATOR_GENERATOR
)
- .flow.toList()
+ .flow
+ .toList()
}
}
@Test
fun insertValidation_emptyRemoteAfterHeaderAdded() = runTest {
- val pageEventFlow = flowOf(
- generatePrepend(
- originalPageOffset = 0,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Incomplete,
- prependRemote = NotLoading.Complete,
+ val pageEventFlow =
+ flowOf(
+ generatePrepend(
+ originalPageOffset = 0,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Incomplete,
+ prependRemote = NotLoading.Complete,
+ ),
),
- ),
- generatePrepend(
- originalPageOffset = 1,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete,
+ generatePrepend(
+ originalPageOffset = 1,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete,
+ ),
),
- ),
- )
+ )
// Verify asserts in separators do not throw IllegalArgumentException for a local prepend
// or append that arrives after remote prepend or append marking endOfPagination.
PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators { _, _ -> -1 }.flow.toList()
+ .insertSeparators { _, _ -> -1 }
+ .flow
+ .toList()
}
@Test
fun insertValidation_emptyRemoteAfterFooterAdded() = runTest {
- val pageEventFlow = flowOf(
- generateAppend(
- originalPageOffset = 0,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Incomplete,
- appendRemote = NotLoading.Complete,
+ val pageEventFlow =
+ flowOf(
+ generateAppend(
+ originalPageOffset = 0,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Incomplete,
+ appendRemote = NotLoading.Complete,
+ ),
),
- ),
- generateAppend(
- originalPageOffset = 1,
- pages = listOf(listOf("a1")),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete,
+ generateAppend(
+ originalPageOffset = 1,
+ pages = listOf(listOf("a1")),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete,
+ ),
),
- ),
- )
+ )
// Verify asserts in separators do not throw IllegalArgumentException for a local prepend
// or append that arrives after remote prepend or append marking endOfPagination.
PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators { _, _ -> -1 }.flow.toList()
+ .insertSeparators { _, _ -> -1 }
+ .flow
+ .toList()
}
@Test
fun emptyPrependThenEmptyRemote_fullyComplete() = runTest {
- val pageEventFlow = flowOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generatePrepend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
- ),
- generatePrepend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete
+ val pageEventFlow =
+ flowOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generatePrepend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
+ ),
+ generatePrepend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete
+ )
)
)
- )
- val expected = listOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generatePrepend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
- ),
- generatePrepend(
- // page offset becomes 0 here, as it's adjacent to page 0, the only page with data.
- originalPageOffset = 0,
- pages = listOf(listOf("A")),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete
+ val expected =
+ listOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generatePrepend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
+ ),
+ generatePrepend(
+ // page offset becomes 0 here, as it's adjacent to page 0, the only page with
+ // data.
+ originalPageOffset = 0,
+ pages = listOf(listOf("A")),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete
+ )
)
)
- )
- val actual = PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- )
- .flow.toList()
+ val actual =
+ PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
+ .insertSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .flow
+ .toList()
assertThat(actual).isEqualTo(expected)
}
@Test
fun emptyPrependThenEmptyRemote_sourceComplete() = runTest {
- val pageEventFlow = flowOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generatePrepend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
- ),
- generatePrepend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete
+ val pageEventFlow =
+ flowOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generatePrepend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
+ ),
+ generatePrepend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete
+ )
)
)
- )
- val expected = listOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generatePrepend(
- // page offset becomes 0 here, as it's adjacent to page 0, the only page with data.
- originalPageOffset = 0,
- pages = listOf(listOf("A")),
- loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
- ),
- generatePrepend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- prependLocal = NotLoading.Complete,
- prependRemote = NotLoading.Complete
+ val expected =
+ listOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generatePrepend(
+ // page offset becomes 0 here, as it's adjacent to page 0, the only page with
+ // data.
+ originalPageOffset = 0,
+ pages = listOf(listOf("A")),
+ loadStates = remoteLoadStatesOf(prependLocal = NotLoading.Complete)
+ ),
+ generatePrepend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ prependLocal = NotLoading.Complete,
+ prependRemote = NotLoading.Complete
+ )
)
)
- )
- val actual = PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- )
- .flow.toList()
+ val actual =
+ PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
+ .insertSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .flow
+ .toList()
assertThat(actual).isEqualTo(expected)
}
@Test
fun emptyAppendThenEmptyRemote_fullyComplete() = runTest {
- val pageEventFlow = flowOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generateAppend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
- ),
- generateAppend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
+ val pageEventFlow =
+ flowOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generateAppend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
+ ),
+ generateAppend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ )
)
)
- )
- val expected = listOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generateAppend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
- ),
- generateAppend(
- // page offset becomes 0 here, as it's adjacent to page 0, the only page with data.
- originalPageOffset = 0,
- pages = listOf(listOf("END")),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
+ val expected =
+ listOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generateAppend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
+ ),
+ generateAppend(
+ // page offset becomes 0 here, as it's adjacent to page 0, the only page with
+ // data.
+ originalPageOffset = 0,
+ pages = listOf(listOf("END")),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ )
)
)
- )
- val actual = PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators(
- terminalSeparatorType = FULLY_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- )
- .flow.toList()
+ val actual =
+ PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
+ .insertSeparators(
+ terminalSeparatorType = FULLY_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .flow
+ .toList()
assertThat(actual).isEqualTo(expected)
}
@Test
fun emptyAppendThenEmptyRemote_sourceComplete() = runTest {
- val pageEventFlow = flowOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generateAppend(
- originalPageOffset = 1,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
- ),
- generateAppend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
+ val pageEventFlow =
+ flowOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generateAppend(
+ originalPageOffset = 1,
+ pages = listOf(),
+ loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
+ ),
+ generateAppend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ )
)
)
- )
- val expected = listOf(
- generateRefresh(listOf("a1"), remoteLoadStatesOf()),
- generateAppend(
- // page offset becomes 0 here, as it's adjacent to page 0, the only page with data.
- originalPageOffset = 0,
- pages = listOf(listOf("END")),
- loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
- ),
- generateAppend(
- originalPageOffset = 2,
- pages = listOf(),
- loadStates = remoteLoadStatesOf(
- appendLocal = NotLoading.Complete,
- appendRemote = NotLoading.Complete
+ val expected =
+ listOf(
+ generateRefresh(listOf("a1"), remoteLoadStatesOf()),
+ generateAppend(
+ // page offset becomes 0 here, as it's adjacent to page 0, the only page with
+ // data.
+ originalPageOffset = 0,
+ pages = listOf(listOf("END")),
+ loadStates = remoteLoadStatesOf(appendLocal = NotLoading.Complete)
+ ),
+ generateAppend(
+ originalPageOffset = 2,
+ pages = listOf(),
+ loadStates =
+ remoteLoadStatesOf(
+ appendLocal = NotLoading.Complete,
+ appendRemote = NotLoading.Complete
+ )
)
)
- )
- val actual = PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
- .insertSeparators(
- terminalSeparatorType = SOURCE_COMPLETE,
- generator = LETTER_SEPARATOR_GENERATOR
- )
- .flow.toList()
+ val actual =
+ PagingData(pageEventFlow, dummyUiReceiver, dummyHintReceiver)
+ .insertSeparators(
+ terminalSeparatorType = SOURCE_COMPLETE,
+ generator = LETTER_SEPARATOR_GENERATOR
+ )
+ .flow
+ .toList()
assertThat(actual).isEqualTo(expected)
}
}
-private fun transformablePage(
- originalPageOffset: Int,
- data: List<String>
-) = TransformablePage(
- originalPageOffsets = intArrayOf(originalPageOffset),
- data = data,
- hintOriginalPageOffset = originalPageOffset,
- hintOriginalIndices = data.fold(mutableListOf()) { acc, s ->
- acc.apply {
- add(
- when {
- acc.isEmpty() -> 0
- s.all { it.isUpperCase() } -> acc.last()
- else -> acc.last() + 1
+private fun transformablePage(originalPageOffset: Int, data: List<String>) =
+ TransformablePage(
+ originalPageOffsets = intArrayOf(originalPageOffset),
+ data = data,
+ hintOriginalPageOffset = originalPageOffset,
+ hintOriginalIndices =
+ data.fold(mutableListOf()) { acc, s ->
+ acc.apply {
+ add(
+ when {
+ acc.isEmpty() -> 0
+ s.all { it.isUpperCase() } -> acc.last()
+ else -> acc.last() + 1
+ }
+ )
}
- )
- }
- }
-)
+ }
+ )
-private fun generateRefresh(
- data: List<String>,
- loadStates: CombinedLoadStates
-) = remoteRefresh(
- pages = listOf(transformablePage(0, data)),
- source = loadStates.source,
- mediator = loadStates.mediator ?: loadStates()
-)
+private fun generateRefresh(data: List<String>, loadStates: CombinedLoadStates) =
+ remoteRefresh(
+ pages = listOf(transformablePage(0, data)),
+ source = loadStates.source,
+ mediator = loadStates.mediator ?: loadStates()
+ )
private fun generatePrepend(
originalPageOffset: Int,
pages: List<List<String>>,
loadStates: CombinedLoadStates
-) = remotePrepend(
- pages = pages.map { data -> transformablePage(originalPageOffset, data) },
- placeholdersBefore = 0,
- source = loadStates.source,
- mediator = loadStates.mediator ?: loadStates()
-)
+) =
+ remotePrepend(
+ pages = pages.map { data -> transformablePage(originalPageOffset, data) },
+ placeholdersBefore = 0,
+ source = loadStates.source,
+ mediator = loadStates.mediator ?: loadStates()
+ )
private fun generateAppend(
originalPageOffset: Int,
pages: List<List<String>>,
loadStates: CombinedLoadStates
-) = remoteAppend(
- pages = pages.map { data -> transformablePage(originalPageOffset, data) },
- placeholdersAfter = 0,
- source = loadStates.source,
- mediator = loadStates.mediator ?: loadStates()
-)
+) =
+ remoteAppend(
+ pages = pages.map { data -> transformablePage(originalPageOffset, data) },
+ placeholdersAfter = 0,
+ source = loadStates.source,
+ mediator = loadStates.mediator ?: loadStates()
+ )
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleChannelFlowTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleChannelFlowTest.kt
index 156b6ec..2a60a0e 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleChannelFlowTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleChannelFlowTest.kt
@@ -47,58 +47,55 @@
class SimpleChannelFlowTest {
val testScope = TestScope(UnconfinedTestDispatcher())
- @Test
- fun basic_ChannelFlow() = basic(Impl.ChannelFlow)
+ @Test fun basic_ChannelFlow() = basic(Impl.ChannelFlow)
- @Test
- fun basic_SimpleChannelFlow() = basic(Impl.SimpleChannelFlow)
+ @Test fun basic_SimpleChannelFlow() = basic(Impl.SimpleChannelFlow)
private fun basic(impl: Impl) {
- val channelFlow = createFlow<Int>(impl) {
- send(1)
- send(2)
- }
+ val channelFlow =
+ createFlow<Int>(impl) {
+ send(1)
+ send(2)
+ }
testScope.runTest {
val items = channelFlow.toList()
assertThat(items).containsExactly(1, 2)
}
}
- @Test
- fun emitWithLaunch_ChannelFlow() = emitWithLaunch(Impl.ChannelFlow)
+ @Test fun emitWithLaunch_ChannelFlow() = emitWithLaunch(Impl.ChannelFlow)
- @Test
- fun emitWithLaunch_SimpleChannelFlow() = emitWithLaunch(Impl.SimpleChannelFlow)
+ @Test fun emitWithLaunch_SimpleChannelFlow() = emitWithLaunch(Impl.SimpleChannelFlow)
private fun emitWithLaunch(impl: Impl) {
- val channelFlow = createFlow<Int>(impl) {
- launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
- send(1)
- delay(100)
- send(2)
+ val channelFlow =
+ createFlow<Int>(impl) {
+ launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
+ send(1)
+ delay(100)
+ send(2)
+ }
+ send(3)
}
- send(3)
- }
testScope.runTest {
val items = channelFlow.toList()
assertThat(items).containsExactly(1, 3, 2).inOrder()
}
}
- @Test
- fun closedByCollector_ChannelFlow() = closedByCollector(Impl.ChannelFlow)
+ @Test fun closedByCollector_ChannelFlow() = closedByCollector(Impl.ChannelFlow)
- @Test
- fun closedByCollector_SimpleChannelFlow() = closedByCollector(Impl.SimpleChannelFlow)
+ @Test fun closedByCollector_SimpleChannelFlow() = closedByCollector(Impl.SimpleChannelFlow)
private fun closedByCollector(impl: Impl) {
val emittedValues = mutableListOf<Int>()
- val channelFlow = createFlow<Int>(impl) {
- repeat(10) {
- send(it)
- emittedValues.add(it)
+ val channelFlow =
+ createFlow<Int>(impl) {
+ repeat(10) {
+ send(it)
+ emittedValues.add(it)
+ }
}
- }
testScope.runTest {
assertThat(channelFlow.take(4).toList()).containsExactly(0, 1, 2, 3)
assertThat(emittedValues).containsExactlyElementsIn((0..9).toList())
@@ -114,12 +111,13 @@
private fun closedByCollector_noBuffer(impl: Impl) {
val emittedValues = mutableListOf<Int>()
- val channelFlow = createFlow<Int>(impl) {
- repeat(10) {
- send(it)
- emittedValues.add(it)
+ val channelFlow =
+ createFlow<Int>(impl) {
+ repeat(10) {
+ send(it)
+ emittedValues.add(it)
+ }
}
- }
testScope.runTest {
assertThat(channelFlow.buffer(0).take(4).toList()).containsExactly(0, 1, 2, 3)
when (impl) {
@@ -134,132 +132,118 @@
}
}
- @Test
- fun awaitClose_ChannelFlow() = awaitClose(Impl.ChannelFlow)
+ @Test fun awaitClose_ChannelFlow() = awaitClose(Impl.ChannelFlow)
- @Test
- fun awaitClose_SimpleChannelFlow() = awaitClose(Impl.SimpleChannelFlow)
+ @Test fun awaitClose_SimpleChannelFlow() = awaitClose(Impl.SimpleChannelFlow)
private fun awaitClose(impl: Impl) {
val lastDispatched = CompletableDeferred<Int>()
- val channelFlow = createFlow<Int>(impl) {
- var dispatched = -1
- launch {
- repeat(10) {
- dispatched = it
- send(it)
- delay(100)
+ val channelFlow =
+ createFlow<Int>(impl) {
+ var dispatched = -1
+ launch {
+ repeat(10) {
+ dispatched = it
+ send(it)
+ delay(100)
+ }
+ }
+ awaitClose {
+ assertThat(lastDispatched.isActive).isTrue()
+ lastDispatched.complete(dispatched)
}
}
- awaitClose {
- assertThat(lastDispatched.isActive).isTrue()
- lastDispatched.complete(dispatched)
- }
- }
testScope.runTest {
channelFlow.takeWhile { it < 3 }.toList()
assertThat(lastDispatched.await()).isEqualTo(3)
}
}
- @Test
- fun scopeGetsCancelled_ChannelFlow() = scopeGetsCancelled(Impl.ChannelFlow)
+ @Test fun scopeGetsCancelled_ChannelFlow() = scopeGetsCancelled(Impl.ChannelFlow)
- @Test
- fun scopeGetsCancelled_SimpleChannelFlow() = scopeGetsCancelled(Impl.SimpleChannelFlow)
+ @Test fun scopeGetsCancelled_SimpleChannelFlow() = scopeGetsCancelled(Impl.SimpleChannelFlow)
private fun scopeGetsCancelled(impl: Impl) {
var producerException: Throwable? = null
val dispatched = mutableListOf<Int>()
- val channelFlow = createFlow<Int>(impl) {
- try {
- repeat(20) {
- send(it)
- dispatched.add(it)
- delay(100)
+ val channelFlow =
+ createFlow<Int>(impl) {
+ try {
+ repeat(20) {
+ send(it)
+ dispatched.add(it)
+ delay(100)
+ }
+ } catch (th: Throwable) {
+ producerException = th
+ throw th
}
- } catch (th: Throwable) {
- producerException = th
- throw th
}
- }
testScope.runTest {
- val collection = launch {
- channelFlow.toList()
- }
+ val collection = launch { channelFlow.toList() }
advanceTimeBy(250)
collection.cancel(CancellationException("test message"))
collection.join()
assertThat(dispatched).containsExactly(0, 1, 2)
- assertThat(producerException).hasMessageThat()
- .contains("test message")
+ assertThat(producerException).hasMessageThat().contains("test message")
}
}
- @Test
- fun collectorThrows_ChannelFlow() = collectorThrows(Impl.ChannelFlow)
+ @Test fun collectorThrows_ChannelFlow() = collectorThrows(Impl.ChannelFlow)
- @Test
- fun collectorThrows_SimpleChannelFlow() = collectorThrows(Impl.SimpleChannelFlow)
+ @Test fun collectorThrows_SimpleChannelFlow() = collectorThrows(Impl.SimpleChannelFlow)
private fun collectorThrows(impl: Impl) {
var producerException: Throwable? = null
- val channelFlow = createFlow<Int>(impl) {
- try {
- send(1)
- delay(1000)
- fail("should not arrive here")
- } catch (th: Throwable) {
- producerException = th
- throw th
- }
- }
- testScope.runTest {
- runCatching {
- channelFlow.collect {
- throw IllegalArgumentException("expected failure")
- }
- }
- }
- assertThat(producerException).hasMessageThat()
- .contains("consumer had failed")
- }
-
- @Test
- fun upstreamThrows_ChannelFlow() = upstreamThrows(Impl.ChannelFlow)
-
- @Test
- fun upstreamThrows_SimpleChannelFlow() = upstreamThrows(Impl.SimpleChannelFlow)
-
- private fun upstreamThrows(impl: Impl) {
- var producerException: Throwable? = null
- val upstream = flow<Int> {
- emit(5)
- delay(100)
- emit(13)
- }
- val combinedFlow = upstream.flatMapLatest { upstreamValue ->
+ val channelFlow =
createFlow<Int>(impl) {
try {
- send(upstreamValue)
- delay(2000)
- send(upstreamValue * 2)
+ send(1)
+ delay(1000)
+ fail("should not arrive here")
} catch (th: Throwable) {
- if (producerException == null) {
- producerException = th
- }
+ producerException = th
throw th
}
}
- }
testScope.runTest {
- assertThat(
- combinedFlow.toList()
- ).containsExactly(
- 5, 13, 26
- )
+ runCatching {
+ channelFlow.collect { throw IllegalArgumentException("expected failure") }
+ }
}
- assertThat(producerException).hasMessageThat()
+ assertThat(producerException).hasMessageThat().contains("consumer had failed")
+ }
+
+ @Test fun upstreamThrows_ChannelFlow() = upstreamThrows(Impl.ChannelFlow)
+
+ @Test fun upstreamThrows_SimpleChannelFlow() = upstreamThrows(Impl.SimpleChannelFlow)
+
+ private fun upstreamThrows(impl: Impl) {
+ var producerException: Throwable? = null
+ val upstream =
+ flow<Int> {
+ emit(5)
+ delay(100)
+ emit(13)
+ }
+ val combinedFlow =
+ upstream.flatMapLatest { upstreamValue ->
+ createFlow<Int>(impl) {
+ try {
+ send(upstreamValue)
+ delay(2000)
+ send(upstreamValue * 2)
+ } catch (th: Throwable) {
+ if (producerException == null) {
+ producerException = th
+ }
+ throw th
+ }
+ }
+ }
+ testScope.runTest { assertThat(combinedFlow.toList()).containsExactly(5, 13, 26) }
+ assertThat(producerException)
+ .hasMessageThat()
.contains("Child of the scoped flow was cancelled")
}
@@ -272,14 +256,13 @@
cancelingChannelClosesTheFlow(Impl.SimpleChannelFlow)
private fun cancelingChannelClosesTheFlow(impl: Impl) {
- val flow = createFlow<Int>(impl) {
- send(1)
- close()
- awaitCancellation()
- }
- testScope.runTest {
- assertThat(flow.toList()).containsExactly(1)
- }
+ val flow =
+ createFlow<Int>(impl) {
+ send(1)
+ close()
+ awaitCancellation()
+ }
+ testScope.runTest { assertThat(flow.toList()).containsExactly(1) }
}
private fun <T> createFlow(
@@ -287,12 +270,9 @@
block: suspend TestProducerScope<T>.() -> Unit
): Flow<T> {
return when (impl) {
- Impl.ChannelFlow -> channelFlow {
- ChannelFlowTestProducerScope(this).block()
- }
- Impl.SimpleChannelFlow -> simpleChannelFlow {
- SimpleChannelFlowTestProducerScope(this).block()
- }
+ Impl.ChannelFlow -> channelFlow { ChannelFlowTestProducerScope(this).block() }
+ Impl.SimpleChannelFlow ->
+ simpleChannelFlow { SimpleChannelFlowTestProducerScope(this).block() }
}
}
@@ -310,9 +290,8 @@
}
}
- class ChannelFlowTestProducerScope<T>(
- private val delegate: ProducerScope<T>
- ) : TestProducerScope<T>, CoroutineScope by delegate, SendChannel<T> by delegate {
+ class ChannelFlowTestProducerScope<T>(private val delegate: ProducerScope<T>) :
+ TestProducerScope<T>, CoroutineScope by delegate, SendChannel<T> by delegate {
override suspend fun awaitClose(block: () -> Unit) {
delegate.awaitClose(block)
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleTransformLatestTest.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleTransformLatestTest.kt
index a1bf8a3..c26ea92 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleTransformLatestTest.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/SimpleTransformLatestTest.kt
@@ -34,48 +34,54 @@
class SimpleTransformLatestTest {
private val testScope = TestScope()
- @Test
- fun delayed_TransformLatest() = delayed(Impl.TransformLatest)
+ @Test fun delayed_TransformLatest() = delayed(Impl.TransformLatest)
- @Test
- fun delayed_SimpleTransformLatest() = delayed(Impl.SimpleTransformLatest)
+ @Test fun delayed_SimpleTransformLatest() = delayed(Impl.SimpleTransformLatest)
- private fun delayed(impl: Impl) = testScope.runTest {
- assertThat(
- flowOf(1, 2, 3)
- .onEach { delay(100) }
- .testTransformLatest<Int, String>(impl) { value ->
- repeat(3) {
- emit("$value - $it")
- delay(75)
- }
- }.toList()
- ).containsExactly(
- "1 - 0", "1 - 1",
- "2 - 0", "2 - 1",
- "3 - 0", "3 - 1", "3 - 2"
- ).inOrder()
- }
+ private fun delayed(impl: Impl) =
+ testScope.runTest {
+ assertThat(
+ flowOf(1, 2, 3)
+ .onEach { delay(100) }
+ .testTransformLatest<Int, String>(impl) { value ->
+ repeat(3) {
+ emit("$value - $it")
+ delay(75)
+ }
+ }
+ .toList()
+ )
+ .containsExactly("1 - 0", "1 - 1", "2 - 0", "2 - 1", "3 - 0", "3 - 1", "3 - 2")
+ .inOrder()
+ }
- @Test
- fun allValues_TransformLatest() = allValues(Impl.TransformLatest)
+ @Test fun allValues_TransformLatest() = allValues(Impl.TransformLatest)
- @Test
- fun allValues_SimpleTransformLatest() = allValues(Impl.SimpleTransformLatest)
+ @Test fun allValues_SimpleTransformLatest() = allValues(Impl.SimpleTransformLatest)
- private fun allValues(impl: Impl) = testScope.runTest {
- assertThat(
- flowOf(1, 2, 3)
- .onEach { delay(1) }
- .testTransformLatest<Int, String>(impl) { value ->
- repeat(3) { emit("$value - $it") }
- }.toList()
- ).containsExactly(
- "1 - 0", "1 - 1", "1 - 2",
- "2 - 0", "2 - 1", "2 - 2",
- "3 - 0", "3 - 1", "3 - 2"
- ).inOrder()
- }
+ private fun allValues(impl: Impl) =
+ testScope.runTest {
+ assertThat(
+ flowOf(1, 2, 3)
+ .onEach { delay(1) }
+ .testTransformLatest<Int, String>(impl) { value ->
+ repeat(3) { emit("$value - $it") }
+ }
+ .toList()
+ )
+ .containsExactly(
+ "1 - 0",
+ "1 - 1",
+ "1 - 2",
+ "2 - 0",
+ "2 - 1",
+ "2 - 2",
+ "3 - 0",
+ "3 - 1",
+ "3 - 2"
+ )
+ .inOrder()
+ }
@Test
fun reusePreviousCollector_TransformLatest() = reusePreviousCollector(Impl.TransformLatest)
@@ -84,31 +90,32 @@
fun reusePreviousCollector_SimpleTransformLatest() =
reusePreviousCollector(Impl.SimpleTransformLatest)
- private fun reusePreviousCollector(impl: Impl) = testScope.runTest {
- var prevCollector: FlowCollector<String>? = null
- assertThat(
- flowOf(1, 2, 3)
- .onEach { delay(1) }
- .testTransformLatest<Int, String>(impl) { value ->
- if (prevCollector == null) {
- prevCollector = this
- awaitCancellation()
- } else {
- prevCollector?.emit("x-$value")
- }
- }.toList()
- ).containsExactly("x-2", "x-3")
- }
+ private fun reusePreviousCollector(impl: Impl) =
+ testScope.runTest {
+ var prevCollector: FlowCollector<String>? = null
+ assertThat(
+ flowOf(1, 2, 3)
+ .onEach { delay(1) }
+ .testTransformLatest<Int, String>(impl) { value ->
+ if (prevCollector == null) {
+ prevCollector = this
+ awaitCancellation()
+ } else {
+ prevCollector?.emit("x-$value")
+ }
+ }
+ .toList()
+ )
+ .containsExactly("x-2", "x-3")
+ }
private fun <T, R> Flow<T>.testTransformLatest(
impl: Impl,
transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> {
return when (impl) {
- Impl.TransformLatest ->
- [email protected](transform)
- Impl.SimpleTransformLatest ->
- [email protected](transform)
+ Impl.TransformLatest -> [email protected](transform)
+ Impl.SimpleTransformLatest -> [email protected](transform)
}
}
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestPagingSourceExt.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestPagingSourceExt.kt
index 6b504895..20beb73 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestPagingSourceExt.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestPagingSourceExt.kt
@@ -19,59 +19,53 @@
import androidx.paging.LoadState.NotLoading
import androidx.paging.TestPagingSource.Companion.ITEMS
-internal fun createRefresh(
- range: IntRange,
- combinedLoadStates: CombinedLoadStates
-) = PageEvent.Insert.Refresh(
- pages = pages(0, range),
- placeholdersBefore = range.first.coerceAtLeast(0),
- placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
- sourceLoadStates = combinedLoadStates.source,
- mediatorLoadStates = combinedLoadStates.mediator,
-)
+internal fun createRefresh(range: IntRange, combinedLoadStates: CombinedLoadStates) =
+ PageEvent.Insert.Refresh(
+ pages = pages(0, range),
+ placeholdersBefore = range.first.coerceAtLeast(0),
+ placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
+ sourceLoadStates = combinedLoadStates.source,
+ mediatorLoadStates = combinedLoadStates.mediator,
+ )
internal fun createRefresh(
range: IntRange,
startState: LoadState = NotLoading.Incomplete,
endState: LoadState = NotLoading.Incomplete
-) = PageEvent.Insert.Refresh(
- pages = pages(0, range),
- placeholdersBefore = range.first.coerceAtLeast(0),
- placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
- sourceLoadStates = loadStates(prepend = startState, append = endState),
- mediatorLoadStates = null,
-)
+) =
+ PageEvent.Insert.Refresh(
+ pages = pages(0, range),
+ placeholdersBefore = range.first.coerceAtLeast(0),
+ placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
+ sourceLoadStates = loadStates(prepend = startState, append = endState),
+ mediatorLoadStates = null,
+ )
internal fun createPrepend(
pageOffset: Int,
range: IntRange,
startState: LoadState = NotLoading.Incomplete,
endState: LoadState = NotLoading.Incomplete
-) = PageEvent.Insert.Prepend(
- pages = pages(pageOffset, range),
- placeholdersBefore = range.first.coerceAtLeast(0),
- sourceLoadStates = loadStates(prepend = startState, append = endState),
- mediatorLoadStates = null,
-)
+) =
+ PageEvent.Insert.Prepend(
+ pages = pages(pageOffset, range),
+ placeholdersBefore = range.first.coerceAtLeast(0),
+ sourceLoadStates = loadStates(prepend = startState, append = endState),
+ mediatorLoadStates = null,
+ )
internal fun createAppend(
pageOffset: Int,
range: IntRange,
startState: LoadState = NotLoading.Incomplete,
endState: LoadState = NotLoading.Incomplete
-) = PageEvent.Insert.Append(
- pages = pages(pageOffset, range),
- placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
- sourceLoadStates = loadStates(prepend = startState, append = endState),
- mediatorLoadStates = null,
-)
-
-private fun pages(
- pageOffset: Int,
- range: IntRange
-) = listOf(
- TransformablePage(
- originalPageOffset = pageOffset,
- data = ITEMS.slice(range)
+) =
+ PageEvent.Insert.Append(
+ pages = pages(pageOffset, range),
+ placeholdersAfter = (ITEMS.size - range.last - 1).coerceAtLeast(0),
+ sourceLoadStates = loadStates(prepend = startState, append = endState),
+ mediatorLoadStates = null,
)
-)
+
+private fun pages(pageOffset: Int, range: IntRange) =
+ listOf(TransformablePage(originalPageOffset = pageOffset, data = ITEMS.slice(range)))
diff --git a/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestUtils.kt b/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestUtils.kt
index 32ddbdf..cdff222 100644
--- a/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestUtils.kt
+++ b/paging/paging-common/src/commonTest/kotlin/androidx/paging/TestUtils.kt
@@ -27,64 +27,71 @@
placeholdersBefore: Int,
placeholdersAfter: Int,
source: LoadStates
-) = when (loadType) {
- REFRESH -> PageEvent.Insert.Refresh(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = source,
- mediatorLoadStates = null
- )
- PREPEND -> PageEvent.Insert.Prepend(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- sourceLoadStates = source,
- mediatorLoadStates = null,
- )
- APPEND -> PageEvent.Insert.Append(
- pages = pages,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = source,
- mediatorLoadStates = null
- )
-}
+) =
+ when (loadType) {
+ REFRESH ->
+ PageEvent.Insert.Refresh(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = source,
+ mediatorLoadStates = null
+ )
+ PREPEND ->
+ PageEvent.Insert.Prepend(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ sourceLoadStates = source,
+ mediatorLoadStates = null,
+ )
+ APPEND ->
+ PageEvent.Insert.Append(
+ pages = pages,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = source,
+ mediatorLoadStates = null
+ )
+ }
internal fun <T : Any> localRefresh(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
placeholdersBefore: Int = 0,
placeholdersAfter: Int = 0,
source: LoadStates = loadStates()
-) = localInsert(
- loadType = REFRESH,
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- source = source
-)
+) =
+ localInsert(
+ loadType = REFRESH,
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ source = source
+ )
internal fun <T : Any> localPrepend(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
placeholdersBefore: Int = 0,
source: LoadStates = loadStates()
-) = localInsert(
- loadType = PREPEND,
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = -1,
- source = source
-)
+) =
+ localInsert(
+ loadType = PREPEND,
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = -1,
+ source = source
+ )
internal fun <T : Any> localAppend(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
placeholdersAfter: Int = 0,
source: LoadStates = loadStates()
-) = localInsert(
- loadType = APPEND,
- pages = pages,
- placeholdersBefore = -1,
- placeholdersAfter = placeholdersAfter,
- source = source
-)
+) =
+ localInsert(
+ loadType = APPEND,
+ pages = pages,
+ placeholdersBefore = -1,
+ placeholdersAfter = placeholdersAfter,
+ source = source
+ )
private fun <T : Any> remoteInsert(
loadType: LoadType,
@@ -93,27 +100,31 @@
placeholdersAfter: Int,
source: LoadStates,
mediator: LoadStates,
-) = when (loadType) {
- REFRESH -> PageEvent.Insert.Refresh(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = source,
- mediatorLoadStates = mediator,
- )
- PREPEND -> PageEvent.Insert.Prepend(
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- sourceLoadStates = source,
- mediatorLoadStates = mediator,
- )
- APPEND -> PageEvent.Insert.Append(
- pages = pages,
- placeholdersAfter = placeholdersAfter,
- sourceLoadStates = source,
- mediatorLoadStates = mediator
- )
-}
+) =
+ when (loadType) {
+ REFRESH ->
+ PageEvent.Insert.Refresh(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = source,
+ mediatorLoadStates = mediator,
+ )
+ PREPEND ->
+ PageEvent.Insert.Prepend(
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ sourceLoadStates = source,
+ mediatorLoadStates = mediator,
+ )
+ APPEND ->
+ PageEvent.Insert.Append(
+ pages = pages,
+ placeholdersAfter = placeholdersAfter,
+ sourceLoadStates = source,
+ mediatorLoadStates = mediator
+ )
+ }
internal fun <T : Any> remoteRefresh(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
@@ -121,55 +132,58 @@
placeholdersAfter: Int = 0,
source: LoadStates = loadStates(),
mediator: LoadStates = loadStates(),
-) = remoteInsert(
- loadType = REFRESH,
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = placeholdersAfter,
- source = source,
- mediator = mediator,
-)
+) =
+ remoteInsert(
+ loadType = REFRESH,
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = placeholdersAfter,
+ source = source,
+ mediator = mediator,
+ )
internal fun <T : Any> remotePrepend(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
placeholdersBefore: Int = 0,
source: LoadStates = loadStates(),
mediator: LoadStates = loadStates(),
-) = remoteInsert(
- loadType = PREPEND,
- pages = pages,
- placeholdersBefore = placeholdersBefore,
- placeholdersAfter = -1,
- source = source,
- mediator = mediator,
-)
+) =
+ remoteInsert(
+ loadType = PREPEND,
+ pages = pages,
+ placeholdersBefore = placeholdersBefore,
+ placeholdersAfter = -1,
+ source = source,
+ mediator = mediator,
+ )
internal fun <T : Any> remoteAppend(
pages: List<TransformablePage<T>> = listOf(emptyPage()),
placeholdersAfter: Int = 0,
source: LoadStates = loadStates(),
mediator: LoadStates = loadStates(),
-) = remoteInsert(
- loadType = APPEND,
- pages = pages,
- placeholdersBefore = -1,
- placeholdersAfter = placeholdersAfter,
- source = source,
- mediator = mediator,
-)
+) =
+ remoteInsert(
+ loadType = APPEND,
+ pages = pages,
+ placeholdersBefore = -1,
+ placeholdersAfter = placeholdersAfter,
+ source = source,
+ mediator = mediator,
+ )
internal fun <T : Any> localLoadStateUpdate(
refreshLocal: LoadState = LoadState.NotLoading.Incomplete,
prependLocal: LoadState = LoadState.NotLoading.Incomplete,
appendLocal: LoadState = LoadState.NotLoading.Incomplete,
-) = LoadStateUpdate<T>(
- source = loadStates(refreshLocal, prependLocal, appendLocal),
- mediator = null
-)
+) =
+ LoadStateUpdate<T>(
+ source = loadStates(refreshLocal, prependLocal, appendLocal),
+ mediator = null
+ )
-internal fun <T : Any> localLoadStateUpdate(
- source: LoadStates = LoadStates.IDLE
-) = LoadStateUpdate<T>(source, null)
+internal fun <T : Any> localLoadStateUpdate(source: LoadStates = LoadStates.IDLE) =
+ LoadStateUpdate<T>(source, null)
internal fun <T : Any> remoteLoadStateUpdate(
source: LoadStates = LoadStates.IDLE,
@@ -183,10 +197,11 @@
refreshRemote: LoadState = LoadState.NotLoading.Incomplete,
prependRemote: LoadState = LoadState.NotLoading.Incomplete,
appendRemote: LoadState = LoadState.NotLoading.Incomplete,
-) = LoadStateUpdate<T>(
- source = loadStates(refreshLocal, prependLocal, appendLocal),
- mediator = loadStates(refreshRemote, prependRemote, appendRemote),
-)
+) =
+ LoadStateUpdate<T>(
+ source = loadStates(refreshLocal, prependLocal, appendLocal),
+ mediator = loadStates(refreshRemote, prependRemote, appendRemote),
+ )
private fun <T : Any> emptyPage() = TransformablePage<T>(0, emptyList())
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ContiguousPagedListTest.kt
index 0d0c175..2b30e80 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -61,15 +61,14 @@
* and alignment restrictions. These tests were written before positional+contiguous enforced
* these behaviors.
*/
- private inner class TestPagingSource(
- val listData: List<Item> = ITEMS
- ) : PagingSource<Int, Item>() {
+ private inner class TestPagingSource(val listData: List<Item> = ITEMS) :
+ PagingSource<Int, Item>() {
var invalidData = false
override fun getRefreshKey(state: PagingState<Int, Item>): Int? {
- return state.anchorPosition
- ?.let { anchorPosition -> state.closestItemToPosition(anchorPosition)?.pos }
- ?: 0
+ return state.anchorPosition?.let { anchorPosition ->
+ state.closestItemToPosition(anchorPosition)?.pos
+ } ?: 0
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
@@ -97,24 +96,27 @@
}
return when {
result == null -> LoadResult.Error(EXCEPTION)
- placeholdersEnabled -> Page(
- data = result,
- prevKey = result.firstOrNull()?.pos,
- nextKey = result.lastOrNull()?.pos,
- itemsBefore = start,
- itemsAfter = listData.size - result.size - start
- )
- else -> Page(
- data = result,
- prevKey = result.firstOrNull()?.pos,
- nextKey = result.lastOrNull()?.pos
- )
+ placeholdersEnabled ->
+ Page(
+ data = result,
+ prevKey = result.firstOrNull()?.pos,
+ nextKey = result.lastOrNull()?.pos,
+ itemsBefore = start,
+ itemsAfter = listData.size - result.size - start
+ )
+ else ->
+ Page(
+ data = result,
+ prevKey = result.firstOrNull()?.pos,
+ nextKey = result.lastOrNull()?.pos
+ )
}
}
private fun loadAfter(params: LoadParams<Int>): LoadResult<Int, Item> {
- val result = getClampedRange(params.key!! + 1, params.key!! + 1 + params.loadSize)
- ?: return LoadResult.Error(EXCEPTION)
+ val result =
+ getClampedRange(params.key!! + 1, params.key!! + 1 + params.loadSize)
+ ?: return LoadResult.Error(EXCEPTION)
if (invalidData) {
invalidData = false
return LoadResult.Invalid()
@@ -127,8 +129,9 @@
}
private fun loadBefore(params: LoadParams<Int>): LoadResult<Int, Item> {
- val result = getClampedRange(params.key!! - params.loadSize, params.key!!)
- ?: return LoadResult.Error(EXCEPTION)
+ val result =
+ getClampedRange(params.key!! - params.loadSize, params.key!!)
+ ?: return LoadResult.Error(EXCEPTION)
if (invalidData) {
invalidData = false
return LoadResult.Invalid()
@@ -202,13 +205,14 @@
initialKey: Int,
loadSize: Int
): Page<Int, Item> {
- val result = load(
- PagingSource.LoadParams.Refresh(
- initialKey,
- loadSize,
- placeholdersEnabled,
+ val result =
+ load(
+ PagingSource.LoadParams.Refresh(
+ initialKey,
+ loadSize,
+ placeholdersEnabled,
+ )
)
- )
return result as? Page ?: throw RuntimeException("Unexpected load failure")
}
@@ -223,18 +227,16 @@
maxSize: Int = Config.MAX_SIZE_UNBOUNDED,
pagingSource: PagingSource<Int, Item> = TestPagingSource(listData)
): PagedList<Item> {
- val initialPage = pagingSource.getInitialPage(
- initialPosition ?: 0,
- initLoadSize
- )
+ val initialPage = pagingSource.getInitialPage(initialPosition ?: 0, initLoadSize)
- val config = Config.Builder()
- .setPageSize(pageSize)
- .setInitialLoadSizeHint(initLoadSize)
- .setPrefetchDistance(prefetchDistance)
- .setMaxSize(maxSize)
- .setEnablePlaceholders(placeholdersEnabled)
- .build()
+ val config =
+ Config.Builder()
+ .setPageSize(pageSize)
+ .setInitialLoadSizeHint(initLoadSize)
+ .setPrefetchDistance(prefetchDistance)
+ .setMaxSize(maxSize)
+ .setEnablePlaceholders(placeholdersEnabled)
+ .build()
return PagedList.Builder(pagingSource, initialPage, config)
.setBoundaryCallback(boundaryCallback)
@@ -260,8 +262,7 @@
@Suppress("DEPRECATION")
val pagedListWithDataSource = PagedList.Builder(ItemDataSource(), 10).build()
- @Suppress("DEPRECATION")
- assertTrue(pagedListWithDataSource.dataSource is ItemDataSource)
+ @Suppress("DEPRECATION") assertTrue(pagedListWithDataSource.dataSource is ItemDataSource)
// snapshot keeps same DataSource
@Suppress("DEPRECATION")
@@ -296,11 +297,7 @@
pagedList.loadAround(pagedList.size)
}
- private fun verifyCallback(
- callback: Callback,
- countedPosition: Int,
- uncountedPosition: Int
- ) {
+ private fun verifyCallback(callback: Callback, countedPosition: Int, uncountedPosition: Int) {
if (placeholdersEnabled) {
verify(callback).onChanged(countedPosition, 20)
} else {
@@ -445,12 +442,13 @@
@Test
fun prefetchFront() = runTest {
- val pagedList = createCountedPagedList(
- initialPosition = 50,
- pageSize = 20,
- initLoadSize = 20,
- prefetchDistance = 1
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 50,
+ pageSize = 20,
+ initLoadSize = 20,
+ prefetchDistance = 1
+ )
verifyRange(40, 20, pagedList)
// access adjacent to front, shouldn't trigger prefetch
@@ -466,12 +464,13 @@
@Test
fun prefetchEnd() = runTest {
- val pagedList = createCountedPagedList(
- initialPosition = 50,
- pageSize = 20,
- initLoadSize = 20,
- prefetchDistance = 1
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 50,
+ pageSize = 20,
+ initLoadSize = 20,
+ prefetchDistance = 1
+ )
verifyRange(40, 20, pagedList)
// access adjacent from end, shouldn't trigger prefetch
@@ -487,13 +486,14 @@
@Test
fun pageDropEnd() = runTest {
- val pagedList = createCountedPagedList(
- initialPosition = 0,
- pageSize = 20,
- initLoadSize = 20,
- prefetchDistance = 1,
- maxSize = 70
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 0,
+ pageSize = 20,
+ initLoadSize = 20,
+ prefetchDistance = 1,
+ maxSize = 70
+ )
val callback = mock<Callback>()
pagedList.addWeakCallback(callback)
verifyRange(0, 20, pagedList)
@@ -524,13 +524,14 @@
@Test
fun pageDropFront() = runTest {
- val pagedList = createCountedPagedList(
- initialPosition = 90,
- pageSize = 20,
- initLoadSize = 20,
- prefetchDistance = 1,
- maxSize = 70
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 90,
+ pageSize = 20,
+ initLoadSize = 20,
+ prefetchDistance = 1,
+ maxSize = 70
+ )
val callback = mock<Callback>()
pagedList.addWeakCallback(callback)
verifyRange(80, 20, pagedList)
@@ -564,13 +565,14 @@
@Test
fun pageDropCancelPrepend() = runTest {
// verify that, based on most recent load position, a prepend can be dropped as it arrives
- val pagedList = createCountedPagedList(
- initialPosition = 2,
- pageSize = 1,
- initLoadSize = 1,
- prefetchDistance = 1,
- maxSize = 3
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 2,
+ pageSize = 1,
+ initLoadSize = 1,
+ prefetchDistance = 1,
+ maxSize = 3
+ )
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
@@ -607,13 +609,14 @@
@Test
fun pageDropCancelAppend() = runTest {
// verify that, based on most recent load position, an append can be dropped as it arrives
- val pagedList = createCountedPagedList(
- initialPosition = 2,
- pageSize = 1,
- initLoadSize = 1,
- prefetchDistance = 1,
- maxSize = 3
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 2,
+ pageSize = 1,
+ initLoadSize = 1,
+ prefetchDistance = 1,
+ maxSize = 3
+ )
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
@@ -652,12 +655,7 @@
pagedList.withLoadStateCapture(APPEND) { states ->
// No loading going on currently
assertEquals(
- listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = false))),
states.getAllAndClear()
)
verifyRange(0, 40, pagedList)
@@ -665,21 +663,13 @@
// trigger load
pagedList.loadAround(35)
mainThread.executeAll()
- assertEquals(
- listOf(StateChange(APPEND, Loading)),
- states.getAllAndClear()
- )
+ assertEquals(listOf(StateChange(APPEND, Loading)), states.getAllAndClear())
verifyRange(0, 40, pagedList)
// load finishes
drain()
assertEquals(
- listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = false))),
states.getAllAndClear()
)
verifyRange(0, 60, pagedList)
@@ -689,37 +679,23 @@
// trigger load which will error
pagedList.loadAround(55)
mainThread.executeAll()
- assertEquals(
- listOf(StateChange(APPEND, Loading)),
- states.getAllAndClear()
- )
+ assertEquals(listOf(StateChange(APPEND, Loading)), states.getAllAndClear())
verifyRange(0, 60, pagedList)
// load now in error state
drain()
- assertEquals(
- listOf(StateChange(APPEND, Error(EXCEPTION))),
- states.getAllAndClear()
- )
+ assertEquals(listOf(StateChange(APPEND, Error(EXCEPTION))), states.getAllAndClear())
verifyRange(0, 60, pagedList)
// retry
pagedList.retry()
mainThread.executeAll()
- assertEquals(
- listOf(StateChange(APPEND, Loading)),
- states.getAllAndClear()
- )
+ assertEquals(listOf(StateChange(APPEND, Loading)), states.getAllAndClear())
// load finishes
drain()
assertEquals(
- listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = false))),
states.getAllAndClear()
)
verifyRange(0, 80, pagedList)
@@ -729,13 +705,14 @@
@Test
fun pageDropCancelPrependError() = runTest {
// verify a prepend in error state can be dropped
- val pagedList = createCountedPagedList(
- initialPosition = 2,
- pageSize = 1,
- initLoadSize = 1,
- prefetchDistance = 1,
- maxSize = 3
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 2,
+ pageSize = 1,
+ initLoadSize = 1,
+ prefetchDistance = 1,
+ maxSize = 3
+ )
pagedList.withLoadStateCapture(PREPEND) { states ->
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
@@ -743,15 +720,9 @@
verifyRange(1, 3, pagedList)
assertEquals(
listOf(
- StateChange(
- PREPEND,
- NotLoading(endOfPaginationReached = false)
- ),
+ StateChange(PREPEND, NotLoading(endOfPaginationReached = false)),
StateChange(PREPEND, Loading),
- StateChange(
- PREPEND,
- NotLoading(endOfPaginationReached = false)
- )
+ StateChange(PREPEND, NotLoading(endOfPaginationReached = false))
),
states.getAllAndClear()
)
@@ -762,23 +733,16 @@
drain()
verifyRange(1, 3, pagedList)
assertEquals(
- listOf(
- StateChange(PREPEND, Loading),
- StateChange(PREPEND, Error(EXCEPTION))
- ),
+ listOf(StateChange(PREPEND, Loading), StateChange(PREPEND, Error(EXCEPTION))),
states.getAllAndClear()
)
- // but without that failure being retried, access near end of list, which drops the error
+ // but without that failure being retried, access near end of list, which drops the
+ // error
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
drain()
assertEquals(
- listOf(
- StateChange(
- PREPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
+ listOf(StateChange(PREPEND, NotLoading(endOfPaginationReached = false))),
states.getAllAndClear()
)
verifyRange(2, 3, pagedList)
@@ -788,13 +752,14 @@
@Test
fun pageDropCancelAppendError() = runTest {
// verify an append in error state can be dropped
- val pagedList = createCountedPagedList(
- initialPosition = 2,
- pageSize = 1,
- initLoadSize = 1,
- prefetchDistance = 1,
- maxSize = 3
- )
+ val pagedList =
+ createCountedPagedList(
+ initialPosition = 2,
+ pageSize = 1,
+ initLoadSize = 1,
+ prefetchDistance = 1,
+ maxSize = 3
+ )
pagedList.withLoadStateCapture(APPEND) { states ->
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
@@ -802,15 +767,9 @@
verifyRange(1, 3, pagedList)
assertEquals(
listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ),
+ StateChange(APPEND, NotLoading(endOfPaginationReached = false)),
StateChange(APPEND, Loading),
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
+ StateChange(APPEND, NotLoading(endOfPaginationReached = false))
),
states.getAllAndClear()
)
@@ -821,23 +780,16 @@
drain()
verifyRange(1, 3, pagedList)
assertEquals(
- listOf(
- StateChange(APPEND, Loading),
- StateChange(APPEND, Error(EXCEPTION))
- ),
+ listOf(StateChange(APPEND, Loading), StateChange(APPEND, Error(EXCEPTION))),
states.getAllAndClear()
)
- // but without that failure being retried, access near start of list, which drops the error
+ // but without that failure being retried, access near start of list, which drops the
+ // error
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
drain()
assertEquals(
- listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- )
- ),
+ listOf(StateChange(APPEND, NotLoading(endOfPaginationReached = false))),
states.getAllAndClear()
)
verifyRange(0, 3, pagedList)
@@ -854,10 +806,7 @@
drain()
assertEquals(
listOf(
- StateChange(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ),
+ StateChange(APPEND, NotLoading(endOfPaginationReached = false)),
StateChange(APPEND, Loading),
StateChange(APPEND, Error(EXCEPTION))
),
@@ -869,12 +818,8 @@
@Test
fun distantPrefetch() = runTest {
- val pagedList = createCountedPagedList(
- 0,
- initLoadSize = 10,
- pageSize = 10,
- prefetchDistance = 30
- )
+ val pagedList =
+ createCountedPagedList(0, initLoadSize = 10, pageSize = 10, prefetchDistance = 30)
val callback = mock<Callback>()
pagedList.addWeakCallback(callback)
@@ -916,8 +861,7 @@
// and verify the snapshot hasn't received them
assertEquals(snapshotCopy, snapshot)
val callback = mock<Callback>()
- @Suppress("DEPRECATION")
- pagedList.addWeakCallback(snapshot, callback)
+ @Suppress("DEPRECATION") pagedList.addWeakCallback(snapshot, callback)
verify(callback).onChanged(0, snapshot.size)
if (!placeholdersEnabled) {
verify(callback).onInserted(60, 20)
@@ -946,8 +890,7 @@
assertEquals(snapshotCopy, snapshot)
val callback = mock<Callback>()
- @Suppress("DEPRECATION")
- pagedList.addWeakCallback(snapshot, callback)
+ @Suppress("DEPRECATION") pagedList.addWeakCallback(snapshot, callback)
verify(callback).onChanged(0, snapshot.size)
if (!placeholdersEnabled) {
// deprecated snapshot compare dispatches as if inserts occur at the end
@@ -958,11 +901,8 @@
@Test
fun initialLoad_lastKey() = runTest {
- val pagedList = createCountedPagedList(
- initialPosition = 4,
- initLoadSize = 20,
- pageSize = 10
- )
+ val pagedList =
+ createCountedPagedList(initialPosition = 4, initLoadSize = 20, pageSize = 10)
verifyRange(0, 20, pagedList)
// lastKey should return result of PagingSource.getRefreshKey after loading 20 items.
@@ -986,8 +926,7 @@
// verify that adding callback notifies naive "everything changed" when snapshot passed
var callback = mock<Callback>()
- @Suppress("DEPRECATION")
- pagedList.addWeakCallback(initSnapshot, callback)
+ @Suppress("DEPRECATION") pagedList.addWeakCallback(initSnapshot, callback)
verify(callback).onChanged(0, pagedList.size)
verifyNoMoreInteractions(callback)
pagedList.removeWeakCallback(callback)
@@ -998,8 +937,7 @@
// verify that adding callback notifies insert going from empty -> content
callback = mock()
- @Suppress("DEPRECATION")
- pagedList.addWeakCallback(initSnapshot, callback)
+ @Suppress("DEPRECATION") pagedList.addWeakCallback(initSnapshot, callback)
verify(callback).onChanged(0, initSnapshot.size)
if (!placeholdersEnabled) {
verify(callback).onInserted(40, 20)
@@ -1009,12 +947,9 @@
@Test
fun boundaryCallback_empty() = runTest {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<BoundaryCallback<Item>>()
- val pagedList = createCountedPagedList(
- 0,
- listData = ArrayList(), boundaryCallback = boundaryCallback
- )
+ @Suppress("UNCHECKED_CAST") val boundaryCallback = mock<BoundaryCallback<Item>>()
+ val pagedList =
+ createCountedPagedList(0, listData = ArrayList(), boundaryCallback = boundaryCallback)
assertEquals(0, pagedList.size)
// nothing yet
@@ -1030,12 +965,14 @@
fun boundaryCallback_singleInitialLoad() = runTest {
val shortList = ITEMS.subList(0, 4)
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<BoundaryCallback<Item>>()
- val pagedList = createCountedPagedList(
- 0, listData = shortList,
- initLoadSize = shortList.size, boundaryCallback = boundaryCallback
- )
+ @Suppress("UNCHECKED_CAST") val boundaryCallback = mock<BoundaryCallback<Item>>()
+ val pagedList =
+ createCountedPagedList(
+ 0,
+ listData = shortList,
+ initLoadSize = shortList.size,
+ boundaryCallback = boundaryCallback
+ )
assertEquals(shortList.size, pagedList.size)
// nothing yet
@@ -1052,12 +989,14 @@
@Test
fun boundaryCallback_delayed() = runTest {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<BoundaryCallback<Item>>()
- val pagedList = createCountedPagedList(
- 90,
- initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback
- )
+ @Suppress("UNCHECKED_CAST") val boundaryCallback = mock<BoundaryCallback<Item>>()
+ val pagedList =
+ createCountedPagedList(
+ 90,
+ initLoadSize = 20,
+ prefetchDistance = 5,
+ boundaryCallback = boundaryCallback
+ )
verifyRange(80, 20, pagedList)
// nothing yet
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index 65e1e83..0c67dd3 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -181,11 +181,11 @@
@Test
fun loadBefore() {
val dataSource = ItemDataSource()
- @Suppress("UNCHECKED_CAST")
- val callback = mock<ItemKeyedDataSource.LoadCallback<Item>>()
+ @Suppress("UNCHECKED_CAST") val callback = mock<ItemKeyedDataSource.LoadCallback<Item>>()
dataSource.loadBefore(
- ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback
+ ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5),
+ callback
)
@Suppress("UNCHECKED_CAST")
@@ -246,9 +246,8 @@
}
private fun findFirstIndexAfter(key: Key): Int {
- return items.indices.firstOrNull {
- KEY_COMPARATOR.compare(key, getKey(items[it])) < 0
- } ?: items.size
+ return items.indices.firstOrNull { KEY_COMPARATOR.compare(key, getKey(items[it])) < 0 }
+ ?: items.size
}
private fun findFirstIndexBefore(key: Key): Int {
@@ -262,30 +261,34 @@
invalidateDataSource: Boolean = false,
callbackInvoker: (callback: ItemKeyedDataSource.LoadInitialCallback<String>) -> Unit
) {
- val dataSource = object : ItemKeyedDataSource<String, String>() {
- override fun getKey(item: String): String {
- return ""
- }
-
- override fun loadInitial(
- params: LoadInitialParams<String>,
- callback: LoadInitialCallback<String>
- ) {
- if (invalidateDataSource) {
- // invalidate data source so it's invalid when onResult() called
- invalidate()
+ val dataSource =
+ object : ItemKeyedDataSource<String, String>() {
+ override fun getKey(item: String): String {
+ return ""
}
- callbackInvoker(callback)
- }
- override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String>) {
- fail("loadAfter not expected")
- }
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String>
+ ) {
+ if (invalidateDataSource) {
+ // invalidate data source so it's invalid when onResult() called
+ invalidate()
+ }
+ callbackInvoker(callback)
+ }
- override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String>) {
- fail("loadBefore not expected")
+ override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String>) {
+ fail("loadAfter not expected")
+ }
+
+ override fun loadBefore(
+ params: LoadParams<String>,
+ callback: LoadCallback<String>
+ ) {
+ fail("loadBefore not expected")
+ }
}
- }
@Suppress("DEPRECATION")
PagedList.Builder(dataSource, 10)
@@ -432,13 +435,9 @@
val loadInitialCallback = mock<ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>>()
val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
val initParams = ItemKeyedDataSource.LoadInitialParams(initKey, 10, false)
- wrapper.loadInitial(
- initParams,
- loadInitialCallback
- )
- verify(loadInitialCallback).onResult(
- ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) }
- )
+ wrapper.loadInitial(initParams, loadInitialCallback)
+ verify(loadInitialCallback)
+ .onResult(ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
val key = orig.getKey(ITEMS_BY_NAME_ID[20])
@Suppress("UNCHECKED_CAST")
@@ -460,9 +459,7 @@
}
@Test
- fun testManualWrappedDataSource() = verifyWrappedDataSource {
- DecoratedWrapperDataSource(it)
- }
+ fun testManualWrappedDataSource() = verifyWrappedDataSource { DecoratedWrapperDataSource(it) }
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
@@ -496,14 +493,16 @@
private val ITEM_COMPARATOR = compareBy<Item> { it.name }.thenByDescending { it.id }
private val KEY_COMPARATOR = compareBy<Key> { it.name }.thenByDescending { it.id }
- private val ITEMS_BY_NAME_ID = List(100) {
- val names = Array(10) { index -> "f" + ('a' + index) }
- Item(
- names[it % 10],
- it,
- Random.nextDouble(1000.0),
- Random.nextInt(200).toString() + " fake st."
- )
- }.sortedWith(ITEM_COMPARATOR)
+ private val ITEMS_BY_NAME_ID =
+ List(100) {
+ val names = Array(10) { index -> "f" + ('a' + index) }
+ Item(
+ names[it % 10],
+ it,
+ Random.nextDouble(1000.0),
+ Random.nextInt(200).toString() + " fake st."
+ )
+ }
+ .sortedWith(ITEM_COMPARATOR)
}
}
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PageKeyedDataSourceTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index a22d2f0..f1c5f40 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -90,11 +90,12 @@
val dispatcher = UnconfinedTestDispatcher()
val testCoroutineScope = CoroutineScope(dispatcher)
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(ItemDataSource(), 100)
- .setCoroutineScope(testCoroutineScope)
- .setNotifyDispatcher(dispatcher)
- .setFetchDispatcher(dispatcher)
- .build()
+ val pagedList =
+ PagedList.Builder(ItemDataSource(), 100)
+ .setCoroutineScope(testCoroutineScope)
+ .setNotifyDispatcher(dispatcher)
+ .setFetchDispatcher(dispatcher)
+ .build()
// validate initial load
assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList)
@@ -113,35 +114,35 @@
@Suppress("DEPRECATION")
private fun performLoadInitial(
invalidateDataSource: Boolean = false,
- callbackInvoker:
- (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
+ callbackInvoker: (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
) {
- val dataSource = object : PageKeyedDataSource<String, String>() {
- override fun loadInitial(
- params: LoadInitialParams<String>,
- callback: LoadInitialCallback<String, String>
- ) {
- if (invalidateDataSource) {
- // invalidate data source so it's invalid when onResult() called
- invalidate()
+ val dataSource =
+ object : PageKeyedDataSource<String, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String, String>
+ ) {
+ if (invalidateDataSource) {
+ // invalidate data source so it's invalid when onResult() called
+ invalidate()
+ }
+ callbackInvoker(callback)
}
- callbackInvoker(callback)
- }
- override fun loadBefore(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- fail("loadBefore not expected")
- }
+ override fun loadBefore(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ fail("loadBefore not expected")
+ }
- override fun loadAfter(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- fail("loadAfter not expected")
+ override fun loadAfter(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ fail("loadAfter not expected")
+ }
}
- }
@Suppress("DEPRECATION")
PagedList.Builder(dataSource, 10)
@@ -211,30 +212,31 @@
@Test
fun testBoundaryCallback() {
@Suppress("DEPRECATION")
- val dataSource = object : PageKeyedDataSource<String, String>() {
- override fun loadInitial(
- params: LoadInitialParams<String>,
- callback: LoadInitialCallback<String, String>
- ) {
- callback.onResult(listOf("B"), "a", "c")
- }
+ val dataSource =
+ object : PageKeyedDataSource<String, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String, String>
+ ) {
+ callback.onResult(listOf("B"), "a", "c")
+ }
- override fun loadBefore(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- assertEquals("a", params.key)
- callback.onResult(listOf("A"), null)
- }
+ override fun loadBefore(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ assertEquals("a", params.key)
+ callback.onResult(listOf("A"), null)
+ }
- override fun loadAfter(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- assertEquals("c", params.key)
- callback.onResult(listOf("C"), null)
+ override fun loadAfter(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ assertEquals("c", params.key)
+ callback.onResult(listOf("C"), null)
+ }
}
- }
@Suppress("UNCHECKED_CAST", "DEPRECATION")
val boundaryCallback =
@@ -243,12 +245,13 @@
val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(dataSource, 10)
- .setBoundaryCallback(boundaryCallback)
- .setCoroutineScope(testCoroutineScope)
- .setFetchDispatcher(Dispatchers.Unconfined)
- .setNotifyDispatcher(dispatcher)
- .build()
+ val pagedList =
+ PagedList.Builder(dataSource, 10)
+ .setBoundaryCallback(boundaryCallback)
+ .setCoroutineScope(testCoroutineScope)
+ .setFetchDispatcher(Dispatchers.Unconfined)
+ .setNotifyDispatcher(dispatcher)
+ .build()
pagedList.loadAround(0)
@@ -265,29 +268,30 @@
@Test
fun testBoundaryCallbackJustInitial() {
@Suppress("DEPRECATION")
- val dataSource = object : PageKeyedDataSource<String, String>() {
- override fun loadInitial(
- params: LoadInitialParams<String>,
- callback: LoadInitialCallback<String, String>
- ) {
- // just the one load, but boundary callbacks should still be triggered
- callback.onResult(listOf("B"), null, null)
- }
+ val dataSource =
+ object : PageKeyedDataSource<String, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String, String>
+ ) {
+ // just the one load, but boundary callbacks should still be triggered
+ callback.onResult(listOf("B"), null, null)
+ }
- override fun loadBefore(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- fail("loadBefore not expected")
- }
+ override fun loadBefore(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ fail("loadBefore not expected")
+ }
- override fun loadAfter(
- params: LoadParams<String>,
- callback: LoadCallback<String, String>
- ) {
- fail("loadBefore not expected")
+ override fun loadAfter(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>
+ ) {
+ fail("loadBefore not expected")
+ }
}
- }
@Suppress("UNCHECKED_CAST", "DEPRECATION")
val boundaryCallback =
@@ -296,12 +300,13 @@
val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(dataSource, 10)
- .setBoundaryCallback(boundaryCallback)
- .setCoroutineScope(testCoroutineScope)
- .setFetchDispatcher(Dispatchers.Unconfined)
- .setNotifyDispatcher(dispatcher)
- .build()
+ val pagedList =
+ PagedList.Builder(dataSource, 10)
+ .setBoundaryCallback(boundaryCallback)
+ .setCoroutineScope(testCoroutineScope)
+ .setFetchDispatcher(Dispatchers.Unconfined)
+ .setNotifyDispatcher(dispatcher)
+ .build()
pagedList.loadAround(0)
@@ -407,22 +412,26 @@
// load initial
@Suppress("UNCHECKED_CAST")
- val loadInitialCallback = mock(PageKeyedDataSource.LoadInitialCallback::class.java)
- as PageKeyedDataSource.LoadInitialCallback<String, String>
+ val loadInitialCallback =
+ mock(PageKeyedDataSource.LoadInitialCallback::class.java)
+ as PageKeyedDataSource.LoadInitialCallback<String, String>
val initParams = PageKeyedDataSource.LoadInitialParams<String>(4, true)
wrapper.loadInitial(initParams, loadInitialCallback)
val expectedInitial = PAGE_MAP.getValue(INIT_KEY)
- verify(loadInitialCallback).onResult(
- expectedInitial.data.map { it.toString() },
- expectedInitial.prev, expectedInitial.next
- )
+ verify(loadInitialCallback)
+ .onResult(
+ expectedInitial.data.map { it.toString() },
+ expectedInitial.prev,
+ expectedInitial.next
+ )
verifyNoMoreInteractions(loadInitialCallback)
@Suppress("UNCHECKED_CAST")
// load after
- var loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
- as PageKeyedDataSource.LoadCallback<String, String>
+ var loadCallback =
+ mock(PageKeyedDataSource.LoadCallback::class.java)
+ as PageKeyedDataSource.LoadCallback<String, String>
wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next!!, 4), loadCallback)
val expectedAfter = PAGE_MAP[expectedInitial.next]!!
verify(loadCallback).onResult(expectedAfter.data.map { it.toString() }, expectedAfter.next)
@@ -433,13 +442,12 @@
// load before
@Suppress("UNCHECKED_CAST")
- loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
- as PageKeyedDataSource.LoadCallback<String, String>
+ loadCallback =
+ mock(PageKeyedDataSource.LoadCallback::class.java)
+ as PageKeyedDataSource.LoadCallback<String, String>
wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback)
- verify(loadCallback).onResult(
- expectedInitial.data.map { it.toString() },
- expectedInitial.prev
- )
+ verify(loadCallback)
+ .onResult(expectedInitial.data.map { it.toString() }, expectedInitial.prev)
verifyNoMoreInteractions(loadCallback)
// load before - error
orig.enqueueError()
@@ -452,9 +460,7 @@
}
@Test
- fun testManualWrappedDataSource() = verifyWrappedDataSource {
- StringWrapperDataSource(it)
- }
+ fun testManualWrappedDataSource() = verifyWrappedDataSource { StringWrapperDataSource(it) }
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PagedStorageTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PagedStorageTest.kt
index c278dad..151b3ad 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PagedStorageTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PagedStorageTest.kt
@@ -28,11 +28,8 @@
import org.mockito.Mockito.verifyNoMoreInteractions
class PagedStorageTest {
- private fun pageOf(vararg strings: String): Page<Any, String> = Page(
- data = strings.asList(),
- prevKey = null,
- nextKey = null
- )
+ private fun pageOf(vararg strings: String): Page<Any, String> =
+ Page(data = strings.asList(), prevKey = null, nextKey = null)
@Test
fun construct() {
@@ -158,9 +155,7 @@
@Test
fun trim_remainderPreventsNoOp() {
- val storage = PagedStorage(
- listOf(pageOf("a", "b"), pageOf("c", "d"), pageOf("d", "e"))
- )
+ val storage = PagedStorage(listOf(pageOf("a", "b"), pageOf("c", "d"), pageOf("d", "e")))
// can't trim, since removing a page would mean fewer items than required
assertFalse(storage.needsTrimFromFront(5, 5))
@@ -223,44 +218,47 @@
val storage = PagedStorage(0, page, 0)
storage.lastLoadAroundIndex = -5
- var pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ var pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(0, pagingState.anchorPosition)
storage.lastLoadAroundIndex = 1
- pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(1, pagingState.anchorPosition)
storage.lastLoadAroundIndex = 5
- pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(2, pagingState.anchorPosition)
}
@@ -271,44 +269,47 @@
val storage = PagedStorage(10, page, 10)
storage.lastLoadAroundIndex = 1
- var pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ var pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(10, pagingState.anchorPosition)
storage.lastLoadAroundIndex = 11
- pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(11, pagingState.anchorPosition)
storage.lastLoadAroundIndex = 21
- pagingState = storage.getRefreshKeyInfo(
- @Suppress("DEPRECATION")
- PagedList.Config(
- pageSize = 3,
- prefetchDistance = 0,
- enablePlaceholders = true,
- initialLoadSizeHint = 3,
- maxSize = 3
+ pagingState =
+ storage.getRefreshKeyInfo(
+ @Suppress("DEPRECATION")
+ PagedList.Config(
+ pageSize = 3,
+ prefetchDistance = 0,
+ enablePlaceholders = true,
+ initialLoadSizeHint = 3,
+ maxSize = 3
+ )
)
- )
assertNotNull(pagingState)
assertEquals(12, pagingState.anchorPosition)
}
@@ -334,12 +335,17 @@
return storage
}
- private val IGNORED_CALLBACK = object : PagedStorage.Callback {
- override fun onInitialized(count: Int) {}
- override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {}
- override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {}
- override fun onPagesRemoved(startOfDrops: Int, count: Int) {}
- override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {}
- }
+ private val IGNORED_CALLBACK =
+ object : PagedStorage.Callback {
+ override fun onInitialized(count: Int) {}
+
+ override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {}
+
+ override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {}
+
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) {}
+
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {}
+ }
}
}
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PositionalDataSourceTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PositionalDataSourceTest.kt
index a02a2f4..87b9329 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PositionalDataSourceTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/PositionalDataSourceTest.kt
@@ -37,9 +37,13 @@
pageSize: Int,
totalCount: Int
): Int {
- val params = PositionalDataSource.LoadInitialParams(
- requestedStartPosition, requestedLoadSize, pageSize, true
- )
+ val params =
+ PositionalDataSource.LoadInitialParams(
+ requestedStartPosition,
+ requestedLoadSize,
+ pageSize,
+ true
+ )
return PositionalDataSource.computeInitialLoadPosition(params, totalCount)
}
@@ -109,43 +113,49 @@
}
@OptIn(ExperimentalCoroutinesApi::class)
- private fun validatePositionOffset(enablePlaceholders: Boolean) = testScope.runTest {
- val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
- val success = mutableListOf(false)
- val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- if (enablePlaceholders) {
- // 36 - ((10 * 3) / 2) = 21, round down to 20
- assertEquals(20, params.requestedStartPosition)
- } else {
- // 36 - ((10 * 3) / 2) = 21, no rounding
- assertEquals(21, params.requestedStartPosition)
+ private fun validatePositionOffset(enablePlaceholders: Boolean) =
+ testScope.runTest {
+ val config =
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
+ val success = mutableListOf(false)
+ val dataSource =
+ object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ if (enablePlaceholders) {
+ // 36 - ((10 * 3) / 2) = 21, round down to 20
+ assertEquals(20, params.requestedStartPosition)
+ } else {
+ // 36 - ((10 * 3) / 2) = 21, no rounding
+ assertEquals(21, params.requestedStartPosition)
+ }
+
+ callback.onResult(listOf("a", "b"), 0, 2)
+ success[0] = true
+ }
+
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<String>
+ ) {
+ fail("loadRange not expected")
+ }
}
- callback.onResult(listOf("a", "b"), 0, 2)
- success[0] = true
- }
-
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- fail("loadRange not expected")
- }
+ @Suppress("DEPRECATION")
+ PagedList.Builder(dataSource, config)
+ .setFetchExecutor(Executor { it.run() })
+ .setNotifyExecutor(Executor { it.run() })
+ .setInitialKey(36)
+ .build()
+ assertTrue(success[0])
}
- @Suppress("DEPRECATION")
- PagedList.Builder(dataSource, config)
- .setFetchExecutor(Executor { it.run() })
- .setNotifyExecutor(Executor { it.run() })
- .setInitialKey(36)
- .build()
- assertTrue(success[0])
- }
-
@Test
fun initialPositionOffset() {
validatePositionOffset(true)
@@ -161,39 +171,46 @@
enablePlaceholders: Boolean = true,
invalidateDataSource: Boolean = false,
callbackInvoker: (callback: PositionalDataSource.LoadInitialCallback<String>) -> Unit
- ) = testScope.runTest {
- val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- if (invalidateDataSource) {
- // invalidate data source so it's invalid when onResult() called
- invalidate()
+ ) =
+ testScope.runTest {
+ val dataSource =
+ object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ if (invalidateDataSource) {
+ // invalidate data source so it's invalid when onResult() called
+ invalidate()
+ }
+ callbackInvoker(callback)
+ }
+
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<String>
+ ) {
+ fail("loadRange not expected")
+ }
}
- callbackInvoker(callback)
- }
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- fail("loadRange not expected")
- }
+ val config =
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
+
+ val params =
+ PositionalDataSource.LoadInitialParams(
+ 0,
+ config.initialLoadSizeHint,
+ config.pageSize,
+ config.enablePlaceholders
+ )
+
+ dataSource.loadInitial(params)
}
- val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
-
- val params = PositionalDataSource.LoadInitialParams(
- 0,
- config.initialLoadSizeHint,
- config.pageSize,
- config.enablePlaceholders
- )
-
- dataSource.loadInitial(params)
- }
-
@Test
fun initialLoadCallbackSuccess() = performLoadInitial {
// LoadInitialCallback correct usage
@@ -201,10 +218,11 @@
}
@Test(expected = IllegalStateException::class)
- fun initialLoadCallbackRequireTotalCount() = performLoadInitial(enablePlaceholders = true) {
- // LoadInitialCallback requires 3 args when placeholders enabled
- it.onResult(listOf("a", "b"), 0)
- }
+ fun initialLoadCallbackRequireTotalCount() =
+ performLoadInitial(enablePlaceholders = true) {
+ // LoadInitialCallback requires 3 args when placeholders enabled
+ it.onResult(listOf("a", "b"), 0)
+ }
@Test(expected = IllegalArgumentException::class)
fun initialLoadCallbackNotPageSizeMultiple() = performLoadInitial {
@@ -238,34 +256,39 @@
}
@Test
- fun initialLoadCallbackSuccessTwoArg() = performLoadInitial(enablePlaceholders = false) {
- // LoadInitialCallback correct 2 arg usage
- it.onResult(listOf("a", "b"), 0)
- }
+ fun initialLoadCallbackSuccessTwoArg() =
+ performLoadInitial(enablePlaceholders = false) {
+ // LoadInitialCallback correct 2 arg usage
+ it.onResult(listOf("a", "b"), 0)
+ }
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackPosNegativeTwoArg() = performLoadInitial(enablePlaceholders = false) {
- // LoadInitialCallback can't accept negative position
- it.onResult(listOf("a", "b"), -1)
- }
+ fun initialLoadCallbackPosNegativeTwoArg() =
+ performLoadInitial(enablePlaceholders = false) {
+ // LoadInitialCallback can't accept negative position
+ it.onResult(listOf("a", "b"), -1)
+ }
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackEmptyWithOffset() = performLoadInitial(enablePlaceholders = false) {
- // LoadInitialCallback can't accept empty result unless pos is 0
- it.onResult(emptyList(), 1)
- }
+ fun initialLoadCallbackEmptyWithOffset() =
+ performLoadInitial(enablePlaceholders = false) {
+ // LoadInitialCallback can't accept empty result unless pos is 0
+ it.onResult(emptyList(), 1)
+ }
@Test
- fun initialLoadCallbackInvalidTwoArg() = performLoadInitial(invalidateDataSource = true) {
- // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
- it.onResult(emptyList(), 1)
- }
+ fun initialLoadCallbackInvalidTwoArg() =
+ performLoadInitial(invalidateDataSource = true) {
+ // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+ it.onResult(emptyList(), 1)
+ }
@Test
- fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
- // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
- it.onResult(emptyList(), 0, 1)
- }
+ fun initialLoadCallbackInvalidThreeArg() =
+ performLoadInitial(invalidateDataSource = true) {
+ // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+ it.onResult(emptyList(), 0, 1)
+ }
private abstract class WrapperDataSource<in A : Any, B : Any>(
private val source: PositionalDataSource<A>
@@ -319,10 +342,8 @@
}
}
- class ListDataSource<T : Any>(
- val list: List<T>,
- val counted: Boolean = true
- ) : PositionalDataSource<T> () {
+ class ListDataSource<T : Any>(val list: List<T>, val counted: Boolean = true) :
+ PositionalDataSource<T>() {
private var error = false
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
@@ -369,8 +390,9 @@
// load initial
@Suppress("UNCHECKED_CAST")
- val loadInitialCallback = mock(PositionalDataSource.LoadInitialCallback::class.java)
- as PositionalDataSource.LoadInitialCallback<String>
+ val loadInitialCallback =
+ mock(PositionalDataSource.LoadInitialCallback::class.java)
+ as PositionalDataSource.LoadInitialCallback<String>
val initParams = PositionalDataSource.LoadInitialParams(0, 2, 1, true)
wrapper.loadInitial(initParams, loadInitialCallback)
verify(loadInitialCallback).onResult(listOf("0", "5"), 0, 5)
@@ -381,8 +403,9 @@
// load range
@Suppress("UNCHECKED_CAST")
- val loadRangeCallback = mock(PositionalDataSource.LoadRangeCallback::class.java)
- as PositionalDataSource.LoadRangeCallback<String>
+ val loadRangeCallback =
+ mock(PositionalDataSource.LoadRangeCallback::class.java)
+ as PositionalDataSource.LoadRangeCallback<String>
wrapper.loadRange(PositionalDataSource.LoadRangeParams(2, 3), loadRangeCallback)
verify(loadRangeCallback).onResult(listOf("4", "8", "12"))
// load range - error
@@ -403,9 +426,7 @@
}
@Test
- fun testManualWrappedDataSource() = verifyWrappedDataSource {
- StringWrapperDataSource(it)
- }
+ fun testManualWrappedDataSource() = verifyWrappedDataSource { StringWrapperDataSource(it) }
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
@@ -438,10 +459,10 @@
@Test
fun addRemoveInvalidateFunction() {
val datasource = ListDataSource(listOf(0, 1, 2))
- val noopCallback = { }
+ val noopCallback = {}
datasource.addInvalidatedCallback(noopCallback)
assert(datasource.invalidateCallbackCount == 1)
- datasource.removeInvalidatedCallback { }
+ datasource.removeInvalidatedCallback {}
assert(datasource.invalidateCallbackCount == 1)
datasource.removeInvalidatedCallback(noopCallback)
assert(datasource.invalidateCallbackCount == 0)
@@ -451,43 +472,46 @@
private val testScope = TestScope(UnconfinedTestDispatcher())
@OptIn(ExperimentalCoroutinesApi::class)
- private fun verifyRefreshIsTerminal(counted: Boolean) = testScope.runTest {
- val dataSource = ListDataSource(list = listOf(0, 1, 2), counted = counted)
- dataSource.load(
- DataSource.Params(
- type = LoadType.REFRESH,
- key = 0,
- initialLoadSize = 3,
- placeholdersEnabled = false,
- pageSize = 1
- )
- ).apply {
- assertEquals(listOf(0, 1, 2), data)
- // prepends always return prevKey = null if they return the first item
- assertEquals(null, prevKey)
- // appends only return nextKey if they return the last item, and are counted
- assertEquals(if (counted) null else 3, nextKey)
+ private fun verifyRefreshIsTerminal(counted: Boolean) =
+ testScope.runTest {
+ val dataSource = ListDataSource(list = listOf(0, 1, 2), counted = counted)
+ dataSource
+ .load(
+ DataSource.Params(
+ type = LoadType.REFRESH,
+ key = 0,
+ initialLoadSize = 3,
+ placeholdersEnabled = false,
+ pageSize = 1
+ )
+ )
+ .apply {
+ assertEquals(listOf(0, 1, 2), data)
+ // prepends always return prevKey = null if they return the first item
+ assertEquals(null, prevKey)
+ // appends only return nextKey if they return the last item, and are counted
+ assertEquals(if (counted) null else 3, nextKey)
+ }
+
+ dataSource
+ .load(
+ DataSource.Params(
+ type = LoadType.PREPEND,
+ key = 1,
+ initialLoadSize = 3,
+ placeholdersEnabled = false,
+ pageSize = 1
+ )
+ )
+ .apply {
+ // prepends should return prevKey = null if they return the first item
+ assertEquals(listOf(0), data)
+ assertEquals(null, prevKey)
+ assertEquals(1, nextKey)
+ }
}
- dataSource.load(
- DataSource.Params(
- type = LoadType.PREPEND,
- key = 1,
- initialLoadSize = 3,
- placeholdersEnabled = false,
- pageSize = 1
- )
- ).apply {
- // prepends should return prevKey = null if they return the first item
- assertEquals(listOf(0), data)
- assertEquals(null, prevKey)
- assertEquals(1, nextKey)
- }
- }
+ @Test fun terminalResultCounted() = verifyRefreshIsTerminal(counted = true)
- @Test
- fun terminalResultCounted() = verifyRefreshIsTerminal(counted = true)
-
- @Test
- fun terminalResultUncounted() = verifyRefreshIsTerminal(counted = false)
+ @Test fun terminalResultUncounted() = verifyRefreshIsTerminal(counted = false)
}
diff --git a/paging/paging-common/src/nativeMain/kotlin/androidx/paging/PagingLogger.native.kt b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/PagingLogger.native.kt
index c00cbbf..15b4880 100644
--- a/paging/paging-common/src/nativeMain/kotlin/androidx/paging/PagingLogger.native.kt
+++ b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/PagingLogger.native.kt
@@ -23,5 +23,6 @@
public actual fun isLoggable(level: Int): Boolean {
return false
}
- public actual fun log(level: Int, message: String, tr: Throwable?) { }
+
+ public actual fun log(level: Int, message: String, tr: Throwable?) {}
}
diff --git a/paging/paging-common/src/nativeMain/kotlin/androidx/paging/internal/Atomics.native.kt b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/internal/Atomics.native.kt
index 35ad2c5..8f7329a 100644
--- a/paging/paging-common/src/nativeMain/kotlin/androidx/paging/internal/Atomics.native.kt
+++ b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/internal/Atomics.native.kt
@@ -37,9 +37,8 @@
import platform.posix.pthread_mutexattr_t
/**
- * Wrapper for platform.posix.PTHREAD_MUTEX_RECURSIVE which
- * is represented as kotlin.Int on darwin platforms and kotlin.UInt on linuxX64
- * See: // https://youtrack.jetbrains.com/issue/KT-41509
+ * Wrapper for platform.posix.PTHREAD_MUTEX_RECURSIVE which is represented as kotlin.Int on darwin
+ * platforms and kotlin.UInt on linuxX64 See: // https://youtrack.jetbrains.com/issue/KT-41509
*/
internal expect val PTHREAD_MUTEX_RECURSIVE: Int
@@ -116,19 +115,22 @@
internal actual class CopyOnWriteArrayList<T> : Iterable<T> {
private var data: List<T> = emptyList()
private val lock = ReentrantLock()
+
actual override fun iterator(): Iterator<T> {
return data.iterator()
}
- actual fun add(value: T) = lock.withLock {
- data = data + value
- true
- }
+ actual fun add(value: T) =
+ lock.withLock {
+ data = data + value
+ true
+ }
- actual fun remove(value: T): Boolean = lock.withLock {
- val newList = data.toMutableList()
- val result = newList.remove(value)
- data = newList
- result
- }
+ actual fun remove(value: T): Boolean =
+ lock.withLock {
+ val newList = data.toMutableList()
+ val result = newList.remove(value)
+ data = newList
+ result
+ }
}
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingDemos.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingDemos.kt
index 34a4b73..e8a40f4 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingDemos.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingDemos.kt
@@ -21,10 +21,11 @@
import androidx.paging.compose.demos.room.PagingRoomDemo
import androidx.paging.compose.samples.PagingBackendSample
-val PagingDemos = DemoCategory(
- "Paging",
- listOf(
- ComposableDemo("Paging Backend Demo") { PagingBackendSample() },
- ComposableDemo("Paging Room Demo") { PagingRoomDemo() }
+val PagingDemos =
+ DemoCategory(
+ "Paging",
+ listOf(
+ ComposableDemo("Paging Backend Demo") { PagingBackendSample() },
+ ComposableDemo("Paging Room Demo") { PagingRoomDemo() }
+ )
)
-)
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
index 1e3b9af..2904cf5 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
@@ -23,12 +23,13 @@
import androidx.paging.compose.samples.PagingWithLazyList
import androidx.paging.compose.samples.PagingWithVerticalPager
-val PagingFoundationDemos = DemoCategory(
- "Paging",
- listOf(
- ComposableDemo("Paging with HorizontalPager") { PagingWithHorizontalPager() },
- ComposableDemo("Paging with VerticalPager") { PagingWithVerticalPager() },
- ComposableDemo("Paging with LazyGrid") { PagingWithLazyGrid() },
- ComposableDemo("Paging with LazyColumn") { PagingWithLazyList() },
+val PagingFoundationDemos =
+ DemoCategory(
+ "Paging",
+ listOf(
+ ComposableDemo("Paging with HorizontalPager") { PagingWithHorizontalPager() },
+ ComposableDemo("Paging with VerticalPager") { PagingWithVerticalPager() },
+ ComposableDemo("Paging with LazyGrid") { PagingWithLazyGrid() },
+ ComposableDemo("Paging with LazyColumn") { PagingWithLazyList() },
+ )
)
-)
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/AppDatabase.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/AppDatabase.kt
index bdd74bf..adc0de7 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/AppDatabase.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/AppDatabase.kt
@@ -27,18 +27,20 @@
abstract fun userDao(): UserDao
companion object {
- @Volatile
- private var Instance: AppDatabase? = null
+ @Volatile private var Instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
if (Instance != null) {
return Instance!!
}
return Room.databaseBuilder(
- context.applicationContext,
- AppDatabase::class.java,
- "app_database"
- ).fallbackToDestructiveMigration(true).build().also { Instance = it }
+ context.applicationContext,
+ AppDatabase::class.java,
+ "app_database"
+ )
+ .fallbackToDestructiveMigration(true)
+ .build()
+ .also { Instance = it }
}
}
}
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
index 133aa41..8f2b8fa 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
@@ -47,13 +47,7 @@
val pageSize = 15
val pager = remember {
- Pager(
- PagingConfig(
- pageSize = pageSize,
- enablePlaceholders = true,
- maxSize = 200
- )
- ) {
+ Pager(PagingConfig(pageSize = pageSize, enablePlaceholders = true, maxSize = 200)) {
dao.allUsers()
}
}
@@ -70,13 +64,7 @@
Text("Add random user")
}
- Button(
- onClick = {
- scope.launch(Dispatchers.IO) {
- dao.clearAll()
- }
- }
- ) {
+ Button(onClick = { scope.launch(Dispatchers.IO) { dao.clearAll() } }) {
Text("Clear all users")
}
@@ -99,10 +87,7 @@
val randomUser = dao.getRandomUser()
if (randomUser != null) {
val newName = Names[Random.nextInt(Names.size)]
- val updatedUser = User(
- randomUser.id,
- newName
- )
+ val updatedUser = User(randomUser.id, newName)
dao.update(updatedUser)
}
}
@@ -129,18 +114,19 @@
}
}
-val Names = listOf(
- "John",
- "Jack",
- "Ben",
- "Sally",
- "Tom",
- "Jinny",
- "Mark",
- "Betty",
- "Liam",
- "Noah",
- "Olivia",
- "Emma",
- "Ava"
-)
+val Names =
+ listOf(
+ "John",
+ "Jack",
+ "Ben",
+ "Sally",
+ "Tom",
+ "Jinny",
+ "Mark",
+ "Betty",
+ "Liam",
+ "Noah",
+ "Olivia",
+ "Emma",
+ "Ava"
+ )
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/User.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/User.kt
index cba31f6..af21392 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/User.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/User.kt
@@ -20,8 +20,4 @@
import androidx.room.PrimaryKey
@Entity(tableName = "users")
-data class User(
- @PrimaryKey(autoGenerate = true)
- val id: Int,
- val name: String
-)
+data class User(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/UserDao.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/UserDao.kt
index 5e46a88..2538d4c 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/UserDao.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/UserDao.kt
@@ -29,18 +29,13 @@
@Query("SELECT * FROM Users ORDER BY name COLLATE NOCASE ASC")
fun allUsers(): PagingSource<Int, User>
- @Insert
- fun insert(user: User)
+ @Insert fun insert(user: User)
- @Delete
- fun delete(user: User)
+ @Delete fun delete(user: User)
- @Update
- fun update(user: User)
+ @Update fun update(user: User)
- @Query("DELETE FROM users")
- fun clearAll()
+ @Query("DELETE FROM users") fun clearAll()
- @Query("SELECT * FROM users ORDER BY RANDOM() LIMIT 1")
- fun getRandomUser(): User?
+ @Query("SELECT * FROM users ORDER BY RANDOM() LIMIT 1") fun getRandomUser(): User?
}
diff --git a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
index 407bb2e..8c94954 100644
--- a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
+++ b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
@@ -44,15 +44,15 @@
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
-private val db: TestBackend = TestBackend(
- loadDelay = 0,
- backendDataList = (0..500).toList().map { "$it" }
-)
+private val db: TestBackend =
+ TestBackend(loadDelay = 0, backendDataList = (0..500).toList().map { "$it" })
-private val pager = Pager(
- config = PagingConfig(pageSize = 5, initialLoadSize = 15, enablePlaceholders = true),
- pagingSourceFactory = { db.getAllData() }
-).flow
+private val pager =
+ Pager(
+ config = PagingConfig(pageSize = 5, initialLoadSize = 15, enablePlaceholders = true),
+ pagingSourceFactory = { db.getAllData() }
+ )
+ .flow
@OptIn(ExperimentalFoundationApi::class)
@Sampled
@@ -118,10 +118,7 @@
contentType = "My Header",
) {
Box(
- modifier = Modifier
- .padding(bottom = 10.dp)
- .background(Color.Red)
- .fillMaxWidth(),
+ modifier = Modifier.padding(bottom = 10.dp).background(Color.Red).fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Text(text = "Header", fontSize = 32.sp)
@@ -141,11 +138,7 @@
@Composable
private fun PagingItem(item: String?) {
Box(
- modifier = Modifier
- .padding(10.dp)
- .background(Color.Blue)
- .fillMaxWidth()
- .aspectRatio(1f),
+ modifier = Modifier.padding(10.dp).background(Color.Blue).fillMaxWidth().aspectRatio(1f),
contentAlignment = Alignment.Center
) {
if (item != null) {
diff --git a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingPreviewSample.kt b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingPreviewSample.kt
index 1c6cb45..d3acce3 100644
--- a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingPreviewSample.kt
+++ b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingPreviewSample.kt
@@ -38,16 +38,14 @@
/**
* The composable that displays data from LazyPagingItems.
*
- * This composable is inlined only for the purposes of this sample. In production code,
- * this function should be its own top-level function.
+ * This composable is inlined only for the purposes of this sample. In production code, this
+ * function should be its own top-level function.
*/
@Composable
fun DisplayPaging(flow: Flow<PagingData<String>>) {
// Flow of real data i.e. flow from a ViewModel, or flow of fake data i.e. from a Preview.
val lazyPagingItems = flow.collectAsLazyPagingItems()
- LazyColumn(modifier = Modifier
- .fillMaxSize()
- .background(Color.Red)) {
+ LazyColumn(modifier = Modifier.fillMaxSize().background(Color.Red)) {
items(count = lazyPagingItems.itemCount) { index ->
val item = lazyPagingItems[index]
Text(text = "$item", fontSize = 35.sp, color = Color.Black)
diff --git a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingSample.kt b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingSample.kt
index 596cade..06d117c 100644
--- a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingSample.kt
+++ b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingSample.kt
@@ -49,7 +49,9 @@
enablePlaceholders = true,
maxSize = 200
)
- ) { myBackend.getAllData() }
+ ) {
+ myBackend.getAllData()
+ }
}
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
@@ -59,8 +61,8 @@
item {
Text(
text = "Waiting for items to load from the backend",
- modifier = Modifier.fillMaxWidth()
- .wrapContentWidth(Alignment.CenterHorizontally)
+ modifier =
+ Modifier.fillMaxWidth().wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
@@ -73,8 +75,8 @@
if (lazyPagingItems.loadState.append == LoadState.Loading) {
item {
CircularProgressIndicator(
- modifier = Modifier.fillMaxWidth()
- .wrapContentWidth(Alignment.CenterHorizontally)
+ modifier =
+ Modifier.fillMaxWidth().wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
@@ -85,10 +87,7 @@
fun ItemsDemo(flow: Flow<PagingData<String>>) {
val lazyPagingItems = flow.collectAsLazyPagingItems()
LazyColumn {
- items(
- count = lazyPagingItems.itemCount,
- key = lazyPagingItems.itemKey()
- ) { index ->
+ items(count = lazyPagingItems.itemCount, key = lazyPagingItems.itemKey()) { index ->
val item = lazyPagingItems[index]
Text("Item is $item")
}
diff --git a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/SampleUtils.kt b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/SampleUtils.kt
index d13cc3e..1a2ad81 100644
--- a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/SampleUtils.kt
+++ b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/SampleUtils.kt
@@ -27,13 +27,9 @@
) {
val DataBatchSize = 5
- class DesiredLoadResultPageResponse(
- val data: List<String>
- )
+ class DesiredLoadResultPageResponse(val data: List<String>)
- /**
- * Returns [DataBatchSize] items for a key
- */
+ /** Returns [DataBatchSize] items for a key */
fun searchItemsByKey(key: Int): DesiredLoadResultPageResponse {
val maxKey = ceil(backendDataList.size.toFloat() / DataBatchSize).toInt()
@@ -72,11 +68,7 @@
// data, we return `null` to signify no more pages should be loaded
val nextKey = if (response.data.isNotEmpty()) pageNumber + 1 else null
- return LoadResult.Page(
- data = response.data,
- prevKey = prevKey,
- nextKey = nextKey
- )
+ return LoadResult.Page(data = response.data, prevKey = prevKey, nextKey = nextKey)
}
override fun getRefreshKey(state: PagingState<Int, String>): Int? {
diff --git a/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsPreviewTest.kt b/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsPreviewTest.kt
index b6c324ce..534519f 100644
--- a/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsPreviewTest.kt
+++ b/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsPreviewTest.kt
@@ -39,27 +39,20 @@
class LazyPagingItemsPreviewTest {
- @get:Rule
- val composeTestRule = createAndroidComposeRule<PreviewActivity>()
+ @get:Rule val composeTestRule = createAndroidComposeRule<PreviewActivity>()
@Test
fun pagingPreviewTest() {
- composeTestRule.setContent {
- PagingPreview()
- }
+ composeTestRule.setContent { PagingPreview() }
for (i in 0..9) {
- composeTestRule.onNodeWithTag("$i")
- .assertIsDisplayed()
+ composeTestRule.onNodeWithTag("$i").assertIsDisplayed()
}
}
@Test
fun emptyPreview() {
- composeTestRule.setContent {
- EmptyPreview()
- }
- composeTestRule.onNodeWithTag("0")
- .assertDoesNotExist()
+ composeTestRule.setContent { EmptyPreview() }
+ composeTestRule.onNodeWithTag("0").assertDoesNotExist()
}
}
diff --git a/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
index 28b1f8f..4055fc4 100644
--- a/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidInstrumentedTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -69,21 +69,21 @@
@LargeTest
@RunWith(AndroidJUnit4::class)
class LazyPagingItemsTest {
- @get:Rule
- val rule = createComposeRule()
+ @get:Rule val rule = createComposeRule()
val items = (1..10).toList().map { it }
private val itemsSizePx = 30f
private val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
private fun createPager(
- config: PagingConfig = PagingConfig(
- pageSize = 1,
- enablePlaceholders = false,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 1,
- ),
+ config: PagingConfig =
+ PagingConfig(
+ pageSize = 1,
+ enablePlaceholders = false,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 1,
+ ),
loadDelay: Long = 0,
pagingSourceFactory: () -> PagingSource<Int, Int> = {
TestPagingSource(items = items, loadDelay = loadDelay)
@@ -93,17 +93,19 @@
}
private fun createPagerWithPlaceholders(
- config: PagingConfig = PagingConfig(
- pageSize = 1,
- enablePlaceholders = true,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 0,
+ config: PagingConfig =
+ PagingConfig(
+ pageSize = 1,
+ enablePlaceholders = true,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 0,
+ )
+ ) =
+ Pager(
+ config = config,
+ pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) }
)
- ) = Pager(
- config = config,
- pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) }
- )
@Test
fun lazyPagingInitialLoadState() {
@@ -116,17 +118,19 @@
rule.waitForIdle()
- val expected = CombinedLoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.NotLoading(false),
- append = LoadState.NotLoading(false),
- source = LoadStates(
- LoadState.Loading,
- LoadState.NotLoading(false),
- LoadState.NotLoading(false)
- ),
- mediator = null
- )
+ val expected =
+ CombinedLoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.NotLoading(false),
+ append = LoadState.NotLoading(false),
+ source =
+ LoadStates(
+ LoadState.Loading,
+ LoadState.NotLoading(false),
+ LoadState.NotLoading(false)
+ ),
+ mediator = null
+ )
assertThat(loadStates).isNotEmpty()
assertThat(loadStates.first()).isEqualTo(expected)
}
@@ -162,17 +166,19 @@
}
assertThat(loadStates).isNotEmpty()
- val expected = CombinedLoadStates(
- refresh = LoadState.Loading,
- prepend = LoadState.NotLoading(false),
- append = LoadState.NotLoading(false),
- source = LoadStates(
- LoadState.Loading,
- LoadState.NotLoading(false),
- LoadState.NotLoading(false)
- ),
- mediator = null
- )
+ val expected =
+ CombinedLoadStates(
+ refresh = LoadState.Loading,
+ prepend = LoadState.NotLoading(false),
+ append = LoadState.NotLoading(false),
+ source =
+ LoadStates(
+ LoadState.Loading,
+ LoadState.NotLoading(false),
+ LoadState.NotLoading(false)
+ ),
+ mediator = null
+ )
assertThat(loadStates.first()).isEqualTo(expected)
}
@@ -191,17 +197,13 @@
rule.waitForIdle()
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
+ rule.onNodeWithTag("1").assertIsDisplayed()
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
+ rule.onNodeWithTag("2").assertIsDisplayed()
- rule.onNodeWithTag("3")
- .assertDoesNotExist()
+ rule.onNodeWithTag("3").assertDoesNotExist()
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
+ rule.onNodeWithTag("4").assertDoesNotExist()
}
@Test
@@ -219,17 +221,13 @@
rule.waitForIdle()
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
+ rule.onNodeWithTag("1").assertIsDisplayed()
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
+ rule.onNodeWithTag("2").assertIsDisplayed()
- rule.onNodeWithTag("3")
- .assertDoesNotExist()
+ rule.onNodeWithTag("3").assertDoesNotExist()
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
+ rule.onNodeWithTag("4").assertDoesNotExist()
}
@Test
@@ -247,17 +245,14 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
- item(contentType = "not-to-reuse--1") {
- Content("-1")
- }
- item(contentType = "reuse") {
- Content("0")
- }
+ item(contentType = "not-to-reuse--1") { Content("-1") }
+ item(contentType = "reuse") { Content("0") }
items(
count = lazyPagingItems.itemCount,
- contentType = lazyPagingItems.itemContentType(
- contentType = { if (it == 8) "reuse" else "not-to-reuse-$it" }
- )
+ contentType =
+ lazyPagingItems.itemContentType(
+ contentType = { if (it == 8) "reuse" else "not-to-reuse-$it" }
+ )
) { index ->
val item = lazyPagingItems[index]
Content("$item")
@@ -272,10 +267,8 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
- rule.onNodeWithTag("0")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
+ rule.onNodeWithTag("0").assertIsDeactivated()
rule.runOnIdle {
runBlocking {
@@ -284,17 +277,12 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
// node reused
- rule.onNodeWithTag("0")
- .assertDoesNotExist()
- rule.onNodeWithTag("7")
- .assertIsDisplayed()
- rule.onNodeWithTag("8")
- .assertIsDisplayed()
- rule.onNodeWithTag("9")
- .assertIsDisplayed()
+ rule.onNodeWithTag("0").assertDoesNotExist()
+ rule.onNodeWithTag("7").assertIsDisplayed()
+ rule.onNodeWithTag("8").assertIsDisplayed()
+ rule.onNodeWithTag("9").assertIsDisplayed()
}
@Test
@@ -315,19 +303,14 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
- item(contentType = "not-to-reuse--1") {
- Content("-1")
- }
+ item(contentType = "not-to-reuse--1") { Content("-1") }
// to be reused later by placeholder item
- item(contentType = PagingPlaceholderContentType) {
- Content("0")
- }
+ item(contentType = PagingPlaceholderContentType) { Content("0") }
items(
count = lazyPagingItems.itemCount,
// item 7 would be null, which should default to PagingPlaceholderContentType
- contentType = lazyPagingItems.itemContentType(
- contentType = { "not-to-reuse-$it" }
- )
+ contentType =
+ lazyPagingItems.itemContentType(contentType = { "not-to-reuse-$it" })
) { index ->
val item = lazyPagingItems[index]
Content("$item")
@@ -335,9 +318,7 @@
}
}
- rule.waitUntil {
- loadedItem6
- }
+ rule.waitUntil { loadedItem6 }
rule.runOnIdle {
runBlocking {
@@ -346,10 +327,8 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
- rule.onNodeWithTag("0")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
+ rule.onNodeWithTag("0").assertIsDeactivated()
rule.runOnIdle {
runBlocking {
@@ -358,11 +337,9 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
// node reused
- rule.onNodeWithTag("0")
- .assertDoesNotExist()
+ rule.onNodeWithTag("0").assertDoesNotExist()
}
@Test
@@ -379,13 +356,9 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
- item(contentType = "not-to-reuse--1") {
- Content("-1")
- }
+ item(contentType = "not-to-reuse--1") { Content("-1") }
// to be reused later by real items
- item(contentType = null) {
- Content("0")
- }
+ item(contentType = null) { Content("0") }
items(
count = lazyPagingItems.itemCount,
// should default to null
@@ -404,10 +377,8 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
- rule.onNodeWithTag("0")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
+ rule.onNodeWithTag("0").assertIsDeactivated()
rule.runOnIdle {
runBlocking {
@@ -416,14 +387,10 @@
}
}
- rule.onNodeWithTag("-1")
- .assertIsDeactivated()
+ rule.onNodeWithTag("-1").assertIsDeactivated()
// node reused
- rule.onNodeWithTag("0")
- .assertDoesNotExist()
- rule.onNodeWithTag("4")
- .assertExists()
- .assertIsDisplayed()
+ rule.onNodeWithTag("0").assertDoesNotExist()
+ rule.onNodeWithTag("4").assertExists().assertIsDisplayed()
}
@Test
@@ -440,9 +407,7 @@
composedCount = lazyPagingItems.itemCount
}
- rule.waitUntil {
- composedCount == items.size
- }
+ rule.waitUntil { composedCount == items.size }
assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(items)
}
@@ -462,9 +427,7 @@
composedCount = lazyPagingItems.itemCount
}
- rule.waitUntil {
- composedCount == 6
- }
+ rule.waitUntil { composedCount == 6 }
rule.runOnIdle {
assertThat(lazyPagingItems.itemCount).isEqualTo(6)
@@ -493,9 +456,7 @@
}
lateinit var lazyPagingItems: LazyPagingItems<Int>
- rule.setContent {
- lazyPagingItems = pager.flow.collectAsLazyPagingItems()
- }
+ rule.setContent { lazyPagingItems = pager.flow.collectAsLazyPagingItems() }
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
@@ -521,9 +482,7 @@
}
lateinit var lazyPagingItems: LazyPagingItems<Int>
- rule.setContent {
- lazyPagingItems = pager.flow.collectAsLazyPagingItems()
- }
+ rule.setContent { lazyPagingItems = pager.flow.collectAsLazyPagingItems() }
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
@@ -537,9 +496,7 @@
@Test
fun itemCountIsObservable() {
var items = listOf(0, 1)
- val pager = createPager {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager = createPager { TestPagingSource(items = items, loadDelay = 0) }
var composedCount = 0
lateinit var lazyPagingItems: LazyPagingItems<Int>
@@ -548,35 +505,27 @@
composedCount = lazyPagingItems.itemCount
}
- rule.waitUntil {
- composedCount == 2
- }
+ rule.waitUntil { composedCount == 2 }
rule.runOnIdle {
items = listOf(0, 1, 2)
lazyPagingItems.refresh()
}
- rule.waitUntil {
- composedCount == 3
- }
+ rule.waitUntil { composedCount == 3 }
rule.runOnIdle {
items = listOf(0)
lazyPagingItems.refresh()
}
- rule.waitUntil {
- composedCount == 1
- }
+ rule.waitUntil { composedCount == 1 }
}
@Test
fun worksWhenUsedWithoutExtension() {
var items = listOf(10, 20)
- val pager = createPager {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager = createPager { TestPagingSource(items = items, loadDelay = 0) }
lateinit var lazyPagingItems: LazyPagingItems<Int>
rule.setContent {
@@ -589,44 +538,39 @@
}
}
- rule.onNodeWithTag("10")
- .assertIsDisplayed()
+ rule.onNodeWithTag("10").assertIsDisplayed()
- rule.onNodeWithTag("20")
- .assertIsDisplayed()
+ rule.onNodeWithTag("20").assertIsDisplayed()
rule.runOnIdle {
items = listOf(30, 20, 40)
lazyPagingItems.refresh()
}
- rule.onNodeWithTag("30")
- .assertIsDisplayed()
+ rule.onNodeWithTag("30").assertIsDisplayed()
- rule.onNodeWithTag("20")
- .assertIsDisplayed()
+ rule.onNodeWithTag("20").assertIsDisplayed()
- rule.onNodeWithTag("40")
- .assertIsDisplayed()
+ rule.onNodeWithTag("40").assertIsDisplayed()
- rule.onNodeWithTag("10")
- .assertDoesNotExist()
+ rule.onNodeWithTag("10").assertDoesNotExist()
}
@Test
fun updatingItem() {
var items = listOf(1, 2, 3)
- val pager = createPager(
- PagingConfig(
- pageSize = 3,
- enablePlaceholders = false,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 3,
- )
- ) {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager =
+ createPager(
+ PagingConfig(
+ pageSize = 3,
+ enablePlaceholders = false,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 3,
+ )
+ ) {
+ TestPagingSource(items = items, loadDelay = 0)
+ }
val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() }
@@ -646,33 +590,30 @@
lazyPagingItems.refresh()
}
- rule.onNodeWithTag("1")
- .assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag("1").assertTopPositionInRootIsEqualTo(0.dp)
- rule.onNodeWithTag("4")
- .assertTopPositionInRootIsEqualTo(itemSize)
+ rule.onNodeWithTag("4").assertTopPositionInRootIsEqualTo(itemSize)
- rule.onNodeWithTag("3")
- .assertTopPositionInRootIsEqualTo(itemSize * 2)
+ rule.onNodeWithTag("3").assertTopPositionInRootIsEqualTo(itemSize * 2)
- rule.onNodeWithTag("2")
- .assertDoesNotExist()
+ rule.onNodeWithTag("2").assertDoesNotExist()
}
@Test
fun addingNewItem() {
var items = listOf(1, 2)
- val pager = createPager(
- PagingConfig(
- pageSize = 3,
- enablePlaceholders = false,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 3,
- )
- ) {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager =
+ createPager(
+ PagingConfig(
+ pageSize = 3,
+ enablePlaceholders = false,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 3,
+ )
+ ) {
+ TestPagingSource(items = items, loadDelay = 0)
+ }
val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() }
@@ -692,30 +633,28 @@
lazyPagingItems.refresh()
}
- rule.onNodeWithTag("1")
- .assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag("1").assertTopPositionInRootIsEqualTo(0.dp)
- rule.onNodeWithTag("2")
- .assertTopPositionInRootIsEqualTo(itemSize)
+ rule.onNodeWithTag("2").assertTopPositionInRootIsEqualTo(itemSize)
- rule.onNodeWithTag("3")
- .assertTopPositionInRootIsEqualTo(itemSize * 2)
+ rule.onNodeWithTag("3").assertTopPositionInRootIsEqualTo(itemSize * 2)
}
@Test
fun removingItem() {
var items = listOf(1, 2, 3)
- val pager = createPager(
- PagingConfig(
- pageSize = 3,
- enablePlaceholders = false,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 3,
- )
- ) {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager =
+ createPager(
+ PagingConfig(
+ pageSize = 3,
+ enablePlaceholders = false,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 3,
+ )
+ ) {
+ TestPagingSource(items = items, loadDelay = 0)
+ }
val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() }
@@ -723,10 +662,8 @@
rule.setContent {
lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn(Modifier.height(itemSize * 3)) {
- items(
- count = lazyPagingItems.itemCount,
- key = lazyPagingItems.itemKey { it }
- ) { index ->
+ items(count = lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it }) {
+ index ->
val item = lazyPagingItems[index]
Spacer(Modifier.height(itemSize).fillParentMaxWidth().testTag("$item"))
}
@@ -738,22 +675,17 @@
lazyPagingItems.refresh()
}
- rule.onNodeWithTag("2")
- .assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag("2").assertTopPositionInRootIsEqualTo(0.dp)
- rule.onNodeWithTag("3")
- .assertTopPositionInRootIsEqualTo(itemSize)
+ rule.onNodeWithTag("3").assertTopPositionInRootIsEqualTo(itemSize)
- rule.onNodeWithTag("1")
- .assertIsNotDisplayed()
+ rule.onNodeWithTag("1").assertIsNotDisplayed()
}
@Test
fun stateIsMovedWithItemWithCustomKey_items() {
var items = listOf(1)
- val pager = createPager {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager = createPager { TestPagingSource(items = items, loadDelay = 0) }
lateinit var lazyPagingItems: LazyPagingItems<Int>
var counter = 0
@@ -765,9 +697,7 @@
key = lazyPagingItems.itemKey { it },
) { index ->
val item = lazyPagingItems[index]
- BasicText(
- "Item=$item. counter=${remember { counter++ }}"
- )
+ BasicText("Item=$item. counter=${remember { counter++ }}")
}
}
}
@@ -777,45 +707,31 @@
lazyPagingItems.refresh()
}
- rule.onNodeWithText("Item=0. counter=1")
- .assertExists()
+ rule.onNodeWithText("Item=0. counter=1").assertExists()
- rule.onNodeWithText("Item=1. counter=0")
- .assertExists()
+ rule.onNodeWithText("Item=1. counter=0").assertExists()
}
@Test
fun collectOnDefaultThread() {
val items = mutableListOf(1, 2, 3)
- val pager = createPager {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager = createPager { TestPagingSource(items = items, loadDelay = 0) }
lateinit var lazyPagingItems: LazyPagingItems<Int>
- rule.setContent {
- lazyPagingItems = pager.flow.collectAsLazyPagingItems()
- }
+ rule.setContent { lazyPagingItems = pager.flow.collectAsLazyPagingItems() }
- rule.waitUntil {
- lazyPagingItems.itemCount == 3
- }
- assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn(
- items
- )
+ rule.waitUntil { lazyPagingItems.itemCount == 3 }
+ assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn(items)
}
@Test
fun collectOnWorkerThread() {
val items = mutableListOf(1, 2, 3)
- val pager = createPager {
- TestPagingSource(items = items, loadDelay = 0)
- }
+ val pager = createPager { TestPagingSource(items = items, loadDelay = 0) }
val context = StandardTestDispatcher()
lateinit var lazyPagingItems: LazyPagingItems<Int>
- rule.setContent {
- lazyPagingItems = pager.flow.collectAsLazyPagingItems(context)
- }
+ rule.setContent { lazyPagingItems = pager.flow.collectAsLazyPagingItems(context) }
rule.runOnIdle {
// collection should not have started yet
@@ -829,12 +745,8 @@
// continue with pagingDataDiffer collections
context.scheduler.advanceUntilIdle()
}
- rule.waitUntil {
- lazyPagingItems.itemCount == items.size
- }
- assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn(
- items
- )
+ rule.waitUntil { lazyPagingItems.itemCount == items.size }
+ assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn(items)
}
@Test
@@ -893,9 +805,7 @@
}
}
- rule.runOnIdle {
- assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
- }
+ rule.runOnIdle { assertThat(lazyPagingItems.itemSnapshotList).isEmpty() }
// we don't let dispatchers load and directly restore state
restorationTester.emulateSavedInstanceStateRestore()
@@ -922,9 +832,7 @@
val flow = MutableStateFlow(PagingData.from(items))
lateinit var lazyPagingItems: LazyPagingItems<Int>
val dispatcher = StandardTestDispatcher()
- rule.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ rule.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
@@ -939,17 +847,16 @@
@Test
fun cachedPagingDataFromWithLoadStates() {
- val flow = MutableStateFlow(
- PagingData.from(
- data = items,
- sourceLoadStates = loadStates(refresh = Loading),
+ val flow =
+ MutableStateFlow(
+ PagingData.from(
+ data = items,
+ sourceLoadStates = loadStates(refresh = Loading),
+ )
)
- )
lateinit var lazyPagingItems: LazyPagingItems<Int>
val dispatcher = StandardTestDispatcher()
- rule.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ rule.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
@@ -967,9 +874,7 @@
val flow = MutableStateFlow(PagingData.from(emptyList<Int>()))
lateinit var lazyPagingItems: LazyPagingItems<Int>
val dispatcher = StandardTestDispatcher()
- rule.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ rule.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
@@ -984,41 +889,34 @@
@Test
fun cachedPagingDataFromWithEmptyDataAndLoadStates() {
- val flow = MutableStateFlow(
- PagingData.from(
- emptyList<Int>(),
- sourceLoadStates = loadStates(
- prepend = NotLoading(true),
- append = NotLoading(true)
+ val flow =
+ MutableStateFlow(
+ PagingData.from(
+ emptyList<Int>(),
+ sourceLoadStates =
+ loadStates(prepend = NotLoading(true), append = NotLoading(true))
)
)
- )
lateinit var lazyPagingItems: LazyPagingItems<Int>
val restorationTester = StateRestorationTester(rule)
val dispatcher = StandardTestDispatcher()
- restorationTester.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ restorationTester.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
// assert before load
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- prependLocal = NotLoading(true),
- appendLocal = NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(prependLocal = NotLoading(true), appendLocal = NotLoading(true))
)
- )
dispatcher.scheduler.advanceUntilIdle()
// assert data is still the same after load
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- prependLocal = NotLoading(true),
- appendLocal = NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(prependLocal = NotLoading(true), appendLocal = NotLoading(true))
)
- )
}
@Test
@@ -1026,9 +924,7 @@
val flow = MutableStateFlow(PagingData.empty<Int>())
lateinit var lazyPagingItems: LazyPagingItems<Int>
val dispatcher = StandardTestDispatcher()
- rule.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ rule.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
@@ -1043,45 +939,38 @@
@Test
fun cachedPagingDataEmptyWithLoadStates() {
- val flow = MutableStateFlow(
- PagingData.empty<Int>(
- sourceLoadStates = loadStates(
- prepend = NotLoading(true),
- append = NotLoading(true)
+ val flow =
+ MutableStateFlow(
+ PagingData.empty<Int>(
+ sourceLoadStates =
+ loadStates(prepend = NotLoading(true), append = NotLoading(true))
)
)
- )
lateinit var lazyPagingItems: LazyPagingItems<Int>
val dispatcher = StandardTestDispatcher()
- rule.setContent {
- lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
- }
+ rule.setContent { lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher) }
rule.waitForIdle()
// assert before load
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- prependLocal = NotLoading(true),
- appendLocal = NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(prependLocal = NotLoading(true), appendLocal = NotLoading(true))
)
- )
dispatcher.scheduler.advanceUntilIdle()
// assert data is still the same after load
assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- prependLocal = NotLoading(true),
- appendLocal = NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(prependLocal = NotLoading(true), appendLocal = NotLoading(true))
)
- )
}
@Test
fun cachedData_withPlaceholders() {
- val flow = createPagerWithPlaceholders().flow
- .cachedIn(TestScope(UnconfinedTestDispatcher()))
+ val flow =
+ createPagerWithPlaceholders().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
lateinit var lazyPagingItems: LazyPagingItems<Int>
val restorationTester = StateRestorationTester(rule)
val dispatcher = StandardTestDispatcher()
@@ -1107,9 +996,8 @@
// ensure we received the cached data + placeholders
rule.runOnIdle {
assertThat(lazyPagingItems.itemCount).isEqualTo(10)
- assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(
- listOf(1, 2, 3, 4, 5, 6, null, null, null, null)
- )
+ assertThat(lazyPagingItems.itemSnapshotList)
+ .isEqualTo(listOf(1, 2, 3, 4, 5, 6, null, null, null, null))
}
// try to load more data
@@ -1120,9 +1008,8 @@
loadedMaxItem
}
rule.runOnIdle {
- assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(
- listOf(1, 2, 3, 4, 5, 6, 7, null, null, null)
- )
+ assertThat(lazyPagingItems.itemSnapshotList)
+ .isEqualTo(listOf(1, 2, 3, 4, 5, 6, 7, null, null, null))
}
}
@@ -1149,24 +1036,26 @@
lazyPagingItems.loadState.source.append is LoadState.NotLoading
}
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- refreshLocal = LoadState.NotLoading(false),
- prependLocal = LoadState.NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(
+ refreshLocal = LoadState.NotLoading(false),
+ prependLocal = LoadState.NotLoading(true)
+ )
)
- )
// we don't advance load dispatchers after restoration to prevent loads
restorationTester.emulateSavedInstanceStateRestore()
// ensure we received the cached loadstates
rule.runOnIdle {
- assertThat(lazyPagingItems.loadState).isEqualTo(
- localLoadStatesOf(
- refreshLocal = LoadState.NotLoading(false),
- prependLocal = LoadState.NotLoading(true)
+ assertThat(lazyPagingItems.loadState)
+ .isEqualTo(
+ localLoadStatesOf(
+ refreshLocal = LoadState.NotLoading(false),
+ prependLocal = LoadState.NotLoading(true)
+ )
)
- )
}
}
@@ -1186,12 +1075,8 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
// Static items are what triggers scroll state to erroneously reset to 0
- item {
- Content("header")
- }
- items(
- lazyPagingItems.itemCount, lazyPagingItems.itemKey()
- ) { index ->
+ item { Content("header") }
+ items(lazyPagingItems.itemCount, lazyPagingItems.itemKey()) { index ->
val item = lazyPagingItems[index]
Content("$item")
}
diff --git a/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyFoundationExtensions.kt b/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyFoundationExtensions.kt
index 73d786d..6391558 100644
--- a/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyFoundationExtensions.kt
+++ b/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyFoundationExtensions.kt
@@ -22,21 +22,22 @@
/**
* Returns a factory of stable and unique keys representing the item.
*
- * Keys are generated with the key lambda that is passed in. If null is passed in, keys will
- * default to a placeholder key. If [PagingConfig.enablePlaceholders] is true,
- * LazyPagingItems may return null items. Null items will also automatically default to
- * a placeholder key.
+ * Keys are generated with the key lambda that is passed in. If null is passed in, keys will default
+ * to a placeholder key. If [PagingConfig.enablePlaceholders] is true, LazyPagingItems may return
+ * null items. Null items will also automatically default to a placeholder key.
*
* This factory can be applied to Lazy foundations such as [LazyGridScope.items] or Pagers.
* Examples:
+ *
* @sample androidx.paging.compose.samples.PagingWithHorizontalPager
+ *
* @sample androidx.paging.compose.samples.PagingWithLazyGrid
*
- * @param [key] a factory of stable and unique keys representing the item. Using the same key
- * for multiple items in the list is not allowed. Type of the key should be saveable
- * via Bundle on Android. When you specify the key the scroll position will be maintained
- * based on the key, which means if you add/remove items before the current visible item the
- * item with the given key will be kept as the first visible one.
+ * @param [key] a factory of stable and unique keys representing the item. Using the same key for
+ * multiple items in the list is not allowed. Type of the key should be saveable via Bundle on
+ * Android. When you specify the key the scroll position will be maintained based on the key,
+ * which means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
*/
public fun <T : Any> LazyPagingItems<T>.itemKey(
key: ((item: @JvmSuppressWildcards T) -> Any)? = null
@@ -55,18 +56,20 @@
* Returns a factory for the content type of the item.
*
* ContentTypes are generated with the contentType lambda that is passed in. If null is passed in,
- * contentType of all items will default to `null`.
- * If [PagingConfig.enablePlaceholders] is true, LazyPagingItems may return null items. Null
- * items will automatically default to placeholder contentType.
+ * contentType of all items will default to `null`. If [PagingConfig.enablePlaceholders] is true,
+ * LazyPagingItems may return null items. Null items will automatically default to placeholder
+ * contentType.
*
* This factory can be applied to Lazy foundations such as [LazyGridScope.items] or Pagers.
* Examples:
+ *
* @sample androidx.paging.compose.samples.PagingWithLazyGrid
+ *
* @sample androidx.paging.compose.samples.PagingWithLazyList
*
- * @param [contentType] a factory of the content types for the item. The item compositions of
- * the same type could be reused more efficiently. Note that null is a valid type and items of
- * such type will be considered compatible.
+ * @param [contentType] a factory of the content types for the item. The item compositions of the
+ * same type could be reused more efficiently. Note that null is a valid type and items of such
+ * type will be considered compatible.
*/
public fun <T : Any> LazyPagingItems<T>.itemContentType(
contentType: ((item: @JvmSuppressWildcards T) -> Any?)? = null
diff --git a/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyPagingItems.kt
index 71928b8..dbcee1e 100644
--- a/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/commonMain/kotlin/androidx/paging/compose/LazyPagingItems.kt
@@ -42,11 +42,10 @@
import kotlinx.coroutines.withContext
/**
- * The class responsible for accessing the data from a [Flow] of [PagingData].
- * In order to obtain an instance of [LazyPagingItems] use the [collectAsLazyPagingItems] extension
- * method of [Flow] with [PagingData].
- * This instance can be used for Lazy foundations such as [LazyListScope.items] to display data
- * received from the [Flow] of [PagingData].
+ * The class responsible for accessing the data from a [Flow] of [PagingData]. In order to obtain an
+ * instance of [LazyPagingItems] use the [collectAsLazyPagingItems] extension method of [Flow] with
+ * [PagingData]. This instance can be used for Lazy foundations such as [LazyListScope.items] to
+ * display data received from the [Flow] of [PagingData].
*
* Previewing [LazyPagingItems] is supported on a list of mock data. See sample for how to preview
* mock data.
@@ -55,10 +54,9 @@
*
* @param T the type of value used by [PagingData].
*/
-public class LazyPagingItems<T : Any> internal constructor(
- /**
- * the [Flow] object which contains a stream of [PagingData] elements.
- */
+public class LazyPagingItems<T : Any>
+internal constructor(
+ /** the [Flow] object which contains a stream of [PagingData] elements. */
private val flow: Flow<PagingData<T>>
) {
private val mainDispatcher = AndroidUiDispatcher.Main
@@ -66,36 +64,34 @@
/**
* If the [flow] is a SharedFlow, it is expected to be the flow returned by from
* pager.flow.cachedIn(scope) which could contain a cached PagingData. We pass the cached
- * PagingData to the presenter so that if the PagingData contains cached data, the presenter
- * can be initialized with the data prior to collection on pager.
+ * PagingData to the presenter so that if the PagingData contains cached data, the presenter can
+ * be initialized with the data prior to collection on pager.
*/
- private val pagingDataPresenter = object : PagingDataPresenter<T>(
- mainContext = mainDispatcher,
- cachedPagingData =
- if (flow is SharedFlow<PagingData<T>>) flow.replayCache.firstOrNull() else null
- ) {
- override suspend fun presentPagingDataEvent(
- event: PagingDataEvent<T>,
- ) {
- updateItemSnapshotList()
+ private val pagingDataPresenter =
+ object :
+ PagingDataPresenter<T>(
+ mainContext = mainDispatcher,
+ cachedPagingData =
+ if (flow is SharedFlow<PagingData<T>>) flow.replayCache.firstOrNull() else null
+ ) {
+ override suspend fun presentPagingDataEvent(
+ event: PagingDataEvent<T>,
+ ) {
+ updateItemSnapshotList()
+ }
}
- }
/**
* Contains the immutable [ItemSnapshotList] of currently presented items, including any
- * placeholders if they are enabled.
- * Note that similarly to [peek] accessing the items in a list will not trigger any loads.
- * Use [get] to achieve such behavior.
+ * placeholders if they are enabled. Note that similarly to [peek] accessing the items in a list
+ * will not trigger any loads. Use [get] to achieve such behavior.
*/
- var itemSnapshotList by mutableStateOf(
- pagingDataPresenter.snapshot()
- )
+ var itemSnapshotList by mutableStateOf(pagingDataPresenter.snapshot())
private set
- /**
- * The number of items which can be accessed.
- */
- val itemCount: Int get() = itemSnapshotList.size
+ /** The number of items which can be accessed. */
+ val itemCount: Int
+ get() = itemSnapshotList.size
private fun updateItemSnapshotList() {
itemSnapshotList = pagingDataPresenter.snapshot()
@@ -131,8 +127,8 @@
* within the same generation of [PagingData].
*
* [LoadState.Error] can be generated from two types of load requests:
- * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
- * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
+ * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
+ * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
*/
fun retry() {
pagingDataPresenter.retry()
@@ -156,39 +152,31 @@
pagingDataPresenter.refresh()
}
- /**
- * A [CombinedLoadStates] object which represents the current loading state.
- */
- public var loadState: CombinedLoadStates by mutableStateOf(
- pagingDataPresenter.loadStateFlow.value
- ?: CombinedLoadStates(
- refresh = InitialLoadStates.refresh,
- prepend = InitialLoadStates.prepend,
- append = InitialLoadStates.append,
- source = InitialLoadStates
- )
+ /** A [CombinedLoadStates] object which represents the current loading state. */
+ public var loadState: CombinedLoadStates by
+ mutableStateOf(
+ pagingDataPresenter.loadStateFlow.value
+ ?: CombinedLoadStates(
+ refresh = InitialLoadStates.refresh,
+ prepend = InitialLoadStates.prepend,
+ append = InitialLoadStates.append,
+ source = InitialLoadStates
+ )
)
private set
internal suspend fun collectLoadState() {
- pagingDataPresenter.loadStateFlow.filterNotNull().collect {
- loadState = it
- }
+ pagingDataPresenter.loadStateFlow.filterNotNull().collect { loadState = it }
}
internal suspend fun collectPagingData() {
- flow.collectLatest {
- pagingDataPresenter.collectFrom(it)
- }
+ flow.collectLatest { pagingDataPresenter.collectFrom(it) }
}
}
private val IncompleteLoadState = LoadState.NotLoading(false)
-private val InitialLoadStates = LoadStates(
- LoadState.Loading,
- IncompleteLoadState,
- IncompleteLoadState
-)
+private val InitialLoadStates =
+ LoadStates(LoadState.Loading, IncompleteLoadState, IncompleteLoadState)
/**
* Collects values from this [Flow] of [PagingData] and represents them inside a [LazyPagingItems]
@@ -197,8 +185,8 @@
*
* @sample androidx.paging.compose.samples.PagingBackendSample
*
- * @param context the [CoroutineContext] to perform the collection of [PagingData]
- * and [CombinedLoadStates].
+ * @param context the [CoroutineContext] to perform the collection of [PagingData] and
+ * [CombinedLoadStates].
*/
@Composable
public fun <T : Any> Flow<PagingData<T>>.collectAsLazyPagingItems(
@@ -211,9 +199,7 @@
if (context == EmptyCoroutineContext) {
lazyPagingItems.collectPagingData()
} else {
- withContext(context) {
- lazyPagingItems.collectPagingData()
- }
+ withContext(context) { lazyPagingItems.collectPagingData() }
}
}
@@ -221,9 +207,7 @@
if (context == EmptyCoroutineContext) {
lazyPagingItems.collectLoadState()
} else {
- withContext(context) {
- lazyPagingItems.collectLoadState()
- }
+ withContext(context) { lazyPagingItems.collectLoadState() }
}
}
diff --git a/paging/paging-guava/src/main/java/androidx/paging/ListenableFuturePagingData.kt b/paging/paging-guava/src/main/java/androidx/paging/ListenableFuturePagingData.kt
index 4ce7253..8b4aa02 100644
--- a/paging/paging-guava/src/main/java/androidx/paging/ListenableFuturePagingData.kt
+++ b/paging/paging-guava/src/main/java/androidx/paging/ListenableFuturePagingData.kt
@@ -26,8 +26,8 @@
import kotlinx.coroutines.withContext
/**
- * Returns a [PagingData] containing the result of applying the given [transform] to each
- * element, as it is loaded.
+ * Returns a [PagingData] containing the result of applying the given [transform] to each element,
+ * as it is loaded.
*
* @param transform [AsyncFunction] to transform an item of type [T] to [R].
* @param executor [Executor] to run the [AsyncFunction] in.
@@ -38,9 +38,7 @@
transform: AsyncFunction<T, R>,
executor: Executor
): PagingData<R> = map {
- withContext(executor.asCoroutineDispatcher()) {
- transform.apply(it).await()
- }
+ withContext(executor.asCoroutineDispatcher()) { transform.apply(it).await() }
}
/**
@@ -56,9 +54,7 @@
transform: AsyncFunction<T, Iterable<R>>,
executor: Executor
): PagingData<R> = flatMap {
- withContext(executor.asCoroutineDispatcher()) {
- transform.apply(it).await()
- }
+ withContext(executor.asCoroutineDispatcher()) { transform.apply(it).await() }
}
/**
@@ -73,23 +69,22 @@
predicate: AsyncFunction<T, Boolean>,
executor: Executor
): PagingData<T> = filter {
- withContext(executor.asCoroutineDispatcher()) {
- predicate.apply(it).await()
- }
+ withContext(executor.asCoroutineDispatcher()) { predicate.apply(it).await() }
}
/**
- * Returns a [PagingData] containing each original element, with an optional separator generated
- * by [generator], given the elements before and after (or null, in boundary conditions).
+ * Returns a [PagingData] containing each original element, with an optional separator generated by
+ * [generator], given the elements before and after (or null, in boundary conditions).
*
* Note that this transform is applied asynchronously, as pages are loaded. Potential separators
* between pages are only computed once both pages are loaded.
*
* @param generator [AsyncFunction] used to generate separator between two [AdjacentItems] or the
- * header or footer if either [AdjacentItems.before] or [AdjacentItems.after] is `null`.
+ * header or footer if either [AdjacentItems.before] or [AdjacentItems.after] is `null`.
* @param executor [Executor] to run the [AsyncFunction] in.
*
* @sample androidx.paging.samples.insertSeparatorsFutureSample
+ *
* @sample androidx.paging.samples.insertSeparatorsUiModelFutureSample
*/
@JvmName("insertSeparators")
@@ -103,7 +98,5 @@
}
}
-/**
- * Represents a pair of adjacent items, null values are used to signal boundary conditions.
- */
+/** Represents a pair of adjacent items, null values are used to signal boundary conditions. */
data class AdjacentItems<T>(val before: T?, val after: T?)
diff --git a/paging/paging-guava/src/main/java/androidx/paging/ListenableFutureRemoteMediator.kt b/paging/paging-guava/src/main/java/androidx/paging/ListenableFutureRemoteMediator.kt
index a28145c..fbeb9b7 100644
--- a/paging/paging-guava/src/main/java/androidx/paging/ListenableFutureRemoteMediator.kt
+++ b/paging/paging-guava/src/main/java/androidx/paging/ListenableFutureRemoteMediator.kt
@@ -16,30 +16,24 @@
package androidx.paging
-import androidx.paging.LoadType.APPEND
-import androidx.paging.LoadType.PREPEND
-import androidx.paging.LoadType.REFRESH
import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.guava.await
-/**
- * [ListenableFuture]-based compatibility wrapper around [RemoteMediator]'s suspending APIs.
- */
+/** [ListenableFuture]-based compatibility wrapper around [RemoteMediator]'s suspending APIs. */
@ExperimentalPagingApi
abstract class ListenableFutureRemoteMediator<Key : Any, Value : Any> :
RemoteMediator<Key, Value>() {
/**
* Implement this method to load additional remote data, which will then be stored for the
* [PagingSource] to access. These loads take one of two forms:
- * * type == [LoadType.PREPEND] / [LoadType.APPEND]
- * The [PagingSource] has loaded a 'boundary' page, with a `null` adjacent key. This means
- * this method should load additional remote data to append / prepend as appropriate, and store
- * it locally.
- * * type == [LoadType.REFRESH]
- * The app (or [initialize]) has requested a remote refresh of data. This means the method
- * should generally load remote data, and **replace** all local data.
+ * * type == [LoadType.PREPEND] / [LoadType.APPEND] The [PagingSource] has loaded a
+ * 'boundary' page, with a `null` adjacent key. This means this method should load
+ * additional remote data to append / prepend as appropriate, and store it locally.
+ * * type == [LoadType.REFRESH] The app (or [initialize]) has requested a remote refresh of
+ * data. This means the method should generally load remote data, and **replace** all local
+ * data.
*
* The runtime of this method defines loading state behavior in boundary conditions, which
* affects e.g., [LoadState] callbacks registered to [androidx.paging.PagingDataAdapter].
@@ -49,17 +43,16 @@
* [LoadType.APPEND] or both. [LoadType.REFRESH] occurs as a result of [initialize].
*
* @param loadType [LoadType] of the boundary condition which triggered this callback.
- * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
- * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
- * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
- * refresh - either driven by the UI, or by [initialize].
- * @param state A copy of the state including the list of pages currently held in
- * memory of the currently presented [PagingData] at the time of starting the load. E.g. for
- * load(loadType = END), you can use the page or item at the end as input for what to load from
- * the network.
+ * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
+ * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
+ * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
+ * refresh - either driven by the UI, or by [initialize].
*
+ * @param state A copy of the state including the list of pages currently held in memory of the
+ * currently presented [PagingData] at the time of starting the load. E.g. for load(loadType =
+ * END), you can use the page or item at the end as input for what to load from the network.
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
- * there's more data available.
+ * there's more data available.
*/
@Suppress("AsyncSuffixFuture")
abstract fun loadFuture(
@@ -73,12 +66,12 @@
* This function runs to completion before any loading is performed.
*
* @return [InitializeAction] indicating the action to take after initialization:
- * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
- * [LoadType.REFRESH], to update paginated content when the stream is initialized.
- * Note: This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
- * [REFRESH] succeeds.
- * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a
- * refresh request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
+ * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
+ * [LoadType.REFRESH], to update paginated content when the stream is initialized. Note:
+ * This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
+ * [REFRESH] succeeds.
+ * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a refresh
+ * request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
*/
@Suppress("AsyncSuffixFuture")
open fun initializeFuture(): ListenableFuture<InitializeAction> {
diff --git a/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingDataTest.kt b/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingDataTest.kt
index ea38a9f..6c50634 100644
--- a/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingDataTest.kt
+++ b/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingDataTest.kt
@@ -38,51 +38,55 @@
private val presenter = TestPagingDataPresenter<String>(testDispatcher)
@Test
- fun map() = testScope.runTest {
- val transformed = original.mapAsync(
- AsyncFunction<String, String> {
- Futures.immediateFuture(it + it)
- },
- testDispatcher.asExecutor()
- )
- presenter.collectFrom(transformed)
- assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
- }
+ fun map() =
+ testScope.runTest {
+ val transformed =
+ original.mapAsync(
+ AsyncFunction<String, String> { Futures.immediateFuture(it + it) },
+ testDispatcher.asExecutor()
+ )
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
+ }
@Test
- fun flatMap() = testScope.runTest {
- val transformed = original.flatMapAsync(
- AsyncFunction<String, Iterable<String>> {
- Futures.immediateFuture(listOf(it!!, it))
- },
- testDispatcher.asExecutor()
- )
- presenter.collectFrom(transformed)
- assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
- }
+ fun flatMap() =
+ testScope.runTest {
+ val transformed =
+ original.flatMapAsync(
+ AsyncFunction<String, Iterable<String>> {
+ Futures.immediateFuture(listOf(it!!, it))
+ },
+ testDispatcher.asExecutor()
+ )
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
+ }
@Test
- fun filter() = testScope.runTest {
- val filtered = original.filterAsync(
- AsyncFunction {
- Futures.immediateFuture(it != "b")
- },
- testDispatcher.asExecutor()
- )
- presenter.collectFrom(filtered)
- assertEquals(listOf("a", "c"), presenter.currentList)
- }
+ fun filter() =
+ testScope.runTest {
+ val filtered =
+ original.filterAsync(
+ AsyncFunction { Futures.immediateFuture(it != "b") },
+ testDispatcher.asExecutor()
+ )
+ presenter.collectFrom(filtered)
+ assertEquals(listOf("a", "c"), presenter.currentList)
+ }
@Test
- fun insertSeparators() = testScope.runTest {
- val separated = original.insertSeparatorsAsync(
- AsyncFunction<AdjacentItems<String>, String?> {
- val (before, after) = it!!
- Futures.immediateFuture(if (before == null || after == null) null else "|")
- },
- testDispatcher.asExecutor()
- )
- presenter.collectFrom(separated)
- assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
- }
+ fun insertSeparators() =
+ testScope.runTest {
+ val separated =
+ original.insertSeparatorsAsync(
+ AsyncFunction<AdjacentItems<String>, String?> {
+ val (before, after) = it!!
+ Futures.immediateFuture(if (before == null || after == null) null else "|")
+ },
+ testDispatcher.asExecutor()
+ )
+ presenter.collectFrom(separated)
+ assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
+ }
}
diff --git a/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt b/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
index ae8a63a..feff4ff 100644
--- a/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
+++ b/paging/paging-guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
@@ -40,27 +40,31 @@
)
}
- private val pagingSource = object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- return loadInternal(params)
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
-
- private val listenableFuturePagingSource = object : ListenableFuturePagingSource<Int, Int>() {
- override fun loadFuture(params: LoadParams<Int>): ListenableFuture<LoadResult<Int, Int>> {
- val future = SettableFuture.create<LoadResult<Int, Int>>()
- try {
- future.set(loadInternal(params))
- } catch (e: IllegalArgumentException) {
- future.setException(e)
+ private val pagingSource =
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ return loadInternal(params)
}
- return future
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
}
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
+ private val listenableFuturePagingSource =
+ object : ListenableFuturePagingSource<Int, Int>() {
+ override fun loadFuture(
+ params: LoadParams<Int>
+ ): ListenableFuture<LoadResult<Int, Int>> {
+ val future = SettableFuture.create<LoadResult<Int, Int>>()
+ try {
+ future.set(loadInternal(params))
+ } catch (e: IllegalArgumentException) {
+ future.setException(e)
+ }
+ return future
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
@Test
fun basic() = runBlocking {
diff --git a/paging/paging-guava/src/test/java/androidx/paging/ListenableFutureRemoteMediatorTest.kt b/paging/paging-guava/src/test/java/androidx/paging/ListenableFutureRemoteMediatorTest.kt
index 2bf2473..62801e6 100644
--- a/paging/paging-guava/src/test/java/androidx/paging/ListenableFutureRemoteMediatorTest.kt
+++ b/paging/paging-guava/src/test/java/androidx/paging/ListenableFutureRemoteMediatorTest.kt
@@ -33,30 +33,32 @@
class ListenableFutureRemoteMediatorTest {
@Test
fun initializeFuture() = runTest {
- val remoteMediator = object : ListenableFutureRemoteMediator<Int, Int>() {
- override fun loadFuture(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): ListenableFuture<MediatorResult> {
- fail("Unexpected call")
- }
+ val remoteMediator =
+ object : ListenableFutureRemoteMediator<Int, Int>() {
+ override fun loadFuture(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): ListenableFuture<MediatorResult> {
+ fail("Unexpected call")
+ }
- override fun initializeFuture() = Futures.immediateFuture(SKIP_INITIAL_REFRESH)
- }
+ override fun initializeFuture() = Futures.immediateFuture(SKIP_INITIAL_REFRESH)
+ }
assertEquals(SKIP_INITIAL_REFRESH, remoteMediator.initialize())
}
@Test
fun initializeFutureDefault() = runTest {
- val remoteMediator = object : ListenableFutureRemoteMediator<Int, Int>() {
- override fun loadFuture(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): ListenableFuture<MediatorResult> {
- fail("Unexpected call")
+ val remoteMediator =
+ object : ListenableFutureRemoteMediator<Int, Int>() {
+ override fun loadFuture(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): ListenableFuture<MediatorResult> {
+ fail("Unexpected call")
+ }
}
- }
assertEquals(LAUNCH_INITIAL_REFRESH, remoteMediator.initialize())
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 23de20f..88f70e2 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -53,12 +53,13 @@
private fun createDiffer(
listUpdateCallback: ListUpdateCallback = IGNORE_CALLBACK
): AsyncPagedListDiffer<String> {
- val differ = AsyncPagedListDiffer(
- listUpdateCallback,
- AsyncDifferConfig.Builder(STRING_DIFF_CALLBACK)
- .setBackgroundThreadExecutor(diffThread)
- .build()
- )
+ val differ =
+ AsyncPagedListDiffer(
+ listUpdateCallback,
+ AsyncDifferConfig.Builder(STRING_DIFF_CALLBACK)
+ .setBackgroundThreadExecutor(diffThread)
+ .build()
+ )
// by default, use ArchExecutor
assertEquals(differ.mainThreadExecutor, ArchTaskExecutor.getMainThreadExecutor())
differ.mainThreadExecutor = mainThread
@@ -77,9 +78,7 @@
.setNotifyExecutor(mainThread)
.setFetchExecutor(pageLoadingThread)
.build()
- .also {
- pageLoadingThread.autoRun = false
- }
+ .also { pageLoadingThread.autoRun = false }
}
@Test
@@ -158,79 +157,45 @@
assertEquals(0, differ.itemCount)
- differ.submitList(
- StringPagedList(
- leadingNulls = 0,
- trailingNulls = 0, "a", "b"
- )
- )
+ differ.submitList(StringPagedList(leadingNulls = 0, trailingNulls = 0, "a", "b"))
- fun submitAndAssert(
- stringPagedList: PagedList<String>,
- vararg expected: Any?
- ) {
+ fun submitAndAssert(stringPagedList: PagedList<String>, vararg expected: Any?) {
val prevEventsSize = callback.allEvents.size
differ.submitList(stringPagedList)
drain()
- assertThat(
- callback.allEvents.subList(prevEventsSize, callback.allEvents.size)
- ).containsExactlyElementsIn(
- expected
- ).inOrder()
+ assertThat(callback.allEvents.subList(prevEventsSize, callback.allEvents.size))
+ .containsExactlyElementsIn(expected)
+ .inOrder()
}
// prepend nulls
submitAndAssert(
- StringPagedList(
- leadingNulls = 4,
- trailingNulls = 0,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 4, trailingNulls = 0, items = arrayOf("a", "b")),
OnInsertedEvent(0, 4)
)
// remove leading nulls
submitAndAssert(
- StringPagedList(
- leadingNulls = 0,
- trailingNulls = 0,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 0, trailingNulls = 0, items = arrayOf("a", "b")),
OnRemovedEvent(0, 4)
)
// append nulls
submitAndAssert(
- StringPagedList(
- leadingNulls = 0,
- trailingNulls = 3,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 0, trailingNulls = 3, items = arrayOf("a", "b")),
OnInsertedEvent(2, 3)
)
// remove trailing nulls
submitAndAssert(
- StringPagedList(
- leadingNulls = 0,
- trailingNulls = 0,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 0, trailingNulls = 0, items = arrayOf("a", "b")),
OnRemovedEvent(2, 3)
)
// add nulls on both ends
submitAndAssert(
- StringPagedList(
- leadingNulls = 3,
- trailingNulls = 2,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 3, trailingNulls = 2, items = arrayOf("a", "b")),
OnInsertedEvent(0, 3),
OnInsertedEvent(5, 2)
)
// remove some nulls from both ends
submitAndAssert(
- StringPagedList(
- leadingNulls = 1,
- trailingNulls = 1,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 1, trailingNulls = 1, items = arrayOf("a", "b")),
OnRemovedEvent(0, 2),
OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
OnRemovedEvent(4, 1),
@@ -238,22 +203,14 @@
)
// add to leading, remove from trailing
submitAndAssert(
- StringPagedList(
- leadingNulls = 5,
- trailingNulls = 0,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 5, trailingNulls = 0, items = arrayOf("a", "b")),
OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
OnInsertedEvent(0, 4),
OnRemovedEvent(7, 1)
)
// add trailing, remove from leading
submitAndAssert(
- StringPagedList(
- leadingNulls = 1,
- trailingNulls = 3,
- items = arrayOf("a", "b")
- ),
+ StringPagedList(leadingNulls = 1, trailingNulls = 3, items = arrayOf("a", "b")),
OnRemovedEvent(0, 4),
OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
OnInsertedEvent(3, 3)
@@ -288,11 +245,12 @@
@Test
fun pagingInContent() {
- val config = PagedList.Config.Builder()
- .setInitialLoadSizeHint(4)
- .setPageSize(2)
- .setPrefetchDistance(2)
- .build()
+ val config =
+ PagedList.Config.Builder()
+ .setInitialLoadSizeHint(4)
+ .setPageSize(2)
+ .setPrefetchDistance(2)
+ .build()
val callback = ListUpdateCallbackFake()
val differ = createDiffer(callback)
@@ -338,9 +296,7 @@
@Test
fun simpleSwap() {
// Page size large enough to load
- val config = PagedList.Config.Builder()
- .setPageSize(50)
- .build()
+ val config = PagedList.Config.Builder().setPageSize(50).build()
val callback = ListUpdateCallbackFake()
val differ = createDiffer(callback)
@@ -372,11 +328,12 @@
@Test
fun oldListUpdateIgnoredWhileDiffing() {
- val config = PagedList.Config.Builder()
- .setInitialLoadSizeHint(4)
- .setPageSize(2)
- .setPrefetchDistance(2)
- .build()
+ val config =
+ PagedList.Config.Builder()
+ .setInitialLoadSizeHint(4)
+ .setPageSize(2)
+ .setPrefetchDistance(2)
+ .build()
val callback = ListUpdateCallbackFake()
val differ = createDiffer(callback)
@@ -415,11 +372,7 @@
@Test
fun newPageChangesDeferredDuringDiff() {
- val config = Config(
- initialLoadSizeHint = 4,
- pageSize = 2,
- prefetchDistance = 2
- )
+ val config = Config(initialLoadSizeHint = 4, pageSize = 2, prefetchDistance = 2)
val callback = ListUpdateCallbackFake()
val differ = createDiffer(callback)
@@ -463,30 +416,29 @@
// provides access to differ, which must be constructed after callback
val differAccessor = arrayOf<AsyncPagedListDiffer<*>?>(null)
- val callback = object : ListUpdateCallback {
- override fun onInserted(position: Int, count: Int) {
- assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
- }
+ val callback =
+ object : ListUpdateCallback {
+ override fun onInserted(position: Int, count: Int) {
+ assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
+ }
- override fun onRemoved(position: Int, count: Int) {
- assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
- }
+ override fun onRemoved(position: Int, count: Int) {
+ assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
+ }
- override fun onMoved(fromPosition: Int, toPosition: Int) {
- fail("not expected")
- }
+ override fun onMoved(fromPosition: Int, toPosition: Int) {
+ fail("not expected")
+ }
- override fun onChanged(position: Int, count: Int, payload: Any?) {
- fail("not expected")
+ override fun onChanged(position: Int, count: Int, payload: Any?) {
+ fail("not expected")
+ }
}
- }
val differ = createDiffer(callback)
differAccessor[0] = differ
- val config = PagedList.Config.Builder()
- .setPageSize(20)
- .build()
+ val config = PagedList.Config.Builder().setPageSize(20).build()
// in the fast-add case...
expectedCount[0] = 5
@@ -512,10 +464,7 @@
fun loadAroundHandlePrepend() {
val differ = createDiffer()
- val config = PagedList.Config.Builder()
- .setPageSize(5)
- .setEnablePlaceholders(false)
- .build()
+ val config = PagedList.Config.Builder().setPageSize(5).setEnablePlaceholders(false).build()
// initialize, initial key position is 0
differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(10, 20), 0))
@@ -532,12 +481,13 @@
@Test
fun submitSubset() {
// Page size large enough to load
- val config = PagedList.Config.Builder()
- .setInitialLoadSizeHint(4)
- .setPageSize(2)
- .setPrefetchDistance(1)
- .setEnablePlaceholders(false)
- .build()
+ val config =
+ PagedList.Config.Builder()
+ .setInitialLoadSizeHint(4)
+ .setPageSize(2)
+ .setPrefetchDistance(1)
+ .setEnablePlaceholders(false)
+ .build()
val differ = createDiffer()
@@ -657,32 +607,30 @@
@Test
fun submitList_initialPagedListEvents() {
- val config = PagedList.Config.Builder().apply {
- setPageSize(1)
- }.build()
+ val config = PagedList.Config.Builder().apply { setPageSize(1) }.build()
val listUpdateCallback = ListUpdateCapture()
val loadStateListener = LoadStateCapture()
- val differ = createDiffer(listUpdateCallback).apply {
- addLoadStateListener(loadStateListener)
- }
+ val differ =
+ createDiffer(listUpdateCallback).apply { addLoadStateListener(loadStateListener) }
// Initial state.
drain()
assertThat(listUpdateCallback.newEvents()).isEmpty()
- assertThat(loadStateListener.newEvents()).containsExactly(
- LoadStateEvent(
- loadType = LoadType.REFRESH,
- loadState = LoadState.NotLoading(endOfPaginationReached = false)
- ),
- LoadStateEvent(
- loadType = LoadType.PREPEND,
- loadState = LoadState.NotLoading(endOfPaginationReached = false)
- ),
- LoadStateEvent(
- loadType = LoadType.APPEND,
- loadState = LoadState.NotLoading(endOfPaginationReached = false)
- ),
- )
+ assertThat(loadStateListener.newEvents())
+ .containsExactly(
+ LoadStateEvent(
+ loadType = LoadType.REFRESH,
+ loadState = LoadState.NotLoading(endOfPaginationReached = false)
+ ),
+ LoadStateEvent(
+ loadType = LoadType.PREPEND,
+ loadState = LoadState.NotLoading(endOfPaginationReached = false)
+ ),
+ LoadStateEvent(
+ loadType = LoadType.APPEND,
+ loadState = LoadState.NotLoading(endOfPaginationReached = false)
+ ),
+ )
// First InitialPagedList.
differ.submitList(
@@ -695,25 +643,24 @@
)
)
drain()
- assertThat(listUpdateCallback.newEvents()).containsExactly(
- ListUpdateEvent.Inserted(position = 0, count = 0),
- )
+ assertThat(listUpdateCallback.newEvents())
+ .containsExactly(
+ ListUpdateEvent.Inserted(position = 0, count = 0),
+ )
assertThat(loadStateListener.newEvents()).isEmpty()
// Real PagedList with non-empty data.
- differ.submitList(
- createPagedListFromListAndPos(config, ALPHABET_LIST, 0)
- )
+ differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 0))
drain()
- assertThat(listUpdateCallback.newEvents()).containsExactly(
- ListUpdateEvent.Inserted(position = 0, count = 26)
- )
- assertThat(loadStateListener.newEvents()).containsExactly(
- LoadStateEvent(
- loadType = LoadType.PREPEND,
- loadState = LoadState.NotLoading(endOfPaginationReached = true)
- ),
- )
+ assertThat(listUpdateCallback.newEvents())
+ .containsExactly(ListUpdateEvent.Inserted(position = 0, count = 26))
+ assertThat(loadStateListener.newEvents())
+ .containsExactly(
+ LoadStateEvent(
+ loadType = LoadType.PREPEND,
+ loadState = LoadState.NotLoading(endOfPaginationReached = true)
+ ),
+ )
// Second InitialPagedList.
differ.submitList(
@@ -727,16 +674,14 @@
)
drain()
assertThat(listUpdateCallback.newEvents()).isEmpty()
- assertThat(loadStateListener.newEvents()).containsExactly(
- LoadStateEvent(
- loadType = LoadType.REFRESH,
- loadState = LoadState.Loading
- ),
- LoadStateEvent(
- loadType = LoadType.PREPEND,
- loadState = LoadState.NotLoading(endOfPaginationReached = false)
- ),
- )
+ assertThat(loadStateListener.newEvents())
+ .containsExactly(
+ LoadStateEvent(loadType = LoadType.REFRESH, loadState = LoadState.Loading),
+ LoadStateEvent(
+ loadType = LoadType.PREPEND,
+ loadState = LoadState.NotLoading(endOfPaginationReached = false)
+ ),
+ )
}
private fun drainExceptDiffThread() {
@@ -759,24 +704,26 @@
companion object {
private val ALPHABET_LIST = List(26) { "" + ('a' + it) }
- private val STRING_DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
- override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ private val STRING_DIFF_CALLBACK =
+ object : DiffUtil.ItemCallback<String>() {
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
}
- override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ private val IGNORE_CALLBACK =
+ object : ListUpdateCallback {
+ override fun onInserted(position: Int, count: Int) {}
+
+ override fun onRemoved(position: Int, count: Int) {}
+
+ override fun onMoved(fromPosition: Int, toPosition: Int) {}
+
+ override fun onChanged(position: Int, count: Int, payload: Any?) {}
}
- }
-
- private val IGNORE_CALLBACK = object : ListUpdateCallback {
- override fun onInserted(position: Int, count: Int) {}
-
- override fun onRemoved(position: Int, count: Int) {}
-
- override fun onMoved(fromPosition: Int, toPosition: Int) {}
-
- override fun onChanged(position: Int, count: Int, payload: Any?) {}
- }
}
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 98b8b06..28116aa 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -72,562 +72,515 @@
private val testScope = TestScope(StandardTestDispatcher())
@get:Rule
- val dispatcherRule = MainDispatcherRule(
- testScope.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
- )
+ val dispatcherRule =
+ MainDispatcherRule(
+ testScope.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
+ )
private val listUpdateCapture = ListUpdateCapture()
- private val differ = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ private val differ =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- updateCallback = listUpdateCapture,
- workerDispatcher = Dispatchers.Main
- )
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ updateCallback = listUpdateCapture,
+ workerDispatcher = Dispatchers.Main
+ )
@SdkSuppress(minSdkVersion = 21) // b/189492631
@Test
- fun performDiff_fastPathLoadStates() = testScope.runTest {
- val loadEvents = mutableListOf<CombinedLoadStates>()
- differ.addLoadStateListener { loadEvents.add(it) }
+ fun performDiff_fastPathLoadStates() =
+ testScope.runTest {
+ val loadEvents = mutableListOf<CombinedLoadStates>()
+ differ.addLoadStateListener { loadEvents.add(it) }
- val pager = Pager(
- config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2
- ),
- initialKey = 50
- ) {
- TestPagingSource()
- }
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
+ }
- val job = launch {
- pager.flow.collect {
- differ.submitData(it)
- }
- }
+ val job = launch { pager.flow.collect { differ.submitData(it) } }
- advanceUntilIdle()
+ advanceUntilIdle()
- // Assert that all load state updates are sent, even when differ enters fast path for
- // empty previous list.
- assertEvents(
- listOf(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
- ),
- loadEvents
- )
- loadEvents.clear()
+ // Assert that all load state updates are sent, even when differ enters fast path for
+ // empty previous list.
+ assertEvents(
+ listOf(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
+ ),
+ loadEvents
+ )
+ loadEvents.clear()
- job.cancel()
+ job.cancel()
- differ.submitData(
- TestLifecycleOwner().lifecycle, PagingData.empty(
- sourceLoadStates = loadStates(
- refresh = NotLoading(endOfPaginationReached = false),
- prepend = NotLoading(endOfPaginationReached = true),
- append = NotLoading(endOfPaginationReached = true),
+ differ.submitData(
+ TestLifecycleOwner().lifecycle,
+ PagingData.empty(
+ sourceLoadStates =
+ loadStates(
+ refresh = NotLoading(endOfPaginationReached = false),
+ prepend = NotLoading(endOfPaginationReached = true),
+ append = NotLoading(endOfPaginationReached = true),
+ )
)
)
- )
- advanceUntilIdle()
+ advanceUntilIdle()
- // Assert that all load state updates are sent, even when differ enters fast path for
- // empty next list.
- assertEvents(
- expected = listOf(
- localLoadStatesOf(
- refreshLocal = NotLoading(endOfPaginationReached = false),
- prependLocal = NotLoading(endOfPaginationReached = true),
- appendLocal = NotLoading(endOfPaginationReached = true),
- ),
- ),
- actual = loadEvents
- )
- }
+ // Assert that all load state updates are sent, even when differ enters fast path for
+ // empty next list.
+ assertEvents(
+ expected =
+ listOf(
+ localLoadStatesOf(
+ refreshLocal = NotLoading(endOfPaginationReached = false),
+ prependLocal = NotLoading(endOfPaginationReached = true),
+ appendLocal = NotLoading(endOfPaginationReached = true),
+ ),
+ ),
+ actual = loadEvents
+ )
+ }
@SdkSuppress(minSdkVersion = 21) // b/189492631
@Test
- fun performDiff_fastPathLoadStatesFlow() = testScope.runTest {
- val loadEvents = mutableListOf<CombinedLoadStates>()
- val loadEventJob = launch {
- differ.loadStateFlow.collect { loadEvents.add(it) }
- }
+ fun performDiff_fastPathLoadStatesFlow() =
+ testScope.runTest {
+ val loadEvents = mutableListOf<CombinedLoadStates>()
+ val loadEventJob = launch { differ.loadStateFlow.collect { loadEvents.add(it) } }
- val pager = Pager(
- config = PagingConfig(
- pageSize = 2,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2
- ),
- initialKey = 50
- ) {
- TestPagingSource()
- }
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 2,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
+ }
- val job = launch {
- pager.flow.collect {
- differ.submitData(it)
- }
- }
+ val job = launch { pager.flow.collect { differ.submitData(it) } }
- advanceUntilIdle()
+ advanceUntilIdle()
- // Assert that all load state updates are sent, even when differ enters fast path for
- // empty previous list.
- assertEvents(
- listOf(
- localLoadStatesOf(refreshLocal = Loading),
- localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
- ),
- loadEvents
- )
- loadEvents.clear()
+ // Assert that all load state updates are sent, even when differ enters fast path for
+ // empty previous list.
+ assertEvents(
+ listOf(
+ localLoadStatesOf(refreshLocal = Loading),
+ localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
+ ),
+ loadEvents
+ )
+ loadEvents.clear()
- job.cancel()
+ job.cancel()
- differ.submitData(
- TestLifecycleOwner().lifecycle, PagingData.empty(
- sourceLoadStates = loadStates(
- refresh = NotLoading(endOfPaginationReached = false),
- prepend = NotLoading(endOfPaginationReached = true),
- append = NotLoading(endOfPaginationReached = true),
+ differ.submitData(
+ TestLifecycleOwner().lifecycle,
+ PagingData.empty(
+ sourceLoadStates =
+ loadStates(
+ refresh = NotLoading(endOfPaginationReached = false),
+ prepend = NotLoading(endOfPaginationReached = true),
+ append = NotLoading(endOfPaginationReached = true),
+ )
)
)
- )
- advanceUntilIdle()
-
- // Assert that all load state updates are sent, even when differ enters fast path for
- // empty next list.
- assertEvents(
- expected = listOf(
- localLoadStatesOf(
- refreshLocal = NotLoading(endOfPaginationReached = false),
- prependLocal = NotLoading(endOfPaginationReached = true),
- appendLocal = NotLoading(endOfPaginationReached = true),
- ),
- ),
- actual = loadEvents
- )
-
- loadEventJob.cancel()
- }
-
- @Test
- fun lastAccessedIndex() = testScope.runTest {
- withContext(coroutineContext) {
- var currentPagedSource: TestPagingSource? = null
- val pager = Pager(
- config = PagingConfig(
- pageSize = 1,
- prefetchDistance = 1,
- enablePlaceholders = true,
- initialLoadSize = 2
- ),
- initialKey = 50
- ) {
- currentPagedSource = TestPagingSource()
- currentPagedSource!!
- }
-
- val job = launch { pager.flow.collectLatest { differ.submitData(it) } }
-
- // Load REFRESH [50, 51]
advanceUntilIdle()
+ // Assert that all load state updates are sent, even when differ enters fast path for
+ // empty next list.
assertEvents(
- listOf(
- Inserted(0, 100), // [(50 placeholders), 50, 51, (48 placeholders)]
- ),
- listUpdateCapture.newEvents()
+ expected =
+ listOf(
+ localLoadStatesOf(
+ refreshLocal = NotLoading(endOfPaginationReached = false),
+ prependLocal = NotLoading(endOfPaginationReached = true),
+ appendLocal = NotLoading(endOfPaginationReached = true),
+ ),
+ ),
+ actual = loadEvents
)
- // Load APPEND [52] to fulfill prefetch distance
- differ.getItem(51)
- advanceUntilIdle()
-
- assertEvents(
- // TODO(b/182510751): Every change event here should have payload.
- listOf(
- Changed(52, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
- ),
- listUpdateCapture.newEvents()
- )
-
- // Load REFRESH [51, 52]
- currentPagedSource!!.invalidate()
- advanceUntilIdle()
-
- // UI access refreshed items. Load PREPEND [50] to fulfill prefetch distance
- differ.getItem(51)
- advanceUntilIdle()
-
- assertEvents(
- // TODO(b/182510751): Every change event here should have payload.
- listOf(
- // refresh
- Changed(50, 1, ITEM_TO_PLACEHOLDER), // 50 got unloaded
- // fix prefetch, 50 got reloaded
- Changed(50, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
- ),
- listUpdateCapture.newEvents()
- )
-
- job.cancel()
+ loadEventJob.cancel()
}
- }
@Test
- fun presentData_cancelsLastSubmit() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
- val pager2 = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
+ fun lastAccessedIndex() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ var currentPagedSource: TestPagingSource? = null
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 2
+ ),
+ initialKey = 50
+ ) {
+ currentPagedSource = TestPagingSource()
+ currentPagedSource!!
+ }
- val lifecycle = TestLifecycleOwner()
- var jobSubmitted = false
- val job = launch {
- pager.flow.collectLatest {
- differ.submitData(lifecycle.lifecycle, it)
- jobSubmitted = true
- }
+ val job = launch { pager.flow.collectLatest { differ.submitData(it) } }
+
+ // Load REFRESH [50, 51]
+ advanceUntilIdle()
+
+ assertEvents(
+ listOf(
+ Inserted(0, 100), // [(50 placeholders), 50, 51, (48 placeholders)]
+ ),
+ listUpdateCapture.newEvents()
+ )
+
+ // Load APPEND [52] to fulfill prefetch distance
+ differ.getItem(51)
+ advanceUntilIdle()
+
+ assertEvents(
+ // TODO(b/182510751): Every change event here should have payload.
+ listOf(
+ Changed(52, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
+ ),
+ listUpdateCapture.newEvents()
+ )
+
+ // Load REFRESH [51, 52]
+ currentPagedSource!!.invalidate()
+ advanceUntilIdle()
+
+ // UI access refreshed items. Load PREPEND [50] to fulfill prefetch distance
+ differ.getItem(51)
+ advanceUntilIdle()
+
+ assertEvents(
+ // TODO(b/182510751): Every change event here should have payload.
+ listOf(
+ // refresh
+ Changed(50, 1, ITEM_TO_PLACEHOLDER), // 50 got unloaded
+ // fix prefetch, 50 got reloaded
+ Changed(50, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
+ ),
+ listUpdateCapture.newEvents()
+ )
+
+ job.cancel()
}
-
- advanceUntilIdle()
-
- val job2 = launch {
- pager2.flow.collectLatest {
- differ.submitData(it)
- }
- }
-
- advanceUntilIdle()
-
- assertTrue(jobSubmitted)
-
- job.cancel()
- job2.cancel()
}
- }
@Test
- fun submitData_cancelsLast() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
- val pager2 = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
+ fun presentData_cancelsLastSubmit() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
+ val pager2 = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
- val lifecycle = TestLifecycleOwner()
- var jobSubmitted = false
- val job = launch {
- pager.flow.collectLatest {
- differ.submitData(lifecycle.lifecycle, it)
- jobSubmitted = true
+ val lifecycle = TestLifecycleOwner()
+ var jobSubmitted = false
+ val job = launch {
+ pager.flow.collectLatest {
+ differ.submitData(lifecycle.lifecycle, it)
+ jobSubmitted = true
+ }
}
+
+ advanceUntilIdle()
+
+ val job2 = launch { pager2.flow.collectLatest { differ.submitData(it) } }
+
+ advanceUntilIdle()
+
+ assertTrue(jobSubmitted)
+
+ job.cancel()
+ job2.cancel()
}
-
- advanceUntilIdle()
-
- var job2Submitted = false
- val job2 = launch {
- pager2.flow.collectLatest {
- differ.submitData(lifecycle.lifecycle, it)
- job2Submitted = true
- }
- }
-
- advanceUntilIdle()
-
- assertTrue(jobSubmitted)
- assertTrue(job2Submitted)
-
- job.cancel()
- job2.cancel()
}
- }
+
+ @Test
+ fun submitData_cancelsLast() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
+ val pager2 = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
+
+ val lifecycle = TestLifecycleOwner()
+ var jobSubmitted = false
+ val job = launch {
+ pager.flow.collectLatest {
+ differ.submitData(lifecycle.lifecycle, it)
+ jobSubmitted = true
+ }
+ }
+
+ advanceUntilIdle()
+
+ var job2Submitted = false
+ val job2 = launch {
+ pager2.flow.collectLatest {
+ differ.submitData(lifecycle.lifecycle, it)
+ job2Submitted = true
+ }
+ }
+
+ advanceUntilIdle()
+
+ assertTrue(jobSubmitted)
+ assertTrue(job2Submitted)
+
+ job.cancel()
+ job2.cancel()
+ }
+ }
@SdkSuppress(minSdkVersion = 21) // b/189492631
@Test
- fun submitData_guaranteesOrder() = testScope.runTest {
- val pager = Pager(config = PagingConfig(2, enablePlaceholders = false), initialKey = 50) {
- TestPagingSource()
- }
-
- val reversedDispatcher = object : CoroutineDispatcher() {
- var lastBlock: Runnable? = null
- override fun dispatch(context: CoroutineContext, block: Runnable) {
- // Save the first block to be dispatched, then run second one first after receiving
- // calls to dispatch both.
- val lastBlock = lastBlock
- if (lastBlock == null) {
- this.lastBlock = block
- } else {
- block.run()
- lastBlock.run()
+ fun submitData_guaranteesOrder() =
+ testScope.runTest {
+ val pager =
+ Pager(config = PagingConfig(2, enablePlaceholders = false), initialKey = 50) {
+ TestPagingSource()
}
- }
- }
- val lifecycle = TestLifecycleOwner()
- differ.submitData(lifecycle.lifecycle, PagingData.empty())
- differ.submitData(lifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+ val reversedDispatcher =
+ object : CoroutineDispatcher() {
+ var lastBlock: Runnable? = null
- // Ensure the second call wins when dispatched in order of execution.
- advanceUntilIdle()
- assertEquals(6, differ.itemCount)
-
- val reversedLifecycle = TestLifecycleOwner(coroutineDispatcher = reversedDispatcher)
- differ.submitData(reversedLifecycle.lifecycle, PagingData.empty())
- differ.submitData(reversedLifecycle.lifecycle, pager.flow.first()) // Loads 6 items
-
- // Ensure the second call wins when dispatched in reverse order of execution.
- advanceUntilIdle()
- assertEquals(6, differ.itemCount)
- }
-
- @Test
- fun submitData_cancelsLastSuspendSubmit() = testScope.runTest {
- withContext(coroutineContext) {
- val pager = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
- val pager2 = Pager(
- config = PagingConfig(2),
- initialKey = 50
- ) { TestPagingSource() }
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ // Save the first block to be dispatched, then run second one first after
+ // receiving
+ // calls to dispatch both.
+ val lastBlock = lastBlock
+ if (lastBlock == null) {
+ this.lastBlock = block
+ } else {
+ block.run()
+ lastBlock.run()
+ }
+ }
+ }
val lifecycle = TestLifecycleOwner()
- var jobSubmitted = false
- val job = launch {
- pager.flow.collectLatest {
- jobSubmitted = true
- differ.submitData(it)
- }
- }
+ differ.submitData(lifecycle.lifecycle, PagingData.empty())
+ differ.submitData(lifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+ // Ensure the second call wins when dispatched in order of execution.
advanceUntilIdle()
+ assertEquals(6, differ.itemCount)
- var job2Submitted = false
- val job2 = launch {
- pager2.flow.collectLatest {
- job2Submitted = true
- differ.submitData(lifecycle.lifecycle, it)
- }
- }
+ val reversedLifecycle = TestLifecycleOwner(coroutineDispatcher = reversedDispatcher)
+ differ.submitData(reversedLifecycle.lifecycle, PagingData.empty())
+ differ.submitData(reversedLifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+ // Ensure the second call wins when dispatched in reverse order of execution.
advanceUntilIdle()
-
- assertTrue(jobSubmitted)
- assertTrue(job2Submitted)
-
- job.cancel()
- job2.cancel()
+ assertEquals(6, differ.itemCount)
}
- }
@Test
- fun submitData_doesNotCancelCollectionsCoroutine() = testScope.runTest {
- lateinit var source1: TestPagingSource
- lateinit var source2: TestPagingSource
- val pager = Pager(
- config = PagingConfig(
- pageSize = 5,
- enablePlaceholders = false,
- prefetchDistance = 1,
- initialLoadSize = 17
- ),
- initialKey = 50
- ) {
- TestPagingSource().also {
- source1 = it
- }
- }
- val pager2 = Pager(
- config = PagingConfig(
- pageSize = 7,
- enablePlaceholders = false,
- prefetchDistance = 1,
- initialLoadSize = 19
- ),
- initialKey = 50
- ) {
- TestPagingSource().also {
- source2 = it
+ fun submitData_cancelsLastSuspendSubmit() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ val pager = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
+ val pager2 = Pager(config = PagingConfig(2), initialKey = 50) { TestPagingSource() }
+
+ val lifecycle = TestLifecycleOwner()
+ var jobSubmitted = false
+ val job = launch {
+ pager.flow.collectLatest {
+ jobSubmitted = true
+ differ.submitData(it)
+ }
+ }
+
+ advanceUntilIdle()
+
+ var job2Submitted = false
+ val job2 = launch {
+ pager2.flow.collectLatest {
+ job2Submitted = true
+ differ.submitData(lifecycle.lifecycle, it)
+ }
+ }
+
+ advanceUntilIdle()
+
+ assertTrue(jobSubmitted)
+ assertTrue(job2Submitted)
+
+ job.cancel()
+ job2.cancel()
}
}
- // Connect pager1
- val job1 = launch {
- pager.flow.collectLatest(differ::submitData)
+ @Test
+ fun submitData_doesNotCancelCollectionsCoroutine() =
+ testScope.runTest {
+ lateinit var source1: TestPagingSource
+ lateinit var source2: TestPagingSource
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 5,
+ enablePlaceholders = false,
+ prefetchDistance = 1,
+ initialLoadSize = 17
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource().also { source1 = it }
+ }
+ val pager2 =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 7,
+ enablePlaceholders = false,
+ prefetchDistance = 1,
+ initialLoadSize = 19
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource().also { source2 = it }
+ }
+
+ // Connect pager1
+ val job1 = launch { pager.flow.collectLatest(differ::submitData) }
+ advanceUntilIdle()
+ assertEquals(17, differ.itemCount)
+
+ // Connect pager2, which should override pager1
+ val job2 = launch { pager2.flow.collectLatest(differ::submitData) }
+ advanceUntilIdle()
+ assertEquals(19, differ.itemCount)
+
+ // now if pager1 gets an invalidation, it overrides pager2
+ source1.invalidate()
+ advanceUntilIdle()
+ // Only loads the initial page, since getRefreshKey returns 0, so there is no more
+ // prepend
+ assertEquals(17, differ.itemCount)
+
+ // now if we refresh via differ, it should go into source 1
+ differ.refresh()
+ advanceUntilIdle()
+ // Only loads the initial page, since getRefreshKey returns 0, so there is no more
+ // prepend
+ assertEquals(17, differ.itemCount)
+
+ // now manual set data that'll clear both
+ differ.submitData(PagingData.empty())
+ advanceUntilIdle()
+ assertEquals(0, differ.itemCount)
+
+ // if source2 has new value, we reconnect to that
+ source2.invalidate()
+ advanceUntilIdle()
+ // Only loads the initial page, since getRefreshKey returns 0, so there is no more
+ // prepend
+ assertEquals(19, differ.itemCount)
+
+ job1.cancelAndJoin()
+ job2.cancelAndJoin()
}
- advanceUntilIdle()
- assertEquals(17, differ.itemCount)
-
- // Connect pager2, which should override pager1
- val job2 = launch {
- pager2.flow.collectLatest(differ::submitData)
- }
- advanceUntilIdle()
- assertEquals(19, differ.itemCount)
-
- // now if pager1 gets an invalidation, it overrides pager2
- source1.invalidate()
- advanceUntilIdle()
- // Only loads the initial page, since getRefreshKey returns 0, so there is no more prepend
- assertEquals(17, differ.itemCount)
-
- // now if we refresh via differ, it should go into source 1
- differ.refresh()
- advanceUntilIdle()
- // Only loads the initial page, since getRefreshKey returns 0, so there is no more prepend
- assertEquals(17, differ.itemCount)
-
- // now manual set data that'll clear both
- differ.submitData(PagingData.empty())
- advanceUntilIdle()
- assertEquals(0, differ.itemCount)
-
- // if source2 has new value, we reconnect to that
- source2.invalidate()
- advanceUntilIdle()
- // Only loads the initial page, since getRefreshKey returns 0, so there is no more prepend
- assertEquals(19, differ.itemCount)
-
- job1.cancelAndJoin()
- job2.cancelAndJoin()
- }
/**
* This test makes sure we don't inject unnecessary IDLE events when pages are cached. Caching
* tests already validate that but it is still good to have an integration test to clarify end
- * to end expected behavior.
- * Repro for b/1987328.
+ * to end expected behavior. Repro for b/1987328.
*/
@SdkSuppress(minSdkVersion = 21) // b/189492631
@Test
- fun refreshEventsAreImmediate_cached() = testScope.runTest {
- val loadStates = mutableListOf<CombinedLoadStates>()
- differ.addLoadStateListener { loadStates.add(it) }
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- initialLoadSize = 30
- )
- ) { TestPagingSource() }
- val job = launch {
- pager.flow.cachedIn(this).collectLatest { differ.submitData(it) }
+ fun refreshEventsAreImmediate_cached() =
+ testScope.runTest {
+ val loadStates = mutableListOf<CombinedLoadStates>()
+ differ.addLoadStateListener { loadStates.add(it) }
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ initialLoadSize = 30
+ )
+ ) {
+ TestPagingSource()
+ }
+ val job = launch { pager.flow.cachedIn(this).collectLatest { differ.submitData(it) } }
+ advanceUntilIdle()
+ assertThat(loadStates.lastOrNull()?.prepend?.endOfPaginationReached).isTrue()
+ loadStates.clear()
+ differ.refresh()
+ advanceUntilIdle()
+ assertThat(loadStates)
+ .containsExactly(
+ localLoadStatesOf(
+ prependLocal = NotLoading(endOfPaginationReached = false),
+ refreshLocal = Loading
+ ),
+ localLoadStatesOf(
+ prependLocal = NotLoading(endOfPaginationReached = true),
+ refreshLocal = NotLoading(endOfPaginationReached = false)
+ )
+ )
+ job.cancelAndJoin()
}
- advanceUntilIdle()
- assertThat(loadStates.lastOrNull()?.prepend?.endOfPaginationReached).isTrue()
- loadStates.clear()
- differ.refresh()
- advanceUntilIdle()
- assertThat(loadStates).containsExactly(
- localLoadStatesOf(
- prependLocal = NotLoading(endOfPaginationReached = false),
- refreshLocal = Loading
- ),
- localLoadStatesOf(
- prependLocal = NotLoading(endOfPaginationReached = true),
- refreshLocal = NotLoading(endOfPaginationReached = false)
- )
- )
- job.cancelAndJoin()
- }
@SdkSuppress(minSdkVersion = 21) // b/189492631
@Test
- fun loadStateFlowSynchronouslyUpdates() = testScope.runTest {
- var combinedLoadStates: CombinedLoadStates? = null
- var itemCount = -1
- val loadStateJob = launch {
- differ.loadStateFlow.collect {
- combinedLoadStates = it
- itemCount = differ.itemCount
- }
- }
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- initialLoadSize = 10,
- prefetchDistance = 1
- ),
- initialKey = 50
- ) { TestPagingSource() }
- val job = launch {
- pager.flow.collectLatest { differ.submitData(it) }
- }
-
- // Initial refresh
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(10, itemCount)
- assertEquals(10, differ.itemCount)
-
- // Append
- differ.getItem(9)
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(20, itemCount)
- assertEquals(20, differ.itemCount)
-
- // Prepend
- differ.getItem(0)
- advanceUntilIdle()
- assertEquals(localLoadStatesOf(), combinedLoadStates)
- assertEquals(30, itemCount)
- assertEquals(30, differ.itemCount)
-
- job.cancel()
- loadStateJob.cancel()
- }
-
- @Test
- fun loadStateListenerSynchronouslyUpdates() = testScope.runTest {
- withContext(coroutineContext) {
+ fun loadStateFlowSynchronouslyUpdates() =
+ testScope.runTest {
var combinedLoadStates: CombinedLoadStates? = null
var itemCount = -1
- differ.addLoadStateListener {
- combinedLoadStates = it
- itemCount = differ.itemCount
+ val loadStateJob = launch {
+ differ.loadStateFlow.collect {
+ combinedLoadStates = it
+ itemCount = differ.itemCount
+ }
}
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- initialLoadSize = 10,
- prefetchDistance = 1
- ),
- initialKey = 50
- ) { TestPagingSource() }
- val job = launch {
- pager.flow.collectLatest { differ.submitData(it) }
- }
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ initialLoadSize = 10,
+ prefetchDistance = 1
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
+ }
+ val job = launch { pager.flow.collectLatest { differ.submitData(it) } }
// Initial refresh
advanceUntilIdle()
@@ -650,73 +603,134 @@
assertEquals(30, differ.itemCount)
job.cancel()
+ loadStateJob.cancel()
}
- }
@Test
- fun listUpdateCallbackSynchronouslyUpdates() = testScope.runTest {
- withContext(coroutineContext) {
- // Keep track of .snapshot() result within each ListUpdateCallback
- val initialSnapshot: ItemSnapshotList<Int> = ItemSnapshotList(0, 0, emptyList())
- var onInsertedSnapshot = initialSnapshot
- var onRemovedSnapshot = initialSnapshot
-
- val listUpdateCallback = object : ListUpdateCallback {
- lateinit var differ: AsyncPagingDataDiffer<Int>
-
- override fun onChanged(position: Int, count: Int, payload: Any?) {
- // TODO: Trigger this callback so we can assert state at this point as well
+ fun loadStateListenerSynchronouslyUpdates() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ var combinedLoadStates: CombinedLoadStates? = null
+ var itemCount = -1
+ differ.addLoadStateListener {
+ combinedLoadStates = it
+ itemCount = differ.itemCount
}
- override fun onMoved(fromPosition: Int, toPosition: Int) {
- // TODO: Trigger this callback so we can assert state at this point as well
- }
-
- override fun onInserted(position: Int, count: Int) {
- onInsertedSnapshot = differ.snapshot()
- }
-
- override fun onRemoved(position: Int, count: Int) {
- onRemovedSnapshot = differ.snapshot()
- }
- }
-
- val differ = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ initialLoadSize = 10,
+ prefetchDistance = 1
+ ),
+ initialKey = 50
+ ) {
+ TestPagingSource()
}
+ val job = launch { pager.flow.collectLatest { differ.submitData(it) } }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- updateCallback = listUpdateCallback,
- mainDispatcher = Dispatchers.Main,
- workerDispatcher = Dispatchers.Main,
- ).also {
- listUpdateCallback.differ = it
+ // Initial refresh
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(10, itemCount)
+ assertEquals(10, differ.itemCount)
+
+ // Append
+ differ.getItem(9)
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(20, itemCount)
+ assertEquals(20, differ.itemCount)
+
+ // Prepend
+ differ.getItem(0)
+ advanceUntilIdle()
+ assertEquals(localLoadStatesOf(), combinedLoadStates)
+ assertEquals(30, itemCount)
+ assertEquals(30, differ.itemCount)
+
+ job.cancel()
}
-
- // Initial insert; this only triggers onInserted
- differ.submitData(PagingData.from(listOf(0)))
- advanceUntilIdle()
-
- val firstList = ItemSnapshotList(0, 0, listOf(0))
- assertEquals(firstList, differ.snapshot())
- assertEquals(firstList, onInsertedSnapshot)
- assertEquals(initialSnapshot, onRemovedSnapshot)
-
- // Switch item to 1; this triggers onInserted + onRemoved
- differ.submitData(PagingData.from(listOf(1)))
- advanceUntilIdle()
-
- val secondList = ItemSnapshotList(0, 0, listOf(1))
- assertEquals(secondList, differ.snapshot())
- assertEquals(secondList, onInsertedSnapshot)
- assertEquals(secondList, onRemovedSnapshot)
}
- }
+
+ @Test
+ fun listUpdateCallbackSynchronouslyUpdates() =
+ testScope.runTest {
+ withContext(coroutineContext) {
+ // Keep track of .snapshot() result within each ListUpdateCallback
+ val initialSnapshot: ItemSnapshotList<Int> = ItemSnapshotList(0, 0, emptyList())
+ var onInsertedSnapshot = initialSnapshot
+ var onRemovedSnapshot = initialSnapshot
+
+ val listUpdateCallback =
+ object : ListUpdateCallback {
+ lateinit var differ: AsyncPagingDataDiffer<Int>
+
+ override fun onChanged(position: Int, count: Int, payload: Any?) {
+ // TODO: Trigger this callback so we can assert state at this point as
+ // well
+ }
+
+ override fun onMoved(fromPosition: Int, toPosition: Int) {
+ // TODO: Trigger this callback so we can assert state at this point as
+ // well
+ }
+
+ override fun onInserted(position: Int, count: Int) {
+ onInsertedSnapshot = differ.snapshot()
+ }
+
+ override fun onRemoved(position: Int, count: Int) {
+ onRemovedSnapshot = differ.snapshot()
+ }
+ }
+
+ val differ =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(
+ oldItem: Int,
+ newItem: Int
+ ): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(
+ oldItem: Int,
+ newItem: Int
+ ): Boolean {
+ return oldItem == newItem
+ }
+ },
+ updateCallback = listUpdateCallback,
+ mainDispatcher = Dispatchers.Main,
+ workerDispatcher = Dispatchers.Main,
+ )
+ .also { listUpdateCallback.differ = it }
+
+ // Initial insert; this only triggers onInserted
+ differ.submitData(PagingData.from(listOf(0)))
+ advanceUntilIdle()
+
+ val firstList = ItemSnapshotList(0, 0, listOf(0))
+ assertEquals(firstList, differ.snapshot())
+ assertEquals(firstList, onInsertedSnapshot)
+ assertEquals(initialSnapshot, onRemovedSnapshot)
+
+ // Switch item to 1; this triggers onInserted + onRemoved
+ differ.submitData(PagingData.from(listOf(1)))
+ advanceUntilIdle()
+
+ val secondList = ItemSnapshotList(0, 0, listOf(1))
+ assertEquals(secondList, differ.snapshot())
+ assertEquals(secondList, onInsertedSnapshot)
+ assertEquals(secondList, onRemovedSnapshot)
+ }
+ }
@Test
fun loadStateListenerYieldsToRecyclerView() {
@@ -725,48 +739,53 @@
val mainDispatcher = Dispatchers.Main.immediate
runTest {
val events = mutableListOf<String>()
- val asyncDiffer = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- // override default Dispatcher.Main with Dispatchers.main.immediate so that
- // main tasks run without queueing, we need this to simulate real life order of
- // events
- mainDispatcher = mainDispatcher,
- updateCallback = listUpdateCapture,
- workerDispatcher = backgroundScope.coroutineContext
- )
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- prefetchDistance = 3,
- initialLoadSize = 10,
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
)
- ) { TestPagingSource() }
- asyncDiffer.addLoadStateListener {
- events.add(it.toString())
- }
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ )
+ ) {
+ TestPagingSource()
+ }
- val collectPager = launch(mainDispatcher) {
- pager.flow.collectLatest { asyncDiffer.submitData(it) }
- }
+ asyncDiffer.addLoadStateListener { events.add(it.toString()) }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
// wait till we get all expected events
asyncDiffer.loadStateFlow.awaitNotLoading()
- assertThat(events).containsExactly(
- localLoadStatesOf(refreshLocal = Loading).toString(),
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading).toString(),
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
events.clear()
// Simulate RV dispatch layout which calls multi onBind --> getItem. LoadStateUpdates
@@ -784,15 +803,15 @@
asyncDiffer.loadStateFlow.awaitNotLoading()
// make sure we received the LoadStateUpdate only after dispatchLayout ended
- assertThat(events).containsExactly(
- "start dispatchLayout",
- "end dispatchLayout",
- localLoadStatesOf(
- appendLocal = Loading,
- prependLocal = NotLoading(true)
- ).toString(),
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ localLoadStatesOf(appendLocal = Loading, prependLocal = NotLoading(true))
+ .toString(),
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
collectPager.cancel()
}
@@ -805,52 +824,58 @@
val mainDispatcher = Dispatchers.Main.immediate
runTest {
val events = mutableListOf<String>()
- val asyncDiffer = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- // override default Dispatcher.Main with Dispatchers.main.immediate so that
- // main tasks run without queueing, we need this to simulate real life order of
- // events
- mainDispatcher = mainDispatcher,
- updateCallback = listUpdateCapture,
- workerDispatcher = backgroundScope.coroutineContext
- )
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- prefetchDistance = 3,
- initialLoadSize = 10,
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
)
- ) { TestPagingSource() }
- val collectLoadState = launch(mainDispatcher) {
- asyncDiffer.loadStateFlow.collect {
- events.add(it.toString())
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ )
+ ) {
+ TestPagingSource()
}
- }
- val collectPager = launch(mainDispatcher) {
- pager.flow.collectLatest { asyncDiffer.submitData(it) }
- }
+ val collectLoadState =
+ launch(mainDispatcher) {
+ asyncDiffer.loadStateFlow.collect { events.add(it.toString()) }
+ }
+
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
// wait till we get all expected events
asyncDiffer.loadStateFlow.awaitNotLoading()
// withContext prevents flake in API 28 where sometimes we start asserting before
// the NotLoading state is added to events list
- assertThat(events).containsExactly(
- localLoadStatesOf(refreshLocal = Loading).toString(),
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ localLoadStatesOf(refreshLocal = Loading).toString(),
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
events.clear()
@@ -869,15 +894,15 @@
asyncDiffer.loadStateFlow.awaitNotLoading()
// make sure we received the LoadStateUpdate only after dispatchLayout ended
- assertThat(events).containsExactly(
- "start dispatchLayout",
- "end dispatchLayout",
- localLoadStatesOf(
- appendLocal = Loading,
- prependLocal = NotLoading(true)
- ).toString(),
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ "end dispatchLayout",
+ localLoadStatesOf(appendLocal = Loading, prependLocal = NotLoading(true))
+ .toString(),
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
collectLoadState.cancel()
collectPager.cancel()
@@ -891,68 +916,74 @@
val mainDispatcher = Dispatchers.Main.immediate
runTest {
val events = mutableListOf<String>()
- val asyncDiffer = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- // override default Dispatcher.Main with Dispatchers.main.immediate so that
- // main tasks run without queueing, we need this to simulate real life order of
- // events
- mainDispatcher = mainDispatcher,
- updateCallback = listUpdateCapture,
- workerDispatcher = backgroundScope.coroutineContext
- )
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- prefetchDistance = 3,
- initialLoadSize = 10,
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
)
- ) { TestPagingSource() }
- val collectInGetItem = launch(mainDispatcher) {
- asyncDiffer.inGetItem.collect {
- events.add("inGetItem $it")
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ )
+ ) {
+ TestPagingSource()
}
- }
- val collectLoadState = launch(mainDispatcher) {
- asyncDiffer.loadStateFlow.collect {
- events.add(it.toString())
+ val collectInGetItem =
+ launch(mainDispatcher) {
+ asyncDiffer.inGetItem.collect { events.add("inGetItem $it") }
}
- }
+
+ val collectLoadState =
+ launch(mainDispatcher) {
+ asyncDiffer.loadStateFlow.collect { events.add(it.toString()) }
+ }
// since we cannot intercept the internal loadStateFlow, we collect from its source
// flow to see when the internal flow first collected the LoadState before
// waiting for getItem
- val collectParallelLoadState = launch(mainDispatcher) {
- asyncDiffer.presenter.loadStateFlow.filterNotNull().collect {
- events.add("internal flow collected")
+ val collectParallelLoadState =
+ launch(mainDispatcher) {
+ asyncDiffer.presenter.loadStateFlow.filterNotNull().collect {
+ events.add("internal flow collected")
+ }
}
- }
- val collectPager = launch(mainDispatcher) {
- pager.flow.collectLatest { asyncDiffer.submitData(it) }
- }
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
// wait till we get all expected events
asyncDiffer.loadStateFlow.awaitNotLoading()
- assertThat(events).containsExactly(
- "inGetItem false",
- "internal flow collected",
- localLoadStatesOf(refreshLocal = Loading).toString(),
- "internal flow collected",
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "inGetItem false",
+ "internal flow collected",
+ localLoadStatesOf(refreshLocal = Loading).toString(),
+ "internal flow collected",
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
// reset events count
events.clear()
@@ -973,26 +1004,26 @@
// assert two things: loadStates were received after dispatchLayout and that
// the internal flow did collect a LoadState while inGetItem is true but had waited
- assertThat(events).containsExactly(
- "start dispatchLayout",
- // getItem(6)
- "inGetItem true",
- "inGetItem false",
- // getItem(7) triggers append
- "inGetItem true",
- "internal flow collected",
- "inGetItem false",
- // getItem(8)
- "inGetItem true",
- "inGetItem false",
- "end dispatchLayout",
- localLoadStatesOf(
- appendLocal = Loading,
- prependLocal = NotLoading(true)
- ).toString(),
- "internal flow collected",
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ // getItem(6)
+ "inGetItem true",
+ "inGetItem false",
+ // getItem(7) triggers append
+ "inGetItem true",
+ "internal flow collected",
+ "inGetItem false",
+ // getItem(8)
+ "inGetItem true",
+ "inGetItem false",
+ "end dispatchLayout",
+ localLoadStatesOf(appendLocal = Loading, prependLocal = NotLoading(true))
+ .toString(),
+ "internal flow collected",
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
collectInGetItem.cancel()
collectLoadState.cancel()
@@ -1008,38 +1039,43 @@
val mainDispatcher = Dispatchers.Main.immediate
runTest {
val events = mutableListOf<String>()
- val asyncDiffer = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ val asyncDiffer =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- // override default Dispatcher.Main with Dispatchers.main.immediate so that
- // main tasks run without queueing, we need this to simulate real life order of
- // events
- mainDispatcher = mainDispatcher,
- updateCallback = listUpdateCapture,
- workerDispatcher = backgroundScope.coroutineContext
- )
-
- val pager = Pager(
- config = PagingConfig(
- pageSize = 10,
- enablePlaceholders = false,
- prefetchDistance = 3,
- initialLoadSize = 10,
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ // override default Dispatcher.Main with Dispatchers.main.immediate so that
+ // main tasks run without queueing, we need this to simulate real life order of
+ // events
+ mainDispatcher = mainDispatcher,
+ updateCallback = listUpdateCapture,
+ workerDispatcher = backgroundScope.coroutineContext
)
- ) { TestPagingSource() }
- val collectInGetItem = launch(mainDispatcher) {
- asyncDiffer.inGetItem.collect {
- events.add("inGetItem $it")
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 10,
+ enablePlaceholders = false,
+ prefetchDistance = 3,
+ initialLoadSize = 10,
+ )
+ ) {
+ TestPagingSource()
}
- }
+
+ val collectInGetItem =
+ launch(mainDispatcher) {
+ asyncDiffer.inGetItem.collect { events.add("inGetItem $it") }
+ }
// override internal loadStateListener that is registered with PagingDataPresenter
asyncDiffer.addLoadStateListenerInternal {
@@ -1048,24 +1084,23 @@
}
// add actual UI listener
- asyncDiffer.addLoadStateListener {
- events.add(it.toString())
- }
+ asyncDiffer.addLoadStateListener { events.add(it.toString()) }
- val collectPager = launch(mainDispatcher) {
- pager.flow.collectLatest { asyncDiffer.submitData(it) }
- }
+ val collectPager =
+ launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
// wait till we get all expected events
asyncDiffer.loadStateFlow.awaitNotLoading()
- assertThat(events).containsExactly(
- "inGetItem false",
- "internal listener invoked",
- localLoadStatesOf(refreshLocal = Loading).toString(),
- "internal listener invoked",
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "inGetItem false",
+ "internal listener invoked",
+ localLoadStatesOf(refreshLocal = Loading).toString(),
+ "internal listener invoked",
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
// reset events count
events.clear()
@@ -1086,26 +1121,26 @@
// assert two things: loadStates were received after dispatchLayout and that
// the internal listener was invoked while inGetItem is true but had waited
- assertThat(events).containsExactly(
- "start dispatchLayout",
- // getItem(6)
- "inGetItem true",
- "inGetItem false",
- // getItem(7) triggers append
- "inGetItem true",
- "internal listener invoked",
- "inGetItem false",
- // getItem(8)
- "inGetItem true",
- "inGetItem false",
- "end dispatchLayout",
- localLoadStatesOf(
- appendLocal = Loading,
- prependLocal = NotLoading(true)
- ).toString(),
- "internal listener invoked",
- localLoadStatesOf(prependLocal = NotLoading(true)).toString()
- ).inOrder()
+ assertThat(events)
+ .containsExactly(
+ "start dispatchLayout",
+ // getItem(6)
+ "inGetItem true",
+ "inGetItem false",
+ // getItem(7) triggers append
+ "inGetItem true",
+ "internal listener invoked",
+ "inGetItem false",
+ // getItem(8)
+ "inGetItem true",
+ "inGetItem false",
+ "end dispatchLayout",
+ localLoadStatesOf(appendLocal = Loading, prependLocal = NotLoading(true))
+ .toString(),
+ "internal listener invoked",
+ localLoadStatesOf(prependLocal = NotLoading(true)).toString()
+ )
+ .inOrder()
collectInGetItem.cancel()
collectPager.cancel()
@@ -1113,241 +1148,207 @@
}
@Test
- fun insertPageEmpty() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 0,
- newItems = 0,
- newNulls = 0,
- prependEvents = emptyList(),
- appendEvents = emptyList()
- )
-
- @Test
- fun insertPageSimple() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 0,
- newItems = 2,
- newNulls = 0,
- prependEvents = listOf(
- Inserted(0, 2)
- ),
- appendEvents = listOf(
- Inserted(2, 2)
+ fun insertPageEmpty() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 0,
+ newNulls = 0,
+ prependEvents = emptyList(),
+ appendEvents = emptyList()
)
- )
@Test
- fun insertPageSimplePlaceholders() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 4,
- newItems = 2,
- newNulls = 2,
- prependEvents = listOf(
- Changed(2, 2, null)
- ),
- appendEvents = listOf(
- Changed(2, 2, null)
+ fun insertPageSimple() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 2,
+ newNulls = 0,
+ prependEvents = listOf(Inserted(0, 2)),
+ appendEvents = listOf(Inserted(2, 2))
)
- )
@Test
- fun insertPageInitPlaceholders() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 0,
- newItems = 2,
- newNulls = 3,
- prependEvents = listOf(
- Inserted(0, 2),
- Inserted(0, 3)
- ),
- appendEvents = listOf(
- // NOTE: theoretically these could be combined
- Inserted(2, 2),
- Inserted(4, 3)
+ fun insertPageSimplePlaceholders() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 4,
+ newItems = 2,
+ newNulls = 2,
+ prependEvents = listOf(Changed(2, 2, null)),
+ appendEvents = listOf(Changed(2, 2, null))
)
- )
@Test
- fun insertPageInitJustPlaceholders() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 0,
- newItems = 0,
- newNulls = 3,
- prependEvents = listOf(
- Inserted(0, 3)
- ),
- appendEvents = listOf(
- Inserted(2, 3)
+ fun insertPageInitPlaceholders() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 2,
+ newNulls = 3,
+ prependEvents = listOf(Inserted(0, 2), Inserted(0, 3)),
+ appendEvents =
+ listOf(
+ // NOTE: theoretically these could be combined
+ Inserted(2, 2),
+ Inserted(4, 3)
+ )
)
- )
@Test
- fun insertPageInsertNulls() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 3,
- newItems = 2,
- newNulls = 2,
- prependEvents = listOf(
- Changed(1, 2, null),
- Inserted(0, 1)
- ),
- appendEvents = listOf(
- Changed(2, 2, null),
- Inserted(5, 1)
+ fun insertPageInitJustPlaceholders() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 0,
+ newItems = 0,
+ newNulls = 3,
+ prependEvents = listOf(Inserted(0, 3)),
+ appendEvents = listOf(Inserted(2, 3))
)
- )
@Test
- fun insertPageRemoveNulls() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 7,
- newItems = 2,
- newNulls = 0,
- prependEvents = listOf(
- Changed(5, 2, null),
- Removed(0, 5)
- ),
- appendEvents = listOf(
- Changed(2, 2, null),
- Removed(4, 5)
+ fun insertPageInsertNulls() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 3,
+ newItems = 2,
+ newNulls = 2,
+ prependEvents = listOf(Changed(1, 2, null), Inserted(0, 1)),
+ appendEvents = listOf(Changed(2, 2, null), Inserted(5, 1))
)
- )
@Test
- fun insertPageReduceNulls() = verifyPrependAppendCallback(
- initialItems = 2,
- initialNulls = 10,
- newItems = 3,
- newNulls = 4,
- prependEvents = listOf(
- Changed(7, 3, null),
- Removed(0, 3)
- ),
- appendEvents = listOf(
- Changed(2, 3, null),
- Removed(9, 3)
+ fun insertPageRemoveNulls() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 7,
+ newItems = 2,
+ newNulls = 0,
+ prependEvents = listOf(Changed(5, 2, null), Removed(0, 5)),
+ appendEvents = listOf(Changed(2, 2, null), Removed(4, 5))
)
- )
@Test
- fun dropPageMulti() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 0,
- newNulls = 0,
- pagesToDrop = 2,
- startEvents = listOf(Removed(0, 3)),
- endEvents = listOf(Removed(2, 3))
- )
-
- @Test
- fun dropPageReturnNulls() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 1,
- newNulls = 4,
- pagesToDrop = 2,
- startEvents = listOf(Changed(1, 3, null)),
- endEvents = listOf(Changed(2, 3, null))
- )
-
- @Test
- fun dropPageFromNoNullsToHavingNulls() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 0,
- newNulls = 3,
- pagesToDrop = 2,
- startEvents = listOf(
- // [null, null, null, 'a', 'b']
- Changed(0, 3, null)
- ),
- endEvents = listOf(
- // ['a', 'b', null, null, null]
- Changed(2, 3, null)
+ fun insertPageReduceNulls() =
+ verifyPrependAppendCallback(
+ initialItems = 2,
+ initialNulls = 10,
+ newItems = 3,
+ newNulls = 4,
+ prependEvents = listOf(Changed(7, 3, null), Removed(0, 3)),
+ appendEvents = listOf(Changed(2, 3, null), Removed(9, 3))
)
- )
@Test
- fun dropPageChangeRemovePlaceholders() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 2,
- newNulls = 4,
- pagesToDrop = 2,
- startEvents = listOf(
- // [null, 'e', 'c', 'd', 'a', 'b']
- Removed(0, 1),
- // [null, null, null, null, 'a', 'b']
- Changed(1, 3, null)
- ),
- endEvents = listOf(
- // ['a', 'b', 'c', 'd', 'e', null]
- Removed(6, 1),
- // ['a', 'b', null, null, null, null]
- Changed(2, 3, null)
+ fun dropPageMulti() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 0,
+ newNulls = 0,
+ pagesToDrop = 2,
+ startEvents = listOf(Removed(0, 3)),
+ endEvents = listOf(Removed(2, 3))
)
- )
@Test
- fun dropPageChangeRemoveItems() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 0,
- newNulls = 1,
- pagesToDrop = 2,
- startEvents = listOf(
- // ['d', 'a', 'b']
- Removed(0, 2),
- // [null, 'a', 'b']
- Changed(0, 1, null)
- ),
- endEvents = listOf(
- // ['a', 'b', 'c']
- Removed(3, 2),
- // ['a', 'b', null]
- Changed(2, 1, null)
+ fun dropPageReturnNulls() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 1,
+ newNulls = 4,
+ pagesToDrop = 2,
+ startEvents = listOf(Changed(1, 3, null)),
+ endEvents = listOf(Changed(2, 3, null))
)
- )
@Test
- fun dropPageChangeDoubleRemove() = verifyDrop(
- initialPages = listOf(
- listOf(1, 2),
- listOf(3, 4),
- listOf(5)
- ),
- initialNulls = 3,
- newNulls = 1,
- pagesToDrop = 2,
- startEvents = listOf(
- // ['d', 'a', 'b']
- Removed(0, 5),
- // [null, 'a', 'b']
- Changed(0, 1, null)
- ),
- endEvents = listOf(
- // ['a', 'b', 'c']
- Removed(3, 5),
- // ['a', 'b', null]
- Changed(2, 1, null)
+ fun dropPageFromNoNullsToHavingNulls() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 0,
+ newNulls = 3,
+ pagesToDrop = 2,
+ startEvents =
+ listOf(
+ // [null, null, null, 'a', 'b']
+ Changed(0, 3, null)
+ ),
+ endEvents =
+ listOf(
+ // ['a', 'b', null, null, null]
+ Changed(2, 3, null)
+ )
)
- )
+
+ @Test
+ fun dropPageChangeRemovePlaceholders() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 2,
+ newNulls = 4,
+ pagesToDrop = 2,
+ startEvents =
+ listOf(
+ // [null, 'e', 'c', 'd', 'a', 'b']
+ Removed(0, 1),
+ // [null, null, null, null, 'a', 'b']
+ Changed(1, 3, null)
+ ),
+ endEvents =
+ listOf(
+ // ['a', 'b', 'c', 'd', 'e', null]
+ Removed(6, 1),
+ // ['a', 'b', null, null, null, null]
+ Changed(2, 3, null)
+ )
+ )
+
+ @Test
+ fun dropPageChangeRemoveItems() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 0,
+ newNulls = 1,
+ pagesToDrop = 2,
+ startEvents =
+ listOf(
+ // ['d', 'a', 'b']
+ Removed(0, 2),
+ // [null, 'a', 'b']
+ Changed(0, 1, null)
+ ),
+ endEvents =
+ listOf(
+ // ['a', 'b', 'c']
+ Removed(3, 2),
+ // ['a', 'b', null]
+ Changed(2, 1, null)
+ )
+ )
+
+ @Test
+ fun dropPageChangeDoubleRemove() =
+ verifyDrop(
+ initialPages = listOf(listOf(1, 2), listOf(3, 4), listOf(5)),
+ initialNulls = 3,
+ newNulls = 1,
+ pagesToDrop = 2,
+ startEvents =
+ listOf(
+ // ['d', 'a', 'b']
+ Removed(0, 5),
+ // [null, 'a', 'b']
+ Changed(0, 1, null)
+ ),
+ endEvents =
+ listOf(
+ // ['a', 'b', 'c']
+ Removed(3, 5),
+ // ['a', 'b', null]
+ Changed(2, 1, null)
+ )
+ )
private fun verifyPrependAppendCallback(
initialItems: Int,
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCallbackFake.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCallbackFake.kt
index 7356f61..e6f19ef 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCallbackFake.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCallbackFake.kt
@@ -28,13 +28,14 @@
val interactions
get() = allEvents.size
- fun itemCountFromEvents() = allEvents.sumOf {
- when (it) {
- is OnInsertedEvent -> it.count
- is OnRemovedEvent -> -it.count
- else -> 0
+ fun itemCountFromEvents() =
+ allEvents.sumOf {
+ when (it) {
+ is OnInsertedEvent -> it.count
+ is OnRemovedEvent -> -it.count
+ else -> 0
+ }
}
- }
override fun onInserted(position: Int, count: Int) {
OnInsertedEvent(position, count).let {
@@ -69,7 +70,10 @@
}
data class OnInsertedEvent(val position: Int, val count: Int)
+
data class OnRemovedEvent(val position: Int, val count: Int)
+
data class OnMovedEvent(val fromPosition: Int, val toPosition: Int)
+
data class OnChangedEvent(val position: Int, val count: Int, val payload: Any?)
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
index 4e96af0..0f6a1d5 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
@@ -40,8 +40,6 @@
}
fun newEvents(): List<ListUpdateEvent> {
- return events.drop(lastEventsListIndex + 1).also {
- lastEventsListIndex = events.lastIndex
- }
+ return events.drop(lastEventsListIndex + 1).also { lastEventsListIndex = events.lastIndex }
}
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 4a5dfdf..47f5ab8 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -53,27 +53,27 @@
private val backgroundExecutor = TestExecutor()
private val lifecycleOwner = TestLifecycleOwner()
- private data class LoadStateEvent(
- val type: LoadType,
- val state: LoadState
- )
+ private data class LoadStateEvent(val type: LoadType, val state: LoadState)
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setup() {
- ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
- override fun executeOnDiskIO(runnable: Runnable) {
- fail("IO executor should be overwritten")
- }
+ ArchTaskExecutor.getInstance()
+ .setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ fail("IO executor should be overwritten")
+ }
- override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
- override fun isMainThread(): Boolean {
- return true
- }
- })
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ }
+ )
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
@@ -99,10 +99,11 @@
var invalidInitialLoadResult = false
- override suspend fun load(params: LoadParams<Int>) = when (params) {
- is LoadParams.Refresh -> loadInitial(params)
- else -> loadRange()
- }
+ override suspend fun load(params: LoadParams<Int>) =
+ when (params) {
+ is LoadParams.Refresh -> loadInitial(params)
+ else -> loadRange()
+ }
override fun getRefreshKey(state: PagingState<Int, String>): Int? = null
@@ -134,19 +135,14 @@
private fun loadRange(): LoadResult<Int, String> {
val data = listOf("c", "d")
- return LoadResult.Page(
- data = data,
- prevKey = 2,
- nextKey = null
- )
+ return LoadResult.Page(data = data, prevKey = 2, nextKey = null)
}
}
}
@Test
fun initialValueAllowsGetDataSource() {
- val livePagedList = LivePagedListBuilder(MockPagingSourceFactory()::create, 2)
- .build()
+ val livePagedList = LivePagedListBuilder(MockPagingSourceFactory()::create, 2).build()
// Calling .dataSource should never throw from the initial paged list.
livePagedList.value!!.dataSource
@@ -158,23 +154,21 @@
// represent the common case when writing tests.
ArchTaskExecutor.getInstance().setDelegate(null)
- LivePagedListBuilder(MockPagingSourceFactory()::create, 2)
- .build()
+ LivePagedListBuilder(MockPagingSourceFactory()::create, 2).build()
}
@Test
fun executorBehavior() {
// specify a background dispatcher via builder, and verify it gets used for all loads,
// overriding default IO dispatcher
- val livePagedList = LivePagedListBuilder(MockPagingSourceFactory()::create, 2)
- .setFetchExecutor(backgroundExecutor)
- .build()
+ val livePagedList =
+ LivePagedListBuilder(MockPagingSourceFactory()::create, 2)
+ .setFetchExecutor(backgroundExecutor)
+ .build()
val pagedListHolder: Array<PagedList<String>?> = arrayOfNulls(1)
- livePagedList.observe(lifecycleOwner) { newList ->
- pagedListHolder[0] = newList
- }
+ livePagedList.observe(lifecycleOwner) { newList -> pagedListHolder[0] = newList }
// initially, immediately get passed empty initial list
assertNotNull(pagedListHolder[0])
@@ -199,15 +193,12 @@
val factory = MockPagingSourceFactory()
factory.enqueueError()
- val livePagedList = LivePagedListBuilder(factory::create, 2)
- .setFetchExecutor(backgroundExecutor)
- .build()
+ val livePagedList =
+ LivePagedListBuilder(factory::create, 2).setFetchExecutor(backgroundExecutor).build()
val pagedListHolder: Array<PagedList<String>?> = arrayOfNulls(1)
- livePagedList.observe(lifecycleOwner) { newList ->
- pagedListHolder[0] = newList
- }
+ livePagedList.observe(lifecycleOwner) { newList -> pagedListHolder[0] = newList }
val loadStates = mutableListOf<LoadStateEvent>()
@@ -230,9 +221,7 @@
// TODO: Investigate removing initial IDLE state from callback updates.
assertEquals(
listOf(
- LoadStateEvent(
- REFRESH, NotLoading(endOfPaginationReached = false)
- ),
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
LoadStateEvent(REFRESH, Loading),
LoadStateEvent(REFRESH, Error(EXCEPTION))
),
@@ -250,10 +239,7 @@
assertEquals(
listOf(
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
LoadStateEvent(REFRESH, Loading),
LoadStateEvent(REFRESH, Error(EXCEPTION)),
LoadStateEvent(REFRESH, Loading)
@@ -266,17 +252,11 @@
pagedListHolder[0]!!.addWeakLoadStateListener(loadStateChangedCallback)
assertEquals(
listOf(
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
LoadStateEvent(REFRESH, Loading),
LoadStateEvent(REFRESH, Error(EXCEPTION)),
LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- )
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false))
),
loadStates
)
@@ -288,20 +268,21 @@
val pagingSources = mutableListOf<MockPagingSourceFactory.MockPagingSource>()
val pagedListHolder = mutableListOf<PagedList<String>>()
- val livePagedList = LivePagedListBuilder(
- {
- factory.create().also { pagingSource ->
- pagingSource as MockPagingSourceFactory.MockPagingSource
- if (pagingSources.size == 0) {
- pagingSource.invalidInitialLoadResult = true
- }
- pagingSources.add(pagingSource)
- }
- },
- pageSize = 2
- )
- .setFetchExecutor(backgroundExecutor)
- .build()
+ val livePagedList =
+ LivePagedListBuilder(
+ {
+ factory.create().also { pagingSource ->
+ pagingSource as MockPagingSourceFactory.MockPagingSource
+ if (pagingSources.size == 0) {
+ pagingSource.invalidInitialLoadResult = true
+ }
+ pagingSources.add(pagingSource)
+ }
+ },
+ pageSize = 2
+ )
+ .setFetchExecutor(backgroundExecutor)
+ .build()
val loadStates = mutableListOf<LoadStateEvent>()
@@ -332,23 +313,15 @@
assertThat(pagedListHolder.size).isEqualTo(2)
assertThat(pagedListHolder[1]).isInstanceOf(ContiguousPagedList::class.java)
- assertThat(loadStates).containsExactly(
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
- LoadStateEvent(REFRESH, Loading),
- // when LoadResult.Invalid is returned, REFRESH is reset back to NotLoading
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
- LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
+ assertThat(loadStates)
+ .containsExactly(
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
+ LoadStateEvent(REFRESH, Loading),
+ // when LoadResult.Invalid is returned, REFRESH is reset back to NotLoading
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
+ LoadStateEvent(REFRESH, Loading),
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false))
)
- )
}
@Test
@@ -357,16 +330,18 @@
val pagedLists = mutableListOf<PagedList<Int>>()
val factory = { TestPagingSource(loadDelay = 0).also { pagingSources.add(it) } }
- val livePagedList = LivePagedListBuilder(
- factory,
- config = PagedList.Config.Builder()
- .setPageSize(2)
- .setInitialLoadSizeHint(6)
- .setEnablePlaceholders(false)
+ val livePagedList =
+ LivePagedListBuilder(
+ factory,
+ config =
+ PagedList.Config.Builder()
+ .setPageSize(2)
+ .setInitialLoadSizeHint(6)
+ .setEnablePlaceholders(false)
+ .build()
+ )
+ .setFetchExecutor(backgroundExecutor)
.build()
- )
- .setFetchExecutor(backgroundExecutor)
- .build()
val loadStates = mutableListOf<LoadStateEvent>()
@@ -407,26 +382,27 @@
assertThat(pagingSources.size).isEqualTo(2)
assertThat(pagedLists.size).isEqualTo(3)
- assertThat(loadStates).containsExactly(
- LoadStateEvent(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ), // first empty paged list
- LoadStateEvent(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ), // second paged list
- LoadStateEvent(APPEND, Loading), // second paged list append
- LoadStateEvent(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ), // append success
- LoadStateEvent(APPEND, Loading), // second paged list append again but fails
- LoadStateEvent(
- APPEND,
- NotLoading(endOfPaginationReached = false)
- ) // third paged list
- )
+ assertThat(loadStates)
+ .containsExactly(
+ LoadStateEvent(
+ APPEND,
+ NotLoading(endOfPaginationReached = false)
+ ), // first empty paged list
+ LoadStateEvent(
+ APPEND,
+ NotLoading(endOfPaginationReached = false)
+ ), // second paged list
+ LoadStateEvent(APPEND, Loading), // second paged list append
+ LoadStateEvent(
+ APPEND,
+ NotLoading(endOfPaginationReached = false)
+ ), // append success
+ LoadStateEvent(APPEND, Loading), // second paged list append again but fails
+ LoadStateEvent(
+ APPEND,
+ NotLoading(endOfPaginationReached = false)
+ ) // third paged list
+ )
}
@Test
@@ -434,36 +410,40 @@
val dataSources = mutableListOf<DataSource<Int, Int>>()
val pagedLists = mutableListOf<PagedList<Int>>()
val requestedLoadSizes = mutableListOf<Int>()
- val livePagedList = LivePagedListBuilder(
- pagingSourceFactory = object : DataSource.Factory<Int, Int>() {
- override fun create(): DataSource<Int, Int> {
- return object : PositionalDataSource<Int>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<Int>
- ) {
- requestedLoadSizes.add(params.requestedLoadSize)
- callback.onResult(listOf(1, 2, 3), 0)
- }
+ val livePagedList =
+ LivePagedListBuilder(
+ pagingSourceFactory =
+ object : DataSource.Factory<Int, Int>() {
+ override fun create(): DataSource<Int, Int> {
+ return object : PositionalDataSource<Int>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<Int>
+ ) {
+ requestedLoadSizes.add(params.requestedLoadSize)
+ callback.onResult(listOf(1, 2, 3), 0)
+ }
- override fun loadRange(
- params: LoadRangeParams,
- callback: LoadRangeCallback<Int>
- ) {
- requestedLoadSizes.add(params.loadSize)
- }
- }.also {
- dataSources.add(it)
- }
- }
- }.asPagingSourceFactory(backgroundExecutor.asCoroutineDispatcher()),
- config = PagedList.Config.Builder()
- .setPageSize(3)
- .setInitialLoadSizeHint(3)
- .setEnablePlaceholders(false)
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<Int>
+ ) {
+ requestedLoadSizes.add(params.loadSize)
+ }
+ }
+ .also { dataSources.add(it) }
+ }
+ }
+ .asPagingSourceFactory(backgroundExecutor.asCoroutineDispatcher()),
+ config =
+ PagedList.Config.Builder()
+ .setPageSize(3)
+ .setInitialLoadSizeHint(3)
+ .setEnablePlaceholders(false)
+ .build()
+ )
+ .setFetchExecutor(backgroundExecutor)
.build()
- ).setFetchExecutor(backgroundExecutor)
- .build()
livePagedList.observeForever { pagedLists.add(it) }
@@ -485,61 +465,72 @@
@Test
fun initialPagedListEvents() {
- ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
- override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
+ ArchTaskExecutor.getInstance()
+ .setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
- override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
- override fun isMainThread(): Boolean {
- return true
- }
- })
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ }
+ )
val listUpdateCallback = ListUpdateCapture()
val loadStateListener = LoadStateCapture()
- val differ = AsyncPagedListDiffer<Int>(
- listUpdateCallback = listUpdateCallback,
- config = AsyncDifferConfig.Builder(
- object : DiffUtil.ItemCallback<Int>() {
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ val differ =
+ AsyncPagedListDiffer<Int>(
+ listUpdateCallback = listUpdateCallback,
+ config =
+ AsyncDifferConfig.Builder(
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- }
- ).apply {
- setBackgroundThreadExecutor(backgroundExecutor)
- }.build()
- )
+ override fun areContentsTheSame(
+ oldItem: Int,
+ newItem: Int
+ ): Boolean {
+ return oldItem == newItem
+ }
+ }
+ )
+ .apply { setBackgroundThreadExecutor(backgroundExecutor) }
+ .build()
+ )
differ.addLoadStateListener(loadStateListener)
val observer = Observer<PagedList<Int>> { t -> differ.submitList(t) }
// LivePagedList which immediately produces a real PagedList, skipping InitialPagedList.
- val livePagedList = LivePagedListBuilder(
- pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
- pageSize = 10,
- ).build()
+ val livePagedList =
+ LivePagedListBuilder(
+ pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
+ pageSize = 10,
+ )
+ .build()
livePagedList.observeForever(observer)
drain()
livePagedList.removeObserver(observer)
- assertThat(listUpdateCallback.newEvents()).containsExactly(
- ListUpdateEvent.Inserted(position = 0, count = 100)
- )
+ assertThat(listUpdateCallback.newEvents())
+ .containsExactly(ListUpdateEvent.Inserted(position = 0, count = 100))
// LivePagedList which will emit InitialPagedList first.
- val livePagedListWithInitialPagedList = LivePagedListBuilder(
- pagingSourceFactory = { TestPagingSource(loadDelay = 1000) },
- pageSize = 10,
- ).build()
+ val livePagedListWithInitialPagedList =
+ LivePagedListBuilder(
+ pagingSourceFactory = { TestPagingSource(loadDelay = 1000) },
+ pageSize = 10,
+ )
+ .build()
livePagedListWithInitialPagedList.observeForever(observer)
drain()
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
index 7d0442c..e840ef8 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
@@ -49,9 +49,7 @@
@Suppress("DEPRECATION")
@OptIn(ExperimentalCoroutinesApi::class)
class LivePagedListTest {
- @JvmField
- @Rule
- val instantTaskExecutorRule = InstantTaskExecutorRule()
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private val testScope = TestCoroutineScope()
@@ -61,24 +59,23 @@
var pagingSourcesCreated = 0
val pagingSourceFactory = {
when (pagingSourcesCreated++) {
- 0 -> TestPagingSource().apply {
- invalidate()
- }
+ 0 -> TestPagingSource().apply { invalidate() }
else -> TestPagingSource()
}
}
- val livePagedList = LivePagedList(
- coroutineScope = GlobalScope,
- initialKey = null,
- config = PagedList.Config.Builder().setPageSize(10).build(),
- boundaryCallback = null,
- pagingSourceFactory = pagingSourceFactory,
- notifyDispatcher = ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
- fetchDispatcher = ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher(),
- )
+ val livePagedList =
+ LivePagedList(
+ coroutineScope = GlobalScope,
+ initialKey = null,
+ config = PagedList.Config.Builder().setPageSize(10).build(),
+ boundaryCallback = null,
+ pagingSourceFactory = pagingSourceFactory,
+ notifyDispatcher = ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
+ fetchDispatcher = ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher(),
+ )
- livePagedList.observeForever { }
+ livePagedList.observeForever {}
assertThat(pagingSourcesCreated).isEqualTo(2)
}
@@ -91,20 +88,21 @@
TestPagingSource()
}
val testDispatcher = TestDispatcher()
- val livePagedList = LivePagedList(
- coroutineScope = GlobalScope,
- initialKey = null,
- config = PagedList.Config.Builder().setPageSize(10).build(),
- boundaryCallback = null,
- pagingSourceFactory = pagingSourceFactory,
- notifyDispatcher = ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
- fetchDispatcher = testDispatcher,
- )
+ val livePagedList =
+ LivePagedList(
+ coroutineScope = GlobalScope,
+ initialKey = null,
+ config = PagedList.Config.Builder().setPageSize(10).build(),
+ boundaryCallback = null,
+ pagingSourceFactory = pagingSourceFactory,
+ notifyDispatcher = ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
+ fetchDispatcher = testDispatcher,
+ )
assertTrue { testDispatcher.queue.isEmpty() }
assertEquals(0, pagingSourcesCreated)
- livePagedList.observeForever { }
+ livePagedList.observeForever {}
assertTrue { testDispatcher.queue.isNotEmpty() }
assertEquals(0, pagingSourcesCreated)
@@ -147,22 +145,21 @@
/**
* Some paging2 tests might be using InstantTaskExecutor and expect first page to be loaded
- * immediately. This test replicates that by checking observe forever receives the value in
- * its own call stack.
+ * immediately. This test replicates that by checking observe forever receives the value in its
+ * own call stack.
*/
@Test
fun instantExecutionWorksWithLegacy() {
val totalSize = 300
val data = (0 until totalSize).map { "$it/$it" }
- val factory = object : DataSource.Factory<Int, String>() {
- override fun create(): DataSource<Int, String> {
- return TestPositionalDataSource(data)
+ val factory =
+ object : DataSource.Factory<Int, String>() {
+ override fun create(): DataSource<Int, String> {
+ return TestPositionalDataSource(data)
+ }
}
- }
- class TestAdapter : PagedListAdapter<String, RecyclerView.ViewHolder>(
- DIFF_STRING
- ) {
+ class TestAdapter : PagedListAdapter<String, RecyclerView.ViewHolder>(DIFF_STRING) {
// open it up by overriding
public override fun getItem(position: Int): String? {
return super.getItem(position)
@@ -175,24 +172,20 @@
return object : RecyclerView.ViewHolder(View(parent.context)) {}
}
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- }
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}
}
- val livePagedList = LivePagedListBuilder(
- factory,
- PagedList.Config.Builder()
- .setEnablePlaceholders(false)
- .setPageSize(30)
+ val livePagedList =
+ LivePagedListBuilder(
+ factory,
+ PagedList.Config.Builder().setEnablePlaceholders(false).setPageSize(30).build()
+ )
.build()
- ).build()
val adapter = TestAdapter()
livePagedList.observeForever { pagedList ->
// make sure observeForever worked sync where it did load the data immediately
- assertThat(
- Throwable().stackTraceToString()
- ).contains("observeForever")
+ assertThat(Throwable().stackTraceToString()).contains("observeForever")
assertThat(pagedList.loadedCount).isEqualTo(90)
}
adapter.submitList(checkNotNull(livePagedList.value))
@@ -206,74 +199,75 @@
@OptIn(ExperimentalStdlibApi::class)
@Test
- fun initialLoad_loadResultInvalid() = testScope.runBlockingTest {
- val dispatcher = coroutineContext[CoroutineDispatcher.Key]!!
- val pagingSources = mutableListOf<TestPagingSource>()
- val factory = {
- TestPagingSource().also {
- if (pagingSources.size == 0) it.nextLoadResult = PagingSource.LoadResult.Invalid()
- pagingSources.add(it)
+ fun initialLoad_loadResultInvalid() =
+ testScope.runBlockingTest {
+ val dispatcher = coroutineContext[CoroutineDispatcher.Key]!!
+ val pagingSources = mutableListOf<TestPagingSource>()
+ val factory = {
+ TestPagingSource().also {
+ if (pagingSources.size == 0)
+ it.nextLoadResult = PagingSource.LoadResult.Invalid()
+ pagingSources.add(it)
+ }
}
+ val config =
+ PagedList.Config.Builder().setEnablePlaceholders(false).setPageSize(3).build()
+
+ val livePagedList =
+ LivePagedList(
+ coroutineScope = testScope,
+ initialKey = null,
+ config = config,
+ boundaryCallback = null,
+ pagingSourceFactory = factory,
+ notifyDispatcher = dispatcher,
+ fetchDispatcher = dispatcher,
+ )
+
+ val pagedLists = mutableListOf<PagedList<Int>>()
+ livePagedList.observeForever { pagedLists.add(it) }
+
+ advanceUntilIdle()
+
+ assertThat(pagedLists.size).isEqualTo(2)
+ assertThat(pagingSources.size).isEqualTo(2)
+ assertThat(pagedLists.size).isEqualTo(2)
+ assertThat(pagedLists[1]).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8)
}
- val config = PagedList.Config.Builder()
- .setEnablePlaceholders(false)
- .setPageSize(3)
- .build()
-
- val livePagedList = LivePagedList(
- coroutineScope = testScope,
- initialKey = null,
- config = config,
- boundaryCallback = null,
- pagingSourceFactory = factory,
- notifyDispatcher = dispatcher,
- fetchDispatcher = dispatcher,
- )
-
- val pagedLists = mutableListOf<PagedList<Int>>()
- livePagedList.observeForever {
- pagedLists.add(it)
- }
-
- advanceUntilIdle()
-
- assertThat(pagedLists.size).isEqualTo(2)
- assertThat(pagingSources.size).isEqualTo(2)
- assertThat(pagedLists.size).isEqualTo(2)
- assertThat(pagedLists[1]).containsExactly(
- 0, 1, 2, 3, 4, 5, 6, 7, 8
- )
- }
companion object {
@Suppress("DEPRECATION")
- private val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
+ private val dataSource =
+ object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {}
+
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<String>
+ ) {}
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {}
- }
+ private val dataSourceFactory =
+ object : DataSource.Factory<Int, String>() {
+ override fun create(): DataSource<Int, String> = dataSource
+ }
- private val dataSourceFactory = object : DataSource.Factory<Int, String>() {
- override fun create(): DataSource<Int, String> = dataSource
- }
-
- private val pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(
- fetchDispatcher = Dispatchers.Main
- )
+ private val pagingSourceFactory =
+ dataSourceFactory.asPagingSourceFactory(fetchDispatcher = Dispatchers.Main)
private val config = Config(10)
- private val DIFF_STRING = object : DiffUtil.ItemCallback<String>() {
- override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
- }
+ private val DIFF_STRING =
+ object : DiffUtil.ItemCallback<String>() {
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
- override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
}
- }
}
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadFullListTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadFullListTest.kt
index be3aede..b4267d1 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadFullListTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadFullListTest.kt
@@ -42,72 +42,48 @@
* Test that repeatedly accessing edge items in paging will make it load all of the page even when
* there is heavy filtering involved.
*/
-class LoadFullListTest(
- private val reverse: Boolean
-) {
+class LoadFullListTest(private val reverse: Boolean) {
private val testScope = TestScope()
@get:Rule
- val dispatcherRule = MainDispatcherRule(
- testScope.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
- )
+ val dispatcherRule =
+ MainDispatcherRule(
+ testScope.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
+ )
- private val differ = AsyncPagingDataDiffer(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
+ private val differ =
+ AsyncPagingDataDiffer(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- updateCallback = ListUpdateCallbackFake(),
- workerDispatcher = Dispatchers.Main
- )
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ updateCallback = ListUpdateCallbackFake(),
+ workerDispatcher = Dispatchers.Main
+ )
+
+ @Test fun noFilter() = loadAll { true }
+
+ @Test fun everyOtherItem() = loadAll { it % 2 == 0 }
+
+ @Test fun only1Item_firstPage() = loadAll { it == 2 }
+
+ @Test fun only1Item_lastPage() = loadAll(sourceSize = 30) { it == 29 }
+
+ @Test fun noItems() = loadAll { false }
+
+ @Test fun firstItemInEachPage() = loadAll { it % pageConfig.pageSize == 0 }
+
+ @Test fun firstItemInEvenPages() = loadAll { it % (pageConfig.pageSize * 2) == 0 }
@Test
- fun noFilter() = loadAll {
- true
- }
-
- @Test
- fun everyOtherItem() = loadAll {
- it % 2 == 0
- }
-
- @Test
- fun only1Item_firstPage() = loadAll {
- it == 2
- }
-
- @Test
- fun only1Item_lastPage() = loadAll(
- sourceSize = 30
- ) {
- it == 29
- }
-
- @Test
- fun noItems() = loadAll {
- false
- }
-
- @Test
- fun firstItemInEachPage() = loadAll {
- it % pageConfig.pageSize == 0
- }
-
- @Test
- fun firstItemInEvenPages() = loadAll {
- it % (pageConfig.pageSize * 2) == 0
- }
-
- @Test
- fun firstItemInOddPages() = loadAll {
- it % (pageConfig.pageSize * 2) == pageConfig.pageSize
- }
+ fun firstItemInOddPages() = loadAll { it % (pageConfig.pageSize * 2) == pageConfig.pageSize }
@Test
fun endOfPrefetchDistance() = loadAll {
@@ -119,62 +95,58 @@
it % (pageConfig.prefetchDistance * 2) == pageConfig.prefetchDistance
}
- private fun loadAll(
- sourceSize: Int = 100,
- testFilter: (Int) -> Boolean
- ) = testScope.runTest {
- val expectedFinalSize = (0 until sourceSize).count(testFilter)
- val pager = Pager(
- config = pageConfig,
- initialKey = if (reverse) {
- sourceSize - 1
- } else {
- 0
- }
- ) {
- TestPagingSource(
- items = List(sourceSize) { it }
- )
- }
-
- val job = launch {
- pager.flow.map { pagingData ->
- pagingData.filter {
- testFilter(it)
+ private fun loadAll(sourceSize: Int = 100, testFilter: (Int) -> Boolean) =
+ testScope.runTest {
+ val expectedFinalSize = (0 until sourceSize).count(testFilter)
+ val pager =
+ Pager(
+ config = pageConfig,
+ initialKey =
+ if (reverse) {
+ sourceSize - 1
+ } else {
+ 0
+ }
+ ) {
+ TestPagingSource(items = List(sourceSize) { it })
}
- }.collectLatest {
- differ.submitData(it)
- }
- }
- advanceUntilIdle()
- // repeatedly load pages until all of the list is loaded
- while (differ.itemCount < expectedFinalSize) {
- val startSize = differ.itemCount
- if (reverse) {
- differ.getItem(0)
- } else {
- differ.getItem(differ.itemCount - 1)
+ val job = launch {
+ pager.flow
+ .map { pagingData -> pagingData.filter { testFilter(it) } }
+ .collectLatest { differ.submitData(it) }
}
+
advanceUntilIdle()
- if (differ.itemCount == startSize) {
- break
+ // repeatedly load pages until all of the list is loaded
+ while (differ.itemCount < expectedFinalSize) {
+ val startSize = differ.itemCount
+ if (reverse) {
+ differ.getItem(0)
+ } else {
+ differ.getItem(differ.itemCount - 1)
+ }
+ advanceUntilIdle()
+ if (differ.itemCount == startSize) {
+ break
+ }
}
- }
- assertThat(differ.itemCount).isEqualTo(expectedFinalSize)
+ assertThat(differ.itemCount).isEqualTo(expectedFinalSize)
- job.cancel()
- }
+ job.cancel()
+ }
companion object {
@JvmStatic
@Parameterized.Parameters(name = "reverse_{0}")
fun params() = listOf(false, true)
- private val pageConfig = PagingConfig(
- pageSize = 5,
- prefetchDistance = 5,
- enablePlaceholders = false,
- initialLoadSize = 5
- )
+
+ private val pageConfig =
+ PagingConfig(
+ pageSize = 5,
+ prefetchDistance = 5,
+ enablePlaceholders = false,
+ initialLoadSize = 5
+ )
}
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadStateAdapterTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadStateAdapterTest.kt
index 03e4797..ef7a959 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadStateAdapterTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/LoadStateAdapterTest.kt
@@ -39,7 +39,9 @@
class LoadStateAdapterTest {
class AdapterEventRecorder : RecyclerView.AdapterDataObserver() {
enum class Event {
- CHANGE, INSERT, REMOVED
+ CHANGE,
+ INSERT,
+ REMOVED
}
private val observedEvents = mutableListOf<Event>()
@@ -93,24 +95,15 @@
return object : RecyclerView.ViewHolder(View(parent.context)) {}
}
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, loadState: LoadState) {
- }
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, loadState: LoadState) {}
}
@Test
fun init() {
val adapter = SimpleLoadStateAdapter()
assertEquals(0, adapter.itemCount)
- assertFalse(
- adapter.displayLoadStateAsItem(
- NotLoading(endOfPaginationReached = false)
- )
- )
- assertFalse(
- adapter.displayLoadStateAsItem(
- NotLoading(endOfPaginationReached = true)
- )
- )
+ assertFalse(adapter.displayLoadStateAsItem(NotLoading(endOfPaginationReached = false)))
+ assertFalse(adapter.displayLoadStateAsItem(NotLoading(endOfPaginationReached = true)))
assertTrue(adapter.displayLoadStateAsItem(Error(Throwable())))
assertTrue(adapter.displayLoadStateAsItem(Loading))
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListAdapterTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListAdapterTest.kt
index a342f2e..f3201dc 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListAdapterTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListAdapterTest.kt
@@ -34,9 +34,10 @@
private val mainThread = TestExecutor()
private val diffThread = TestExecutor()
- private val differConfig = AsyncDifferConfig.Builder(STRING_DIFF_CALLBACK)
- .setBackgroundThreadExecutor(diffThread)
- .build()
+ private val differConfig =
+ AsyncDifferConfig.Builder(STRING_DIFF_CALLBACK)
+ .setBackgroundThreadExecutor(diffThread)
+ .build()
@Suppress("DEPRECATION")
inner class Adapter(
@@ -217,14 +218,15 @@
}
companion object {
- private val STRING_DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
- override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
- }
+ private val STRING_DIFF_CALLBACK =
+ object : DiffUtil.ItemCallback<String>() {
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
- override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
}
- }
}
}
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListListenerFake.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListListenerFake.kt
index 9bd09f5..8436389 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListListenerFake.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagedListListenerFake.kt
@@ -21,12 +21,7 @@
val onCurrentListChangedEvents = mutableListOf<OnCurrentListChangedEvent<T>>()
override fun onCurrentListChanged(previousList: PagedList<T>?, currentList: PagedList<T>?) {
- onCurrentListChangedEvents.add(
- OnCurrentListChangedEvent(
- previousList,
- currentList
- )
- )
+ onCurrentListChangedEvents.add(OnCurrentListChangedEvent(previousList, currentList))
}
data class OnCurrentListChangedEvent<T : Any>(
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagingDataAdapterTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagingDataAdapterTest.kt
index 9c2c2ee..502bed2 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/PagingDataAdapterTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/PagingDataAdapterTest.kt
@@ -43,28 +43,28 @@
@Test
fun hasStableIds() {
- val pagingDataAdapter = object : PagingDataAdapter<Int, ViewHolder>(
- diffCallback = object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
+ val pagingDataAdapter =
+ object :
+ PagingDataAdapter<Int, ViewHolder>(
+ diffCallback =
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ }
+ ) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ fail("Should never get here")
}
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ fail("Should never get here")
}
}
- ) {
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): ViewHolder {
- fail("Should never get here")
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- fail("Should never get here")
- }
- }
assertFailsWith<UnsupportedOperationException> { pagingDataAdapter.setHasStableIds(true) }
}
@@ -73,29 +73,28 @@
fun workerContext() = runTest {
val workerExecutor = TestExecutor()
val workerContext: CoroutineContext = workerExecutor.asCoroutineDispatcher()
- val adapter = object : PagingDataAdapter<Int, ViewHolder>(
- object : DiffUtil.ItemCallback<Int>() {
- override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
+ val adapter =
+ object :
+ PagingDataAdapter<Int, ViewHolder>(
+ object : DiffUtil.ItemCallback<Int>() {
+ override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+ return oldItem == newItem
+ }
+ },
+ coroutineContext,
+ workerContext,
+ ) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return object : ViewHolder(TextView(parent.context)) {}
}
- override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
- return oldItem == newItem
- }
- },
- coroutineContext,
- workerContext,
- ) {
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): ViewHolder {
- return object : ViewHolder(TextView(parent.context)) {}
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
}
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
- }
-
val job = launch {
adapter.submitData(PagingData.from(listOf(1)))
adapter.submitData(PagingData.from(listOf(2)))
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffHelperTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffHelperTest.kt
index 9a2eb07..5c919f0 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffHelperTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffHelperTest.kt
@@ -40,8 +40,10 @@
override val placeholdersAfter: Int
) : PlaceholderPaddedList<String> {
override fun getItem(index: Int): String = data[index]
+
override val size: Int
get() = placeholdersBefore + data.size + placeholdersAfter
+
override val dataCount: Int
get() = data.size
}
@@ -58,14 +60,8 @@
@Test
fun appendFill() {
- validateTwoListDiff(
- Storage(5, listOf("a", "b"), 5),
- Storage(5, listOf("a", "b", "c"), 4)
- ) {
- assertEquals(
- OnChangedEvent(7, 1, PLACEHOLDER_TO_ITEM),
- it.onChangedEvents[0]
- )
+ validateTwoListDiff(Storage(5, listOf("a", "b"), 5), Storage(5, listOf("a", "b", "c"), 4)) {
+ assertEquals(OnChangedEvent(7, 1, PLACEHOLDER_TO_ITEM), it.onChangedEvents[0])
assertEquals(1, it.interactions)
}
}
@@ -76,24 +72,15 @@
Storage(5, listOf("a", "b", "c"), 4),
Storage(5, listOf("a", "b"), 5),
) {
- assertEquals(
- OnChangedEvent(7, 1, ITEM_TO_PLACEHOLDER),
- it.onChangedEvents[0]
- )
+ assertEquals(OnChangedEvent(7, 1, ITEM_TO_PLACEHOLDER), it.onChangedEvents[0])
assertEquals(1, it.interactions)
}
}
@Test
fun prependFill() {
- validateTwoListDiff(
- Storage(5, listOf("b", "c"), 5),
- Storage(4, listOf("a", "b", "c"), 5)
- ) {
- assertEquals(
- OnChangedEvent(4, 1, PLACEHOLDER_TO_ITEM),
- it.onChangedEvents[0]
- )
+ validateTwoListDiff(Storage(5, listOf("b", "c"), 5), Storage(4, listOf("a", "b", "c"), 5)) {
+ assertEquals(OnChangedEvent(4, 1, PLACEHOLDER_TO_ITEM), it.onChangedEvents[0])
assertEquals(1, it.interactions)
}
}
@@ -104,10 +91,7 @@
Storage(4, listOf("a", "b", "c"), 5),
Storage(5, listOf("b", "c"), 5),
) {
- assertEquals(
- OnChangedEvent(4, 1, ITEM_TO_PLACEHOLDER),
- it.onChangedEvents[0]
- )
+ assertEquals(OnChangedEvent(4, 1, ITEM_TO_PLACEHOLDER), it.onChangedEvents[0])
assertEquals(1, it.interactions)
}
}
@@ -142,20 +126,19 @@
Storage(5, listOf("a", "b", "c"), 5),
Storage(5, listOf("c", "x", "y", "a", "b"), 5)
) {
- assertThat(
- it.allEvents
- ).containsExactly(
- // convert 2 placeholders to x,y
- OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM),
- // move c to before x,y
- OnMovedEvent(7, 3),
- // these placeholders will shift down in the list as we'll re-add 2 placeholders
- OnChangedEvent(0, 3, PLACEHOLDER_POSITION_CHANGE),
- // now we need 2 new placeholders
- OnInsertedEvent(0, 2),
- // all trailing placeholders shifted 2 positions
- OnChangedEvent(10, 5, PLACEHOLDER_POSITION_CHANGE),
- )
+ assertThat(it.allEvents)
+ .containsExactly(
+ // convert 2 placeholders to x,y
+ OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM),
+ // move c to before x,y
+ OnMovedEvent(7, 3),
+ // these placeholders will shift down in the list as we'll re-add 2 placeholders
+ OnChangedEvent(0, 3, PLACEHOLDER_POSITION_CHANGE),
+ // now we need 2 new placeholders
+ OnInsertedEvent(0, 2),
+ // all trailing placeholders shifted 2 positions
+ OnChangedEvent(10, 5, PLACEHOLDER_POSITION_CHANGE),
+ )
}
}
@@ -165,12 +148,8 @@
Storage(5, listOf("a", "b", "c"), 5),
Storage(3, listOf("c", "x", "y", "a", "b"), 5)
) {
- assertThat(
- it.allEvents
- ).containsExactly(
- OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM),
- OnMovedEvent(7, 3)
- )
+ assertThat(it.allEvents)
+ .containsExactly(OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM), OnMovedEvent(7, 3))
}
}
@@ -180,16 +159,15 @@
Storage(5, listOf("a", "b", "c"), 5),
Storage(5, listOf("b", "c", "x", "y", "a"), 5)
) {
- assertThat(
- it.allEvents
- ).containsExactly(
- // insert x, y as placeholder changes
- OnChangedEvent(8, 2, PLACEHOLDER_TO_ITEM),
- // move a to after x,y
- OnMovedEvent(5, 9),
- // insert new placeholders to the end
- OnInsertedEvent(13, 2)
- )
+ assertThat(it.allEvents)
+ .containsExactly(
+ // insert x, y as placeholder changes
+ OnChangedEvent(8, 2, PLACEHOLDER_TO_ITEM),
+ // move a to after x,y
+ OnMovedEvent(5, 9),
+ // insert new placeholders to the end
+ OnInsertedEvent(13, 2)
+ )
}
}
@@ -199,22 +177,21 @@
Storage(4, listOf("a", "b", "c", "d", "e"), 1),
Storage(1, listOf("d", "e", "f", "g"), 20)
) {
- assertThat(
- it.allEvents
- ).containsExactly(
- // add f to replace the placeholder after e
- OnChangedEvent(9, 1, PLACEHOLDER_TO_ITEM),
- // add g, we don't have a placeholder for it so it is an insertion
- OnInsertedEvent(10, 1),
- // rm a,b,c
- OnRemovedEvent(4, 3),
- // rm 3 unnecessary leading placeholders
- OnRemovedEvent(0, 3),
- // 3rd placeholder moved to pos 0, so dispatch a change for it
- OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
- // add 20 trailing placeholders
- OnInsertedEvent(5, 20)
- )
+ assertThat(it.allEvents)
+ .containsExactly(
+ // add f to replace the placeholder after e
+ OnChangedEvent(9, 1, PLACEHOLDER_TO_ITEM),
+ // add g, we don't have a placeholder for it so it is an insertion
+ OnInsertedEvent(10, 1),
+ // rm a,b,c
+ OnRemovedEvent(4, 3),
+ // rm 3 unnecessary leading placeholders
+ OnRemovedEvent(0, 3),
+ // 3rd placeholder moved to pos 0, so dispatch a change for it
+ OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
+ // add 20 trailing placeholders
+ OnInsertedEvent(5, 20)
+ )
}
}
@@ -282,20 +259,16 @@
@Test
fun transformAnchorIndex_offset() {
- validateTwoListDiffTransform(
- Storage(5, listOf("a"), 6),
- Storage(7, listOf("a"), 8)
- ) { transformAnchorIndex ->
+ validateTwoListDiffTransform(Storage(5, listOf("a"), 6), Storage(7, listOf("a"), 8)) {
+ transformAnchorIndex ->
assertEquals(7, transformAnchorIndex(5))
}
}
@Test
fun transformAnchorIndex_nullBehavior() {
- validateTwoListDiffTransform(
- Storage(3, listOf("a"), 4),
- Storage(1, listOf("a"), 2)
- ) { transformAnchorIndex ->
+ validateTwoListDiffTransform(Storage(3, listOf("a"), 4), Storage(1, listOf("a"), 2)) {
+ transformAnchorIndex ->
// null, so map to same position in new list
assertEquals(0, transformAnchorIndex(0))
assertEquals(1, transformAnchorIndex(1))
@@ -313,10 +286,8 @@
@Test
fun transformAnchorIndex_boundaryBehavior() {
- validateTwoListDiffTransform(
- Storage(3, listOf("a"), 4),
- Storage(1, listOf("a"), 2)
- ) { transformAnchorIndex ->
+ validateTwoListDiffTransform(Storage(3, listOf("a"), 4), Storage(1, listOf("a"), 2)) {
+ transformAnchorIndex ->
// shouldn't happen, but to be safe, indices are clamped
assertEquals(0, transformAnchorIndex(-1))
assertEquals(3, transformAnchorIndex(100))
@@ -329,14 +300,16 @@
Storage(4, listOf("c_4", "4", "d_5", "5", "e_6", "6"), 3),
Storage(0, listOf("a_0", "0", "b_1", "1"), 8)
) {
- assertThat(it.allEvents).containsExactly(
- // replace previous items with placeholders
- OnChangedEvent(4, 6, ITEM_TO_PLACEHOLDER),
- // swap first 4 placeholders with newly loaded items
- OnChangedEvent(0, 4, PLACEHOLDER_TO_ITEM),
- // remove extra placeholder
- OnRemovedEvent(12, 1)
- ).inOrder()
+ assertThat(it.allEvents)
+ .containsExactly(
+ // replace previous items with placeholders
+ OnChangedEvent(4, 6, ITEM_TO_PLACEHOLDER),
+ // swap first 4 placeholders with newly loaded items
+ OnChangedEvent(0, 4, PLACEHOLDER_TO_ITEM),
+ // remove extra placeholder
+ OnRemovedEvent(12, 1)
+ )
+ .inOrder()
}
}
@@ -346,32 +319,34 @@
Storage(4, listOf("a_4", "4", "b_5", "5", "c_6", "6"), 3),
Storage(8, listOf("d_8", "8", "e_9", "9"), 0),
) {
- assertThat(it.allEvents).containsExactly(
- // remove c_6 and 6, their positions overlap w/ newly loaded items
- OnRemovedEvent(8, 2),
- // now insert d_8 and 8
- OnInsertedEvent(8, 2),
- // first 4 of the loaded items becomes placeholders: "a_4", "4", "b_5", "5"
- OnChangedEvent(4, 4, ITEM_TO_PLACEHOLDER),
- // insert e_9 and 9 using placeholders
- OnChangedEvent(10, 2, PLACEHOLDER_TO_ITEM),
- // finally, remove the last placeholder that we won't use
- OnRemovedEvent(12, 1)
- )
+ assertThat(it.allEvents)
+ .containsExactly(
+ // remove c_6 and 6, their positions overlap w/ newly loaded items
+ OnRemovedEvent(8, 2),
+ // now insert d_8 and 8
+ OnInsertedEvent(8, 2),
+ // first 4 of the loaded items becomes placeholders: "a_4", "4", "b_5", "5"
+ OnChangedEvent(4, 4, ITEM_TO_PLACEHOLDER),
+ // insert e_9 and 9 using placeholders
+ OnChangedEvent(10, 2, PLACEHOLDER_TO_ITEM),
+ // finally, remove the last placeholder that we won't use
+ OnRemovedEvent(12, 1)
+ )
}
}
companion object {
- private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
- override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
- // first char means same item
- return oldItem[0] == newItem[0]
- }
+ private val DIFF_CALLBACK =
+ object : DiffUtil.ItemCallback<String>() {
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+ // first char means same item
+ return oldItem[0] == newItem[0]
+ }
- override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+ return oldItem == newItem
+ }
}
- }
private fun validateTwoListDiff(
oldList: Storage,
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffWithRecyclerViewTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffWithRecyclerViewTest.kt
index 812da03..139c077 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffWithRecyclerViewTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/PlaceholderPaddedListDiffWithRecyclerViewTest.kt
@@ -36,8 +36,8 @@
import org.junit.runner.RunWith
/**
- * For some tests, this test uses a real recyclerview with a real adapter to serve as an
- * integration test so that we can validate all updates and state restorations after updates.
+ * For some tests, this test uses a real recyclerview with a real adapter to serve as an integration
+ * test so that we can validate all updates and state restorations after updates.
*/
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -49,12 +49,11 @@
@Before
fun init() {
context = ApplicationProvider.getApplicationContext()
- recyclerView = RecyclerView(
- context
- ).also {
- it.layoutManager = LinearLayoutManager(context)
- it.itemAnimator = null
- }
+ recyclerView =
+ RecyclerView(context).also {
+ it.layoutManager = LinearLayoutManager(context)
+ it.itemAnimator = null
+ }
adapter = PlaceholderPaddedListAdapter()
recyclerView.adapter = adapter
}
@@ -71,35 +70,39 @@
@Test
fun basic() {
- val storage = PlaceholderPaddedStorage(
- placeholdersBefore = 0,
- data = createItems(0, 10),
- placeholdersAfter = 0
- )
+ val storage =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 0,
+ data = createItems(0, 10),
+ placeholdersAfter = 0
+ )
adapter.setItems(storage)
measureAndLayout()
val snapshot = captureUISnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- createExpectedSnapshot(
- firstItemTopOffset = 0,
- startItemIndex = 0,
- backingList = storage
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ createExpectedSnapshot(
+ firstItemTopOffset = 0,
+ startItemIndex = 0,
+ backingList = storage
+ )
)
- )
}
@Test
fun distinctLists_fullyOverlappingRange() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 8),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 100, count = 8),
- placeholdersAfter = 30
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 8),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 100, count = 8),
+ placeholdersAfter = 30
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -108,34 +111,35 @@
@Test
fun distinctLists_loadedBefore_or_After() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 10),
- placeholdersAfter = 10
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 5,
- data = createItems(startId = 5, count = 5),
- placeholdersAfter = 20
- )
- distinctListTest_withVariousInitialPositions(
- pre = pre,
- post = post
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 10),
+ placeholdersAfter = 10
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 5,
+ data = createItems(startId = 5, count = 5),
+ placeholdersAfter = 20
+ )
+ distinctListTest_withVariousInitialPositions(pre = pre, post = post)
}
@Test
fun distinctLists_partiallyOverlapping() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 0, count = 8),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 15,
- data = createItems(startId = 100, count = 8),
- placeholdersAfter = 30
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 0, count = 8),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 15,
+ data = createItems(startId = 100, count = 8),
+ placeholdersAfter = 30
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -144,16 +148,18 @@
@Test
fun distinctLists_fewerItemsLoaded_withMorePlaceholdersBefore() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 8),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 15,
- data = createItems(startId = 100, count = 3),
- placeholdersAfter = 30
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 8),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 15,
+ data = createItems(startId = 100, count = 3),
+ placeholdersAfter = 30
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -162,16 +168,18 @@
@Test
fun distinctLists_noPlaceholdersLeft() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 8),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 0,
- data = createItems(startId = 100, count = 3),
- placeholdersAfter = 0
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 8),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 0,
+ data = createItems(startId = 100, count = 3),
+ placeholdersAfter = 0
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -180,16 +188,18 @@
@Test
fun distinctLists_moreItemsLoaded() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 3),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 100, count = 8),
- placeholdersAfter = 30
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 3),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 100, count = 8),
+ placeholdersAfter = 30
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -198,16 +208,18 @@
@Test
fun distinctLists_moreItemsLoaded_andAlsoMoreOffset() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(startId = 10, count = 3),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 15,
- data = createItems(startId = 100, count = 8),
- placeholdersAfter = 30
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(startId = 10, count = 3),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 15,
+ data = createItems(startId = 100, count = 8),
+ placeholdersAfter = 30
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
@@ -216,25 +228,25 @@
@Test
fun distinctLists_expandShrink() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(10, 10),
- placeholdersAfter = 20
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 0,
- data = createItems(100, 1),
- placeholdersAfter = 0
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(10, 10),
+ placeholdersAfter = 20
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 0,
+ data = createItems(100, 1),
+ placeholdersAfter = 0
+ )
distinctListTest_withVariousInitialPositions(
pre = pre,
post = post,
)
}
- /**
- * Runs a state restoration test with various "current scroll positions".
- */
+ /** Runs a state restoration test with various "current scroll positions". */
private fun distinctListTest_withVariousInitialPositions(
pre: PlaceholderPaddedStorage,
post: PlaceholderPaddedStorage
@@ -260,45 +272,48 @@
@Test
fun distinctLists_visibleRangeRemoved() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(10, 10),
- placeholdersAfter = 30
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 0,
- data = createItems(100, 4),
- placeholdersAfter = 20
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(10, 10),
+ placeholdersAfter = 30
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 0,
+ data = createItems(100, 4),
+ placeholdersAfter = 20
+ )
swapListTest(
pre = pre,
post = post,
- preSwapAction = {
- recyclerView.scrollBy(0, 30 * ITEM_HEIGHT)
- },
+ preSwapAction = { recyclerView.scrollBy(0, 30 * ITEM_HEIGHT) },
validate = { _, newSnapshot ->
- assertThat(newSnapshot).containsExactlyElementsIn(
- createExpectedSnapshot(
- startItemIndex = post.size - RV_HEIGHT / ITEM_HEIGHT,
- backingList = post
+ assertThat(newSnapshot)
+ .containsExactlyElementsIn(
+ createExpectedSnapshot(
+ startItemIndex = post.size - RV_HEIGHT / ITEM_HEIGHT,
+ backingList = post
+ )
)
- )
}
)
}
@Test
fun distinctLists_validateDiff() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 10,
- data = createItems(10, 10), // their positions won't be in the new list
- placeholdersAfter = 20
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 0,
- data = createItems(100, 1),
- placeholdersAfter = 0
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 10,
+ data = createItems(10, 10), // their positions won't be in the new list
+ placeholdersAfter = 20
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 0,
+ data = createItems(100, 1),
+ placeholdersAfter = 0
+ )
updateDiffTest(pre, post)
}
@@ -308,14 +323,12 @@
// this is a random test but if it fails, the exception will have enough information to
// create an isolated test
val rand = Random(System.nanoTime())
- fun randomPlaceholderPaddedStorage(startId: Int) = PlaceholderPaddedStorage(
- placeholdersBefore = rand.nextInt(0, 20),
- data = createItems(
- startId = startId,
- count = rand.nextInt(0, 20)
- ),
- placeholdersAfter = rand.nextInt(0, 20)
- )
+ fun randomPlaceholderPaddedStorage(startId: Int) =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = rand.nextInt(0, 20),
+ data = createItems(startId = startId, count = rand.nextInt(0, 20)),
+ placeholdersAfter = rand.nextInt(0, 20)
+ )
repeat(RANDOM_TEST_REPEAT_SIZE) {
updateDiffTest(
pre = randomPlaceholderPaddedStorage(0),
@@ -326,61 +339,69 @@
@Test
fun continuousMatch_1() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 4,
- data = createItems(startId = 0, count = 16),
- placeholdersAfter = 1
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 1,
- data = createItems(startId = 13, count = 4),
- placeholdersAfter = 19
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 4,
+ data = createItems(startId = 0, count = 16),
+ placeholdersAfter = 1
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 1,
+ data = createItems(startId = 13, count = 4),
+ placeholdersAfter = 19
+ )
updateDiffTest(pre, post)
}
@Test
fun continuousMatch_2() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 6,
- data = createItems(startId = 0, count = 9),
- placeholdersAfter = 19
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 14,
- data = createItems(startId = 4, count = 3),
- placeholdersAfter = 11
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 6,
+ data = createItems(startId = 0, count = 9),
+ placeholdersAfter = 19
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 14,
+ data = createItems(startId = 4, count = 3),
+ placeholdersAfter = 11
+ )
updateDiffTest(pre, post)
}
@Test
fun continuousMatch_3() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 11,
- data = createItems(startId = 0, count = 4),
- placeholdersAfter = 6
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 7,
- data = createItems(startId = 0, count = 1),
- placeholdersAfter = 11
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 11,
+ data = createItems(startId = 0, count = 4),
+ placeholdersAfter = 6
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 7,
+ data = createItems(startId = 0, count = 1),
+ placeholdersAfter = 11
+ )
updateDiffTest(pre, post)
}
@Test
fun continuousMatch_4() {
- val pre = PlaceholderPaddedStorage(
- placeholdersBefore = 4,
- data = createItems(startId = 0, count = 15),
- placeholdersAfter = 18
- )
- val post = PlaceholderPaddedStorage(
- placeholdersBefore = 11,
- data = createItems(startId = 5, count = 17),
- placeholdersAfter = 9
- )
+ val pre =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 4,
+ data = createItems(startId = 0, count = 15),
+ placeholdersAfter = 18
+ )
+ val post =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = 11,
+ data = createItems(startId = 5, count = 17),
+ placeholdersAfter = 9
+ )
updateDiffTest(pre, post)
}
@@ -397,47 +418,39 @@
}
/**
- * Tests that if two lists have some overlaps, we dispatch the right diff events.
- * It can also optionally shuffle the lists.
+ * Tests that if two lists have some overlaps, we dispatch the right diff events. It can also
+ * optionally shuffle the lists.
*/
private fun randomContinuousMatchTest(shuffle: Boolean) {
// this is a random test but if it fails, the exception will have enough information to
// create an isolated test
val rand = Random(System.nanoTime())
- fun randomPlaceholderPaddedStorage(startId: Int) = PlaceholderPaddedStorage(
- placeholdersBefore = rand.nextInt(0, 20),
- data = createItems(
- startId = startId,
- count = rand.nextInt(0, 20)
- ).let {
- if (shuffle) it.shuffled()
- else it
- },
- placeholdersAfter = rand.nextInt(0, 20)
- )
+ fun randomPlaceholderPaddedStorage(startId: Int) =
+ PlaceholderPaddedStorage(
+ placeholdersBefore = rand.nextInt(0, 20),
+ data =
+ createItems(startId = startId, count = rand.nextInt(0, 20)).let {
+ if (shuffle) it.shuffled() else it
+ },
+ placeholdersAfter = rand.nextInt(0, 20)
+ )
repeat(RANDOM_TEST_REPEAT_SIZE) {
val pre = randomPlaceholderPaddedStorage(0)
- val post = randomPlaceholderPaddedStorage(
- startId = if (pre.dataCount > 0) {
- pre.getItem(rand.nextInt(pre.dataCount)).id
- } else {
- 0
- }
- )
- updateDiffTest(
- pre = pre,
- post = post
- )
+ val post =
+ randomPlaceholderPaddedStorage(
+ startId =
+ if (pre.dataCount > 0) {
+ pre.getItem(rand.nextInt(pre.dataCount)).id
+ } else {
+ 0
+ }
+ )
+ updateDiffTest(pre = pre, post = post)
}
}
- /**
- * Validates that the update events between [pre] and [post] are correct.
- */
- private fun updateDiffTest(
- pre: PlaceholderPaddedStorage,
- post: PlaceholderPaddedStorage
- ) {
+ /** Validates that the update events between [pre] and [post] are correct. */
+ private fun updateDiffTest(pre: PlaceholderPaddedStorage, post: PlaceholderPaddedStorage) {
val callback = ValidatingListUpdateCallback(pre, post)
val diffResult = pre.computeDiff(post, PlaceholderPaddedListItem.CALLBACK)
pre.dispatchDiff(callback, post, diffResult)
@@ -455,34 +468,29 @@
swapListTest(
pre = pre,
post = post,
- preSwapAction = {
- recyclerView.scrollBy(
- 0,
- initialListPos * ITEM_HEIGHT
- )
- },
+ preSwapAction = { recyclerView.scrollBy(0, initialListPos * ITEM_HEIGHT) },
validate = { _, snapshot ->
assertWithMessage(
- """
+ """
initial pos: $initialListPos
expected final pos: $finalListPos
pre: $pre
post: $post
- """.trimIndent()
- ).that(snapshot).containsExactlyElementsIn(
- createExpectedSnapshot(
- startItemIndex = finalListPos,
- backingList = post
+ """
+ .trimIndent()
)
- )
+ .that(snapshot)
+ .containsExactlyElementsIn(
+ createExpectedSnapshot(startItemIndex = finalListPos, backingList = post)
+ )
}
)
}
/**
* Helper function to run tests where we submit the [pre] list, run [preSwapAction] (where it
- * can scroll etc) then submit [post] list, run [postSwapAction] and then call [validate]
- * with UI snapshots.
+ * can scroll etc) then submit [post] list, run [postSwapAction] and then call [validate] with
+ * UI snapshots.
*/
private fun swapListTest(
pre: PlaceholderPaddedStorage,
@@ -502,9 +510,7 @@
validate(preSnapshot, postSnapshot)
}
- /**
- * Captures positions and data of each visible item in the RecyclerView.
- */
+ /** Captures positions and data of each visible item in the RecyclerView. */
private fun captureUISnapshot(): List<UIItemSnapshot> {
return (0 until recyclerView.childCount).mapNotNull { childPos ->
val view = recyclerView.getChildAt(childPos)!!
@@ -522,9 +528,7 @@
}
}
- /**
- * Custom adapter class that also validates its update events to ensure they are correct.
- */
+ /** Custom adapter class that also validates its update events to ensure they are correct. */
private class PlaceholderPaddedListAdapter :
RecyclerView.Adapter<PlaceholderPaddedListViewHolder>() {
private var items: PlaceholderPaddedList<PlaceholderPaddedListItem>? = null
@@ -551,10 +555,8 @@
viewType: Int
): PlaceholderPaddedListViewHolder {
return PlaceholderPaddedListViewHolder(parent.context).also {
- it.itemView.layoutParams = RecyclerView.LayoutParams(
- RecyclerView.LayoutParams.MATCH_PARENT,
- ITEM_HEIGHT
- )
+ it.itemView.layoutParams =
+ RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, ITEM_HEIGHT)
}
}
@@ -569,34 +571,32 @@
}
}
- private data class PlaceholderPaddedListItem(
- val id: Int,
- val value: String
- ) {
+ private data class PlaceholderPaddedListItem(val id: Int, val value: String) {
companion object {
- val CALLBACK = object : DiffUtil.ItemCallback<PlaceholderPaddedListItem>() {
- override fun areItemsTheSame(
- oldItem: PlaceholderPaddedListItem,
- newItem: PlaceholderPaddedListItem
- ): Boolean {
- return oldItem.id == newItem.id
- }
+ val CALLBACK =
+ object : DiffUtil.ItemCallback<PlaceholderPaddedListItem>() {
+ override fun areItemsTheSame(
+ oldItem: PlaceholderPaddedListItem,
+ newItem: PlaceholderPaddedListItem
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
- override fun areContentsTheSame(
- oldItem: PlaceholderPaddedListItem,
- newItem: PlaceholderPaddedListItem
- ): Boolean {
- return oldItem == newItem
+ override fun areContentsTheSame(
+ oldItem: PlaceholderPaddedListItem,
+ newItem: PlaceholderPaddedListItem
+ ): Boolean {
+ return oldItem == newItem
+ }
}
- }
}
}
- private class PlaceholderPaddedListViewHolder(
- context: Context
- ) : RecyclerView.ViewHolder(View(context)) {
+ private class PlaceholderPaddedListViewHolder(context: Context) :
+ RecyclerView.ViewHolder(View(context)) {
var boundItem: PlaceholderPaddedListItem? = null
var boundPos: Int = -1
+
override fun toString(): String {
return "VH[$boundPos , $boundItem]"
}
@@ -620,7 +620,8 @@
"""
$placeholdersBefore:${data.size}:$placeholdersAfter
$data
- """.trimIndent()
+ """
+ .trimIndent()
}
override fun getItem(index: Int): PlaceholderPaddedListItem = data[index]
@@ -634,41 +635,25 @@
override fun toString() = stringRepresentation
}
- private fun createItems(
- startId: Int,
- count: Int
- ): List<PlaceholderPaddedListItem> {
+ private fun createItems(startId: Int, count: Int): List<PlaceholderPaddedListItem> {
return (startId until startId + count).map {
- PlaceholderPaddedListItem(
- id = it,
- value = "$it"
- )
+ PlaceholderPaddedListItem(id = it, value = "$it")
}
}
- /**
- * Creates an expected UI snapshot based on the given list and scroll position / offset.
- */
+ /** Creates an expected UI snapshot based on the given list and scroll position / offset. */
private fun createExpectedSnapshot(
firstItemTopOffset: Int = 0,
startItemIndex: Int,
backingList: PlaceholderPaddedList<PlaceholderPaddedListItem>
): List<UIItemSnapshot> {
- check(firstItemTopOffset <= 0) {
- "first item offset should not be negative"
- }
+ check(firstItemTopOffset <= 0) { "first item offset should not be negative" }
var remainingHeight = RV_HEIGHT - firstItemTopOffset
var pos = startItemIndex
var top = firstItemTopOffset
val result = mutableListOf<UIItemSnapshot>()
while (remainingHeight > 0 && pos < backingList.size) {
- result.add(
- UIItemSnapshot(
- top = top,
- boundItem = backingList.get(pos),
- boundPos = pos
- )
- )
+ result.add(UIItemSnapshot(top = top, boundItem = backingList.get(pos), boundPos = pos))
top += ITEM_HEIGHT
remainingHeight -= ITEM_HEIGHT
pos++
@@ -678,20 +663,20 @@
/**
* A ListUpdateCallback implementation that tracks all change notifications and then validate
- * that
- * a) changes are correct
- * b) no unnecessary events are dispatched (e.g. dispatching change for an item then removing
- * it)
+ * that a) changes are correct b) no unnecessary events are dispatched (e.g. dispatching change
+ * for an item then removing it)
*/
private class ValidatingListUpdateCallback<T>(
previousList: PlaceholderPaddedList<T>?,
private val newList: PlaceholderPaddedList<T>
) : ListUpdateCallback {
// used in assertion messages
- val msg = """
+ val msg =
+ """
oldList: $previousList
newList: $newList
- """.trimIndent()
+ """
+ .trimIndent()
// all changes are applied to this list, at the end, we'll validate against the new list
// to ensure all updates made sense and no unnecessary updates are made
@@ -709,9 +694,7 @@
override fun onInserted(position: Int, count: Int) {
position.assertWithinBounds()
assertWithMessage(msg).that(count).isAtLeast(1)
- repeat(count) {
- runningList.add(position, ListSnapshotItem.Inserted)
- }
+ repeat(count) { runningList.add(position, ListSnapshotItem.Inserted) }
}
override fun onRemoved(position: Int, count: Int) {
@@ -720,15 +703,13 @@
assertWithMessage(msg).that(count).isAtLeast(1)
(position until position + count).forEach { pos ->
assertWithMessage(
- "$msg\nshouldn't be removing an item that already got a change event" +
- " pos: $pos , ${runningList[pos]}"
- )
+ "$msg\nshouldn't be removing an item that already got a change event" +
+ " pos: $pos , ${runningList[pos]}"
+ )
.that(runningList[pos].isOriginalItem())
.isTrue()
}
- repeat(count) {
- runningList.removeAt(position)
- }
+ repeat(count) { runningList.removeAt(position) }
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
@@ -744,25 +725,25 @@
(position until position + count).forEach { pos ->
// make sure we don't dispatch overlapping updates
assertWithMessage(
- "$msg\nunnecessary change event for position $pos $payload " +
- "${runningList[pos]}"
- )
+ "$msg\nunnecessary change event for position $pos $payload " +
+ "${runningList[pos]}"
+ )
.that(runningList[pos].isOriginalItem())
.isTrue()
- if (payload == DiffingChangePayload.PLACEHOLDER_TO_ITEM ||
- payload == DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
+ if (
+ payload == DiffingChangePayload.PLACEHOLDER_TO_ITEM ||
+ payload == DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
) {
- assertWithMessage(msg).that(runningList[pos]).isInstanceOf(
- ListSnapshotItem.Placeholder::class.java
- )
+ assertWithMessage(msg)
+ .that(runningList[pos])
+ .isInstanceOf(ListSnapshotItem.Placeholder::class.java)
} else {
- assertWithMessage(msg).that(runningList[pos]).isInstanceOf(
- ListSnapshotItem.Item::class.java
- )
+ assertWithMessage(msg)
+ .that(runningList[pos])
+ .isInstanceOf(ListSnapshotItem.Item::class.java)
}
- runningList[pos] = ListSnapshotItem.Changed(
- payload = payload as? DiffingChangePayload
- )
+ runningList[pos] =
+ ListSnapshotItem.Changed(payload = payload as? DiffingChangePayload)
}
}
@@ -772,10 +753,7 @@
val newListSnapshot = newList.createSnapshot()
runningList.forEachIndexed { index, listSnapshotItem ->
val newListItem = newListSnapshot[index]
- listSnapshotItem.assertReplacement(
- msg,
- newListItem
- )
+ listSnapshotItem.assertReplacement(msg, newListItem)
if (!listSnapshotItem.isOriginalItem()) {
// if it changed, replace from new snapshot
runningList[index] = newListSnapshot[index]
@@ -827,70 +805,39 @@
return getItem(storageIndex)
}
-/**
- * Create a snapshot of this current that can be used to verify diffs.
- */
-private fun <T> PlaceholderPaddedList<T>.createSnapshot():
- MutableList<ListSnapshotItem> = (0 until size)
- .mapTo(mutableListOf()) { pos ->
- get(pos)?.let {
- ListSnapshotItem.Item(it)
- } ?: ListSnapshotItem.Placeholder(pos)
+/** Create a snapshot of this current that can be used to verify diffs. */
+private fun <T> PlaceholderPaddedList<T>.createSnapshot(): MutableList<ListSnapshotItem> =
+ (0 until size).mapTo(mutableListOf()) { pos ->
+ get(pos)?.let { ListSnapshotItem.Item(it) } ?: ListSnapshotItem.Placeholder(pos)
}
-/**
- * Sealed classes to identify items in the list.
- */
+/** Sealed classes to identify items in the list. */
internal sealed class ListSnapshotItem {
// means the item didn't change at all in diffs.
fun isOriginalItem() = this is Item<*> || this is Placeholder
- /**
- * Asserts that this item properly represents the replacement (newListItem).
- */
- abstract fun assertReplacement(
- msg: String,
- newListItem: ListSnapshotItem
- )
+ /** Asserts that this item properly represents the replacement (newListItem). */
+ abstract fun assertReplacement(msg: String, newListItem: ListSnapshotItem)
data class Item<T>(val item: T) : ListSnapshotItem() {
- override fun assertReplacement(
- msg: String,
- newListItem: ListSnapshotItem
- ) {
+ override fun assertReplacement(msg: String, newListItem: ListSnapshotItem) {
// no change
- assertWithMessage(msg).that(
- newListItem
- ).isEqualTo(this)
+ assertWithMessage(msg).that(newListItem).isEqualTo(this)
}
}
data class Placeholder(val pos: Int) : ListSnapshotItem() {
- override fun assertReplacement(
- msg: String,
- newListItem: ListSnapshotItem
- ) {
- assertWithMessage(msg).that(
- newListItem
- ).isInstanceOf(
- Placeholder::class.java
- )
+ override fun assertReplacement(msg: String, newListItem: ListSnapshotItem) {
+ assertWithMessage(msg).that(newListItem).isInstanceOf(Placeholder::class.java)
val replacement = newListItem as Placeholder
// make sure position didn't change. If it did, we would be replaced with a [Changed].
- assertWithMessage(msg).that(
- pos
- ).isEqualTo(replacement.pos)
+ assertWithMessage(msg).that(pos).isEqualTo(replacement.pos)
}
}
- /**
- * Inserted into the list when we receive a change notification about an item/placeholder.
- */
+ /** Inserted into the list when we receive a change notification about an item/placeholder. */
data class Changed(val payload: DiffingChangePayload?) : ListSnapshotItem() {
- override fun assertReplacement(
- msg: String,
- newListItem: ListSnapshotItem
- ) {
+ override fun assertReplacement(msg: String, newListItem: ListSnapshotItem) {
// there are 4 cases for changes.
// is either placeholder -> placeholder with new position
// placeholder to item
@@ -898,29 +845,23 @@
// item change from original diffing.
when (payload) {
DiffingChangePayload.ITEM_TO_PLACEHOLDER -> {
- assertWithMessage(msg).that(newListItem)
- .isInstanceOf(Placeholder::class.java)
+ assertWithMessage(msg).that(newListItem).isInstanceOf(Placeholder::class.java)
}
DiffingChangePayload.PLACEHOLDER_TO_ITEM -> {
- assertWithMessage(msg).that(newListItem)
- .isInstanceOf(Item::class.java)
+ assertWithMessage(msg).that(newListItem).isInstanceOf(Item::class.java)
}
DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE -> {
- assertWithMessage(msg).that(newListItem)
- .isInstanceOf(Placeholder::class.java)
+ assertWithMessage(msg).that(newListItem).isInstanceOf(Placeholder::class.java)
}
else -> {
// item change that came from diffing.
- assertWithMessage(msg).that(newListItem)
- .isInstanceOf(Item::class.java)
+ assertWithMessage(msg).that(newListItem).isInstanceOf(Item::class.java)
}
}
}
}
- /**
- * Used when an item/placeholder is inserted to the list
- */
+ /** Used when an item/placeholder is inserted to the list */
object Inserted : ListSnapshotItem() {
override fun assertReplacement(msg: String, newListItem: ListSnapshotItem) {
// nothing to assert here, it can represent anything in the new list.
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/RecordingCallbackTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/RecordingCallbackTest.kt
index 4af368a..70d766f 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/RecordingCallbackTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/RecordingCallbackTest.kt
@@ -31,11 +31,14 @@
val recordingCallback = RecordingCallback()
@Suppress("DEPRECATION")
- val failCallback = object : PagedList.Callback() {
- override fun onChanged(position: Int, count: Int) = fail("not expected")
- override fun onInserted(position: Int, count: Int) = fail("not expected")
- override fun onRemoved(position: Int, count: Int) = fail("not expected")
- }
+ val failCallback =
+ object : PagedList.Callback() {
+ override fun onChanged(position: Int, count: Int) = fail("not expected")
+
+ override fun onInserted(position: Int, count: Int) = fail("not expected")
+
+ override fun onRemoved(position: Int, count: Int) = fail("not expected")
+ }
// nothing recorded, verify nothing dispatched
recordingCallback.dispatchRecordingTo(failCallback)
@@ -46,28 +49,29 @@
var inc = 0
@Suppress("DEPRECATION")
- val verifyCallback = object : PagedList.Callback() {
- override fun onChanged(position: Int, count: Int) {
- assertEquals(inc, 0)
- assertEquals(position, 1)
- assertEquals(count, 2)
- inc += 1
- }
+ val verifyCallback =
+ object : PagedList.Callback() {
+ override fun onChanged(position: Int, count: Int) {
+ assertEquals(inc, 0)
+ assertEquals(position, 1)
+ assertEquals(count, 2)
+ inc += 1
+ }
- override fun onInserted(position: Int, count: Int) {
- assertEquals(inc, 1)
- assertEquals(position, 3)
- assertEquals(count, 4)
- inc += 1
- }
+ override fun onInserted(position: Int, count: Int) {
+ assertEquals(inc, 1)
+ assertEquals(position, 3)
+ assertEquals(count, 4)
+ inc += 1
+ }
- override fun onRemoved(position: Int, count: Int) {
- assertEquals(inc, 2)
- assertEquals(position, 5)
- assertEquals(count, 6)
- inc += 1
+ override fun onRemoved(position: Int, count: Int) {
+ assertEquals(inc, 2)
+ assertEquals(position, 5)
+ assertEquals(count, 6)
+ inc += 1
+ }
}
- }
recordingCallback.dispatchRecordingTo(verifyCallback)
assertEquals(3, inc)
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
index 11e54c5..c6c2146 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
@@ -56,15 +56,15 @@
import org.junit.runner.RunWith
/**
- * We are only capable of restoring state if one the two is valid:
- * a) pager's flow is cached in the view model (only for config change)
- * b) data source is counted and placeholders are enabled (both config change and app restart)
+ * We are only capable of restoring state if one the two is valid: a) pager's flow is cached in the
+ * view model (only for config change) b) data source is counted and placeholders are enabled (both
+ * config change and app restart)
*
- * Both of these cases actually work without using the initial key, except it is relatively
- * slower in option B because we need to load all items from initial key to the required position.
+ * Both of these cases actually work without using the initial key, except it is relatively slower
+ * in option B because we need to load all items from initial key to the required position.
*
- * This test validates those two cases for now. For more complicated cases, we need some helper
- * as developer needs to intervene to provide more information.
+ * This test validates those two cases for now. For more complicated cases, we need some helper as
+ * developer needs to intervene to provide more information.
*/
@ExperimentalCoroutinesApi
@ExperimentalTime
@@ -72,10 +72,9 @@
@RunWith(AndroidJUnit4::class)
class StateRestorationTest {
/**
- * List of dispatchers we track in the test for idling + pushing execution.
- * We have 3 dispatchers for more granular control:
- * main, and background for pager.
- * testScope for running tests.
+ * List of dispatchers we track in the test for idling + pushing execution. We have 3
+ * dispatchers for more granular control: main, and background for pager. testScope for running
+ * tests.
*/
private val scheduler = TestCoroutineScheduler()
private val mainDispatcher = StandardTestDispatcher(scheduler)
@@ -98,12 +97,7 @@
@Test
fun restoreState_withPlaceholders() {
runTest {
- collectPagesAsync(
- createPager(
- pageSize = 100,
- enablePlaceholders = true
- ).flow
- )
+ collectPagesAsync(createPager(pageSize = 100, enablePlaceholders = true).flow)
measureAndLayout()
val visible = recyclerView.captureSnapshot()
assertThat(visible).isNotEmpty()
@@ -111,36 +105,22 @@
val expected = recyclerView.captureSnapshot()
saveAndRestore()
// make sure state is not restored before items are loaded
- assertThat(
- layoutManager.restoredState
- ).isFalse()
+ assertThat(layoutManager.restoredState).isFalse()
// pause item loads
- val delayedJob = launch(start = CoroutineStart.LAZY) {
- collectPagesAsync(
- createPager(
- pageSize = 10,
- enablePlaceholders = true
- ).flow
- )
- }
+ val delayedJob =
+ launch(start = CoroutineStart.LAZY) {
+ collectPagesAsync(createPager(pageSize = 10, enablePlaceholders = true).flow)
+ }
measureAndLayout()
// item load is paused, still shouldn't restore state
- assertThat(
- layoutManager.restoredState
- ).isFalse()
+ assertThat(layoutManager.restoredState).isFalse()
// now load items
delayedJob.start()
measureAndLayout()
- assertThat(
- layoutManager.restoredState
- ).isTrue()
- assertThat(
- recyclerView.captureSnapshot()
- ).containsExactlyElementsIn(
- expected
- )
+ assertThat(layoutManager.restoredState).isTrue()
+ assertThat(recyclerView.captureSnapshot()).containsExactlyElementsIn(expected)
}
}
@@ -148,10 +128,7 @@
@Test
fun restoreState_withoutPlaceholders_cachedIn() {
runTest {
- val pager = createPager(
- pageSize = 60,
- enablePlaceholders = false
- )
+ val pager = createPager(pageSize = 60, enablePlaceholders = false)
val cacheScope = TestScope(Job() + scheduler)
val cachedFlow = pager.flow.cachedIn(cacheScope)
collectPagesAsync(cachedFlow)
@@ -160,14 +137,10 @@
scrollToPosition(50)
val snapshot = recyclerView.captureSnapshot()
saveAndRestore()
- assertThat(
- layoutManager.restoredState
- ).isFalse()
+ assertThat(layoutManager.restoredState).isFalse()
collectPagesAsync(cachedFlow)
measureAndLayout()
- assertThat(
- layoutManager.restoredState
- ).isTrue()
+ assertThat(layoutManager.restoredState).isTrue()
val restoredSnapshot = recyclerView.captureSnapshot()
assertThat(restoredSnapshot).containsExactlyElementsIn(snapshot)
cacheScope.cancel()
@@ -179,21 +152,14 @@
fun emptyNewPage_allowRestoration() {
// check that we don't block restoration indefinitely if new pager is empty.
runTest {
- val pager = createPager(
- pageSize = 60,
- enablePlaceholders = true
- )
+ val pager = createPager(pageSize = 60, enablePlaceholders = true)
collectPagesAsync(pager.flow)
measureAndLayout()
scrollToPosition(50)
saveAndRestore()
assertThat(layoutManager.restoredState).isFalse()
- val emptyPager = createPager(
- pageSize = 10,
- itemCount = 0,
- enablePlaceholders = true
- )
+ val emptyPager = createPager(pageSize = 10, itemCount = 0, enablePlaceholders = true)
collectPagesAsync(emptyPager.flow)
measureAndLayout()
assertThat(layoutManager.restoredState).isTrue()
@@ -204,35 +170,25 @@
@Test
fun userOverridesStateRestoration() {
runTest {
- val pager = createPager(
- pageSize = 40,
- enablePlaceholders = true
- )
+ val pager = createPager(pageSize = 40, enablePlaceholders = true)
collectPagesAsync(pager.flow)
measureAndLayout()
scrollToPosition(20)
val snapshot = recyclerView.captureSnapshot()
saveAndRestore()
- val pager2 = createPager(
- pageSize = 40,
- enablePlaceholders = true
- )
+ val pager2 = createPager(pageSize = 40, enablePlaceholders = true)
// when user calls prevent, we should not trigger state restoration even after we
// receive the first page
adapter.stateRestorationPolicy = PREVENT
collectPagesAsync(pager2.flow)
measureAndLayout()
- assertThat(
- layoutManager.restoredState
- ).isFalse()
+ assertThat(layoutManager.restoredState).isFalse()
// make sure test did work as expected, that is, new items are loaded
assertThat(adapter.itemCount).isGreaterThan(0)
// now if user allows it, restoration should happen properly
adapter.stateRestorationPolicy = ALLOW
measureAndLayout()
- assertThat(
- layoutManager.restoredState
- ).isTrue()
+ assertThat(layoutManager.restoredState).isTrue()
assertThat(recyclerView.captureSnapshot()).isEqualTo(snapshot)
}
}
@@ -242,9 +198,7 @@
if (this::lifecycleScope.isInitialized) {
this.lifecycleScope.cancel()
}
- lifecycleScope = TestScope(
- SupervisorJob() + mainDispatcher
- )
+ lifecycleScope = TestScope(SupervisorJob() + mainDispatcher)
val context = ApplicationProvider.getApplicationContext<Application>()
recyclerView = TestRecyclerView(context)
recyclerView.itemAnimator = null
@@ -268,14 +222,10 @@
recyclerView.scrollToPosition(pos)
measureAndLayout()
val child = layoutManager.findViewByPosition(pos)
- assertWithMessage("scrolled child $pos exists")
- .that(child)
- .isNotNull()
+ assertWithMessage("scrolled child $pos exists").that(child).isNotNull()
val vh = recyclerView.getChildViewHolder(child!!) as ItemViewHolder
- assertWithMessage("scrolled child should be fully loaded")
- .that(vh.item)
- .isNotNull()
+ assertWithMessage("scrolled child should be fully loaded").that(vh.item).isNotNull()
}
private fun measureAndLayout() {
@@ -313,20 +263,12 @@
lifecycleScope.cancel()
}
}
- }
-
- /**
- * collects pages in the lifecycle scope and sends them to the adapter
- */
- private fun collectPagesAsync(
- flow: Flow<PagingData<Item>>
- ) {
- val targetAdapter = adapter
- lifecycleScope.launch {
- flow.collectLatest {
- targetAdapter.submitData(it)
- }
}
+
+ /** collects pages in the lifecycle scope and sends them to the adapter */
+ private fun collectPagesAsync(flow: Flow<PagingData<Item>>) {
+ val targetAdapter = adapter
+ lifecycleScope.launch { flow.collectLatest { targetAdapter.submitData(it) } }
}
private fun createPager(
@@ -336,10 +278,11 @@
initialKey: Int? = null
): Pager<Int, Item> {
return Pager(
- config = PagingConfig(
- pageSize = pageSize,
- enablePlaceholders = enablePlaceholders,
- ),
+ config =
+ PagingConfig(
+ pageSize = pageSize,
+ enablePlaceholders = enablePlaceholders,
+ ),
initialKey = initialKey,
pagingSourceFactory = {
ItemPagingSource(
@@ -350,9 +293,7 @@
)
}
- /**
- * Returns the list of all visible items in the recyclerview including their locations.
- */
+ /** Returns the list of all visible items in the recyclerview including their locations. */
private fun RecyclerView.captureSnapshot(): List<PositionSnapshot> {
return (0 until childCount).mapNotNull {
val child = getChildAt(it)
@@ -380,11 +321,7 @@
fun captureSnapshot(): PositionSnapshot {
val item = checkNotNull(item)
- return PositionSnapshot(
- item = item,
- top = itemView.top,
- bottom = itemView.bottom
- )
+ return PositionSnapshot(item = item, top = itemView.top, bottom = itemView.bottom)
}
fun bindTo(item: Item?) {
@@ -394,28 +331,27 @@
}
}
- data class Item(
- val id: Int,
- val height: Int = (RV_HEIGHT / 10) + (1 + (id % 10))
- ) {
+ data class Item(val id: Int, val height: Int = (RV_HEIGHT / 10) + (1 + (id % 10))) {
companion object {
- val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Item>() {
- override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
- return oldItem.id == newItem.id
- }
+ val DIFF_CALLBACK =
+ object : DiffUtil.ItemCallback<Item>() {
+ override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
+ return oldItem.id == newItem.id
+ }
- override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
- return oldItem == newItem
+ override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
+ return oldItem == newItem
+ }
}
- }
}
}
- inner class TestAdapter : PagingDataAdapter<Item, ItemViewHolder>(
- diffCallback = Item.DIFF_CALLBACK,
- mainDispatcher = mainDispatcher,
- workerDispatcher = backgroundDispatcher
- ) {
+ inner class TestAdapter :
+ PagingDataAdapter<Item, ItemViewHolder>(
+ diffCallback = Item.DIFF_CALLBACK,
+ mainDispatcher = mainDispatcher,
+ workerDispatcher = backgroundDispatcher
+ ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(parent.context)
}
@@ -427,10 +363,8 @@
fun triggerItemLoad(pos: Int) = super.getItem(pos)
}
- class ItemPagingSource(
- private val context: CoroutineContext,
- private val items: List<Item>
- ) : PagingSource<Int, Item>() {
+ class ItemPagingSource(private val context: CoroutineContext, private val items: List<Item>) :
+ PagingSource<Int, Item>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
return withContext(context) {
val key = params.key ?: 0
@@ -451,18 +385,10 @@
override fun getRefreshKey(state: PagingState<Int, Item>): Int? = null
}
- /**
- * Snapshot of an item in RecyclerView.
- */
- data class PositionSnapshot(
- val item: Item,
- val top: Int,
- val bottom: Int
- )
+ /** Snapshot of an item in RecyclerView. */
+ data class PositionSnapshot(val item: Item, val top: Int, val bottom: Int)
- /**
- * RecyclerView class that allows saving and restoring state.
- */
+ /** RecyclerView class that allows saving and restoring state. */
class TestRecyclerView(context: Context) : RecyclerView(context) {
fun restoreState(state: Parcelable) {
super.onRestoreInstanceState(state)
@@ -478,6 +404,7 @@
*/
class RestoreAwareLayoutManager(context: Context) : LinearLayoutManager(context) {
var restoredState = false
+
override fun onRestoreInstanceState(state: Parcelable) {
super.onRestoreInstanceState(state)
restoredState = true
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index 4f550ff..6d3fde3 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -39,9 +39,7 @@
)
}
// TODO: prevent null-key load start/end
- return Error(
- IllegalArgumentException("This test source only supports initial load")
- )
+ return Error(IllegalArgumentException("This test source only supports initial load"))
}
override fun getRefreshKey(state: PagingState<Any, Value>): Any? = null
@@ -55,13 +53,14 @@
vararg items: String
): PagedList<String> = runBlocking {
PagedList.create(
- initialPage = Page<Any, String>(
- data = items.toList(),
- prevKey = null,
- nextKey = null,
- itemsBefore = leadingNulls,
- itemsAfter = trailingNulls
- ),
+ initialPage =
+ Page<Any, String>(
+ data = items.toList(),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = leadingNulls,
+ itemsAfter = trailingNulls
+ ),
pagingSource = FakeSource(leadingNulls, trailingNulls, items.toList()),
coroutineScope = GlobalScope,
notifyDispatcher = DirectDispatcher,
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
index 27f5d14..9e58919 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
@@ -42,8 +42,8 @@
* adapter. It listens to PagedList loading callbacks, and uses DiffUtil on a background thread to
* compute updates as new PagedLists are received.
*
- * It provides a simple list-like API with [getItem] and [itemCount] for an adapter to acquire
- * and present data objects.
+ * It provides a simple list-like API with [getItem] and [itemCount] for an adapter to acquire and
+ * present data objects.
*
* A complete usage pattern with Room would look like this:
* ```
@@ -116,10 +116,7 @@
*/
@Deprecated(
message = "AsyncPagedListDiffer is deprecated and has been replaced by AsyncPagingDataDiffer",
- replaceWith = ReplaceWith(
- "AsyncPagingDataDiffer<T>",
- "androidx.paging.AsyncPagingDataDiffer"
- )
+ replaceWith = ReplaceWith("AsyncPagingDataDiffer<T>", "androidx.paging.AsyncPagingDataDiffer")
)
open class AsyncPagedListDiffer<T : Any> {
/**
@@ -137,15 +134,11 @@
@VisibleForTesting
internal val listeners = CopyOnWriteArrayList<PagedListListener<T>>()
- @Suppress("DEPRECATION")
- private var pagedList: PagedList<T>? = null
+ @Suppress("DEPRECATION") private var pagedList: PagedList<T>? = null
- @Suppress("DEPRECATION")
- private var snapshot: PagedList<T>? = null
+ @Suppress("DEPRECATION") private var snapshot: PagedList<T>? = null
- /**
- * Max generation of currently scheduled runnable
- */
+ /** Max generation of currently scheduled runnable */
@Suppress("MemberVisibilityCanBePrivate") // synthetic access
internal var maxScheduledGeneration: Int = 0
@@ -164,18 +157,19 @@
CopyOnWriteArrayList()
@Suppress("DEPRECATION")
- private val pagedListCallback: PagedList.Callback = object : PagedList.Callback() {
- override fun onInserted(position: Int, count: Int) =
- updateCallback.onInserted(position, count)
+ private val pagedListCallback: PagedList.Callback =
+ object : PagedList.Callback() {
+ override fun onInserted(position: Int, count: Int) =
+ updateCallback.onInserted(position, count)
- override fun onRemoved(position: Int, count: Int) =
- updateCallback.onRemoved(position, count)
+ override fun onRemoved(position: Int, count: Int) =
+ updateCallback.onRemoved(position, count)
- override fun onChanged(position: Int, count: Int) {
- // NOTE: pass a null payload to convey null -> item
- updateCallback.onChanged(position, count, null)
+ override fun onChanged(position: Int, count: Int) {
+ // NOTE: pass a null payload to convey null -> item
+ updateCallback.onChanged(position, count, null)
+ }
}
- }
/**
* Get the number of items currently presented by this Differ. This value can be directly
@@ -247,16 +241,17 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """AsyncPagingDataDiffer(
+ replaceWith =
+ ReplaceWith(
+ """AsyncPagingDataDiffer(
Dispatchers.Main,
Dispatchers.IO,
diffCallback,
listUpdateCallback
)""",
- "androidx.paging.AsyncPagingDataDiffer",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.AsyncPagingDataDiffer",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(adapter: RecyclerView.Adapter<*>, diffCallback: DiffUtil.ItemCallback<T>) {
updateCallback = AdapterListUpdateCallback(adapter)
@@ -265,21 +260,21 @@
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """AsyncPagingDataDiffer(
+ replaceWith =
+ ReplaceWith(
+ """AsyncPagingDataDiffer(
Dispatchers.Main,
Dispatchers.IO,
config.diffCallback,
listUpdateCallback
)""",
- "androidx.paging.AsyncPagingDataDiffer",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.AsyncPagingDataDiffer",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
listUpdateCallback: ListUpdateCallback,
- @Suppress("ListenerLast")
- config: AsyncDifferConfig<T>
+ @Suppress("ListenerLast") config: AsyncDifferConfig<T>
) {
updateCallback = listUpdateCallback
this.config = config
@@ -292,7 +287,6 @@
*
* @param index Index of item to get, must be >= 0, and < `getItemCount`.
* @return The item, or null, if a null placeholder is at the specified position.
- *
* @throws IndexOutOfBoundsException if [itemCount] is 0.
*/
open fun getItem(index: Int): T? {
@@ -329,14 +323,13 @@
* [ListUpdateCallback]), and the new PagedList will be swapped in as the
* [current list][currentList].
*
- * The commit callback can be used to know when the PagedList is committed, but note that it
- * may not be executed. If PagedList B is submitted immediately after PagedList A, and is
- * committed directly, the callback associated with PagedList A will not be run.
+ * The commit callback can be used to know when the PagedList is committed, but note that it may
+ * not be executed. If PagedList B is submitted immediately after PagedList A, and is committed
+ * directly, the callback associated with PagedList A will not be run.
*
* @param pagedList The new [PagedList].
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
- * it is committed.
- *
+ * it is committed.
* @throws IllegalStateException if previous PagedList wasn't snapshotted correctly.
*/
open fun submitList(
@@ -417,15 +410,14 @@
throw IllegalStateException("must be in snapshot state to diff")
}
- @Suppress("DEPRECATION")
- val newSnapshot = pagedList.snapshot() as PagedList<T>
+ @Suppress("DEPRECATION") val newSnapshot = pagedList.snapshot() as PagedList<T>
val recordingCallback = RecordingCallback()
pagedList.addWeakCallback(recordingCallback)
config.backgroundThreadExecutor.execute {
- val result = oldSnapshot.getPlaceholderPaddedList().computeDiff(
- newSnapshot.getPlaceholderPaddedList(),
- config.diffCallback
- )
+ val result =
+ oldSnapshot
+ .getPlaceholderPaddedList()
+ .computeDiff(newSnapshot.getPlaceholderPaddedList(), config.diffCallback)
mainThreadExecutor.execute {
if (maxScheduledGeneration == runGeneration) {
@@ -461,11 +453,13 @@
snapshot = null
// dispatch updates to UI from previousSnapshot -> newSnapshot
- previousSnapshot.getPlaceholderPaddedList().dispatchDiff(
- callback = updateCallback,
- newList = diffSnapshot.getPlaceholderPaddedList(),
- diffResult = diffResult
- )
+ previousSnapshot
+ .getPlaceholderPaddedList()
+ .dispatchDiff(
+ callback = updateCallback,
+ newList = diffSnapshot.getPlaceholderPaddedList(),
+ diffResult = diffResult
+ )
// dispatch updates to UI from newSnapshot -> currentList
// after this, the callback will be up to date with current pagedList...
@@ -481,11 +475,14 @@
// Note: we don't take into account loads between new list snapshot and new list, but
// this is only a problem in rare cases when placeholders are disabled, and a load
// starts (for some reason) and finishes before diff completes.
- val newPosition = previousSnapshot.getPlaceholderPaddedList().transformAnchorIndex(
- diffResult,
- diffSnapshot.getPlaceholderPaddedList(),
- lastAccessIndex
- )
+ val newPosition =
+ previousSnapshot
+ .getPlaceholderPaddedList()
+ .transformAnchorIndex(
+ diffResult,
+ diffSnapshot.getPlaceholderPaddedList(),
+ lastAccessIndex
+ )
// Trigger load in new list at this position, clamped to list bounds.
// This is a load, not just an update of last load position, since the new list may be
@@ -510,7 +507,6 @@
* Add a [PagedListListener] to receive updates when the current [PagedList] changes.
*
* @param listener Listener to receive updates.
- *
* @see currentList
* @see removePagedListListener
*/
@@ -522,7 +518,6 @@
* Add a callback to receive updates when the current [PagedList] changes.
*
* @param callback to receive updates.
- *
* @see currentList
* @see removePagedListListener
*/
@@ -536,7 +531,6 @@
* Remove a previously registered [PagedListListener].
*
* @param listener Previously registered listener.
- *
* @see currentList
* @see addPagedListListener
*/
@@ -548,7 +542,6 @@
* Remove a previously registered callback via [addPagedListListener].
*
* @param callback Previously registered callback.
- *
* @see currentList
* @see addPagedListListener
*/
@@ -565,7 +558,6 @@
* current [LoadType.REFRESH], [LoadType.PREPEND], and [LoadType.APPEND] states.
*
* @param listener [LoadState] listener to receive updates.
- *
* @see removeLoadStateListener
*/
open fun addLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
@@ -582,7 +574,6 @@
* Remove a previously registered [LoadState] listener.
*
* @param listener Previously registered listener.
- *
* @see currentList
* @see addPagedListListener
*/
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index b046267..b1fd93d 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -57,15 +57,15 @@
* Construct an [AsyncPagingDataDiffer].
*
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
- * path for generating the UI events required to display the new list.
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
* @param updateCallback [ListUpdateCallback] which receives UI events dispatched by this
- * [AsyncPagingDataDiffer] as items are loaded.
+ * [AsyncPagingDataDiffer] as items are loaded.
* @param mainDispatcher [CoroutineContext] where UI events are dispatched. Typically, this should
- * be [Dispatchers.Main].
+ * be [Dispatchers.Main].
* @param workerDispatcher [CoroutineContext] where the work to generate UI events is dispatched,
- * for example when diffing lists on [REFRESH]. Typically, this should dispatch on a background
- * thread; [Dispatchers.Default] by default.
+ * for example when diffing lists on [REFRESH]. Typically, this should dispatch on a background
+ * thread; [Dispatchers.Default] by default.
*/
@JvmOverloads
constructor(
@@ -81,12 +81,12 @@
* Construct an [AsyncPagingDataDiffer].
*
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
- * path for generating the UI events required to display the new list.
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
* @param updateCallback [ListUpdateCallback] which receives UI events dispatched by this
- * [AsyncPagingDataDiffer] as items are loaded.
- * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically,
- * this should be [Dispatchers.Main].
+ * [AsyncPagingDataDiffer] as items are loaded.
+ * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically, this
+ * should be [Dispatchers.Main].
*/
@Deprecated(
message = "Superseded by constructors which accept CoroutineContext",
@@ -112,15 +112,15 @@
* Construct an [AsyncPagingDataDiffer].
*
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
- * path for generating the UI events required to display the new list.
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
* @param updateCallback [ListUpdateCallback] which receives UI events dispatched by this
- * [AsyncPagingDataDiffer] as items are loaded.
- * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically,
- * this should be [Dispatchers.Main].
+ * [AsyncPagingDataDiffer] as items are loaded.
+ * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically, this
+ * should be [Dispatchers.Main].
* @param workerDispatcher [CoroutineDispatcher] where the work to generate UI events is
- * dispatched, for example when diffing lists on [REFRESH]. Typically, this should dispatch on a
- * background thread; [Dispatchers.Default] by default.
+ * dispatched, for example when diffing lists on [REFRESH]. Typically, this should dispatch on
+ * a background thread; [Dispatchers.Default] by default.
*/
@Deprecated(
message = "Superseded by constructors which accept CoroutineContext",
@@ -147,178 +147,194 @@
/** True if we're currently executing [getItem] */
internal val inGetItem = MutableStateFlow(false)
- internal val presenter = object : PagingDataPresenter<T>(mainDispatcher) {
- override suspend fun presentPagingDataEvent(event: PagingDataEvent<T>) {
- when (event) {
- is PagingDataEvent.Refresh -> event.apply {
- when {
- // fast path for no items -> some items
- previousList.size == 0 -> {
- if (newList.size > 0) {
- updateCallback.onInserted(0, newList.size)
+ internal val presenter =
+ object : PagingDataPresenter<T>(mainDispatcher) {
+ override suspend fun presentPagingDataEvent(event: PagingDataEvent<T>) {
+ when (event) {
+ is PagingDataEvent.Refresh ->
+ event.apply {
+ when {
+ // fast path for no items -> some items
+ previousList.size == 0 -> {
+ if (newList.size > 0) {
+ updateCallback.onInserted(0, newList.size)
+ }
+ }
+ // fast path for some items -> no items
+ newList.size == 0 -> {
+ if (previousList.size > 0) {
+ updateCallback.onRemoved(0, previousList.size)
+ }
+ }
+ else -> {
+ val diffResult =
+ withContext(workerDispatcher) {
+ previousList.computeDiff(newList, diffCallback)
+ }
+ previousList.dispatchDiff(updateCallback, newList, diffResult)
+ }
}
}
- // fast path for some items -> no items
- newList.size == 0 -> {
- if (previousList.size > 0) {
- updateCallback.onRemoved(0, previousList.size)
+ /**
+ * For each [PagingDataEvent.Prepend] or [PagingDataEvent.Append] there are
+ * three potential events handled in the following order:
+ * 1) change this covers any placeholder/item conversions, and is done first
+ * 2) item insert/remove this covers any remaining items that are
+ * inserted/removed, but aren't swapping with placeholders
+ * 3) placeholder insert/remove after the above, placeholder count can be wrong
+ * for a number of reasons - approximate counting or filtering are the most
+ * common. In either case, we adjust placeholders at the far end of the list,
+ * so that they don't trigger animations near the user.
+ */
+ is PagingDataEvent.Prepend ->
+ event.apply {
+ val insertSize = inserted.size
+
+ val placeholdersChangedCount = minOf(oldPlaceholdersBefore, insertSize)
+ val placeholdersChangedPos =
+ oldPlaceholdersBefore - placeholdersChangedCount
+ val itemsInsertedCount = insertSize - placeholdersChangedCount
+ val itemsInsertedPos = 0
+
+ // ... then trigger callbacks, so callbacks won't see inconsistent state
+ if (placeholdersChangedCount > 0) {
+ updateCallback.onChanged(
+ placeholdersChangedPos,
+ placeholdersChangedCount,
+ null
+ )
+ }
+ if (itemsInsertedCount > 0) {
+ updateCallback.onInserted(itemsInsertedPos, itemsInsertedCount)
+ }
+ val placeholderInsertedCount =
+ newPlaceholdersBefore - oldPlaceholdersBefore +
+ placeholdersChangedCount
+ if (placeholderInsertedCount > 0) {
+ updateCallback.onInserted(0, placeholderInsertedCount)
+ } else if (placeholderInsertedCount < 0) {
+ updateCallback.onRemoved(0, -placeholderInsertedCount)
}
}
- else -> {
- val diffResult = withContext(workerDispatcher) {
- previousList.computeDiff(newList, diffCallback)
+ is PagingDataEvent.Append ->
+ event.apply {
+ val insertSize = inserted.size
+ val placeholdersChangedCount = minOf(oldPlaceholdersAfter, insertSize)
+ val placeholdersChangedPos = startIndex
+ val itemsInsertedCount = insertSize - placeholdersChangedCount
+ val itemsInsertedPos = placeholdersChangedPos + placeholdersChangedCount
+
+ if (placeholdersChangedCount > 0) {
+ updateCallback.onChanged(
+ placeholdersChangedPos,
+ placeholdersChangedCount,
+ null
+ )
}
- previousList.dispatchDiff(updateCallback, newList, diffResult)
+ if (itemsInsertedCount > 0) {
+ updateCallback.onInserted(itemsInsertedPos, itemsInsertedCount)
+ }
+ val placeholderInsertedCount =
+ newPlaceholdersAfter - oldPlaceholdersAfter +
+ placeholdersChangedCount
+ val newTotalSize = startIndex + insertSize + newPlaceholdersAfter
+ if (placeholderInsertedCount > 0) {
+ updateCallback.onInserted(
+ newTotalSize - placeholderInsertedCount,
+ placeholderInsertedCount
+ )
+ } else if (placeholderInsertedCount < 0) {
+ updateCallback.onRemoved(newTotalSize, -placeholderInsertedCount)
+ }
}
- }
- }
- /**
- * For each [PagingDataEvent.Prepend] or [PagingDataEvent.Append] there are
- * three potential events handled in the following order:
- *
- * 1) change
- * this covers any placeholder/item conversions, and is done first
- * 2) item insert/remove
- * this covers any remaining items that are inserted/removed, but aren't swapping with
- * placeholders
- * 3) placeholder insert/remove
- * after the above, placeholder count can be wrong for a number of reasons - approximate
- * counting or filtering are the most common. In either case, we adjust placeholders at
- * the far end of the list, so that they don't trigger animations near the user.
- */
- is PagingDataEvent.Prepend -> event.apply {
- val insertSize = inserted.size
+ /**
+ * For [PagingDataEvent.DropPrepend] or [PagingDataEvent.DropAppend] events
+ * there are two potential events handled in the following order
+ * 1) placeholder insert/remove We first adjust placeholders at the far end of
+ * the list, so that they don't trigger animations near the user.
+ * 2) change this covers any placeholder/item conversions, and is done after
+ * placeholders are trimmed/inserted to match new expected size
+ *
+ * Note: For drops we never run DiffUtil because it is safe to assume that empty
+ * pages can never become non-empty no matter what transformations they go
+ * through. [ListUpdateCallback] events generated by this helper always drop
+ * contiguous sets of items because pages that depend on multiple
+ * originalPageOffsets will always be the next closest page that's non-empty.
+ */
+ is PagingDataEvent.DropPrepend ->
+ event.apply {
+ // Trim or insert placeholders to match expected newSize.
+ val placeholdersToInsert =
+ newPlaceholdersBefore - dropCount - oldPlaceholdersBefore
+ if (placeholdersToInsert > 0) {
+ updateCallback.onInserted(0, placeholdersToInsert)
+ } else if (placeholdersToInsert < 0) {
+ updateCallback.onRemoved(0, -placeholdersToInsert)
+ }
+ // Compute the index of the first item that must be rebound as a
+ // placeholder.
+ // If any placeholders were inserted above, we only need to send
+ // onChanged for the next
+ // n = (newPlaceholdersBefore - placeholdersToInsert) items. E.g., if
+ // two nulls
+ // were inserted above, then the onChanged event can start from index =
+ // 2.
+ // Note: In cases where more items were dropped than there were
+ // previously placeholders,
+ // we can simply rebind n = newPlaceholdersBefore items starting from
+ // position = 0.
+ val firstItemIndex =
+ maxOf(0, oldPlaceholdersBefore + placeholdersToInsert)
+ // Compute the number of previously loaded items that were dropped and
+ // now need to be
+ // updated to null. This computes the distance between firstItemIndex
+ // (inclusive),
+ // and index of the last leading placeholder (inclusive) in the final
+ // list.
+ val changeCount = newPlaceholdersBefore - firstItemIndex
+ if (changeCount > 0) {
+ updateCallback.onChanged(firstItemIndex, changeCount, null)
+ }
+ }
+ is PagingDataEvent.DropAppend ->
+ event.apply {
+ val placeholdersToInsert =
+ newPlaceholdersAfter - dropCount - oldPlaceholdersAfter
+ val newSize = startIndex + newPlaceholdersAfter
+ if (placeholdersToInsert > 0) {
+ updateCallback.onInserted(
+ newSize - placeholdersToInsert,
+ placeholdersToInsert
+ )
+ } else if (placeholdersToInsert < 0) {
+ updateCallback.onRemoved(newSize, -placeholdersToInsert)
+ }
- val placeholdersChangedCount =
- minOf(oldPlaceholdersBefore, insertSize)
- val placeholdersChangedPos = oldPlaceholdersBefore - placeholdersChangedCount
- val itemsInsertedCount = insertSize - placeholdersChangedCount
- val itemsInsertedPos = 0
-
- // ... then trigger callbacks, so callbacks won't see inconsistent state
- if (placeholdersChangedCount > 0) {
- updateCallback.onChanged(
- placeholdersChangedPos, placeholdersChangedCount, null
- )
- }
- if (itemsInsertedCount > 0) {
- updateCallback.onInserted(itemsInsertedPos, itemsInsertedCount)
- }
- val placeholderInsertedCount =
- newPlaceholdersBefore - oldPlaceholdersBefore + placeholdersChangedCount
- if (placeholderInsertedCount > 0) {
- updateCallback.onInserted(0, placeholderInsertedCount)
- } else if (placeholderInsertedCount < 0) {
- updateCallback.onRemoved(0, -placeholderInsertedCount)
- }
- }
- is PagingDataEvent.Append -> event.apply {
- val insertSize = inserted.size
- val placeholdersChangedCount = minOf(oldPlaceholdersAfter, insertSize)
- val placeholdersChangedPos = startIndex
- val itemsInsertedCount = insertSize - placeholdersChangedCount
- val itemsInsertedPos = placeholdersChangedPos + placeholdersChangedCount
-
- if (placeholdersChangedCount > 0) {
- updateCallback.onChanged(
- placeholdersChangedPos, placeholdersChangedCount, null
- )
- }
- if (itemsInsertedCount > 0) {
- updateCallback.onInserted(itemsInsertedPos, itemsInsertedCount)
- }
- val placeholderInsertedCount =
- newPlaceholdersAfter - oldPlaceholdersAfter + placeholdersChangedCount
- val newTotalSize = startIndex + insertSize + newPlaceholdersAfter
- if (placeholderInsertedCount > 0) {
- updateCallback.onInserted(
- newTotalSize - placeholderInsertedCount,
- placeholderInsertedCount
- )
- } else if (placeholderInsertedCount < 0) {
- updateCallback.onRemoved(newTotalSize, -placeholderInsertedCount)
- }
- }
- /**
- * For [PagingDataEvent.DropPrepend] or [PagingDataEvent.DropAppend] events
- * there are two potential events handled in the following order
- *
- * 1) placeholder insert/remove
- * We first adjust placeholders at the far end of the list, so that they
- * don't trigger animations near the user.
- * 2) change
- * this covers any placeholder/item conversions, and is done after placeholders
- * are trimmed/inserted to match new expected size
- *
- * Note: For drops we never run DiffUtil because it is safe to assume
- * that empty pages can never become non-empty no matter what transformations they
- * go through. [ListUpdateCallback] events generated by this helper always
- * drop contiguous sets of items because pages that depend on multiple
- * originalPageOffsets will always be the next closest page that's non-empty.
- */
- is PagingDataEvent.DropPrepend -> event.apply {
- // Trim or insert placeholders to match expected newSize.
- val placeholdersToInsert = newPlaceholdersBefore - dropCount -
- oldPlaceholdersBefore
- if (placeholdersToInsert > 0) {
- updateCallback.onInserted(0, placeholdersToInsert)
- } else if (placeholdersToInsert < 0) {
- updateCallback.onRemoved(0, -placeholdersToInsert)
- }
- // Compute the index of the first item that must be rebound as a placeholder.
- // If any placeholders were inserted above, we only need to send onChanged for the next
- // n = (newPlaceholdersBefore - placeholdersToInsert) items. E.g., if two nulls
- // were inserted above, then the onChanged event can start from index = 2.
- // Note: In cases where more items were dropped than there were previously placeholders,
- // we can simply rebind n = newPlaceholdersBefore items starting from position = 0.
- val firstItemIndex = maxOf(
- 0,
- oldPlaceholdersBefore + placeholdersToInsert
- )
- // Compute the number of previously loaded items that were dropped and now need to be
- // updated to null. This computes the distance between firstItemIndex (inclusive),
- // and index of the last leading placeholder (inclusive) in the final list.
- val changeCount = newPlaceholdersBefore - firstItemIndex
- if (changeCount > 0) {
- updateCallback.onChanged(firstItemIndex, changeCount, null)
- }
- }
- is PagingDataEvent.DropAppend -> event.apply {
- val placeholdersToInsert = newPlaceholdersAfter - dropCount -
- oldPlaceholdersAfter
- val newSize = startIndex + newPlaceholdersAfter
- if (placeholdersToInsert > 0) {
- updateCallback.onInserted(
- newSize - placeholdersToInsert, placeholdersToInsert
- )
- } else if (placeholdersToInsert < 0) {
- updateCallback.onRemoved(newSize, -placeholdersToInsert)
- }
-
- // Number of trailing placeholders in the list, before dropping, that were
- // removed above during size adjustment.
- val oldPlaceholdersRemoved = when {
- placeholdersToInsert < 0 ->
- minOf(oldPlaceholdersAfter, -placeholdersToInsert)
- else -> 0
- }
- // Compute the number of previously loaded items that were dropped and now need
- // to be updated to null. This subtracts the total number of existing
- // placeholders in the list, before dropping, that were not removed above
- // during size adjustment, from the total number of expected placeholders.
- val changeCount = newPlaceholdersAfter - oldPlaceholdersAfter +
- oldPlaceholdersRemoved
- if (changeCount > 0) {
- updateCallback.onChanged(
- startIndex,
- changeCount,
- null
- )
- }
+ // Number of trailing placeholders in the list, before dropping, that
+ // were
+ // removed above during size adjustment.
+ val oldPlaceholdersRemoved =
+ when {
+ placeholdersToInsert < 0 ->
+ minOf(oldPlaceholdersAfter, -placeholdersToInsert)
+ else -> 0
+ }
+ // Compute the number of previously loaded items that were dropped and
+ // now need
+ // to be updated to null. This subtracts the total number of existing
+ // placeholders in the list, before dropping, that were not removed
+ // above
+ // during size adjustment, from the total number of expected
+ // placeholders.
+ val changeCount =
+ newPlaceholdersAfter - oldPlaceholdersAfter + oldPlaceholdersRemoved
+ if (changeCount > 0) {
+ updateCallback.onChanged(startIndex, changeCount, null)
+ }
+ }
}
}
}
- }
private val submitDataId = AtomicInteger(0)
@@ -374,8 +390,8 @@
* within the same generation of [PagingData].
*
* [LoadState.Error] can be generated from two types of load requests:
- * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
- * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
+ * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
+ * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
*/
fun retry() {
presenter.retry()
@@ -450,41 +466,40 @@
* A hot [Flow] of [CombinedLoadStates] that emits a snapshot whenever the loading state of the
* current [PagingData] changes.
*
- * This flow is conflated, so it buffers the last update to [CombinedLoadStates] and
- * delivers the current load states on collection when RecyclerView is not dispatching layout.
+ * This flow is conflated, so it buffers the last update to [CombinedLoadStates] and delivers
+ * the current load states on collection when RecyclerView is not dispatching layout.
*
* @sample androidx.paging.samples.loadStateFlowSample
*/
- val loadStateFlow: Flow<CombinedLoadStates> = presenter.loadStateFlow
- .filterNotNull()
- .buffer(CONFLATED)
- .transform { it ->
- if (inGetItem.value) {
- yield()
- inGetItem.firstOrNull { isGettingItem ->
- !isGettingItem
+ val loadStateFlow: Flow<CombinedLoadStates> =
+ presenter.loadStateFlow
+ .filterNotNull()
+ .buffer(CONFLATED)
+ .transform { it ->
+ if (inGetItem.value) {
+ yield()
+ inGetItem.firstOrNull { isGettingItem -> !isGettingItem }
}
+ emit(it)
}
- emit(it)
- }.flowOn(Dispatchers.Main)
+ .flowOn(Dispatchers.Main)
/**
- * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
- * actual items presented don't change.
+ * A hot [Flow] that emits after the pages presented to the UI are updated, even if the actual
+ * items presented don't change.
*
* An update is triggered from one of the following:
- * * [submitData] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [submitData] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
- * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
- * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
- * trigger multiple times for each intermediate update that was presented while your collector
- * was still working. To avoid this behavior, you can
- * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
- * update, which is useful in cases where you are simply updating UI and don't care about
- * tracking the exact number of page updates.
+ * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay 0 items
+ * with a buffer of size 64. If a collector lags behind page updates, it may trigger multiple
+ * times for each intermediate update that was presented while your collector was still working.
+ * To avoid this behavior, you can [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so
+ * that you only receive the latest update, which is useful in cases where you are simply
+ * updating UI and don't care about tracking the exact number of page updates.
*/
val onPagesUpdatedFlow: Flow<Unit> = presenter.onPagesUpdatedFlow
@@ -493,13 +508,12 @@
* actual items presented don't change.
*
* An update is triggered from one of the following:
- * * [submitData] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [submitData] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
* @param listener called after pages presented are updated.
- *
* @see removeOnPagesUpdatedListener
*/
fun addOnPagesUpdatedListener(listener: () -> Unit) {
@@ -507,11 +521,10 @@
}
/**
- * Remove a previously registered listener for new [PagingData] generations completing
- * initial load and presenting to the UI.
+ * Remove a previously registered listener for new [PagingData] generations completing initial
+ * load and presenting to the UI.
*
* @param listener Previously registered listener.
- *
* @see addOnPagesUpdatedListener
*/
fun removeOnPagesUpdatedListener(listener: () -> Unit) {
@@ -522,15 +535,15 @@
* The loadStateListener registered internally with [PagingDataPresenter.addLoadStateListener]
* when there are [childLoadStateListeners].
*
- * LoadStateUpdates are dispatched to this single internal listener, which will further
- * dispatch the loadState to [childLoadStateListeners] when [inGetItem] is false.
+ * LoadStateUpdates are dispatched to this single internal listener, which will further dispatch
+ * the loadState to [childLoadStateListeners] when [inGetItem] is false.
*/
private val parentLoadStateListener: AtomicReference<((CombinedLoadStates) -> Unit)?> =
AtomicReference(null)
/**
- * Stores the list of listeners added through [addLoadStateListener]. Invoked
- * when inGetItem is false.
+ * Stores the list of listeners added through [addLoadStateListener]. Invoked when inGetItem is
+ * false.
*/
private val childLoadStateListeners = CopyOnWriteArrayList<(CombinedLoadStates) -> Unit>()
@@ -541,7 +554,6 @@
* reflect the current [CombinedLoadStates].
*
* @param listener [LoadStates] listener to receive updates.
- *
* @see removeLoadStateListener
*
* @sample androidx.paging.samples.addLoadStateListenerSample
@@ -588,14 +600,12 @@
private val LoadStateListenerHandler by lazy { Handler(Looper.getMainLooper()) }
- private val LoadStateListenerRunnable = object : Runnable {
- var loadState = AtomicReference<CombinedLoadStates>(null)
+ private val LoadStateListenerRunnable =
+ object : Runnable {
+ var loadState = AtomicReference<CombinedLoadStates>(null)
- override fun run() {
- loadState.get()
- ?.let { state ->
- childLoadStateListeners.forEach { it(state) }
- }
+ override fun run() {
+ loadState.get()?.let { state -> childLoadStateListeners.forEach { it(state) } }
+ }
}
- }
}
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/paging-runtime/src/main/java/androidx/paging/LivePagedList.kt
index f4826a1..662fd96 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -40,15 +40,16 @@
private val pagingSourceFactory: () -> PagingSource<Key, Value>,
private val notifyDispatcher: CoroutineDispatcher,
private val fetchDispatcher: CoroutineDispatcher
-) : LiveData<PagedList<Value>>(
- InitialPagedList(
- coroutineScope = coroutineScope,
- notifyDispatcher = notifyDispatcher,
- backgroundDispatcher = fetchDispatcher,
- config = config,
- initialLastKey = initialKey
- )
-) {
+) :
+ LiveData<PagedList<Value>>(
+ InitialPagedList(
+ coroutineScope = coroutineScope,
+ notifyDispatcher = notifyDispatcher,
+ backgroundDispatcher = fetchDispatcher,
+ config = config,
+ initialLastKey = initialKey
+ )
+ ) {
private var currentData: PagedList<Value>
private var currentJob: Job? = null
@@ -71,53 +72,46 @@
if (currentJob != null && !force) return
currentJob?.cancel()
- currentJob = coroutineScope.launch(fetchDispatcher) {
- currentData.pagingSource.unregisterInvalidatedCallback(callback)
- val pagingSource = pagingSourceFactory()
- pagingSource.registerInvalidatedCallback(callback)
- if (pagingSource is LegacyPagingSource) {
- pagingSource.setPageSize(config.pageSize)
- }
-
- withContext(notifyDispatcher) {
- currentData.setInitialLoadState(REFRESH, Loading)
- }
-
- @Suppress("UNCHECKED_CAST")
- val lastKey = currentData.lastKey as Key?
- val params = config.toRefreshLoadParams(lastKey)
-
- when (val initialResult = pagingSource.load(params)) {
- is PagingSource.LoadResult.Invalid -> {
- currentData.setInitialLoadState(
- REFRESH,
- LoadState.NotLoading(false)
- )
- pagingSource.invalidate()
+ currentJob =
+ coroutineScope.launch(fetchDispatcher) {
+ currentData.pagingSource.unregisterInvalidatedCallback(callback)
+ val pagingSource = pagingSourceFactory()
+ pagingSource.registerInvalidatedCallback(callback)
+ if (pagingSource is LegacyPagingSource) {
+ pagingSource.setPageSize(config.pageSize)
}
- is PagingSource.LoadResult.Error -> {
- currentData.setInitialLoadState(
- REFRESH,
- Error(initialResult.throwable)
- )
- }
- is PagingSource.LoadResult.Page -> {
- val pagedList = PagedList.create(
- pagingSource,
- initialResult,
- coroutineScope,
- notifyDispatcher,
- fetchDispatcher,
- boundaryCallback,
- config,
- lastKey
- )
- onItemUpdate(currentData, pagedList)
- currentData = pagedList
- postValue(pagedList)
+
+ withContext(notifyDispatcher) { currentData.setInitialLoadState(REFRESH, Loading) }
+
+ @Suppress("UNCHECKED_CAST") val lastKey = currentData.lastKey as Key?
+ val params = config.toRefreshLoadParams(lastKey)
+
+ when (val initialResult = pagingSource.load(params)) {
+ is PagingSource.LoadResult.Invalid -> {
+ currentData.setInitialLoadState(REFRESH, LoadState.NotLoading(false))
+ pagingSource.invalidate()
+ }
+ is PagingSource.LoadResult.Error -> {
+ currentData.setInitialLoadState(REFRESH, Error(initialResult.throwable))
+ }
+ is PagingSource.LoadResult.Page -> {
+ val pagedList =
+ PagedList.create(
+ pagingSource,
+ initialResult,
+ coroutineScope,
+ notifyDispatcher,
+ fetchDispatcher,
+ boundaryCallback,
+ config,
+ lastKey
+ )
+ onItemUpdate(currentData, pagedList)
+ currentData = pagedList
+ postValue(pagedList)
+ }
}
}
- }
}
private fun onItemUpdate(previous: PagedList<Value>, next: PagedList<Value>) {
@@ -137,14 +131,14 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param fetchExecutor [Executor] for fetching data from [PagingSource]s.
- *
* @see LivePagedListBuilder
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -155,11 +149,11 @@
initialLoadKey,
this.asPagingSourceFactory(fetchExecutor.asCoroutineDispatcher())
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData",
- "kotlinx.coroutines.asCoroutineDispatcher"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData",
+ "kotlinx.coroutines.asCoroutineDispatcher"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
config: PagedList.Config,
@@ -185,23 +179,23 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param fetchExecutor Executor for fetching data from DataSources.
- *
* @see LivePagedListBuilder
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this.asPagingSourceFactory(fetchExecutor.asCoroutineDispatcher())
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData",
- "kotlinx.coroutines.asCoroutineDispatcher"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData",
+ "kotlinx.coroutines.asCoroutineDispatcher"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
pageSize: Int,
@@ -226,21 +220,22 @@
* @param config Paging configuration.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
- * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The
- * set [coroutineScope] allows a [PagingSource] to cancel running load operations when the results
- * are no longer needed - for example, when the containing activity is destroyed.
+ * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The set
+ * [coroutineScope] allows a [PagingSource] to cancel running load operations when the results are
+ * no longer needed - for example, when the containing activity is destroyed.
*
* Defaults to [GlobalScope].
- * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagingSource]s.
*
+ * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagingSource]s.
* @see LivePagedListBuilder
*/
@OptIn(DelicateCoroutinesApi::class)
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -251,18 +246,18 @@
initialLoadKey,
this
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toLiveData(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
coroutineScope: CoroutineScope = GlobalScope,
- fetchDispatcher: CoroutineDispatcher = ArchTaskExecutor.getIOThreadExecutor()
- .asCoroutineDispatcher()
+ fetchDispatcher: CoroutineDispatcher =
+ ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher()
): LiveData<PagedList<Value>> {
return LivePagedList(
coroutineScope,
@@ -285,37 +280,38 @@
* @param pageSize Page size.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
- * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The
- * set [coroutineScope] allows a [PagingSource] to cancel running load operations when the results
- * are no longer needed - for example, when the containing activity is destroyed.
+ * @param coroutineScope Set the [CoroutineScope] that page loads should be launched within. The set
+ * [coroutineScope] allows a [PagingSource] to cancel running load operations when the results are
+ * no longer needed - for example, when the containing activity is destroyed.
*
* Defaults to [GlobalScope].
- * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagingSource]s.
*
+ * @param fetchDispatcher [CoroutineDispatcher] for fetching data from [PagingSource]s.
* @see LivePagedListBuilder
*/
@OptIn(DelicateCoroutinesApi::class)
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toLiveData(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
coroutineScope: CoroutineScope = GlobalScope,
- fetchDispatcher: CoroutineDispatcher = ArchTaskExecutor.getIOThreadExecutor()
- .asCoroutineDispatcher()
+ fetchDispatcher: CoroutineDispatcher =
+ ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher()
): LiveData<PagedList<Value>> {
return LivePagedList(
coroutineScope,
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt b/paging/paging-runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
index 34a64c7..e256b8e 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
@@ -32,9 +32,8 @@
* optionally enable extra features (such as initial load key, or BoundaryCallback).
*
* @param Key Type of input valued used to load data from the [DataSource]. Must be [Int] if you're
- * using [PositionalDataSource].
+ * using [PositionalDataSource].
* @param Value Item type being presented.
- *
* @see toLiveData
*/
@Deprecated("PagedList is deprecated and has been replaced by PagingData")
@@ -42,14 +41,11 @@
private val pagingSourceFactory: (() -> PagingSource<Key, Value>)?
private val dataSourceFactory: DataSource.Factory<Key, Value>?
- @Suppress("DEPRECATION")
- private val config: PagedList.Config
- @OptIn(DelicateCoroutinesApi::class)
- private var coroutineScope: CoroutineScope = GlobalScope
+ @Suppress("DEPRECATION") private val config: PagedList.Config
+ @OptIn(DelicateCoroutinesApi::class) private var coroutineScope: CoroutineScope = GlobalScope
private var initialLoadKey: Key? = null
- @Suppress("DEPRECATION")
- private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
+ @Suppress("DEPRECATION") private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
private var fetchDispatcher = ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher()
/**
@@ -60,8 +56,9 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -72,16 +69,15 @@
initialLoadKey,
dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
dataSourceFactory: DataSource.Factory<Key, Value>,
- @Suppress("DEPRECATION")
- config: PagedList.Config
+ @Suppress("DEPRECATION") config: PagedList.Config
) {
this.pagingSourceFactory = null
this.dataSourceFactory = dataSourceFactory
@@ -103,22 +99,23 @@
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
- constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this(
- dataSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ constructor(
+ dataSourceFactory: DataSource.Factory<Key, Value>,
+ pageSize: Int
+ ) : this(dataSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* Creates a [LivePagedListBuilder] with required parameters.
@@ -132,12 +129,14 @@
* [pagingSourceFactory] will invoked to construct a new [PagedList] and [PagingSource] when the
* current [PagingSource] is invalidated, and pass the new [PagedList] through the
* `LiveData<PagedList>` to observers.
+ *
* @param config Paging configuration.
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -148,15 +147,14 @@
initialLoadKey,
this
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData"
+ )
)
constructor(
pagingSourceFactory: () -> PagingSource<Key, Value>,
- @Suppress("DEPRECATION")
- config: PagedList.Config
+ @Suppress("DEPRECATION") config: PagedList.Config
) {
this.pagingSourceFactory = pagingSourceFactory
this.dataSourceFactory = null
@@ -181,26 +179,28 @@
* [pagingSourceFactory] will invoked to construct a new [PagedList] and [PagingSource] when the
* current [PagingSource] is invalidated, and pass the new [PagedList] through the
* `LiveData<PagedList>` to observers.
+ *
* @param pageSize Size of pages to load.
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).liveData""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.liveData"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.liveData"
+ )
)
- constructor(pagingSourceFactory: () -> PagingSource<Key, Value>, pageSize: Int) : this(
- pagingSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ constructor(
+ pagingSourceFactory: () -> PagingSource<Key, Value>,
+ pageSize: Int
+ ) : this(pagingSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* Set the [CoroutineScope] that page loads should be launched within. The set [coroutineScope]
@@ -213,9 +213,8 @@
* @return this
*/
@Suppress("unused") // Public API
- fun setCoroutineScope(coroutineScope: CoroutineScope) = this.apply {
- this.coroutineScope = coroutineScope
- }
+ fun setCoroutineScope(coroutineScope: CoroutineScope) =
+ this.apply { this.coroutineScope = coroutineScope }
/**
* First loading key passed to the first PagedList/DataSource.
@@ -226,36 +225,31 @@
* @param key Initial load key passed to the first PagedList/DataSource.
* @return this
*/
- fun setInitialLoadKey(key: Key?) = this.apply {
- initialLoadKey = key
- }
+ fun setInitialLoadKey(key: Key?) = this.apply { initialLoadKey = key }
/**
- * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created,
- * typically used to load additional data from network when paging from local storage.
+ * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created, typically used
+ * to load additional data from network when paging from local storage.
*
* Pass a [PagedList.BoundaryCallback] to listen to when the PagedList runs out of data to load.
* If this method is not called, or `null` is passed, you will not be notified when each
* [PagingSource] runs out of data to provide to its [PagedList].
*
* If you are paging from a DataSource.Factory backed by local storage, you can set a
- * BoundaryCallback to know when there is no more information to page from local storage.
- * This is useful to page from the network when local storage is a cache of network data.
+ * BoundaryCallback to know when there is no more information to page from local storage. This
+ * is useful to page from the network when local storage is a cache of network data.
*
- * Note that when using a BoundaryCallback with a `LiveData<PagedList>`, method calls
- * on the callback may be dispatched multiple times - one for each PagedList/DataSource
- * pair. If loading network data from a BoundaryCallback, you should prevent multiple
- * dispatches of the same method from triggering multiple simultaneous network loads.
+ * Note that when using a BoundaryCallback with a `LiveData<PagedList>`, method calls on the
+ * callback may be dispatched multiple times - one for each PagedList/DataSource pair. If
+ * loading network data from a BoundaryCallback, you should prevent multiple dispatches of the
+ * same method from triggering multiple simultaneous network loads.
*
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @return this
*/
fun setBoundaryCallback(
- @Suppress("DEPRECATION")
- boundaryCallback: PagedList.BoundaryCallback<Value>?
- ) = this.apply {
- this.boundaryCallback = boundaryCallback
- }
+ @Suppress("DEPRECATION") boundaryCallback: PagedList.BoundaryCallback<Value>?
+ ) = this.apply { this.boundaryCallback = boundaryCallback }
/**
* Sets [Executor] used for background fetching of [PagedList]s, and the pages within.
@@ -269,9 +263,8 @@
* @param fetchExecutor [Executor] for fetching data from [PagingSource]s.
* @return this
*/
- fun setFetchExecutor(fetchExecutor: Executor) = this.apply {
- this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
- }
+ fun setFetchExecutor(fetchExecutor: Executor) =
+ this.apply { this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher() }
/**
* Constructs the `LiveData<PagedList>`.
@@ -283,8 +276,8 @@
*/
@Suppress("DEPRECATION")
fun build(): LiveData<PagedList<Value>> {
- val pagingSourceFactory = pagingSourceFactory
- ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
+ val pagingSourceFactory =
+ pagingSourceFactory ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
check(pagingSourceFactory != null) {
"LivePagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/LoadStateAdapter.kt b/paging/paging-runtime/src/main/java/androidx/paging/LoadStateAdapter.kt
index abb837a..e811c55 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/LoadStateAdapter.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/LoadStateAdapter.kt
@@ -20,14 +20,13 @@
import androidx.recyclerview.widget.RecyclerView
/**
- * Adapter for displaying a RecyclerView item based on [LoadState], such as a loading spinner, or
- * a retry error button.
+ * Adapter for displaying a RecyclerView item based on [LoadState], such as a loading spinner, or a
+ * retry error button.
*
- * By default will use one shared [view type][RecyclerView.Adapter.getItemViewType] for all
- * items.
+ * By default will use one shared [view type][RecyclerView.Adapter.getItemViewType] for all items.
*
- * By default, both [LoadState.Loading] and [LoadState.Error] are presented as adapter items,
- * other states are not. To configure this, override [displayLoadStateAsItem].
+ * By default, both [LoadState.Loading] and [LoadState.Error] are presented as adapter items, other
+ * states are not. To configure this, override [displayLoadStateAsItem].
*
* To present this Adapter as a header and or footer alongside your [PagingDataAdapter], see
* [PagingDataAdapter.withLoadStateHeaderAndFooter], or use
@@ -78,10 +77,9 @@
/**
* Called to create a ViewHolder for the given LoadState.
*
- * @param parent The ViewGroup into which the new View will be added after it is bound to
- * an adapter position.
+ * @param parent The ViewGroup into which the new View will be added after it is bound to an
+ * adapter position.
* @param loadState The LoadState to be initially presented by the new ViewHolder.
- *
* @see [getItemViewType]
* @see [displayLoadStateAsItem]
*/
@@ -92,7 +90,6 @@
*
* @param holder the ViewHolder to bind to
* @param loadState LoadState to display.
- *
* @see [getItemViewType]
* @see [displayLoadStateAsItem]
*/
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/PagedListAdapter.kt b/paging/paging-runtime/src/main/java/androidx/paging/PagedListAdapter.kt
index b0dbec5..1bb06c2 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/PagedListAdapter.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/PagedListAdapter.kt
@@ -99,22 +99,18 @@
* ```
*
* Advanced users that wish for more control over adapter behavior, or to provide a specific base
- * class should refer to [AsyncPagedListDiffer], which provides the mapping from paging
- * events to adapter-friendly callbacks.
+ * class should refer to [AsyncPagedListDiffer], which provides the mapping from paging events to
+ * adapter-friendly callbacks.
*
* @param T Type of the PagedLists this Adapter will receive.
* @param VH A class that extends ViewHolder that will be used by the adapter.
*/
@Deprecated(
message = "PagedListAdapter is deprecated and has been replaced by PagingDataAdapter",
- replaceWith = ReplaceWith(
- "PagingDataAdapter<T, VH>",
- "androidx.paging.PagingDataAdapter"
- )
+ replaceWith = ReplaceWith("PagingDataAdapter<T, VH>", "androidx.paging.PagingDataAdapter")
)
abstract class PagedListAdapter<T : Any, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH> {
- @Suppress("DEPRECATION")
- internal val differ: AsyncPagedListDiffer<T>
+ @Suppress("DEPRECATION") internal val differ: AsyncPagedListDiffer<T>
@Suppress("DEPRECATION")
private val listener = { previousList: PagedList<T>?, currentList: PagedList<T>? ->
[email protected](currentList)
@@ -129,7 +125,6 @@
* currentList value. May be null if no PagedList is being presented.
*
* @return The list currently being displayed.
- *
* @see onCurrentListChanged
*/
@Suppress("DEPRECATION")
@@ -142,8 +137,7 @@
*
* Convenience for [PagedListAdapter], which uses default threading behavior.
*
- * @param diffCallback The [DiffUtil.ItemCallback] instance to
- * compare items in the list.
+ * @param diffCallback The [DiffUtil.ItemCallback] instance to compare items in the list.
*/
protected constructor(diffCallback: DiffUtil.ItemCallback<T>) {
@Suppress("DEPRECATION")
@@ -174,13 +168,13 @@
* If a list is already being displayed, a diff will be computed on a background thread, which
* will dispatch Adapter.notifyItem events on the main thread.
*
- * The commit callback can be used to know when the PagedList is committed, but note that it
- * may not be executed. If PagedList B is submitted immediately after PagedList A, and is
- * committed directly, the callback associated with PagedList A will not be run.
+ * The commit callback can be used to know when the PagedList is committed, but note that it may
+ * not be executed. If PagedList B is submitted immediately after PagedList A, and is committed
+ * directly, the callback associated with PagedList A will not be run.
*
* @param pagedList The new list to be displayed.
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
- * it is committed.
+ * it is committed.
*/
open fun submitList(
@Suppress("DEPRECATION") pagedList: PagedList<T>?,
@@ -194,48 +188,42 @@
/**
* Called when the current PagedList is updated.
*
- * This may be dispatched as part of [submitList] if a background diff isn't
- * needed (such as when the first list is passed, or the list is cleared). In either case,
- * PagedListAdapter will simply call
- * [notifyItemRangeInserted/Removed(0, mPreviousSize)][notifyItemRangeInserted].
+ * This may be dispatched as part of [submitList] if a background diff isn't needed (such as
+ * when the first list is passed, or the list is cleared). In either case, PagedListAdapter will
+ * simply call [notifyItemRangeInserted/Removed(0, mPreviousSize)][notifyItemRangeInserted].
*
- * This method will *not*be called when the Adapter switches from presenting a PagedList
- * to a snapshot version of the PagedList during a diff. This means you cannot observe each
- * PagedList via this method.
+ * This method will *not*be called when the Adapter switches from presenting a PagedList to a
+ * snapshot version of the PagedList during a diff. This means you cannot observe each PagedList
+ * via this method.
*
* @param currentList new PagedList being displayed, may be null.
- *
* @see currentList
*/
@Deprecated(
"Use the two argument variant instead.",
ReplaceWith("onCurrentListChanged(previousList, currentList)")
)
- open fun onCurrentListChanged(@Suppress("DEPRECATION") currentList: PagedList<T>?) {
- }
+ open fun onCurrentListChanged(@Suppress("DEPRECATION") currentList: PagedList<T>?) {}
/**
* Called when the current PagedList is updated.
*
- * This may be dispatched as part of [submitList] if a background diff isn't
- * needed (such as when the first list is passed, or the list is cleared). In either case,
- * PagedListAdapter will simply call
- * [notifyItemRangeInserted/Removed(0, mPreviousSize)][notifyItemRangeInserted].
+ * This may be dispatched as part of [submitList] if a background diff isn't needed (such as
+ * when the first list is passed, or the list is cleared). In either case, PagedListAdapter will
+ * simply call [notifyItemRangeInserted/Removed(0, mPreviousSize)][notifyItemRangeInserted].
*
- * This method will *not*be called when the Adapter switches from presenting a PagedList
- * to a snapshot version of the PagedList during a diff. This means you cannot observe each
- * PagedList via this method.
+ * This method will *not*be called when the Adapter switches from presenting a PagedList to a
+ * snapshot version of the PagedList during a diff. This means you cannot observe each PagedList
+ * via this method.
*
* @param previousList [PagedList] that was previously displayed, may be null.
* @param currentList new [PagedList] being displayed, may be null.
- *
* @see currentList
*/
open fun onCurrentListChanged(
@Suppress("DEPRECATION") previousList: PagedList<T>?,
@Suppress("DEPRECATION") currentList: PagedList<T>?
- ) {
- }
+ ) {}
/**
* Add a [LoadState] listener to observe the loading state of the current [PagedList].
@@ -244,7 +232,6 @@
* current [LoadType.REFRESH], [LoadType.PREPEND], and [LoadType.APPEND] states.
*
* @param listener Listener to receive [LoadState] updates.
- *
* @see removeLoadStateListener
*/
open fun addLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
@@ -265,9 +252,7 @@
* Create a [ConcatAdapter] with the provided [LoadStateAdapter]s displaying the
* [LoadType.PREPEND] [LoadState] as a list item at the end of the presented list.
*/
- fun withLoadStateHeader(
- header: LoadStateAdapter<*>
- ): ConcatAdapter {
+ fun withLoadStateHeader(header: LoadStateAdapter<*>): ConcatAdapter {
addLoadStateListener { loadType, loadState ->
if (loadType == LoadType.PREPEND) {
header.loadState = loadState
@@ -280,9 +265,7 @@
* Create a [ConcatAdapter] with the provided [LoadStateAdapter]s displaying the
* [LoadType.APPEND] [LoadState] as a list item at the start of the presented list.
*/
- fun withLoadStateFooter(
- footer: LoadStateAdapter<*>
- ): ConcatAdapter {
+ fun withLoadStateFooter(footer: LoadStateAdapter<*>): ConcatAdapter {
addLoadStateListener { loadType, loadState ->
if (loadType == LoadType.APPEND) {
footer.loadState = loadState
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/PagingDataAdapter.kt b/paging/paging-runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
index d85f5d0..e342b5c 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
@@ -33,8 +33,8 @@
import kotlinx.coroutines.flow.Flow
/**
- * [RecyclerView.Adapter] base class for presenting paged data from [PagingData]s in
- * a [RecyclerView].
+ * [RecyclerView.Adapter] base class for presenting paged data from [PagingData]s in a
+ * [RecyclerView].
*
* This class is a convenience wrapper around [AsyncPagingDataDiffer] that implements common default
* behavior for item counting, and listening to update events.
@@ -53,10 +53,10 @@
* *State Restoration*: To be able to restore [RecyclerView] state (e.g. scroll position) after a
* configuration change / application recreate, [PagingDataAdapter] calls
* [RecyclerView.Adapter.setStateRestorationPolicy] with
- * [RecyclerView.Adapter.StateRestorationPolicy.PREVENT] upon initialization and waits for the
- * first page to load before allowing state restoration.
- * Any other call to [RecyclerView.Adapter.setStateRestorationPolicy] by the application will
- * disable this logic and will rely on the user set value.
+ * [RecyclerView.Adapter.StateRestorationPolicy.PREVENT] upon initialization and waits for the first
+ * page to load before allowing state restoration. Any other call to
+ * [RecyclerView.Adapter.setStateRestorationPolicy] by the application will disable this logic and
+ * will rely on the user set value.
*
* @sample androidx.paging.samples.pagingDataAdapterSample
*/
@@ -64,14 +64,14 @@
/**
* Construct a [PagingDataAdapter].
*
- * @param mainDispatcher [CoroutineContext] where UI events are dispatched. Typically, this should be
- * [Dispatchers.Main].
- * @param workerDispatcher [CoroutineContext] where the work to generate UI events is dispatched, for
- * example when diffing lists on [REFRESH]. Typically, this should have a background
- * [CoroutineDispatcher] set; [Dispatchers.Default] by default.
+ * @param mainDispatcher [CoroutineContext] where UI events are dispatched. Typically, this should
+ * be [Dispatchers.Main].
+ * @param workerDispatcher [CoroutineContext] where the work to generate UI events is dispatched,
+ * for example when diffing lists on [REFRESH]. Typically, this should have a background
+ * [CoroutineDispatcher] set; [Dispatchers.Default] by default.
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster path
- * for generating the UI events required to display the new list.
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
*/
@JvmOverloads
constructor(
@@ -84,10 +84,10 @@
* Construct a [PagingDataAdapter].
*
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
- * path for generating the UI events required to display the new list.
- * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically,
- * this should be [Dispatchers.Main].
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
+ * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically, this
+ * should be [Dispatchers.Main].
*/
@Deprecated(
message = "Superseded by constructors which accept CoroutineContext",
@@ -109,13 +109,13 @@
* Construct a [PagingDataAdapter].
*
* @param diffCallback Callback for calculating the diff between two non-disjoint lists on
- * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
- * path for generating the UI events required to display the new list.
- * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically,
- * this should be [Dispatchers.Main].
+ * [REFRESH]. Used as a fallback for item-level diffing when Paging is unable to find a faster
+ * path for generating the UI events required to display the new list.
+ * @param mainDispatcher [CoroutineDispatcher] where UI events are dispatched. Typically, this
+ * should be [Dispatchers.Main].
* @param workerDispatcher [CoroutineDispatcher] where the work to generate UI events is
- * dispatched, for example when diffing lists on [REFRESH]. Typically, this should dispatch on a
- * background thread; [Dispatchers.Default] by default.
+ * dispatched, for example when diffing lists on [REFRESH]. Typically, this should dispatch on
+ * a background thread; [Dispatchers.Default] by default.
*/
@Deprecated(
message = "Superseded by constructors which accept CoroutineContext",
@@ -145,12 +145,13 @@
super.setStateRestorationPolicy(strategy)
}
- private val differ = AsyncPagingDataDiffer(
- diffCallback = diffCallback,
- updateCallback = AdapterListUpdateCallback(this),
- mainDispatcher = mainDispatcher,
- workerDispatcher = workerDispatcher
- )
+ private val differ =
+ AsyncPagingDataDiffer(
+ diffCallback = diffCallback,
+ updateCallback = AdapterListUpdateCallback(this),
+ mainDispatcher = mainDispatcher,
+ workerDispatcher = workerDispatcher
+ )
init {
// Wait on state restoration until the first insert event.
@@ -165,30 +166,34 @@
// Watch for adapter insert before triggering state restoration. This is almost redundant
// with loadState below, but can handle cached case.
@Suppress("LeakingThis")
- registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
- override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
- considerAllowingStateRestoration()
- unregisterAdapterDataObserver(this)
- super.onItemRangeInserted(positionStart, itemCount)
+ registerAdapterDataObserver(
+ object : RecyclerView.AdapterDataObserver() {
+ override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+ considerAllowingStateRestoration()
+ unregisterAdapterDataObserver(this)
+ super.onItemRangeInserted(positionStart, itemCount)
+ }
}
- })
+ )
// Watch for loadState update before triggering state restoration. This is almost
// redundant with data observer above, but can handle empty page case.
- addLoadStateListener(object : Function1<CombinedLoadStates, Unit> {
- // Ignore the first event we get, which is always the initial state, since we only
- // want to observe for Insert events.
- private var ignoreNextEvent = true
+ addLoadStateListener(
+ object : Function1<CombinedLoadStates, Unit> {
+ // Ignore the first event we get, which is always the initial state, since we only
+ // want to observe for Insert events.
+ private var ignoreNextEvent = true
- override fun invoke(loadStates: CombinedLoadStates) {
- if (ignoreNextEvent) {
- ignoreNextEvent = false
- } else if (loadStates.source.refresh is NotLoading) {
- considerAllowingStateRestoration()
- removeLoadStateListener(this)
+ override fun invoke(loadStates: CombinedLoadStates) {
+ if (ignoreNextEvent) {
+ ignoreNextEvent = false
+ } else if (loadStates.source.refresh is NotLoading) {
+ considerAllowingStateRestoration()
+ removeLoadStateListener(this)
+ }
}
}
- })
+ )
}
/**
@@ -208,9 +213,8 @@
* result in an [UnsupportedOperationException].
*
* @param hasStableIds Whether items in data set have unique identifiers or not.
- *
* @throws UnsupportedOperationException Always thrown, since this is unsupported by
- * [PagingDataAdapter].
+ * [PagingDataAdapter].
*/
final override fun setHasStableIds(hasStableIds: Boolean) {
throw UnsupportedOperationException("Stable ids are unsupported on PagingDataAdapter.")
@@ -247,6 +251,7 @@
* via [CoroutineScope][kotlinx.coroutines.CoroutineScope] instead of relying of [Lifecycle].
*
* @sample androidx.paging.samples.submitDataLiveDataSample
+ *
* @sample androidx.paging.samples.submitDataRxSample
*
* @see submitData
@@ -264,8 +269,8 @@
* within the same generation of [PagingData].
*
* [LoadState.Error] can be generated from two types of load requests:
- * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
- * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
+ * * [PagingSource.load] returning [PagingSource.LoadResult.Error]
+ * * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
*/
fun retry() {
differ.retry()
@@ -298,8 +303,7 @@
* @param position Index of the presented item to return, including placeholders.
* @return The presented item at [position], `null` if it is a placeholder
*/
- @MainThread
- protected fun getItem(@IntRange(from = 0) position: Int) = differ.getItem(position)
+ @MainThread protected fun getItem(@IntRange(from = 0) position: Int) = differ.getItem(position)
/**
* Returns the presented item at the specified position, without notifying Paging of the item
@@ -308,8 +312,7 @@
* @param index Index of the presented item to return, including placeholders.
* @return The presented item at position [index], `null` if it is a placeholder.
*/
- @MainThread
- fun peek(@IntRange(from = 0) index: Int) = differ.peek(index)
+ @MainThread fun peek(@IntRange(from = 0) index: Int) = differ.peek(index)
/**
* Returns a new [ItemSnapshotList] representing the currently presented items, including any
@@ -323,28 +326,27 @@
* A hot [Flow] of [CombinedLoadStates] that emits a snapshot whenever the loading state of the
* current [PagingData] changes.
*
- * This flow is conflated, so it buffers the last update to [CombinedLoadStates] and
- * immediately delivers the current load states on collection.
+ * This flow is conflated, so it buffers the last update to [CombinedLoadStates] and immediately
+ * delivers the current load states on collection.
*/
val loadStateFlow: Flow<CombinedLoadStates> = differ.loadStateFlow
/**
- * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
- * actual items presented don't change.
+ * A hot [Flow] that emits after the pages presented to the UI are updated, even if the actual
+ * items presented don't change.
*
* An update is triggered from one of the following:
- * * [submitData] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [submitData] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
- * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
- * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
- * trigger multiple times for each intermediate update that was presented while your collector
- * was still working. To avoid this behavior, you can
- * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
- * update, which is useful in cases where you are simply updating UI and don't care about
- * tracking the exact number of page updates.
+ * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay 0 items
+ * with a buffer of size 64. If a collector lags behind page updates, it may trigger multiple
+ * times for each intermediate update that was presented while your collector was still working.
+ * To avoid this behavior, you can [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so
+ * that you only receive the latest update, which is useful in cases where you are simply
+ * updating UI and don't care about tracking the exact number of page updates.
*/
val onPagesUpdatedFlow: Flow<Unit> = differ.onPagesUpdatedFlow
@@ -355,8 +357,8 @@
* reflect the current [CombinedLoadStates].
*
* @param listener [LoadStates] listener to receive updates.
- *
* @see removeLoadStateListener
+ *
* @sample androidx.paging.samples.addLoadStateListenerSample
*/
fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
@@ -378,13 +380,12 @@
* actual items presented don't change.
*
* An update is triggered from one of the following:
- * * [submitData] is called and initial load completes, regardless of any differences in
- * the loaded data
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
- * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+ * * [submitData] is called and initial load completes, regardless of any differences in the
+ * loaded data
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+ * * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
*
* @param listener called after pages presented are updated.
- *
* @see removeOnPagesUpdatedListener
*/
fun addOnPagesUpdatedListener(listener: () -> Unit) {
@@ -392,11 +393,10 @@
}
/**
- * Remove a previously registered listener for new [PagingData] generations completing
- * initial load and presenting to the UI.
+ * Remove a previously registered listener for new [PagingData] generations completing initial
+ * load and presenting to the UI.
*
* @param listener Previously registered listener.
- *
* @see addOnPagesUpdatedListener
*/
fun removeOnPagesUpdatedListener(listener: () -> Unit) {
@@ -411,12 +411,8 @@
* @see withLoadStateHeaderAndFooter
* @see withLoadStateFooter
*/
- fun withLoadStateHeader(
- header: LoadStateAdapter<*>
- ): ConcatAdapter {
- addLoadStateListener { loadStates ->
- header.loadState = loadStates.prepend
- }
+ fun withLoadStateHeader(header: LoadStateAdapter<*>): ConcatAdapter {
+ addLoadStateListener { loadStates -> header.loadState = loadStates.prepend }
return ConcatAdapter(header, this)
}
@@ -428,12 +424,8 @@
* @see withLoadStateHeaderAndFooter
* @see withLoadStateHeader
*/
- fun withLoadStateFooter(
- footer: LoadStateAdapter<*>
- ): ConcatAdapter {
- addLoadStateListener { loadStates ->
- footer.loadState = loadStates.append
- }
+ fun withLoadStateFooter(footer: LoadStateAdapter<*>): ConcatAdapter {
+ addLoadStateListener { loadStates -> footer.loadState = loadStates.append }
return ConcatAdapter(this, footer)
}
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/PagingLiveData.kt b/paging/paging-runtime/src/main/java/androidx/paging/PagingLiveData.kt
index 51126b8..040fda2 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/PagingLiveData.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/PagingLiveData.kt
@@ -33,9 +33,9 @@
*
* NOTE: Instances of [PagingData] emitted by this [LiveData] are not re-usable and cannot be
* submitted multiple times. This is especially relevant because [LiveData] will replays the latest
- * value downstream. To ensure you get a new instance of [PagingData] for each downstream
- * observer, you should use the [cachedIn] operator which multicasts the [LiveData] in a way that
- * returns a new instance of [PagingData] with cached data pre-loaded.
+ * value downstream. To ensure you get a new instance of [PagingData] for each downstream observer,
+ * you should use the [cachedIn] operator which multicasts the [LiveData] in a way that returns a
+ * new instance of [PagingData] with cached data pre-loaded.
*/
val <Key : Any, Value : Any> Pager<Key, Value>.liveData: LiveData<PagingData<Value>>
get() = flow.asLiveData()
@@ -43,57 +43,54 @@
/**
* Operator which caches a [LiveData] of [PagingData] within the scope of a [Lifecycle].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
* @param lifecycle The [Lifecycle] where the page cache will be kept alive.
*/
-fun <T : Any> LiveData<PagingData<T>>.cachedIn(lifecycle: Lifecycle) = asFlow()
- .cachedIn(lifecycle.coroutineScope)
- .asLiveData()
+fun <T : Any> LiveData<PagingData<T>>.cachedIn(lifecycle: Lifecycle) =
+ asFlow().cachedIn(lifecycle.coroutineScope).asLiveData()
/**
* Operator which caches a [LiveData] of [PagingData] within the scope of a [ViewModel].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param viewModel The [ViewModel] whose [viewModelScope] will dictate how long the page
- * cache will be kept alive.
+ * @param viewModel The [ViewModel] whose [viewModelScope] will dictate how long the page cache will
+ * be kept alive.
*/
-fun <T : Any> LiveData<PagingData<T>>.cachedIn(viewModel: ViewModel) = asFlow()
- .cachedIn(viewModel.viewModelScope)
- .asLiveData()
+fun <T : Any> LiveData<PagingData<T>>.cachedIn(viewModel: ViewModel) =
+ asFlow().cachedIn(viewModel.viewModelScope).asLiveData()
/**
* Operator which caches a [LiveData] of [PagingData] within a [CoroutineScope].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
- * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
- * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
- * manually cancelled to avoid memory leaks.
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this would
+ * be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after the
+ * [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
*/
-fun <T : Any> LiveData<PagingData<T>>.cachedIn(scope: CoroutineScope) = asFlow()
- .cachedIn(scope)
- .asLiveData()
+fun <T : Any> LiveData<PagingData<T>>.cachedIn(scope: CoroutineScope) =
+ asFlow().cachedIn(scope).asLiveData()
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/PlaceholderPaddedListDiffHelper.kt b/paging/paging-runtime/src/main/java/androidx/paging/PlaceholderPaddedListDiffHelper.kt
index 7f0ce24..06730b8 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/PlaceholderPaddedListDiffHelper.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/PlaceholderPaddedListDiffHelper.kt
@@ -29,8 +29,8 @@
* To minimize the amount of diffing caused by placeholders, we only execute DiffUtil in a reduced
* 'diff space' - in the range (computeLeadingNulls..size-computeTrailingNulls).
*
- * This allows the diff of a PagedList, e.g.:
- * 100 nulls, placeholder page, (empty page) x 5, page, 100 nulls
+ * This allows the diff of a PagedList, e.g.: 100 nulls, placeholder page, (empty page) x 5, page,
+ * 100 nulls
*
* To only inform DiffUtil about single loaded page in this case, by pruning all other nulls from
* consideration.
@@ -42,60 +42,62 @@
val oldSize = dataCount
val newSize = newList.dataCount
- val diffResult = DiffUtil.calculateDiff(
- object : DiffUtil.Callback() {
- override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
- val oldItem = getItem(oldItemPosition)
- val newItem = newList.getItem(newItemPosition)
+ val diffResult =
+ DiffUtil.calculateDiff(
+ object : DiffUtil.Callback() {
+ override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
+ val oldItem = getItem(oldItemPosition)
+ val newItem = newList.getItem(newItemPosition)
- return when {
- oldItem === newItem -> true
- else -> diffCallback.getChangePayload(oldItem, newItem)
+ return when {
+ oldItem === newItem -> true
+ else -> diffCallback.getChangePayload(oldItem, newItem)
+ }
}
- }
- override fun getOldListSize() = oldSize
+ override fun getOldListSize() = oldSize
- override fun getNewListSize() = newSize
+ override fun getNewListSize() = newSize
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- val oldItem = getItem(oldItemPosition)
- val newItem = newList.getItem(newItemPosition)
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ val oldItem = getItem(oldItemPosition)
+ val newItem = newList.getItem(newItemPosition)
- return when {
- oldItem === newItem -> true
- else -> diffCallback.areItemsTheSame(oldItem, newItem)
+ return when {
+ oldItem === newItem -> true
+ else -> diffCallback.areItemsTheSame(oldItem, newItem)
+ }
}
- }
- override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- val oldItem = getItem(oldItemPosition)
- val newItem = newList.getItem(newItemPosition)
+ override fun areContentsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int
+ ): Boolean {
+ val oldItem = getItem(oldItemPosition)
+ val newItem = newList.getItem(newItemPosition)
- return when {
- oldItem === newItem -> true
- else -> diffCallback.areContentsTheSame(oldItem, newItem)
+ return when {
+ oldItem === newItem -> true
+ else -> diffCallback.areContentsTheSame(oldItem, newItem)
+ }
}
- }
- },
- true
- )
+ },
+ true
+ )
// find first overlap
- val hasOverlap = (0 until dataCount).any {
- diffResult.convertOldPositionToNew(it) != RecyclerView.NO_POSITION
- }
- return PlaceholderPaddedDiffResult(
- diff = diffResult,
- hasOverlap = hasOverlap
- )
+ val hasOverlap =
+ (0 until dataCount).any {
+ diffResult.convertOldPositionToNew(it) != RecyclerView.NO_POSITION
+ }
+ return PlaceholderPaddedDiffResult(diff = diffResult, hasOverlap = hasOverlap)
}
/**
* See PlaceholderPaddedDiffing.md for how this works and why it works that way :).
*
- * 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.
+ * 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.
*/
internal fun <T : Any> PlaceholderPaddedList<T>.dispatchDiff(
callback: ListUpdateCallback,
@@ -122,8 +124,8 @@
}
/**
- * Given an oldPosition representing an anchor in the old data set, computes its new position
- * after the diff, or a guess if it no longer exists.
+ * Given an oldPosition representing an anchor in the old data set, computes its new position after
+ * the diff, or a guess if it no longer exists.
*/
internal fun PlaceholderPaddedList<*>.transformAnchorIndex(
diffResult: PlaceholderPaddedDiffResult,
@@ -169,9 +171,7 @@
val hasOverlap: Boolean
)
-/**
- * Helper class to implement the heuristic documented in PlaceholderPaddedDiffing.md.
- */
+/** Helper class to implement the heuristic documented in PlaceholderPaddedDiffing.md. */
internal object OverlappingListsDiffDispatcher {
fun <T> dispatchDiff(
oldList: PlaceholderPaddedList<T>,
@@ -179,11 +179,12 @@
callback: ListUpdateCallback,
diffResult: PlaceholderPaddedDiffResult
) {
- val callbackWrapper = PlaceholderUsingUpdateCallback(
- oldList = oldList,
- newList = newList,
- callback = callback
- )
+ val callbackWrapper =
+ PlaceholderUsingUpdateCallback(
+ oldList = oldList,
+ newList = newList,
+ callback = callback
+ )
diffResult.diff.dispatchUpdatesTo(callbackWrapper)
callbackWrapper.fixPlaceholders()
}
@@ -205,9 +206,7 @@
private var placeholdersBeforeState = UNUSED
private var placeholdersAfterState = UNUSED
- /**
- * Offsets a value based on placeholders to make it suitable to pass into the callback.
- */
+ /** Offsets a value based on placeholders to make it suitable to pass into the callback. */
private inline fun Int.offsetForDispatch() = this + placeholdersBefore
fun fixPlaceholders() {
@@ -234,10 +233,7 @@
// always remove from the end
// notice that postPlaceholdersToAdd is negative, thats why it is added to
// runningListEnd
- callback.onRemoved(
- runningListSize + postPlaceholdersToAdd,
- -postPlaceholdersToAdd
- )
+ callback.onRemoved(runningListSize + postPlaceholdersToAdd, -postPlaceholdersToAdd)
// remove them from unchanged placeholders, notice that it is an addition because
// postPlaceholdersToAdd is negative
unchangedPlaceholders += postPlaceholdersToAdd
@@ -273,7 +269,8 @@
// these have been shifted up, send a change event for them. We add the negative
// number of `prePlaceholdersToAdd` not to send change events for them
callback.onChanged(
- 0, unchangedPlaceholders + prePlaceholdersToAdd,
+ 0,
+ unchangedPlaceholders + prePlaceholdersToAdd,
PLACEHOLDER_POSITION_CHANGE
)
}
@@ -297,9 +294,7 @@
storageCount += count
}
- /**
- * Return true if it is dispatched, false otherwise.
- */
+ /** Return true if it is dispatched, false otherwise. */
private fun dispatchInsertAsPlaceholderBefore(position: Int, count: Int): Boolean {
if (position > 0) {
return false // not at the edge
@@ -313,22 +308,20 @@
// this index is negative because we are going back. offsetForDispatch will fix it
val index = (0 - asPlaceholderChange)
callback.onChanged(
- index.offsetForDispatch(), asPlaceholderChange, PLACEHOLDER_TO_ITEM
+ index.offsetForDispatch(),
+ asPlaceholderChange,
+ PLACEHOLDER_TO_ITEM
)
placeholdersBefore -= asPlaceholderChange
}
val asInsert = count - asPlaceholderChange
if (asInsert > 0) {
- callback.onInserted(
- 0.offsetForDispatch(), asInsert
- )
+ callback.onInserted(0.offsetForDispatch(), asInsert)
}
return true
}
- /**
- * Return true if it is dispatched, false otherwise.
- */
+ /** Return true if it is dispatched, false otherwise. */
private fun dispatchInsertAsPlaceholderAfter(position: Int, count: Int): Boolean {
if (position < storageCount) {
return false // not at the edge
@@ -340,15 +333,15 @@
if (asPlaceholderChange > 0) {
placeholdersAfterState = USED_FOR_ADDITION
callback.onChanged(
- position.offsetForDispatch(), asPlaceholderChange, PLACEHOLDER_TO_ITEM
+ position.offsetForDispatch(),
+ asPlaceholderChange,
+ PLACEHOLDER_TO_ITEM
)
placeholdersAfter -= asPlaceholderChange
}
val asInsert = count - asPlaceholderChange
if (asInsert > 0) {
- callback.onInserted(
- (position + asPlaceholderChange).offsetForDispatch(), asInsert
- )
+ callback.onInserted((position + asPlaceholderChange).offsetForDispatch(), asInsert)
}
return true
}
@@ -369,9 +362,7 @@
storageCount -= count
}
- /**
- * Return true if it is dispatched, false otherwise.
- */
+ /** Return true if it is dispatched, false otherwise. */
private fun dispatchRemovalAsPlaceholdersBefore(position: Int, count: Int): Boolean {
if (position > 0) {
return false
@@ -392,19 +383,13 @@
}
if (asPlaceholders > 0) {
placeholdersBeforeState = USED_FOR_REMOVAL
- callback.onChanged(
- 0.offsetForDispatch(),
- asPlaceholders,
- ITEM_TO_PLACEHOLDER
- )
+ callback.onChanged(0.offsetForDispatch(), asPlaceholders, ITEM_TO_PLACEHOLDER)
placeholdersBefore += asPlaceholders
}
return true
}
- /**
- * Return true if it is dispatched, false otherwise.
- */
+ /** Return true if it is dispatched, false otherwise. */
private fun dispatchRemovalAsPlaceholdersAfter(position: Int, count: Int): Boolean {
val end = position + count
if (end < storageCount) {
@@ -457,9 +442,8 @@
* Helper object to dispatch diffs when two lists do not overlap at all.
*
* We try to send change events when an item's position is replaced with a placeholder or vice
- * versa.
- * If there is an item in a given position in before and after lists, we dispatch add/remove for
- * them not to trigger unexpected change animations.
+ * versa. If there is an item in a given position in before and after lists, we dispatch add/remove
+ * for them not to trigger unexpected change animations.
*/
internal object DistinctListsDiffDispatcher {
fun <T : Any> dispatchDiff(
@@ -467,13 +451,12 @@
oldList: PlaceholderPaddedList<T>,
newList: PlaceholderPaddedList<T>,
) {
- val storageOverlapStart = maxOf(
- oldList.placeholdersBefore, newList.placeholdersBefore
- )
- val storageOverlapEnd = minOf(
- oldList.placeholdersBefore + oldList.dataCount,
- newList.placeholdersBefore + newList.dataCount
- )
+ val storageOverlapStart = maxOf(oldList.placeholdersBefore, newList.placeholdersBefore)
+ val storageOverlapEnd =
+ minOf(
+ oldList.placeholdersBefore + oldList.dataCount,
+ newList.placeholdersBefore + newList.dataCount
+ )
// we need to dispatch add/remove for overlapping storage positions
val overlappingStorageSize = storageOverlapEnd - storageOverlapStart
if (overlappingStorageSize > 0) {
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/RecordingCallback.kt b/paging/paging-runtime/src/main/java/androidx/paging/RecordingCallback.kt
index b9489f5..de6f09a 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/RecordingCallback.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/RecordingCallback.kt
@@ -19,6 +19,7 @@
@Suppress("DEPRECATION")
internal class RecordingCallback : PagedList.Callback() {
private val list = mutableListOf<Int>()
+
override fun onChanged(position: Int, count: Int) {
list.add(Changed)
list.add(position)
diff --git a/paging/paging-rxjava2/src/androidTest/java/androidx/paging/RxPagedListTest.kt b/paging/paging-rxjava2/src/androidTest/java/androidx/paging/RxPagedListTest.kt
index d5b0792..5a498de 100644
--- a/paging/paging-rxjava2/src/androidTest/java/androidx/paging/RxPagedListTest.kt
+++ b/paging/paging-rxjava2/src/androidTest/java/androidx/paging/RxPagedListTest.kt
@@ -28,14 +28,11 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
class RxPagedListTest {
- @JvmField
- @Rule
- val instantTaskExecutorRule = InstantTaskExecutorRule()
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun observable_config() {
- @Suppress("DEPRECATION")
- val observable = dataSourceFactory.toObservable(config)
+ @Suppress("DEPRECATION") val observable = dataSourceFactory.toObservable(config)
val first = observable.blockingFirst()
assertNotNull(first)
assertEquals(config, first.config)
@@ -43,8 +40,7 @@
@Test
fun observable_pageSize() {
- @Suppress("DEPRECATION")
- val observable = dataSourceFactory.toObservable(20)
+ @Suppress("DEPRECATION") val observable = dataSourceFactory.toObservable(20)
val first = observable.blockingFirst()
assertNotNull(first)
assertEquals(20, first.config.pageSize)
@@ -52,8 +48,7 @@
@Test
fun flowable_config() {
- @Suppress("DEPRECATION")
- val flowable = dataSourceFactory.toFlowable(config)
+ @Suppress("DEPRECATION") val flowable = dataSourceFactory.toFlowable(config)
val first = flowable.blockingFirst()
assertNotNull(first)
assertEquals(config, first.config)
@@ -61,8 +56,7 @@
@Test
fun flowable_pageSize() {
- @Suppress("DEPRECATION")
- val flowable = dataSourceFactory.toFlowable(20)
+ @Suppress("DEPRECATION") val flowable = dataSourceFactory.toFlowable(20)
val first = flowable.blockingFirst()
assertNotNull(first)
assertEquals(20, first.config.pageSize)
@@ -70,22 +64,27 @@
companion object {
@Suppress("DEPRECATION")
- private val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- callback.onResult(listOf(), 0, 0)
+ private val dataSource =
+ object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ callback.onResult(listOf(), 0, 0)
+ }
+
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<String>
+ ) {
+ // never completes...
+ }
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- // never completes...
+ private val dataSourceFactory =
+ object : DataSource.Factory<Int, String>() {
+ override fun create() = dataSource
}
- }
-
- private val dataSourceFactory = object : DataSource.Factory<Int, String>() {
- override fun create() = dataSource
- }
private val config = Config(10)
}
diff --git a/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedList.kt b/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedList.kt
index 12b5747..b0c3959 100644
--- a/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedList.kt
+++ b/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedList.kt
@@ -30,9 +30,10 @@
fetchScheduler: Scheduler?,
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
- val builder = RxPagedListBuilder(dataSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ val builder =
+ RxPagedListBuilder(dataSourceFactory, config)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -47,9 +48,10 @@
fetchScheduler: Scheduler?,
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
- val builder = RxPagedListBuilder(pagingSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ val builder =
+ RxPagedListBuilder(pagingSourceFactory, config)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -67,18 +69,18 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -89,12 +91,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getObservable",
- "kotlinx.coroutines.rx2.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getObservable",
+ "kotlinx.coroutines.rx2.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
config: PagedList.Config,
@@ -104,13 +106,14 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
@@ -125,28 +128,28 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getObservable",
- "kotlinx.coroutines.rx2.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getObservable",
+ "kotlinx.coroutines.rx2.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
pageSize: Int,
@@ -156,40 +159,41 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
* Constructs a `Flowable<PagedList>`, from this [DataSource.Factory], convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param config Paging configuration.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -200,12 +204,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getFlowable",
- "kotlinx.coroutines.rx2.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getFlowable",
+ "kotlinx.coroutines.rx2.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
config: PagedList.Config,
@@ -216,50 +220,51 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
* Constructs a `Flowable<PagedList>`, from this [DataSource.Factory], convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param pageSize Page size.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getFlowable",
- "kotlinx.coroutines.rx2.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getFlowable",
+ "kotlinx.coroutines.rx2.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
pageSize: Int,
@@ -270,13 +275,14 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
@@ -291,18 +297,18 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * thread pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -313,12 +319,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getObservable",
- "kotlinx.coroutines.rx2.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getObservable",
+ "kotlinx.coroutines.rx2.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toObservable(
config: PagedList.Config,
@@ -328,13 +334,14 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ pagingSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
@@ -349,26 +356,26 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * thread pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getObservable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getObservable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toObservable(
pageSize: Int,
@@ -378,40 +385,41 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ pagingSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
* Constructs a `Flowable<PagedList>`, from this [PagingSource] factory, convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param config Paging configuration.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * thread pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -422,10 +430,10 @@
initialLoadKey,
this
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getFlowable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getFlowable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toFlowable(
config: PagedList.Config,
@@ -436,48 +444,49 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ pagingSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
* Constructs a `Flowable<PagedList>`, from this [PagingSource] factory, convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param pageSize Page size.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * thread pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava2.getFlowable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava2.getFlowable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toFlowable(
pageSize: Int,
@@ -488,11 +497,12 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ pagingSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
diff --git a/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt b/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
index 7bbc4d7..d70d567 100644
--- a/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
+++ b/paging/paging-rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
@@ -45,22 +45,19 @@
* will dispatch new PagedLists, as well as their updates to that scheduler.
*
* @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
- * you're using [PositionalDataSource].
+ * you're using [PositionalDataSource].
* @param Value Item type being presented.
- *
*/
@Deprecated("PagedList is deprecated and has been replaced by PagingData")
class RxPagedListBuilder<Key : Any, Value : Any> {
private val pagingSourceFactory: (() -> PagingSource<Key, Value>)?
private val dataSourceFactory: DataSource.Factory<Key, Value>?
- @Suppress("DEPRECATION")
- private val config: PagedList.Config
+ @Suppress("DEPRECATION") private val config: PagedList.Config
private var initialLoadKey: Key? = null
- @Suppress("DEPRECATION")
- private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
+ @Suppress("DEPRECATION") private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
private var notifyDispatcher: CoroutineDispatcher? = null
private var notifyScheduler: Scheduler? = null
private var fetchDispatcher: CoroutineDispatcher? = null
@@ -74,8 +71,9 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -86,10 +84,10 @@
initialKey = null,
pagingSourceFactory = pagingSourceFactory
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava2.getFlowable"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava2.getFlowable"
+ )
)
constructor(
pagingSourceFactory: () -> PagingSource<Key, Value>,
@@ -117,21 +115,22 @@
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(pageSize),
initialKey = null,
pagingSourceFactory = pagingSourceFactory
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava2.getFlowable"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava2.getFlowable"
+ )
)
- constructor(pagingSourceFactory: () -> PagingSource<Key, Value>, pageSize: Int) : this(
- pagingSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ constructor(
+ pagingSourceFactory: () -> PagingSource<Key, Value>,
+ pageSize: Int
+ ) : this(pagingSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* Creates a [RxPagedListBuilder] with required parameters.
@@ -141,8 +140,9 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -153,11 +153,11 @@
initialKey = null,
pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava2.getFlowable",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava2.getFlowable",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
dataSourceFactory: DataSource.Factory<Key, Value>,
@@ -185,25 +185,23 @@
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(pageSize),
initialKey = null,
pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava2.getFlowable",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava2.getFlowable",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
dataSourceFactory: DataSource.Factory<Key, Value>,
pageSize: Int
- ) : this(
- dataSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ ) : this(dataSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* First loading key passed to the first PagedList/DataSource.
@@ -214,26 +212,24 @@
* @param key Initial load key passed to the first PagedList/DataSource.
* @return this
*/
- fun setInitialLoadKey(key: Key?) = apply {
- initialLoadKey = key
- }
+ fun setInitialLoadKey(key: Key?) = apply { initialLoadKey = key }
/**
- * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created,
- * typically used to load additional data from network when paging from local storage.
+ * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created, typically used
+ * to load additional data from network when paging from local storage.
*
* Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
- * method is not called, or `null` is passed, you will not be notified when each
- * DataSource runs out of data to provide to its PagedList.
+ * method is not called, or `null` is passed, you will not be notified when each DataSource runs
+ * out of data to provide to its PagedList.
*
* If you are paging from a DataSource.Factory backed by local storage, you can set a
- * BoundaryCallback to know when there is no more information to page from local storage.
- * This is useful to page from the network when local storage is a cache of network data.
+ * BoundaryCallback to know when there is no more information to page from local storage. This
+ * is useful to page from the network when local storage is a cache of network data.
*
- * Note that when using a BoundaryCallback with a `Observable<PagedList>`, method calls
- * on the callback may be dispatched multiple times - one for each PagedList/DataSource
- * pair. If loading network data from a BoundaryCallback, you should prevent multiple
- * dispatches of the same method from triggering multiple simultaneous network loads.
+ * Note that when using a BoundaryCallback with a `Observable<PagedList>`, method calls on the
+ * callback may be dispatched multiple times - one for each PagedList/DataSource pair. If
+ * loading network data from a BoundaryCallback, you should prevent multiple dispatches of the
+ * same method from triggering multiple simultaneous network loads.
*
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @return this
@@ -252,7 +248,7 @@
* receiving PagedLists will also receive the internal updates to the PagedList.
*
* @param scheduler Scheduler that receives PagedList updates, and where [PagedList.Callback]
- * calls are dispatched. Generally, this is the UI/main thread.
+ * calls are dispatched. Generally, this is the UI/main thread.
* @return this
*/
fun setNotifyScheduler(scheduler: Scheduler) = apply {
@@ -269,7 +265,7 @@
* The built [Observable] / [Flowable] will be subscribed on this scheduler.
*
* @param scheduler [Scheduler] used to fetch from DataSources, generally a background thread
- * pool for e.g. I/O or network loading.
+ * pool for e.g. I/O or network loading.
* @return this
*/
fun setFetchScheduler(scheduler: Scheduler) = apply {
@@ -287,23 +283,22 @@
*/
@Suppress("BuilderSetStyle", "DEPRECATION")
fun buildObservable(): Observable<PagedList<Value>> {
- val notifyScheduler = notifyScheduler
- ?: ScheduledExecutor(ArchTaskExecutor.getMainThreadExecutor())
+ val notifyScheduler =
+ notifyScheduler ?: ScheduledExecutor(ArchTaskExecutor.getMainThreadExecutor())
val notifyDispatcher = notifyDispatcher ?: notifyScheduler.asCoroutineDispatcher()
- val fetchScheduler = fetchScheduler
- ?: ScheduledExecutor(ArchTaskExecutor.getIOThreadExecutor())
+ val fetchScheduler =
+ fetchScheduler ?: ScheduledExecutor(ArchTaskExecutor.getIOThreadExecutor())
val fetchDispatcher = fetchDispatcher ?: fetchScheduler.asCoroutineDispatcher()
- val pagingSourceFactory = pagingSourceFactory
- ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
+ val pagingSourceFactory =
+ pagingSourceFactory ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
check(pagingSourceFactory != null) {
"RxPagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
}
- return Observable
- .create(
+ return Observable.create(
PagingObservableOnSubscribe(
initialLoadKey,
config,
@@ -346,20 +341,19 @@
private var currentJob: Job? = null
private lateinit var emitter: ObservableEmitter<PagedList<Value>>
- private val callback = {
- invalidate(true)
- }
+ private val callback = { invalidate(true) }
private val refreshRetryCallback = Runnable { invalidate(true) }
init {
- currentData = InitialPagedList(
- coroutineScope = GlobalScope,
- notifyDispatcher = notifyDispatcher,
- backgroundDispatcher = fetchDispatcher,
- config = config,
- initialLastKey = initialLoadKey
- )
+ currentData =
+ InitialPagedList(
+ coroutineScope = GlobalScope,
+ notifyDispatcher = notifyDispatcher,
+ backgroundDispatcher = fetchDispatcher,
+ config = config,
+ initialLastKey = initialLoadKey
+ )
currentData.setRetryCallback(refreshRetryCallback)
}
@@ -384,52 +378,53 @@
if (currentJob != null && !force) return
currentJob?.cancel()
- currentJob = GlobalScope.launch(fetchDispatcher) {
- currentData.pagingSource.unregisterInvalidatedCallback(callback)
- val pagingSource = pagingSourceFactory()
- pagingSource.registerInvalidatedCallback(callback)
- if (pagingSource is LegacyPagingSource) {
- pagingSource.setPageSize(config.pageSize)
- }
+ currentJob =
+ GlobalScope.launch(fetchDispatcher) {
+ currentData.pagingSource.unregisterInvalidatedCallback(callback)
+ val pagingSource = pagingSourceFactory()
+ pagingSource.registerInvalidatedCallback(callback)
+ if (pagingSource is LegacyPagingSource) {
+ pagingSource.setPageSize(config.pageSize)
+ }
- withContext(notifyDispatcher) {
- currentData.setInitialLoadState(LoadType.REFRESH, Loading)
- }
+ withContext(notifyDispatcher) {
+ currentData.setInitialLoadState(LoadType.REFRESH, Loading)
+ }
- @Suppress("UNCHECKED_CAST")
- val lastKey = currentData.lastKey as Key?
- val params = config.toRefreshLoadParams(lastKey)
- when (val initialResult = pagingSource.load(params)) {
- is PagingSource.LoadResult.Invalid -> {
- currentData.setInitialLoadState(
- LoadType.REFRESH,
- LoadState.NotLoading(endOfPaginationReached = false)
- )
- pagingSource.invalidate()
- }
- is PagingSource.LoadResult.Error -> {
- currentData.setInitialLoadState(
- LoadType.REFRESH,
- LoadState.Error(initialResult.throwable)
- )
- }
- is PagingSource.LoadResult.Page -> {
- val pagedList = PagedList.create(
- pagingSource,
- initialResult,
- GlobalScope,
- notifyDispatcher,
- fetchDispatcher,
- boundaryCallback,
- config,
- lastKey
- )
- onItemUpdate(currentData, pagedList)
- currentData = pagedList
- emitter.onNext(pagedList)
+ @Suppress("UNCHECKED_CAST") val lastKey = currentData.lastKey as Key?
+ val params = config.toRefreshLoadParams(lastKey)
+ when (val initialResult = pagingSource.load(params)) {
+ is PagingSource.LoadResult.Invalid -> {
+ currentData.setInitialLoadState(
+ LoadType.REFRESH,
+ LoadState.NotLoading(endOfPaginationReached = false)
+ )
+ pagingSource.invalidate()
+ }
+ is PagingSource.LoadResult.Error -> {
+ currentData.setInitialLoadState(
+ LoadType.REFRESH,
+ LoadState.Error(initialResult.throwable)
+ )
+ }
+ is PagingSource.LoadResult.Page -> {
+ val pagedList =
+ PagedList.create(
+ pagingSource,
+ initialResult,
+ GlobalScope,
+ notifyDispatcher,
+ fetchDispatcher,
+ boundaryCallback,
+ config,
+ lastKey
+ )
+ onItemUpdate(currentData, pagedList)
+ currentData = pagedList
+ emitter.onNext(pagedList)
+ }
}
}
- }
}
private fun onItemUpdate(previous: PagedList<Value>, next: PagedList<Value>) {
diff --git a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/PagingRx.kt b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/PagingRx.kt
index d86ce89..40c3add 100644
--- a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/PagingRx.kt
+++ b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/PagingRx.kt
@@ -43,13 +43,11 @@
* returns a new instance of [PagingData] with cached data pre-loaded.
*/
val <Key : Any, Value : Any> Pager<Key, Value>.observable: Observable<PagingData<Value>>
- get() = flow
- .conflate()
- .asObservable()
+ get() = flow.conflate().asObservable()
/**
- * A [Flowable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes
- * it as a [Flowable].
+ * A [Flowable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes it
+ * as a [Flowable].
*
* NOTE: Instances of [PagingData] emitted by this [Flowable] are not re-usable and cannot be
* submitted multiple times. This is especially relevant for transforms, which would replay the
@@ -58,55 +56,48 @@
* returns a new instance of [PagingData] with cached data pre-loaded.
*/
val <Key : Any, Value : Any> Pager<Key, Value>.flowable: Flowable<PagingData<Value>>
- get() = flow
- .conflate()
- .asFlowable()
+ get() = flow.conflate().asFlowable()
/**
* Operator which caches an [Observable] of [PagingData] within a [CoroutineScope].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
- * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
- * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
- * manually cancelled to avoid memory leaks.
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this would
+ * be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after the
+ * [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
*/
@ExperimentalCoroutinesApi
fun <T : Any> Observable<PagingData<T>>.cachedIn(scope: CoroutineScope): Observable<PagingData<T>> {
- return toFlowable(BackpressureStrategy.LATEST)
- .asFlow()
- .cachedIn(scope)
- .asObservable()
+ return toFlowable(BackpressureStrategy.LATEST).asFlow().cachedIn(scope).asObservable()
}
/**
* Operator which caches a [Flowable] of [PagingData] within a [CoroutineScope].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
- * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
- * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
- * manually cancelled to avoid memory leaks.
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this would
+ * be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after the
+ * [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
*/
@ExperimentalCoroutinesApi
fun <T : Any> Flowable<PagingData<T>>.cachedIn(scope: CoroutineScope): Flowable<PagingData<T>> {
- return asFlow()
- .cachedIn(scope)
- .asFlowable()
+ return asFlow().cachedIn(scope).asFlowable()
}
diff --git a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxPagingData.kt b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxPagingData.kt
index 35d63af..3ef4df3 100644
--- a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxPagingData.kt
+++ b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxPagingData.kt
@@ -31,14 +31,14 @@
import kotlinx.coroutines.rx2.awaitSingleOrNull
/**
- * Returns a [PagingData] containing the result of applying the given [transform] to each
- * element, as it is loaded.
+ * Returns a [PagingData] containing the result of applying the given [transform] to each element,
+ * as it is loaded.
*/
@JvmName("map")
@CheckResult
-fun <T : Any, R : Any> PagingData<T>.mapAsync(
- transform: (T) -> Single<R>
-): PagingData<R> = map { transform(it).await() }
+fun <T : Any, R : Any> PagingData<T>.mapAsync(transform: (T) -> Single<R>): PagingData<R> = map {
+ transform(it).await()
+}
/**
* Returns a [PagingData] of all elements returned from applying the given [transform] to each
@@ -50,23 +50,22 @@
transform: (T) -> Single<Iterable<R>>
): PagingData<R> = flatMap { transform(it).await() }
-/**
- * Returns a [PagingData] containing only elements matching the given [predicate].
- */
+/** Returns a [PagingData] containing only elements matching the given [predicate]. */
@JvmName("filter")
@CheckResult
-fun <T : Any> PagingData<T>.filterAsync(
- predicate: (T) -> Single<Boolean>
-): PagingData<T> = filter { predicate(it).await() }
+fun <T : Any> PagingData<T>.filterAsync(predicate: (T) -> Single<Boolean>): PagingData<T> = filter {
+ predicate(it).await()
+}
/**
- * Returns a [PagingData] containing each original element, with an optional separator generated
- * by [generator], given the elements before and after (or null, in boundary conditions).
+ * Returns a [PagingData] containing each original element, with an optional separator generated by
+ * [generator], given the elements before and after (or null, in boundary conditions).
*
* Note that this transform is applied asynchronously, as pages are loaded. Potential separators
* between pages are only computed once both pages are loaded.
*
* @sample androidx.paging.samples.insertSeparatorsRxSample
+ *
* @sample androidx.paging.samples.insertSeparatorsUiModelRxSample
*/
@JvmName("insertSeparators")
diff --git a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxRemoteMediator.kt b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxRemoteMediator.kt
index 076d04d..3242dcc 100644
--- a/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxRemoteMediator.kt
+++ b/paging/paging-rxjava2/src/main/java/androidx/paging/rxjava2/RxRemoteMediator.kt
@@ -19,9 +19,6 @@
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadState
import androidx.paging.LoadType
-import androidx.paging.LoadType.APPEND
-import androidx.paging.LoadType.PREPEND
-import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
@@ -32,21 +29,18 @@
import io.reactivex.Single
import kotlinx.coroutines.rx2.await
-/**
- * RxJava2 compatibility wrapper around [RemoteMediator]'s suspending APIs.
- */
+/** RxJava2 compatibility wrapper around [RemoteMediator]'s suspending APIs. */
@ExperimentalPagingApi
abstract class RxRemoteMediator<Key : Any, Value : Any> : RemoteMediator<Key, Value>() {
/**
* Implement this method to load additional remote data, which will then be stored for the
* [PagingSource] to access. These loads take one of two forms:
- * * type == [LoadType.PREPEND] / [LoadType.APPEND]
- * The [PagingSource] has loaded a 'boundary' page, with a `null` adjacent key. This means
- * this method should load additional remote data to append / prepend as appropriate, and store
- * it locally.
- * * type == [LoadType.REFRESH]
- * The app (or [initialize]) has requested a remote refresh of data. This means the method
- * should generally load remote data, and **replace** all local data.
+ * * type == [LoadType.PREPEND] / [LoadType.APPEND] The [PagingSource] has loaded a
+ * 'boundary' page, with a `null` adjacent key. This means this method should load
+ * additional remote data to append / prepend as appropriate, and store it locally.
+ * * type == [LoadType.REFRESH] The app (or [initialize]) has requested a remote refresh of
+ * data. This means the method should generally load remote data, and **replace** all local
+ * data.
*
* The runtime of this method defines loading state behavior in boundary conditions, which
* affects e.g., [LoadState] callbacks registered to [androidx.paging.PagingDataAdapter].
@@ -56,17 +50,16 @@
* [LoadType.APPEND] or both. [LoadType.REFRESH] occurs as a result of [initialize].
*
* @param loadType [LoadType] of the boundary condition which triggered this callback.
- * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
- * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
- * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
- * refresh - either driven by the UI, or by [initialize].
- * @param state A copy of the state including the list of pages currently held in
- * memory of the currently presented [PagingData] at the time of starting the load. E.g. for
- * load(loadType = END), you can use the page or item at the end as input for what to load from
- * the network.
+ * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
+ * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
+ * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
+ * refresh - either driven by the UI, or by [initialize].
*
+ * @param state A copy of the state including the list of pages currently held in memory of the
+ * currently presented [PagingData] at the time of starting the load. E.g. for load(loadType =
+ * END), you can use the page or item at the end as input for what to load from the network.
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
- * there's more data available.
+ * there's more data available.
*/
abstract fun loadSingle(
loadType: LoadType,
@@ -79,19 +72,21 @@
* This function runs to completion before any loading is performed.
*
* @return [InitializeAction] indicating the action to take after initialization:
- * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
- * [LoadType.REFRESH], to update paginated content when the stream is initialized.
- * Note: This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
- * [REFRESH] succeeds.
- * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a
- * refresh request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
+ * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
+ * [LoadType.REFRESH], to update paginated content when the stream is initialized. Note:
+ * This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
+ * [REFRESH] succeeds.
+ * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a refresh
+ * request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
*/
open fun initializeSingle(): Single<InitializeAction> = Single.just(LAUNCH_INITIAL_REFRESH)
- final override suspend fun load(loadType: LoadType, state: PagingState<Key, Value>):
- MediatorResult {
- return loadSingle(loadType, state).await()
- }
+ final override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Key, Value>
+ ): MediatorResult {
+ return loadSingle(loadType, state).await()
+ }
final override suspend fun initialize(): InitializeAction {
return initializeSingle().await()
diff --git a/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt b/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
index 0c02a18..10b78b8 100644
--- a/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
+++ b/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
@@ -40,17 +40,13 @@
@Suppress("DEPRECATION")
@RunWith(JUnit4::class)
class RxPagedListBuilderTest {
- private data class LoadStateEvent(
- val type: LoadType,
- val state: LoadState
- )
+ private data class LoadStateEvent(val type: LoadType, val state: LoadState)
- /**
- * Creates a data source that will sequentially supply the passed lists
- */
+ /** Creates a data source that will sequentially supply the passed lists */
private fun testDataSourceSequence(data: List<List<String>>): DataSource.Factory<Int, String> {
return object : DataSource.Factory<Int, String>() {
var localData = data
+
override fun create(): DataSource<Int, String> {
val currentList = localData.first()
localData = localData.drop(1)
@@ -86,10 +82,11 @@
if (invalidInitialLoad) {
invalidInitialLoad = false
LoadResult.Invalid()
- } else when (params) {
- is LoadParams.Refresh -> loadInitial(params)
- else -> loadRange()
- }
+ } else
+ when (params) {
+ is LoadParams.Refresh -> loadInitial(params)
+ else -> loadRange()
+ }
}
}
@@ -125,18 +122,14 @@
@Test
fun basic() {
- val factory = testDataSourceSequence(
- listOf(
- listOf("a", "b"),
- listOf("c", "d")
- )
- )
+ val factory = testDataSourceSequence(listOf(listOf("a", "b"), listOf("c", "d")))
val scheduler = TestScheduler()
- val observable = RxPagedListBuilder(factory, 10)
- .setFetchScheduler(scheduler)
- .setNotifyScheduler(scheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory, 10)
+ .setFetchScheduler(scheduler)
+ .setNotifyScheduler(scheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
@@ -166,10 +159,11 @@
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val observable: Observable<PagedList<String>> = RxPagedListBuilder(factory, 10)
- .setFetchScheduler(fetchScheduler)
- .setNotifyScheduler(notifyScheduler)
- .buildObservable()
+ val observable: Observable<PagedList<String>> =
+ RxPagedListBuilder(factory, 10)
+ .setFetchScheduler(fetchScheduler)
+ .setNotifyScheduler(notifyScheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
observable.subscribe(observer)
@@ -197,10 +191,11 @@
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val observable = RxPagedListBuilder(factory::create, 2)
- .setFetchScheduler(fetchScheduler)
- .setNotifyScheduler(notifyScheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory::create, 2)
+ .setFetchScheduler(fetchScheduler)
+ .setNotifyScheduler(notifyScheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
observable.subscribe(observer)
@@ -223,22 +218,14 @@
}
}
initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertEquals(
- listOf(
- LoadStateEvent(REFRESH, Loading)
- ),
- loadStates
- )
+ assertEquals(listOf(LoadStateEvent(REFRESH, Loading)), loadStates)
fetchScheduler.triggerActions()
notifyScheduler.triggerActions()
observer.assertValueCount(1)
assertEquals(
- listOf(
- LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(REFRESH, Error(EXCEPTION))
- ),
+ listOf(LoadStateEvent(REFRESH, Loading), LoadStateEvent(REFRESH, Error(EXCEPTION))),
loadStates
)
@@ -270,10 +257,7 @@
LoadStateEvent(REFRESH, Loading),
LoadStateEvent(REFRESH, Error(EXCEPTION)),
LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- )
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false))
),
loadStates
)
@@ -294,10 +278,11 @@
}
// this is essentially a direct scheduler so jobs are run immediately
val scheduler = Schedulers.from(DirectDispatcher.asExecutor())
- val observable = RxPagedListBuilder(factory, 2)
- .setFetchScheduler(scheduler)
- .setNotifyScheduler(scheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory, 2)
+ .setFetchScheduler(scheduler)
+ .setNotifyScheduler(scheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
// subscribe triggers the PagingObservableOnSubscribe's invalidate() to create first
@@ -321,15 +306,17 @@
initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertThat(loadStates).containsExactly(
- // before first load() is called, REFRESH is set to loading, represents load
- // attempt on first pagingSource
- LoadStateEvent(REFRESH, Loading)
- )
+ assertThat(loadStates)
+ .containsExactly(
+ // before first load() is called, REFRESH is set to loading, represents load
+ // attempt on first pagingSource
+ LoadStateEvent(REFRESH, Loading)
+ )
// execute first load, represents load attempt on first paging source
//
- // using removeFirst().run() instead of executeAll(), because executeAll() + immediate schedulers
+ // using removeFirst().run() instead of executeAll(), because executeAll() + immediate
+ // schedulers
// result in first load + subsequent loads executing immediately and we won't be able to
// assert the pagedLists/loads incrementally
loadDispatcher.queue.removeFirst().run()
@@ -340,18 +327,16 @@
assertTrue(pagingSources[0].invalid)
assertThat(pagingSources.size).isEqualTo(2)
- assertThat(loadStates).containsExactly(
- // the first load attempt
- LoadStateEvent(REFRESH, Loading),
- // LoadResult.Invalid resets RERFRESH state
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
- // before second load() is called, REFRESH is set to loading, represents load
- // attempt on second pagingSource
- LoadStateEvent(REFRESH, Loading),
- )
+ assertThat(loadStates)
+ .containsExactly(
+ // the first load attempt
+ LoadStateEvent(REFRESH, Loading),
+ // LoadResult.Invalid resets RERFRESH state
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
+ // before second load() is called, REFRESH is set to loading, represents load
+ // attempt on second pagingSource
+ LoadStateEvent(REFRESH, Loading),
+ )
// execute the load attempt on second pagingSource which succeeds
loadDispatcher.queue.removeFirst().run()
@@ -364,18 +349,19 @@
assertThat(secondPagedList).isInstanceOf(ContiguousPagedList::class.java)
secondPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertThat(loadStates).containsExactly(
- LoadStateEvent(REFRESH, Loading), // first load
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ), // first load reset
- LoadStateEvent(REFRESH, Loading), // second load
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ), // second load succeeds
- )
+ assertThat(loadStates)
+ .containsExactly(
+ LoadStateEvent(REFRESH, Loading), // first load
+ LoadStateEvent(
+ REFRESH,
+ NotLoading(endOfPaginationReached = false)
+ ), // first load reset
+ LoadStateEvent(REFRESH, Loading), // second load
+ LoadStateEvent(
+ REFRESH,
+ NotLoading(endOfPaginationReached = false)
+ ), // second load succeeds
+ )
}
@Test
@@ -387,18 +373,21 @@
}
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val rxPagedList = RxPagedListBuilder(
- pagingSourceFactory = pagingSourceFactory,
- pageSize = 10,
- ).apply {
- setNotifyScheduler(notifyScheduler)
- setFetchScheduler(fetchScheduler)
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pagingSourceFactory = pagingSourceFactory,
+ pageSize = 10,
+ )
+ .apply {
+ setNotifyScheduler(notifyScheduler)
+ setFetchScheduler(fetchScheduler)
+ }
+ .buildObservable()
fetchScheduler.triggerActions()
assertEquals(0, pagingSourcesCreated)
- rxPagedList.subscribe { }
+ rxPagedList.subscribe {}
assertEquals(0, pagingSourcesCreated)
@@ -408,13 +397,16 @@
@Test
fun initialValueAllowsGetDataSource() {
- val rxPagedList = RxPagedListBuilder(
- pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
- pageSize = 10,
- ).apply {
- setNotifyScheduler(Schedulers.from { it.run() })
- setFetchScheduler(Schedulers.from { it.run() })
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
+ pageSize = 10,
+ )
+ .apply {
+ setNotifyScheduler(Schedulers.from { it.run() })
+ setFetchScheduler(Schedulers.from { it.run() })
+ }
+ .buildObservable()
// Calling .dataSource should never throw from the initial paged list.
rxPagedList.firstOrError().blockingGet().dataSource
@@ -426,21 +418,22 @@
var pagingSourcesCreated = 0
val pagingSourceFactory = {
when (pagingSourcesCreated++) {
- 0 -> TestPagingSource().apply {
- invalidate()
- }
+ 0 -> TestPagingSource().apply { invalidate() }
else -> TestPagingSource()
}
}
val testScheduler = TestScheduler()
- val rxPagedList = RxPagedListBuilder(
- pageSize = 10,
- pagingSourceFactory = pagingSourceFactory,
- ).apply {
- setNotifyScheduler(testScheduler)
- setFetchScheduler(testScheduler)
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pageSize = 10,
+ pagingSourceFactory = pagingSourceFactory,
+ )
+ .apply {
+ setNotifyScheduler(testScheduler)
+ setFetchScheduler(testScheduler)
+ }
+ .buildObservable()
rxPagedList.subscribe()
testScheduler.triggerActions()
diff --git a/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt b/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
index 7ca476d..7a0dfda 100644
--- a/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
+++ b/paging/paging-rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
@@ -37,23 +37,23 @@
)
}
- private val pagingSource = object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- return loadInternal(params)
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
-
- private val rxPagingSource = object : RxPagingSource<Int, Int>() {
- override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Int>> {
- return Single.create { emitter ->
- emitter.onSuccess(loadInternal(params))
+ private val pagingSource =
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ return loadInternal(params)
}
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
}
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
+ private val rxPagingSource =
+ object : RxPagingSource<Int, Int>() {
+ override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Int>> {
+ return Single.create { emitter -> emitter.onSuccess(loadInternal(params)) }
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
@Test
fun basic() = runBlocking {
diff --git a/paging/paging-rxjava2/src/test/java/androidx/paging/RxRemoteMediatorTest.kt b/paging/paging-rxjava2/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
index 4816060..cfb0d97 100644
--- a/paging/paging-rxjava2/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
+++ b/paging/paging-rxjava2/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
@@ -33,32 +33,34 @@
class RxRemoteMediatorTest {
@Test
fun initializeSingle() = runTest {
- val remoteMediator = object : RxRemoteMediator<Int, Int>() {
- override fun loadSingle(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): Single<MediatorResult> {
- fail("Unexpected call")
- }
+ val remoteMediator =
+ object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
- override fun initializeSingle(): Single<InitializeAction> {
- return Single.just(SKIP_INITIAL_REFRESH)
+ override fun initializeSingle(): Single<InitializeAction> {
+ return Single.just(SKIP_INITIAL_REFRESH)
+ }
}
- }
assertEquals(SKIP_INITIAL_REFRESH, remoteMediator.initialize())
}
@Test
fun initializeSingleDefault() = runTest {
- val remoteMediator = object : RxRemoteMediator<Int, Int>() {
- override fun loadSingle(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): Single<MediatorResult> {
- fail("Unexpected call")
+ val remoteMediator =
+ object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
}
- }
assertEquals(LAUNCH_INITIAL_REFRESH, remoteMediator.initialize())
}
diff --git a/paging/paging-rxjava2/src/test/java/androidx/paging/rxjava2/RxPagingDataTest.kt b/paging/paging-rxjava2/src/test/java/androidx/paging/rxjava2/RxPagingDataTest.kt
index 7fcfc05..2089d99 100644
--- a/paging/paging-rxjava2/src/test/java/androidx/paging/rxjava2/RxPagingDataTest.kt
+++ b/paging/paging-rxjava2/src/test/java/androidx/paging/rxjava2/RxPagingDataTest.kt
@@ -39,32 +39,38 @@
private val presenter = TestPagingDataPresenter<String>(testDispatcher)
@Test
- fun map() = testScope.runTest {
- val transformed = original.mapAsync { Single.just(it + it) }
- presenter.collectFrom(transformed)
- assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
- }
-
- @Test
- fun flatMap() = testScope.runTest {
- val transformed = original.flatMapAsync { Single.just(listOf(it, it) as Iterable<String>) }
- presenter.collectFrom(transformed)
- assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
- }
-
- @Test
- fun filter() = testScope.runTest {
- val filtered = original.filterAsync { Single.just(it != "b") }
- presenter.collectFrom(filtered)
- assertEquals(listOf("a", "c"), presenter.currentList)
- }
-
- @Test
- fun insertSeparators() = testScope.runTest {
- val separated = original.insertSeparatorsAsync { left, right ->
- if (left == null || right == null) Maybe.empty() else Maybe.just("|")
+ fun map() =
+ testScope.runTest {
+ val transformed = original.mapAsync { Single.just(it + it) }
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
}
- presenter.collectFrom(separated)
- assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
- }
+
+ @Test
+ fun flatMap() =
+ testScope.runTest {
+ val transformed =
+ original.flatMapAsync { Single.just(listOf(it, it) as Iterable<String>) }
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
+ }
+
+ @Test
+ fun filter() =
+ testScope.runTest {
+ val filtered = original.filterAsync { Single.just(it != "b") }
+ presenter.collectFrom(filtered)
+ assertEquals(listOf("a", "c"), presenter.currentList)
+ }
+
+ @Test
+ fun insertSeparators() =
+ testScope.runTest {
+ val separated =
+ original.insertSeparatorsAsync { left, right ->
+ if (left == null || right == null) Maybe.empty() else Maybe.just("|")
+ }
+ presenter.collectFrom(separated)
+ assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
+ }
}
diff --git a/paging/paging-rxjava3/src/androidTest/java/androidx/paging/RxPagedListTest.kt b/paging/paging-rxjava3/src/androidTest/java/androidx/paging/RxPagedListTest.kt
index becbace..86ca4bb 100644
--- a/paging/paging-rxjava3/src/androidTest/java/androidx/paging/RxPagedListTest.kt
+++ b/paging/paging-rxjava3/src/androidTest/java/androidx/paging/RxPagedListTest.kt
@@ -30,14 +30,11 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
class RxPagedListTest {
- @JvmField
- @Rule
- val instantTaskExecutorRule = InstantTaskExecutorRule()
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun observable_config() {
- @Suppress("DEPRECATION")
- val observable = dataSourceFactory.toObservable(config)
+ @Suppress("DEPRECATION") val observable = dataSourceFactory.toObservable(config)
val first = observable.blockingFirst()
assertNotNull(first)
assertEquals(config, first.config)
@@ -45,8 +42,7 @@
@Test
fun observable_pageSize() {
- @Suppress("DEPRECATION")
- val observable = dataSourceFactory.toObservable(20)
+ @Suppress("DEPRECATION") val observable = dataSourceFactory.toObservable(20)
val first = observable.blockingFirst()
assertNotNull(first)
assertEquals(20, first.config.pageSize)
@@ -54,8 +50,7 @@
@Test
fun flowable_config() {
- @Suppress("DEPRECATION")
- val flowable = dataSourceFactory.toFlowable(config)
+ @Suppress("DEPRECATION") val flowable = dataSourceFactory.toFlowable(config)
val first = flowable.blockingFirst()
assertNotNull(first)
assertEquals(config, first.config)
@@ -63,8 +58,7 @@
@Test
fun flowable_pageSize() {
- @Suppress("DEPRECATION")
- val flowable = dataSourceFactory.toFlowable(20)
+ @Suppress("DEPRECATION") val flowable = dataSourceFactory.toFlowable(20)
val first = flowable.blockingFirst()
assertNotNull(first)
assertEquals(20, first.config.pageSize)
@@ -72,22 +66,27 @@
companion object {
@Suppress("DEPRECATION")
- private val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- callback.onResult(listOf(), 0, 0)
+ private val dataSource =
+ object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ callback.onResult(listOf(), 0, 0)
+ }
+
+ override fun loadRange(
+ params: LoadRangeParams,
+ callback: LoadRangeCallback<String>
+ ) {
+ // never completes...
+ }
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- // never completes...
+ private val dataSourceFactory =
+ object : DataSource.Factory<Int, String>() {
+ override fun create() = dataSource
}
- }
-
- private val dataSourceFactory = object : DataSource.Factory<Int, String>() {
- override fun create() = dataSource
- }
private val config = Config(10)
}
diff --git a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt
index df1e230..28eb677 100644
--- a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt
+++ b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt
@@ -43,13 +43,11 @@
* returns a new instance of [PagingData] with cached data pre-loaded.
*/
val <Key : Any, Value : Any> Pager<Key, Value>.observable: Observable<PagingData<Value>>
- get() = flow
- .conflate()
- .asObservable()
+ get() = flow.conflate().asObservable()
/**
- * A [Flowable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes
- * it as a [Flowable].
+ * A [Flowable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes it
+ * as a [Flowable].
*
* NOTE: Instances of [PagingData] emitted by this [Flowable] are not re-usable and cannot be
* submitted multiple times. This is especially relevant for transforms, which would replay the
@@ -58,55 +56,48 @@
* returns a new instance of [PagingData] with cached data pre-loaded.
*/
val <Key : Any, Value : Any> Pager<Key, Value>.flowable: Flowable<PagingData<Value>>
- get() = flow
- .conflate()
- .asFlowable()
+ get() = flow.conflate().asFlowable()
/**
* Operator which caches an [Observable] of [PagingData] within a [CoroutineScope].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
- * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
- * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
- * manually cancelled to avoid memory leaks.
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this would
+ * be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after the
+ * [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
*/
@ExperimentalCoroutinesApi
fun <T : Any> Observable<PagingData<T>>.cachedIn(scope: CoroutineScope): Observable<PagingData<T>> {
- return toFlowable(BackpressureStrategy.LATEST)
- .asFlow()
- .cachedIn(scope)
- .asObservable()
+ return toFlowable(BackpressureStrategy.LATEST).asFlow().cachedIn(scope).asObservable()
}
/**
* Operator which caches a [Flowable] of [PagingData] within a [CoroutineScope].
*
- * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple
- * observers on the same instance of [PagingData] to receive the same events, avoiding redundant
- * work, but comes at the cost of buffering those pages in memory.
+ * [cachedIn] multicasts pages loaded and transformed by a [PagingData], allowing multiple observers
+ * on the same instance of [PagingData] to receive the same events, avoiding redundant work, but
+ * comes at the cost of buffering those pages in memory.
*
* Calling [cachedIn] is required to allow calling
- * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData]
- * emitted by [Pager] or any of its transformed derivatives, as reloading data from scratch on the
- * same generation of [PagingData] is an unsupported operation.
+ * [submitData][androidx.paging.AsyncPagingDataAdapter] on the same instance of [PagingData] emitted
+ * by [Pager] or any of its transformed derivatives, as reloading data from scratch on the same
+ * generation of [PagingData] is an unsupported operation.
*
- * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
- * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
- * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
- * manually cancelled to avoid memory leaks.
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this would
+ * be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after the
+ * [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
*/
@ExperimentalCoroutinesApi
fun <T : Any> Flowable<PagingData<T>>.cachedIn(scope: CoroutineScope): Flowable<PagingData<T>> {
- return asFlow()
- .cachedIn(scope)
- .asFlowable()
+ return asFlow().cachedIn(scope).asFlowable()
}
diff --git a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedList.kt b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedList.kt
index 7923b35..4ce7dda 100644
--- a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedList.kt
+++ b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedList.kt
@@ -34,9 +34,10 @@
fetchScheduler: Scheduler?,
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
- val builder = RxPagedListBuilder(dataSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ val builder =
+ RxPagedListBuilder(dataSourceFactory, config)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -51,9 +52,10 @@
fetchScheduler: Scheduler?,
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
- val builder = RxPagedListBuilder(pagingSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ val builder =
+ RxPagedListBuilder(pagingSourceFactory, config)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -71,18 +73,18 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -93,12 +95,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.observable",
- "kotlinx.coroutines.rx3.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.observable",
+ "kotlinx.coroutines.rx3.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
config: PagedList.Config,
@@ -108,13 +110,14 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
@@ -129,28 +132,28 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.observable",
- "kotlinx.coroutines.rx3.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.observable",
+ "kotlinx.coroutines.rx3.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
pageSize: Int,
@@ -160,40 +163,41 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
* Constructs a `Flowable<PagedList>`, from this [DataSource.Factory], convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param config Paging configuration.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -204,12 +208,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.flowable",
- "kotlinx.coroutines.rx3.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.flowable",
+ "kotlinx.coroutines.rx3.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
config: PagedList.Config,
@@ -220,50 +224,51 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
* Constructs a `Flowable<PagedList>`, from this [DataSource.Factory], convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param pageSize Page size.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [DataSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
- * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * @param fetchScheduler [Scheduler] used to fetch from [DataSource]s, generally a background thread
+ * pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.flowable",
- "kotlinx.coroutines.rx3.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.flowable",
+ "kotlinx.coroutines.rx3.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
pageSize: Int,
@@ -274,13 +279,14 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
@@ -295,18 +301,18 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * thread pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -317,12 +323,12 @@
initialLoadKey,
this.asPagingSourceFactory(fetchScheduler?.asCoroutineDispatcher() ?: Dispatchers.IO)
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.observable",
- "kotlinx.coroutines.rx3.asCoroutineDispatcher",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.observable",
+ "kotlinx.coroutines.rx3.asCoroutineDispatcher",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toObservable(
config: PagedList.Config,
@@ -332,13 +338,14 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ pagingSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
@@ -353,26 +360,26 @@
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
- *
+ * thread pool for e.g. I/O or network loading.
* @see RxPagedListBuilder
* @see toFlowable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).observable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.observable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.observable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toObservable(
pageSize: Int,
@@ -382,40 +389,41 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildObservable()
+ pagingSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildObservable()
}
/**
* Constructs a `Flowable<PagedList>`, from this [PagingSource] factory, convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param config Paging configuration.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * thread pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -426,10 +434,10 @@
initialLoadKey,
this
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.flowable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.flowable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toFlowable(
config: PagedList.Config,
@@ -440,48 +448,49 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ pagingSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
/**
* Constructs a `Flowable<PagedList>`, from this [PagingSource] factory, convenience for
* [RxPagedListBuilder].
*
- * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform
- * all loading on that scheduler. It will already be observed on [notifyScheduler], and will
- * dispatch new [PagedList]s, as well as their updates to that scheduler.
+ * The returned [Flowable] will already be subscribed on the [fetchScheduler], and will perform all
+ * loading on that scheduler. It will already be observed on [notifyScheduler], and will dispatch
+ * new [PagedList]s, as well as their updates to that scheduler.
*
* @param pageSize Page size.
* @param initialLoadKey Initial load key passed to the first [PagedList] / [PagingSource].
* @param boundaryCallback The boundary callback for listening to [PagedList] load state.
* @param notifyScheduler [Scheduler] that receives [PagedList] updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
+ * [PagedList.Callback] calls are dispatched. Generally, this is the UI / main thread.
* @param fetchScheduler [Scheduler] used to fetch from [PagingSource]s, generally a background
- * thread pool for e.g. I/O or network loading.
+ * thread pool for e.g. I/O or network loading.
* @param backpressureStrategy [BackpressureStrategy] for the [Flowable] to use.
- *
* @see RxPagedListBuilder
* @see toObservable
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
PagingConfig(pageSize),
initialLoadKey,
this
).flowable""",
- "androidx.paging.Pager",
- "androidx.paging.PagingConfig",
- "androidx.paging.rxjava3.flowable"
- )
+ "androidx.paging.Pager",
+ "androidx.paging.PagingConfig",
+ "androidx.paging.rxjava3.flowable"
+ )
)
fun <Key : Any, Value : Any> (() -> PagingSource<Key, Value>).toFlowable(
pageSize: Int,
@@ -492,11 +501,12 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- pagingSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler
- ).buildFlowable(backpressureStrategy)
+ pagingSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ )
+ .buildFlowable(backpressureStrategy)
}
diff --git a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedListBuilder.kt b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedListBuilder.kt
index 98e745a..4a8c097 100644
--- a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedListBuilder.kt
+++ b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedListBuilder.kt
@@ -53,22 +53,19 @@
* will dispatch new PagedLists, as well as their updates to that scheduler.
*
* @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
- * you're using [PositionalDataSource].
+ * you're using [PositionalDataSource].
* @param Value Item type being presented.
- *
*/
@Deprecated("PagedList is deprecated and has been replaced by PagingData")
class RxPagedListBuilder<Key : Any, Value : Any> {
private val pagingSourceFactory: (() -> PagingSource<Key, Value>)?
private val dataSourceFactory: DataSource.Factory<Key, Value>?
- @Suppress("DEPRECATION")
- private val config: PagedList.Config
+ @Suppress("DEPRECATION") private val config: PagedList.Config
private var initialLoadKey: Key? = null
- @Suppress("DEPRECATION")
- private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
+ @Suppress("DEPRECATION") private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
private var notifyDispatcher: CoroutineDispatcher? = null
private var notifyScheduler: Scheduler? = null
private var fetchDispatcher: CoroutineDispatcher? = null
@@ -82,8 +79,9 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -94,10 +92,10 @@
initialKey = null,
pagingSourceFactory = pagingSourceFactory
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava3.flowable"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava3.flowable"
+ )
)
constructor(
pagingSourceFactory: () -> PagingSource<Key, Value>,
@@ -125,21 +123,22 @@
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(pageSize),
initialKey = null,
pagingSourceFactory = pagingSourceFactory
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava3.flowable"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava3.flowable"
+ )
)
- constructor(pagingSourceFactory: () -> PagingSource<Key, Value>, pageSize: Int) : this(
- pagingSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ constructor(
+ pagingSourceFactory: () -> PagingSource<Key, Value>,
+ pageSize: Int
+ ) : this(pagingSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* Creates a [RxPagedListBuilder] with required parameters.
@@ -149,8 +148,9 @@
*/
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(
config.pageSize,
config.prefetchDistance,
@@ -161,11 +161,11 @@
initialKey = null,
pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava3.flowable",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava3.flowable",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
dataSourceFactory: DataSource.Factory<Key, Value>,
@@ -193,25 +193,23 @@
@Suppress("DEPRECATION")
@Deprecated(
message = "PagedList is deprecated and has been replaced by PagingData",
- replaceWith = ReplaceWith(
- """Pager(
+ replaceWith =
+ ReplaceWith(
+ """Pager(
config = PagingConfig(pageSize),
initialKey = null,
pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
).flowable""",
- "androidx.paging.PagingConfig",
- "androidx.paging.Pager",
- "androidx.paging.rxjava3.flowable",
- "kotlinx.coroutines.Dispatchers"
- )
+ "androidx.paging.PagingConfig",
+ "androidx.paging.Pager",
+ "androidx.paging.rxjava3.flowable",
+ "kotlinx.coroutines.Dispatchers"
+ )
)
constructor(
dataSourceFactory: DataSource.Factory<Key, Value>,
pageSize: Int
- ) : this(
- dataSourceFactory,
- PagedList.Config.Builder().setPageSize(pageSize).build()
- )
+ ) : this(dataSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
/**
* First loading key passed to the first PagedList/DataSource.
@@ -222,26 +220,24 @@
* @param key Initial load key passed to the first PagedList/DataSource.
* @return this
*/
- fun setInitialLoadKey(key: Key?) = apply {
- initialLoadKey = key
- }
+ fun setInitialLoadKey(key: Key?) = apply { initialLoadKey = key }
/**
- * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created,
- * typically used to load additional data from network when paging from local storage.
+ * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created, typically used
+ * to load additional data from network when paging from local storage.
*
* Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
- * method is not called, or `null` is passed, you will not be notified when each
- * DataSource runs out of data to provide to its PagedList.
+ * method is not called, or `null` is passed, you will not be notified when each DataSource runs
+ * out of data to provide to its PagedList.
*
* If you are paging from a DataSource.Factory backed by local storage, you can set a
- * BoundaryCallback to know when there is no more information to page from local storage.
- * This is useful to page from the network when local storage is a cache of network data.
+ * BoundaryCallback to know when there is no more information to page from local storage. This
+ * is useful to page from the network when local storage is a cache of network data.
*
- * Note that when using a BoundaryCallback with a `Observable<PagedList>`, method calls
- * on the callback may be dispatched multiple times - one for each PagedList/DataSource
- * pair. If loading network data from a BoundaryCallback, you should prevent multiple
- * dispatches of the same method from triggering multiple simultaneous network loads.
+ * Note that when using a BoundaryCallback with a `Observable<PagedList>`, method calls on the
+ * callback may be dispatched multiple times - one for each PagedList/DataSource pair. If
+ * loading network data from a BoundaryCallback, you should prevent multiple dispatches of the
+ * same method from triggering multiple simultaneous network loads.
*
* @param boundaryCallback The boundary callback for listening to PagedList load state.
* @return this
@@ -260,7 +256,7 @@
* receiving PagedLists will also receive the internal updates to the PagedList.
*
* @param scheduler Scheduler that receives PagedList updates, and where [PagedList.Callback]
- * calls are dispatched. Generally, this is the UI/main thread.
+ * calls are dispatched. Generally, this is the UI/main thread.
* @return this
*/
fun setNotifyScheduler(scheduler: Scheduler) = apply {
@@ -277,7 +273,7 @@
* The built [Observable] / [Flowable] will be subscribed on this scheduler.
*
* @param scheduler [Scheduler] used to fetch from DataSources, generally a background thread
- * pool for e.g. I/O or network loading.
+ * pool for e.g. I/O or network loading.
* @return this
*/
fun setFetchScheduler(scheduler: Scheduler) = apply {
@@ -295,23 +291,22 @@
*/
@Suppress("BuilderSetStyle", "DEPRECATION")
fun buildObservable(): Observable<PagedList<Value>> {
- val notifyScheduler = notifyScheduler
- ?: ScheduledExecutor(ArchTaskExecutor.getMainThreadExecutor())
+ val notifyScheduler =
+ notifyScheduler ?: ScheduledExecutor(ArchTaskExecutor.getMainThreadExecutor())
val notifyDispatcher = notifyDispatcher ?: notifyScheduler.asCoroutineDispatcher()
- val fetchScheduler = fetchScheduler
- ?: ScheduledExecutor(ArchTaskExecutor.getIOThreadExecutor())
+ val fetchScheduler =
+ fetchScheduler ?: ScheduledExecutor(ArchTaskExecutor.getIOThreadExecutor())
val fetchDispatcher = fetchDispatcher ?: fetchScheduler.asCoroutineDispatcher()
- val pagingSourceFactory = pagingSourceFactory
- ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
+ val pagingSourceFactory =
+ pagingSourceFactory ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
check(pagingSourceFactory != null) {
"RxPagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
}
- return Observable
- .create(
+ return Observable.create(
PagingObservableOnSubscribe(
initialLoadKey,
config,
@@ -354,20 +349,19 @@
private var currentJob: Job? = null
private lateinit var emitter: ObservableEmitter<PagedList<Value>>
- private val callback = {
- invalidate(true)
- }
+ private val callback = { invalidate(true) }
private val refreshRetryCallback = Runnable { invalidate(true) }
init {
- currentData = InitialPagedList(
- coroutineScope = GlobalScope,
- notifyDispatcher = notifyDispatcher,
- backgroundDispatcher = fetchDispatcher,
- config = config,
- initialLastKey = initialLoadKey
- )
+ currentData =
+ InitialPagedList(
+ coroutineScope = GlobalScope,
+ notifyDispatcher = notifyDispatcher,
+ backgroundDispatcher = fetchDispatcher,
+ config = config,
+ initialLastKey = initialLoadKey
+ )
currentData.setRetryCallback(refreshRetryCallback)
}
@@ -392,52 +386,53 @@
if (currentJob != null && !force) return
currentJob?.cancel()
- currentJob = GlobalScope.launch(fetchDispatcher) {
- currentData.pagingSource.unregisterInvalidatedCallback(callback)
- val pagingSource = pagingSourceFactory()
- pagingSource.registerInvalidatedCallback(callback)
- if (pagingSource is LegacyPagingSource) {
- pagingSource.setPageSize(config.pageSize)
- }
+ currentJob =
+ GlobalScope.launch(fetchDispatcher) {
+ currentData.pagingSource.unregisterInvalidatedCallback(callback)
+ val pagingSource = pagingSourceFactory()
+ pagingSource.registerInvalidatedCallback(callback)
+ if (pagingSource is LegacyPagingSource) {
+ pagingSource.setPageSize(config.pageSize)
+ }
- withContext(notifyDispatcher) {
- currentData.setInitialLoadState(LoadType.REFRESH, Loading)
- }
+ withContext(notifyDispatcher) {
+ currentData.setInitialLoadState(LoadType.REFRESH, Loading)
+ }
- @Suppress("UNCHECKED_CAST")
- val lastKey = currentData.lastKey as Key?
- val params = config.toRefreshLoadParams(lastKey)
- when (val initialResult = pagingSource.load(params)) {
- is PagingSource.LoadResult.Invalid -> {
- currentData.setInitialLoadState(
- LoadType.REFRESH,
- LoadState.NotLoading(endOfPaginationReached = false)
- )
- pagingSource.invalidate()
- }
- is PagingSource.LoadResult.Error -> {
- currentData.setInitialLoadState(
- LoadType.REFRESH,
- LoadState.Error(initialResult.throwable)
- )
- }
- is PagingSource.LoadResult.Page -> {
- val pagedList = PagedList.create(
- pagingSource,
- initialResult,
- GlobalScope,
- notifyDispatcher,
- fetchDispatcher,
- boundaryCallback,
- config,
- lastKey
- )
- onItemUpdate(currentData, pagedList)
- currentData = pagedList
- emitter.onNext(pagedList)
+ @Suppress("UNCHECKED_CAST") val lastKey = currentData.lastKey as Key?
+ val params = config.toRefreshLoadParams(lastKey)
+ when (val initialResult = pagingSource.load(params)) {
+ is PagingSource.LoadResult.Invalid -> {
+ currentData.setInitialLoadState(
+ LoadType.REFRESH,
+ LoadState.NotLoading(endOfPaginationReached = false)
+ )
+ pagingSource.invalidate()
+ }
+ is PagingSource.LoadResult.Error -> {
+ currentData.setInitialLoadState(
+ LoadType.REFRESH,
+ LoadState.Error(initialResult.throwable)
+ )
+ }
+ is PagingSource.LoadResult.Page -> {
+ val pagedList =
+ PagedList.create(
+ pagingSource,
+ initialResult,
+ GlobalScope,
+ notifyDispatcher,
+ fetchDispatcher,
+ boundaryCallback,
+ config,
+ lastKey
+ )
+ onItemUpdate(currentData, pagedList)
+ currentData = pagedList
+ emitter.onNext(pagedList)
+ }
}
}
- }
}
private fun onItemUpdate(previous: PagedList<Value>, next: PagedList<Value>) {
diff --git a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingData.kt b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingData.kt
index d5d9a2e..4591123 100644
--- a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingData.kt
+++ b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingData.kt
@@ -31,14 +31,14 @@
import kotlinx.coroutines.rx3.awaitSingleOrNull
/**
- * Returns a [PagingData] containing the result of applying the given [transform] to each
- * element, as it is loaded.
+ * Returns a [PagingData] containing the result of applying the given [transform] to each element,
+ * as it is loaded.
*/
@JvmName("map")
@CheckResult
-fun <T : Any, R : Any> PagingData<T>.mapAsync(
- transform: (T) -> Single<R>
-): PagingData<R> = map { transform(it).await() }
+fun <T : Any, R : Any> PagingData<T>.mapAsync(transform: (T) -> Single<R>): PagingData<R> = map {
+ transform(it).await()
+}
/**
* Returns a [PagingData] of all elements returned from applying the given [transform] to each
@@ -50,23 +50,22 @@
transform: (T) -> Single<Iterable<R>>
): PagingData<R> = flatMap { transform(it).await() }
-/**
- * Returns a [PagingData] containing only elements matching the given [predicate].
- */
+/** Returns a [PagingData] containing only elements matching the given [predicate]. */
@JvmName("filter")
@CheckResult
-fun <T : Any> PagingData<T>.filterAsync(
- predicate: (T) -> Single<Boolean>
-): PagingData<T> = filter { predicate(it).await() }
+fun <T : Any> PagingData<T>.filterAsync(predicate: (T) -> Single<Boolean>): PagingData<T> = filter {
+ predicate(it).await()
+}
/**
- * Returns a [PagingData] containing each original element, with an optional separator generated
- * by [generator], given the elements before and after (or null, in boundary conditions).
+ * Returns a [PagingData] containing each original element, with an optional separator generated by
+ * [generator], given the elements before and after (or null, in boundary conditions).
*
* Note that this transform is applied asynchronously, as pages are loaded. Potential separators
* between pages are only computed once both pages are loaded.
*
* @sample androidx.paging.samples.insertSeparatorsRxSample
+ *
* @sample androidx.paging.samples.insertSeparatorsUiModelRxSample
*/
@JvmName("insertSeparators")
diff --git a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt
index e359c6c..c5504e6 100644
--- a/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt
+++ b/paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt
@@ -19,9 +19,6 @@
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadState
import androidx.paging.LoadType
-import androidx.paging.LoadType.APPEND
-import androidx.paging.LoadType.PREPEND
-import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
@@ -31,21 +28,18 @@
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.rx3.await
-/**
- * RxJava3 compatibility wrapper around [RemoteMediator]'s suspending APIs.
- */
+/** RxJava3 compatibility wrapper around [RemoteMediator]'s suspending APIs. */
@ExperimentalPagingApi
abstract class RxRemoteMediator<Key : Any, Value : Any> : RemoteMediator<Key, Value>() {
/**
* Implement this method to load additional remote data, which will then be stored for the
* [PagingSource] to access. These loads take one of two forms:
- * * type == [LoadType.PREPEND] / [LoadType.APPEND]
- * The [PagingSource] has loaded a 'boundary' page, with a `null` adjacent key. This means
- * this method should load additional remote data to append / prepend as appropriate, and store
- * it locally.
- * * type == [LoadType.REFRESH]
- * The app (or [initialize]) has requested a remote refresh of data. This means the method
- * should generally load remote data, and **replace** all local data.
+ * * type == [LoadType.PREPEND] / [LoadType.APPEND] The [PagingSource] has loaded a
+ * 'boundary' page, with a `null` adjacent key. This means this method should load
+ * additional remote data to append / prepend as appropriate, and store it locally.
+ * * type == [LoadType.REFRESH] The app (or [initialize]) has requested a remote refresh of
+ * data. This means the method should generally load remote data, and **replace** all local
+ * data.
*
* The runtime of this method defines loading state behavior in boundary conditions, which
* affects e.g., [LoadState] callbacks registered to [androidx.paging.PagingDataAdapter].
@@ -55,17 +49,16 @@
* [LoadType.APPEND] or both. [LoadType.REFRESH] occurs as a result of [initialize].
*
* @param loadType [LoadType] of the boundary condition which triggered this callback.
- * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
- * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
- * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
- * refresh - either driven by the UI, or by [initialize].
- * @param state A copy of the state including the list of pages currently held in
- * memory of the currently presented [PagingData] at the time of starting the load. E.g. for
- * load(loadType = END), you can use the page or item at the end as input for what to load from
- * the network.
+ * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
+ * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
+ * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
+ * refresh - either driven by the UI, or by [initialize].
*
+ * @param state A copy of the state including the list of pages currently held in memory of the
+ * currently presented [PagingData] at the time of starting the load. E.g. for load(loadType =
+ * END), you can use the page or item at the end as input for what to load from the network.
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
- * there's more data available.
+ * there's more data available.
*/
abstract fun loadSingle(
loadType: LoadType,
@@ -78,19 +71,21 @@
* This function runs to completion before any loading is performed.
*
* @return [InitializeAction] indicating the action to take after initialization:
- * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
- * [LoadType.REFRESH], to update paginated content when the stream is initialized.
- * Note: This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
- * [REFRESH] succeeds.
- * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a
- * refresh request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
+ * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
+ * [LoadType.REFRESH], to update paginated content when the stream is initialized. Note:
+ * This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until
+ * [REFRESH] succeeds.
+ * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a refresh
+ * request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
*/
open fun initializeSingle(): Single<InitializeAction> = Single.just(LAUNCH_INITIAL_REFRESH)
- final override suspend fun load(loadType: LoadType, state: PagingState<Key, Value>):
- MediatorResult {
- return loadSingle(loadType, state).await()
- }
+ final override suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Key, Value>
+ ): MediatorResult {
+ return loadSingle(loadType, state).await()
+ }
final override suspend fun initialize(): InitializeAction {
return initializeSingle().await()
diff --git a/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagedListBuilderTest.kt b/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
index 3bd051e..abf3604 100644
--- a/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
+++ b/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
@@ -42,17 +42,13 @@
@RunWith(JUnit4::class)
class RxPagedListBuilderTest {
- private data class LoadStateEvent(
- val type: LoadType,
- val state: LoadState
- )
+ private data class LoadStateEvent(val type: LoadType, val state: LoadState)
- /**
- * Creates a data source that will sequentially supply the passed lists
- */
+ /** Creates a data source that will sequentially supply the passed lists */
private fun testDataSourceSequence(data: List<List<String>>): DataSource.Factory<Int, String> {
return object : DataSource.Factory<Int, String>() {
var localData = data
+
override fun create(): DataSource<Int, String> {
val currentList = localData.first()
localData = localData.drop(1)
@@ -88,10 +84,11 @@
if (invalidInitialLoad) {
invalidInitialLoad = false
LoadResult.Invalid()
- } else when (params) {
- is LoadParams.Refresh -> loadInitial(params)
- else -> loadRange()
- }
+ } else
+ when (params) {
+ is LoadParams.Refresh -> loadInitial(params)
+ else -> loadRange()
+ }
}
}
@@ -127,18 +124,14 @@
@Test
fun basic() {
- val factory = testDataSourceSequence(
- listOf(
- listOf("a", "b"),
- listOf("c", "d")
- )
- )
+ val factory = testDataSourceSequence(listOf(listOf("a", "b"), listOf("c", "d")))
val scheduler = TestScheduler()
- val observable = RxPagedListBuilder(factory, 10)
- .setFetchScheduler(scheduler)
- .setNotifyScheduler(scheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory, 10)
+ .setFetchScheduler(scheduler)
+ .setNotifyScheduler(scheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
@@ -168,10 +161,11 @@
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val observable: Observable<PagedList<String>> = RxPagedListBuilder(factory, 10)
- .setFetchScheduler(fetchScheduler)
- .setNotifyScheduler(notifyScheduler)
- .buildObservable()
+ val observable: Observable<PagedList<String>> =
+ RxPagedListBuilder(factory, 10)
+ .setFetchScheduler(fetchScheduler)
+ .setNotifyScheduler(notifyScheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
observable.subscribe(observer)
@@ -199,10 +193,11 @@
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val observable = RxPagedListBuilder(factory::create, 2)
- .setFetchScheduler(fetchScheduler)
- .setNotifyScheduler(notifyScheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory::create, 2)
+ .setFetchScheduler(fetchScheduler)
+ .setNotifyScheduler(notifyScheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
observable.subscribe(observer)
@@ -225,22 +220,14 @@
}
}
initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertEquals(
- listOf(
- LoadStateEvent(REFRESH, Loading)
- ),
- loadStates
- )
+ assertEquals(listOf(LoadStateEvent(REFRESH, Loading)), loadStates)
fetchScheduler.triggerActions()
notifyScheduler.triggerActions()
observer.assertValueCount(1)
assertEquals(
- listOf(
- LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(REFRESH, Error(EXCEPTION))
- ),
+ listOf(LoadStateEvent(REFRESH, Loading), LoadStateEvent(REFRESH, Error(EXCEPTION))),
loadStates
)
@@ -272,10 +259,7 @@
LoadStateEvent(REFRESH, Loading),
LoadStateEvent(REFRESH, Error(EXCEPTION)),
LoadStateEvent(REFRESH, Loading),
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- )
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false))
),
loadStates
)
@@ -296,10 +280,11 @@
}
// this is essentially a direct scheduler so jobs are run immediately
val scheduler = Schedulers.from(DirectDispatcher.asExecutor())
- val observable = RxPagedListBuilder(factory, 2)
- .setFetchScheduler(scheduler)
- .setNotifyScheduler(scheduler)
- .buildObservable()
+ val observable =
+ RxPagedListBuilder(factory, 2)
+ .setFetchScheduler(scheduler)
+ .setNotifyScheduler(scheduler)
+ .buildObservable()
val observer = TestObserver<PagedList<String>>()
// subscribe triggers the PagingObservableOnSubscribe's invalidate() to create first
@@ -323,15 +308,17 @@
initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertThat(loadStates).containsExactly(
- // before first load() is called, REFRESH is set to loading, represents load
- // attempt on first pagingSource
- LoadStateEvent(REFRESH, Loading)
- )
+ assertThat(loadStates)
+ .containsExactly(
+ // before first load() is called, REFRESH is set to loading, represents load
+ // attempt on first pagingSource
+ LoadStateEvent(REFRESH, Loading)
+ )
// execute first load, represents load attempt on first paging source
//
- // using removeFirst().run() instead of executeAll(), because executeAll() + immediate schedulers
+ // using removeFirst().run() instead of executeAll(), because executeAll() + immediate
+ // schedulers
// result in first load + subsequent loads executing immediately and we won't be able to
// assert the pagedLists/loads incrementally
loadDispatcher.queue.removeFirst().run()
@@ -342,18 +329,16 @@
assertTrue(pagingSources[0].invalid)
assertThat(pagingSources.size).isEqualTo(2)
- assertThat(loadStates).containsExactly(
- // the first load attempt
- LoadStateEvent(REFRESH, Loading),
- // LoadResult.Invalid resets RERFRESH state
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ),
- // before second load() is called, REFRESH is set to loading, represents load
- // attempt on second pagingSource
- LoadStateEvent(REFRESH, Loading),
- )
+ assertThat(loadStates)
+ .containsExactly(
+ // the first load attempt
+ LoadStateEvent(REFRESH, Loading),
+ // LoadResult.Invalid resets RERFRESH state
+ LoadStateEvent(REFRESH, NotLoading(endOfPaginationReached = false)),
+ // before second load() is called, REFRESH is set to loading, represents load
+ // attempt on second pagingSource
+ LoadStateEvent(REFRESH, Loading),
+ )
// execute the load attempt on second pagingSource which succeeds
loadDispatcher.queue.removeFirst().run()
@@ -366,18 +351,19 @@
assertThat(secondPagedList).isInstanceOf(ContiguousPagedList::class.java)
secondPagedList.addWeakLoadStateListener(loadStateChangedCallback)
- assertThat(loadStates).containsExactly(
- LoadStateEvent(REFRESH, Loading), // first load
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ), // first load reset
- LoadStateEvent(REFRESH, Loading), // second load
- LoadStateEvent(
- REFRESH,
- NotLoading(endOfPaginationReached = false)
- ), // second load succeeds
- )
+ assertThat(loadStates)
+ .containsExactly(
+ LoadStateEvent(REFRESH, Loading), // first load
+ LoadStateEvent(
+ REFRESH,
+ NotLoading(endOfPaginationReached = false)
+ ), // first load reset
+ LoadStateEvent(REFRESH, Loading), // second load
+ LoadStateEvent(
+ REFRESH,
+ NotLoading(endOfPaginationReached = false)
+ ), // second load succeeds
+ )
}
@Test
@@ -389,18 +375,21 @@
}
val notifyScheduler = TestScheduler()
val fetchScheduler = TestScheduler()
- val rxPagedList = RxPagedListBuilder(
- pagingSourceFactory = pagingSourceFactory,
- pageSize = 10,
- ).apply {
- setNotifyScheduler(notifyScheduler)
- setFetchScheduler(fetchScheduler)
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pagingSourceFactory = pagingSourceFactory,
+ pageSize = 10,
+ )
+ .apply {
+ setNotifyScheduler(notifyScheduler)
+ setFetchScheduler(fetchScheduler)
+ }
+ .buildObservable()
fetchScheduler.triggerActions()
assertEquals(0, pagingSourcesCreated)
- rxPagedList.subscribe { }
+ rxPagedList.subscribe {}
assertEquals(0, pagingSourcesCreated)
@@ -410,13 +399,16 @@
@Test
fun initialValueAllowsGetDataSource() {
- val rxPagedList = RxPagedListBuilder(
- pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
- pageSize = 10,
- ).apply {
- setNotifyScheduler(Schedulers.from { it.run() })
- setFetchScheduler(Schedulers.from { it.run() })
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pagingSourceFactory = { TestPagingSource(loadDelay = 0) },
+ pageSize = 10,
+ )
+ .apply {
+ setNotifyScheduler(Schedulers.from { it.run() })
+ setFetchScheduler(Schedulers.from { it.run() })
+ }
+ .buildObservable()
// Calling .dataSource should never throw from the initial paged list.
rxPagedList.firstOrError().blockingGet().dataSource
@@ -428,21 +420,22 @@
var pagingSourcesCreated = 0
val pagingSourceFactory = {
when (pagingSourcesCreated++) {
- 0 -> TestPagingSource().apply {
- invalidate()
- }
+ 0 -> TestPagingSource().apply { invalidate() }
else -> TestPagingSource()
}
}
val testScheduler = TestScheduler()
- val rxPagedList = RxPagedListBuilder(
- pageSize = 10,
- pagingSourceFactory = pagingSourceFactory,
- ).apply {
- setNotifyScheduler(testScheduler)
- setFetchScheduler(testScheduler)
- }.buildObservable()
+ val rxPagedList =
+ RxPagedListBuilder(
+ pageSize = 10,
+ pagingSourceFactory = pagingSourceFactory,
+ )
+ .apply {
+ setNotifyScheduler(testScheduler)
+ setFetchScheduler(testScheduler)
+ }
+ .buildObservable()
rxPagedList.subscribe()
testScheduler.triggerActions()
diff --git a/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt b/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
index 77be184..529e9ea 100644
--- a/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
+++ b/paging/paging-rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
@@ -37,23 +37,23 @@
)
}
- private val pagingSource = object : PagingSource<Int, Int>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
- return loadInternal(params)
- }
-
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
-
- private val rxPagingSource = object : RxPagingSource<Int, Int>() {
- override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Int>> {
- return Single.create { emitter ->
- emitter.onSuccess(loadInternal(params))
+ private val pagingSource =
+ object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ return loadInternal(params)
}
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
}
- override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
- }
+ private val rxPagingSource =
+ object : RxPagingSource<Int, Int>() {
+ override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Int>> {
+ return Single.create { emitter -> emitter.onSuccess(loadInternal(params)) }
+ }
+
+ override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+ }
@Test
fun basic() = runBlocking {
diff --git a/paging/paging-rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt b/paging/paging-rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
index 1861c8f..a385e03 100644
--- a/paging/paging-rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
+++ b/paging/paging-rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
@@ -33,32 +33,34 @@
class RxRemoteMediatorTest {
@Test
fun initializeSingle() = runTest {
- val remoteMediator = object : RxRemoteMediator<Int, Int>() {
- override fun loadSingle(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): Single<MediatorResult> {
- fail("Unexpected call")
- }
+ val remoteMediator =
+ object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
- override fun initializeSingle(): Single<InitializeAction> {
- return Single.just(SKIP_INITIAL_REFRESH)
+ override fun initializeSingle(): Single<InitializeAction> {
+ return Single.just(SKIP_INITIAL_REFRESH)
+ }
}
- }
assertEquals(SKIP_INITIAL_REFRESH, remoteMediator.initialize())
}
@Test
fun initializeSingleDefault() = runTest {
- val remoteMediator = object : RxRemoteMediator<Int, Int>() {
- override fun loadSingle(
- loadType: LoadType,
- state: PagingState<Int, Int>
- ): Single<MediatorResult> {
- fail("Unexpected call")
+ val remoteMediator =
+ object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
}
- }
assertEquals(LAUNCH_INITIAL_REFRESH, remoteMediator.initialize())
}
diff --git a/paging/paging-rxjava3/src/test/java/androidx/paging/rxjava3/RxPagingDataTest.kt b/paging/paging-rxjava3/src/test/java/androidx/paging/rxjava3/RxPagingDataTest.kt
index 53cb3ee..20734d4 100644
--- a/paging/paging-rxjava3/src/test/java/androidx/paging/rxjava3/RxPagingDataTest.kt
+++ b/paging/paging-rxjava3/src/test/java/androidx/paging/rxjava3/RxPagingDataTest.kt
@@ -39,32 +39,38 @@
private val presenter = TestPagingDataPresenter<String>(testDispatcher)
@Test
- fun map() = testScope.runTest {
- val transformed = original.mapAsync { Single.just(it + it) }
- presenter.collectFrom(transformed)
- assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
- }
-
- @Test
- fun flatMap() = testScope.runTest {
- val transformed = original.flatMapAsync { Single.just(listOf(it, it) as Iterable<String>) }
- presenter.collectFrom(transformed)
- assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
- }
-
- @Test
- fun filter() = testScope.runTest {
- val filtered = original.filterAsync { Single.just(it != "b") }
- presenter.collectFrom(filtered)
- assertEquals(listOf("a", "c"), presenter.currentList)
- }
-
- @Test
- fun insertSeparators() = testScope.runTest {
- val separated = original.insertSeparatorsAsync { left, right ->
- if (left == null || right == null) Maybe.empty() else Maybe.just("|")
+ fun map() =
+ testScope.runTest {
+ val transformed = original.mapAsync { Single.just(it + it) }
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("aa", "bb", "cc"), presenter.currentList)
}
- presenter.collectFrom(separated)
- assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
- }
+
+ @Test
+ fun flatMap() =
+ testScope.runTest {
+ val transformed =
+ original.flatMapAsync { Single.just(listOf(it, it) as Iterable<String>) }
+ presenter.collectFrom(transformed)
+ assertEquals(listOf("a", "a", "b", "b", "c", "c"), presenter.currentList)
+ }
+
+ @Test
+ fun filter() =
+ testScope.runTest {
+ val filtered = original.filterAsync { Single.just(it != "b") }
+ presenter.collectFrom(filtered)
+ assertEquals(listOf("a", "c"), presenter.currentList)
+ }
+
+ @Test
+ fun insertSeparators() =
+ testScope.runTest {
+ val separated =
+ original.insertSeparatorsAsync { left, right ->
+ if (left == null || right == null) Maybe.empty() else Maybe.just("|")
+ }
+ presenter.collectFrom(separated)
+ assertEquals(listOf("a", "|", "b", "|", "c"), presenter.currentList)
+ }
}
diff --git a/paging/paging-testing/src/commonJvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt b/paging/paging-testing/src/commonJvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt
index b9532be..468122d 100644
--- a/paging/paging-testing/src/commonJvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt
+++ b/paging/paging-testing/src/commonJvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
@file:Suppress("ACTUAL_WITHOUT_EXPECT") // https://youtrack.jetbrains.com/issue/KT-37316
+
package androidx.paging.testing.internal
internal actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
index 0f7fd64..25e4cf3 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
@@ -24,48 +24,41 @@
import androidx.paging.PagingSource.LoadResult
/**
- * An interface to implement the error recovery strategy when [PagingSource]
- * returns a [LoadResult.Error].
+ * An interface to implement the error recovery strategy when [PagingSource] returns a
+ * [LoadResult.Error].
*/
@VisibleForTesting
public fun interface LoadErrorHandler {
/**
- * The lambda that should return an [ErrorRecovery] given the [CombinedLoadStates]
- * indicating which [LoadState] contains the [LoadState.Error].
+ * The lambda that should return an [ErrorRecovery] given the [CombinedLoadStates] indicating
+ * which [LoadState] contains the [LoadState.Error].
*
- * Sample use case:
- * val onError = LoadErrorHandler { combinedLoadStates ->
- * if (combinedLoadStates.refresh is LoadResult.Error) {
- * ErrorRecovery.RETRY
- * } else {
- * ErrorRecovery.THROW
- * }
- * }
+ * Sample use case: val onError = LoadErrorHandler { combinedLoadStates -> if
+ * (combinedLoadStates.refresh is LoadResult.Error) { ErrorRecovery.RETRY } else {
+ * ErrorRecovery.THROW } }
*/
public fun onError(combinedLoadStates: CombinedLoadStates): ErrorRecovery
}
/**
- * The method of recovery when [PagingSource] returns [LoadResult.Error]. The error
- * is indicated when [PagingDataPresenter.loadStateFlow] emits a [CombinedLoadStates] where one or
- * more of the [LoadState] is [LoadState.Error].
+ * The method of recovery when [PagingSource] returns [LoadResult.Error]. The error is indicated
+ * when [PagingDataPresenter.loadStateFlow] emits a [CombinedLoadStates] where one or more of the
+ * [LoadState] is [LoadState.Error].
*/
@VisibleForTesting
public enum class ErrorRecovery {
/**
- * Rethrow the original [Throwable][LoadState.Error.error] that was caught when loading from
- * the data source.
+ * Rethrow the original [Throwable][LoadState.Error.error] that was caught when loading from the
+ * data source.
*/
THROW,
/**
- * Retry the failed load. This does not guarantee a successful load as the data source
- * may still return an error.
+ * Retry the failed load. This does not guarantee a successful load as the data source may still
+ * return an error.
*/
RETRY,
- /**
- * Returns a snapshot with any data that has been loaded up till the point of error.
- */
+ /** Returns a snapshot with any data that has been loaded up till the point of error. */
RETURN_CURRENT_SNAPSHOT,
}
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
index b1af845..ba14c87 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
@@ -41,70 +41,69 @@
import kotlinx.coroutines.launch
/**
- * Runs the [SnapshotLoader] load operations that are passed in and returns a List of data
- * that would be presented to the UI after all load operations are complete.
+ * Runs the [SnapshotLoader] load operations that are passed in and returns a List of data that
+ * would be presented to the UI after all load operations are complete.
*
* @param onError The error recovery strategy when PagingSource returns LoadResult.Error. A lambda
- * that returns an [ErrorRecovery] value. The default strategy is [ErrorRecovery.THROW].
- *
+ * that returns an [ErrorRecovery] value. The default strategy is [ErrorRecovery.THROW].
* @param loadOperations The block containing [SnapshotLoader] load operations.
*/
@VisibleForTesting
public suspend fun <Value : Any> Flow<PagingData<Value>>.asSnapshot(
onError: LoadErrorHandler = LoadErrorHandler { THROW },
- loadOperations: suspend SnapshotLoader<Value>.() -> @JvmSuppressWildcards Unit = { }
+ loadOperations: suspend SnapshotLoader<Value>.() -> @JvmSuppressWildcards Unit = {}
): @JvmSuppressWildcards List<Value> = coroutineScope {
-
lateinit var loader: SnapshotLoader<Value>
// PagingDataPresenter will collect from coroutineContext instead of main dispatcher
- val presenter = object : CompletablePagingDataPresenter<Value>(coroutineContext) {
- override suspend fun presentPagingDataEvent(event: PagingDataEvent<Value>) {
- if (event is PagingDataEvent.Refresh) {
- /**
- * On new generation, SnapshotLoader needs the latest [ItemSnapshotList]
- * state so that it can initialize lastAccessedIndex to prepend/append from onwards.
- *
- * This initial lastAccessedIndex is necessary because initial load
- * key may not be 0, for example when [Pager].initialKey != 0. We don't know which
- * items are immediately displayed so we can only best-effort estimate that the middle
- * item has been presented.
- *
- * Therefore we calculate the actual index based on
- * [ItemSnapshotList.placeholdersBefore] + [1/2 initial load size].
- *
- * Any subsequent SnapshotLoader loads are based on the index tracked by
- * [SnapshotLoader] internally.
- */
- val lastLoadedIndex = event.newList.placeholdersBefore +
- (event.newList.dataCount / 2)
- loader.onDataSetChanged(
- loader.generations.value,
- LoaderCallback(LoadType.REFRESH, lastLoadedIndex, event.newList.size),
- this@coroutineScope
- )
- }
- /**
- * We only care about callbacks for prepend inserts so that we can adjust
- * the lastAccessedIndex. For more detail, refer to docs on method
- * #computeIndexOffset in SnapshotLoader.
- */
- if (event is PagingDataEvent.Prepend) {
- val insertSize = event.inserted.size
- val placeholdersChangedCount = minOf(event.oldPlaceholdersBefore, insertSize)
- val itemsInsertedCount = insertSize - placeholdersChangedCount
- val itemsInsertedPos = 0
-
- if (itemsInsertedCount > 0) {
+ val presenter =
+ object : CompletablePagingDataPresenter<Value>(coroutineContext) {
+ override suspend fun presentPagingDataEvent(event: PagingDataEvent<Value>) {
+ if (event is PagingDataEvent.Refresh) {
+ /**
+ * On new generation, SnapshotLoader needs the latest [ItemSnapshotList] state
+ * so that it can initialize lastAccessedIndex to prepend/append from onwards.
+ *
+ * This initial lastAccessedIndex is necessary because initial load key may not
+ * be 0, for example when [Pager].initialKey != 0. We don't know which items are
+ * immediately displayed so we can only best-effort estimate that the middle
+ * item has been presented.
+ *
+ * Therefore we calculate the actual index based on
+ * [ItemSnapshotList.placeholdersBefore] + [1/2 initial load size].
+ *
+ * Any subsequent SnapshotLoader loads are based on the index tracked by
+ * [SnapshotLoader] internally.
+ */
+ val lastLoadedIndex =
+ event.newList.placeholdersBefore + (event.newList.dataCount / 2)
loader.onDataSetChanged(
loader.generations.value,
- LoaderCallback(LoadType.PREPEND, itemsInsertedPos, itemsInsertedCount),
- null
+ LoaderCallback(LoadType.REFRESH, lastLoadedIndex, event.newList.size),
+ this@coroutineScope
)
}
+ /**
+ * We only care about callbacks for prepend inserts so that we can adjust the
+ * lastAccessedIndex. For more detail, refer to docs on method #computeIndexOffset
+ * in SnapshotLoader.
+ */
+ if (event is PagingDataEvent.Prepend) {
+ val insertSize = event.inserted.size
+ val placeholdersChangedCount = minOf(event.oldPlaceholdersBefore, insertSize)
+ val itemsInsertedCount = insertSize - placeholdersChangedCount
+ val itemsInsertedPos = 0
+
+ if (itemsInsertedCount > 0) {
+ loader.onDataSetChanged(
+ loader.generations.value,
+ LoaderCallback(LoadType.PREPEND, itemsInsertedPos, itemsInsertedCount),
+ null
+ )
+ }
+ }
}
}
- }
loader = SnapshotLoader(presenter, onError)
@@ -112,7 +111,7 @@
* Launches collection on this [Pager.flow].
*
* The collection job is cancelled automatically after [loadOperations] completes.
- */
+ */
val collectPagingData = launch {
[email protected] {
incrementGeneration(loader)
@@ -125,8 +124,8 @@
* Runs the input [loadOperations].
*
* Awaits for initial refresh to complete before invoking [loadOperations]. Automatically
- * cancels the collection on this [Pager.flow] after [loadOperations] completes and Paging
- * is idle.
+ * cancels the collection on this [Pager.flow] after [loadOperations] completes and Paging is
+ * idle.
*/
try {
presenter.awaitNotLoading(onError)
@@ -152,34 +151,34 @@
*/
val hasCompleted = MutableStateFlow(false)
- val completableLoadStateFlow = loadStateFlow.combine(
- hasCompleted
- ) { loadStates, hasCompleted ->
- if (hasCompleted) {
- CombinedLoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true),
- source = LoadStates(
+ val completableLoadStateFlow =
+ loadStateFlow.combine(hasCompleted) { loadStates, hasCompleted ->
+ if (hasCompleted) {
+ CombinedLoadStates(
refresh = LoadState.NotLoading(true),
prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
+ append = LoadState.NotLoading(true),
+ source =
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
)
- )
- } else {
- loadStates
+ } else {
+ loadStates
+ }
}
- }
}
/**
* Awaits until both source and mediator states are NotLoading. We do not care about the state of
- * endOfPaginationReached. Source and mediator states need to be checked individually because
- * the aggregated LoadStates can reflect `NotLoading` when source states are `Loading`.
+ * endOfPaginationReached. Source and mediator states need to be checked individually because the
+ * aggregated LoadStates can reflect `NotLoading` when source states are `Loading`.
*
* We debounce(1ms) to prevent returning too early if this collected a `NotLoading` from the
- * previous load. Without a way to determine whether the `NotLoading` it collected was from
- * a previous operation or current operation, we debounce 1ms to allow collection on a potential
+ * previous load. Without a way to determine whether the `NotLoading` it collected was from a
+ * previous operation or current operation, we debounce 1ms to allow collection on a potential
* incoming `Loading` state.
*/
@OptIn(kotlinx.coroutines.FlowPreview::class)
@@ -204,6 +203,7 @@
RETURN_CURRENT_SNAPSHOT -> throw ReturnSnapshotStub()
}
}
+
private class ReturnSnapshotStub : Exception()
private fun CombinedLoadStates.getErrorState(): LoadState.Error {
@@ -219,8 +219,6 @@
private fun <Value : Any> incrementGeneration(loader: SnapshotLoader<Value>) {
val currGen = loader.generations.value
if (currGen.id == loader.generations.value.id) {
- loader.generations.value = Generation(
- id = currGen.id + 1
- )
+ loader.generations.value = Generation(id = currGen.id + 1)
}
}
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
index ecb52eb..3376d7c 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
@@ -41,7 +41,8 @@
* [PagingDataPresenter] operations.
*/
@VisibleForTesting
-public class SnapshotLoader<Value : Any> internal constructor(
+public class SnapshotLoader<Value : Any>
+internal constructor(
private val presenter: CompletablePagingDataPresenter<Value>,
private val errorHandler: LoadErrorHandler,
) {
@@ -50,8 +51,8 @@
/**
* Refresh the data that is presented on the UI.
*
- * [refresh] triggers a new generation of [PagingData] / [PagingSource]
- * to represent an updated snapshot of the backing dataset.
+ * [refresh] triggers a new generation of [PagingData] / [PagingSource] to represent an updated
+ * snapshot of the backing dataset.
*
* This fake paging operation mimics UI-driven refresh signals such as swipe-to-refresh.
*/
@@ -62,21 +63,20 @@
}
/**
- * Imitates scrolling down paged items, [appending][APPEND] data until the given
- * predicate returns false.
+ * Imitates scrolling down paged items, [appending][APPEND] data until the given predicate
+ * returns false.
*
- * Note: This API loads an item before passing it into the predicate. This means the
- * loaded pages may include the page which contains the item that does not match the
- * predicate. For example, if pageSize = 2, the predicate
- * {item: Int -> item < 3 } will return items [[1, 2],[3, 4]] where [3, 4] is the page
- * containing the boundary item[3] not matching the predicate.
+ * Note: This API loads an item before passing it into the predicate. This means the loaded
+ * pages may include the page which contains the item that does not match the predicate. For
+ * example, if pageSize = 2, the predicate {item: Int -> item < 3 } will return items [[1,
+ * 2],[3, 4]] where [3, 4] is the page containing the boundary item[3] not matching the
+ * predicate.
*
* The loaded pages are also dependent on [PagingConfig] settings such as
* [PagingConfig.prefetchDistance]:
- * - if `prefetchDistance` > 0, the resulting appends will include prefetched items.
- * For example, if pageSize = 2 and prefetchDistance = 2, the predicate
- * {item: Int -> item < 3 } will load items [[1, 2], [3, 4], [5, 6]] where [5, 6] is the
- * prefetched page.
+ * - if `prefetchDistance` > 0, the resulting appends will include prefetched items. For
+ * example, if pageSize = 2 and prefetchDistance = 2, the predicate {item: Int -> item < 3 }
+ * will load items [[1, 2], [3, 4], [5, 6]] where [5, 6] is the prefetched page.
*
* @param [predicate] the predicate to match (return true) to continue append scrolls
*/
@@ -89,21 +89,21 @@
}
/**
- * Imitates scrolling up paged items, [prepending][PREPEND] data until the given
- * predicate returns false.
+ * Imitates scrolling up paged items, [prepending][PREPEND] data until the given predicate
+ * returns false.
*
- * Note: This API loads an item before passing it into the predicate. This means the
- * loaded pages may include the page which contains the item that does not match the
- * predicate. For example, if pageSize = 2, initialKey = 3, the predicate
- * {item: Int -> item >= 3 } will return items [[1, 2],[3, 4]] where [1, 2] is the page
- * containing the boundary item[2] not matching the predicate.
+ * Note: This API loads an item before passing it into the predicate. This means the loaded
+ * pages may include the page which contains the item that does not match the predicate. For
+ * example, if pageSize = 2, initialKey = 3, the predicate {item: Int -> item >= 3 } will return
+ * items [[1, 2],[3, 4]] where [1, 2] is the page containing the boundary item[2] not matching
+ * the predicate.
*
* The loaded pages are also dependent on [PagingConfig] settings such as
* [PagingConfig.prefetchDistance]:
- * - if `prefetchDistance` > 0, the resulting prepends will include prefetched items.
- * For example, if pageSize = 2, initialKey = 3, and prefetchDistance = 2, the predicate
- * {item: Int -> item > 4 } will load items [[1, 2], [3, 4], [5, 6]] where both [1,2] and
- * [5, 6] are the prefetched pages.
+ * - if `prefetchDistance` > 0, the resulting prepends will include prefetched items. For
+ * example, if pageSize = 2, initialKey = 3, and prefetchDistance = 2, the predicate {item:
+ * Int -> item > 4 } will load items [[1, 2], [3, 4], [5, 6]] where both [1,2] and [5, 6] are
+ * the prefetched pages.
*
* @param [predicate] the predicate to match (return true) to continue prepend scrolls
*/
@@ -133,25 +133,24 @@
* through.
*
* The scroll direction (prepend or append) is dependent on current index and target index. In
- * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger
- * index triggers [APPEND].
+ * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger index
+ * triggers [APPEND].
*
* When [PagingConfig.enablePlaceholders] is false, the [index] is scoped within currently
* loaded items. For example, in a list of items(0-20) with currently loaded items(10-15),
* index[0] = item(10), index[4] = item(15).
*
- * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders]
- * is false:
- * 1. For prepends, it supports negative indices for as long as there are still available
- * data to load from. For example, take a list of items(0-20), pageSize = 1, with currently
- * loaded items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and
- * update index[0] = item(6).
+ * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders] is
+ * false:
+ * 1. For prepends, it supports negative indices for as long as there are still available data
+ * to load from. For example, take a list of items(0-20), pageSize = 1, with currently loaded
+ * items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and update
+ * index[0] = item(6).
* 2. For appends, it supports indices >= loadedDataSize. For example, take a list of
- * items(0-20), pageSize = 1, with currently loaded items(10-15). With
- * index[4] = item(15), a `scrollTo(7)` will scroll to item(18) and update
- * index[7] = item(18).
- * Note that both examples does not account for prefetches.
-
+ * items(0-20), pageSize = 1, with currently loaded items(10-15). With index[4] = item(15), a
+ * `scrollTo(7)` will scroll to item(18) and update index[7] = item(18). Note that both
+ * examples does not account for prefetches.
+ *
* The [index] accounts for separators/headers/footers where each one of those consumes one
* scrolled index.
*
@@ -159,9 +158,8 @@
* distance if there are no more data to load from.
*
* @param [index] The target index to scroll to
- *
- * @see [flingTo] for faking a scroll that continues scrolling without waiting for items to
- * be loaded in. Supports jumping.
+ * @see [flingTo] for faking a scroll that continues scrolling without waiting for items to be
+ * loaded in. Supports jumping.
*/
public suspend fun scrollTo(index: Int): @JvmSuppressWildcards Unit {
presenter.awaitNotLoading(errorHandler)
@@ -172,11 +170,11 @@
/**
* Scrolls from current index to targeted [index].
*
- * Internally this method scrolls until it fulfills requested index
- * differential (Math.abs(requested index - current index)) rather than scrolling
- * to the exact requested index. This is because item indices can shift depending on scroll
- * direction and placeholders. Therefore we try to fulfill the expected amount of scrolling
- * rather than the actual requested index.
+ * Internally this method scrolls until it fulfills requested index differential
+ * (Math.abs(requested index - current index)) rather than scrolling to the exact requested
+ * index. This is because item indices can shift depending on scroll direction and placeholders.
+ * Therefore we try to fulfill the expected amount of scrolling rather than the actual requested
+ * index.
*/
private suspend fun appendOrPrependScrollTo(index: Int) {
val startIndex = generations.value.lastAccessedIndex.get()
@@ -186,35 +184,33 @@
}
/**
- * Imitates flinging from current index to the target index. It will continue scrolling
- * even as data is being loaded in. Returns all available data that has been scrolled
- * through.
+ * Imitates flinging from current index to the target index. It will continue scrolling even as
+ * data is being loaded in. Returns all available data that has been scrolled through.
*
* The scroll direction (prepend or append) is dependent on current index and target index. In
- * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger
- * index triggers [APPEND].
+ * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger index
+ * triggers [APPEND].
*
* This function will scroll into placeholders. This means jumping is supported when
- * [PagingConfig.enablePlaceholders] is true and the amount of placeholders traversed
- * has reached [PagingConfig.jumpThreshold]. Jumping is disabled when
+ * [PagingConfig.enablePlaceholders] is true and the amount of placeholders traversed has
+ * reached [PagingConfig.jumpThreshold]. Jumping is disabled when
* [PagingConfig.enablePlaceholders] is false.
*
* When [PagingConfig.enablePlaceholders] is false, the [index] is scoped within currently
* loaded items. For example, in a list of items(0-20) with currently loaded items(10-15),
* index[0] = item(10), index[4] = item(15).
*
- * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders]
- * is false:
- * 1. For prepends, it supports negative indices for as long as there are still available
- * data to load from. For example, take a list of items(0-20), pageSize = 1, with currently
- * loaded items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and
- * update index[0] = item(6).
+ * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders] is
+ * false:
+ * 1. For prepends, it supports negative indices for as long as there are still available data
+ * to load from. For example, take a list of items(0-20), pageSize = 1, with currently loaded
+ * items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and update
+ * index[0] = item(6).
* 2. For appends, it supports indices >= loadedDataSize. For example, take a list of
- * items(0-20), pageSize = 1, with currently loaded items(10-15). With
- * index[4] = item(15), a `scrollTo(7)` will scroll to item(18) and update
- * index[7] = item(18).
- * Note that both examples does not account for prefetches.
-
+ * items(0-20), pageSize = 1, with currently loaded items(10-15). With index[4] = item(15), a
+ * `scrollTo(7)` will scroll to item(18) and update index[7] = item(18). Note that both
+ * examples does not account for prefetches.
+ *
* The [index] accounts for separators/headers/footers where each one of those consumes one
* scrolled index.
*
@@ -222,9 +218,8 @@
* distance if there are no more data to load from.
*
* @param [index] The target index to scroll to
- *
- * @see [scrollTo] for faking scrolls that awaits for placeholders to load before continuing
- * to scroll.
+ * @see [scrollTo] for faking scrolls that awaits for placeholders to load before continuing to
+ * scroll.
*/
public suspend fun flingTo(index: Int): @JvmSuppressWildcards Unit {
presenter.awaitNotLoading(errorHandler)
@@ -233,8 +228,8 @@
}
/**
- * We start scrolling from startIndex +/- 1 so we don't accidentally trigger
- * a prefetch on the opposite direction.
+ * We start scrolling from startIndex +/- 1 so we don't accidentally trigger a prefetch on the
+ * opposite direction.
*/
private suspend fun appendOrPrependFlingTo(index: Int) {
val startIndex = generations.value.lastAccessedIndex.get()
@@ -273,8 +268,8 @@
/**
* Append flings to target index.
*
- * If target index is beyond [PagingDataPresenter.size] - 1, from index(presenter.size) and onwards,
- * it will normal scroll until it fulfills remaining distance.
+ * If target index is beyond [PagingDataPresenter.size] - 1, from index(presenter.size) and
+ * onwards, it will normal scroll until it fulfills remaining distance.
*/
private suspend fun appendFlingTo(startIndex: Int, index: Int) {
var lastAccessedIndex = startIndex
@@ -294,15 +289,15 @@
}
/**
- * Delegated work from [flingTo] that is responsible for scrolling to indices that is
- * beyond the range of [0 to presenter.size-1].
+ * Delegated work from [flingTo] that is responsible for scrolling to indices that is beyond the
+ * range of [0 to presenter.size-1].
*
- * When [PagingConfig.enablePlaceholders] is true, this function is no-op because
- * there is no more data to load from.
+ * When [PagingConfig.enablePlaceholders] is true, this function is no-op because there is no
+ * more data to load from.
*
* When [PagingConfig.enablePlaceholders] is false, its delegated work to [awaitScroll]
- * essentially loops (trigger next page --> await for next page) until
- * it fulfills remaining (out of bounds) requested scroll distance.
+ * essentially loops (trigger next page --> await for next page) until it fulfills remaining
+ * (out of bounds) requested scroll distance.
*/
private suspend fun flingToOutOfBounds(
loadType: LoadType,
@@ -319,9 +314,7 @@
}
private suspend fun awaitScroll(loadType: LoadType, scrollCount: Int) {
- repeat(scrollCount) {
- awaitNextItem(loadType) ?: return
- }
+ repeat(scrollCount) { awaitNextItem(loadType) ?: return }
}
/**
@@ -375,29 +368,32 @@
var offsetIndex = index
// awaits for the item to be loaded
- return generations.map { generation ->
- // reset callbackState to null so it doesn't get applied on the next load
- val callbackState = generation.callbackState.getAndSet(null)
- // offsetIndex accounts for items prepended when placeholders are disabled. This
- // is necessary because the new items shift the position of existing items, and
- // the index no longer tracks the correct item.
- offsetIndex += callbackState?.computeIndexOffset() ?: 0
- presenter.peek(offsetIndex)
- }.filterNotNull().first() to offsetIndex
+ return generations
+ .map { generation ->
+ // reset callbackState to null so it doesn't get applied on the next load
+ val callbackState = generation.callbackState.getAndSet(null)
+ // offsetIndex accounts for items prepended when placeholders are disabled. This
+ // is necessary because the new items shift the position of existing items, and
+ // the index no longer tracks the correct item.
+ offsetIndex += callbackState?.computeIndexOffset() ?: 0
+ presenter.peek(offsetIndex)
+ }
+ .filterNotNull()
+ .first() to offsetIndex
}
/**
* Computes the offset to add to the index when loading items from presenter.
*
- * The purpose of this is to address shifted item positions when new items are prepended
- * with placeholders disabled. For example, loaded items(10-12) in the PlaceholderPaddedList
- * would have item(12) at presenter[2]. If we prefetched items(7-9), item(12) would now be in
+ * The purpose of this is to address shifted item positions when new items are prepended with
+ * placeholders disabled. For example, loaded items(10-12) in the PlaceholderPaddedList would
+ * have item(12) at presenter[2]. If we prefetched items(7-9), item(12) would now be in
* presenter[5].
*
- * Without the offset, [PREPEND] operations would call presenter[1] to load next item(11)
- * which would now yield item(8) instead of item(11). The offset would add the
- * inserted count to the next load index such that after prepending 3 new items(7-9), the next
- * [PREPEND] operation would call presenter[1+3 = 4] to properly load next item(11).
+ * Without the offset, [PREPEND] operations would call presenter[1] to load next item(11) which
+ * would now yield item(8) instead of item(11). The offset would add the inserted count to the
+ * next load index such that after prepending 3 new items(7-9), the next [PREPEND] operation
+ * would call presenter[1+3 = 4] to properly load next item(11).
*
* This method is essentially no-op unless the callback meets three conditions:
* - the [LoaderCallback.loadType] is [LoadType.PREPEND]
@@ -412,9 +408,7 @@
generations.value.lastAccessedIndex.set(index)
}
- /**
- * The callback to be invoked when presenter emits a new PagingDataEvent.
- */
+ /** The callback to be invoked when presenter emits a new PagingDataEvent. */
internal fun onDataSetChanged(
gen: Generation,
callback: LoaderCallback,
@@ -437,9 +431,8 @@
}
}
if (loadType == LoadType.PREPEND) {
- generations.value = gen.copy(
- callbackState = currGen.callbackState.apply { set(callback) }
- )
+ generations.value =
+ gen.copy(callbackState = currGen.callbackState.apply { set(callback) })
}
}
}
@@ -459,9 +452,7 @@
*/
val callbackState: AtomicRef<LoaderCallback?> = AtomicRef(null),
- /**
- * Tracks the last accessed(peeked) index on the presenter for this generation
- */
+ /** Tracks the last accessed(peeked) index on the presenter for this generation */
var lastAccessedIndex: AtomicInt = AtomicInt(0)
)
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
index 36496ca..0b9c84b 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
@@ -25,13 +25,12 @@
import androidx.paging.PagingState
/**
- * An implementation of [PagingSource] that loads data from a static [dataList]. The source
- * of data is expected to be immutable. This [PagingSource] should be be invalidated
- * externally whenever the [dataList] passed to this [PagingSource] becomes obsolete.
+ * An implementation of [PagingSource] that loads data from a static [dataList]. The source of data
+ * is expected to be immutable. This [PagingSource] should be be invalidated externally whenever the
+ * [dataList] passed to this [PagingSource] becomes obsolete.
*/
-internal class StaticListPagingSource<Value : Any>(
- private val dataList: List<Value>
-) : PagingSource<Int, Value>() {
+internal class StaticListPagingSource<Value : Any>(private val dataList: List<Value>) :
+ PagingSource<Int, Value>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Value> {
val key = params.key ?: 0
@@ -50,19 +49,18 @@
/**
* Returns the index to start loading from the [dataList] for each [load] call.
*
- * Prepend: The [key] represents the index before the first loaded Page's first index, i.e.
- * if first loaded page contains items in index[25, 26, 27], then [key] = 24. The `startIndex`
- * is offset negatively by [LoadParams.loadSize]. For example, if [key] = 24 and loadSize = 5,
- * then the startIndex = 19, such that items in index[20, 21, 22, 23, 24] are loaded.
+ * Prepend: The [key] represents the index before the first loaded Page's first index, i.e. if
+ * first loaded page contains items in index[25, 26, 27], then [key] = 24. The `startIndex` is
+ * offset negatively by [LoadParams.loadSize]. For example, if [key] = 24 and loadSize = 5, then
+ * the startIndex = 19, such that items in index[20, 21, 22, 23, 24] are loaded.
*
- * Refresh: The [key] may derive from either [getRefreshKey] or from the `initialKey` passed
- * in to [Pager]. If the [key] is larger than [dataList].lastIndex (i.e. an initialKey
- * that is out of bounds), the `startIndex` will be offset negatively from the lastIndex
- * by [LoadParams.loadSize] such that it will start loading from the last page.
- * For example if index[0 - 49] is available with [key] = 55 and loadSize = 5,
- * the `startIndex` will be offset to 45 such that items in index[45, 46, 47, 48, 49] are
- * loaded. The largest possible `startIndex` that can be returned for
- * Refresh is [dataList].lastIndex.
+ * Refresh: The [key] may derive from either [getRefreshKey] or from the `initialKey` passed in
+ * to [Pager]. If the [key] is larger than [dataList].lastIndex (i.e. an initialKey that is out
+ * of bounds), the `startIndex` will be offset negatively from the lastIndex by
+ * [LoadParams.loadSize] such that it will start loading from the last page. For example if
+ * index[0 - 49] is available with [key] = 55 and loadSize = 5, the `startIndex` will be offset
+ * to 45 such that items in index[45, 46, 47, 48, 49] are loaded. The largest possible
+ * `startIndex` that can be returned for Refresh is [dataList].lastIndex.
*
* Negative startIndices are clipped to 0.
*/
@@ -82,17 +80,17 @@
/**
* Returns the index to stop loading from the [dataList] for each [load] call.
*
- * Prepend: The [key] represents the index before the first loaded Page's first index, i.e.
- * if first loaded page contains items in index[25, 26, 27], then [key] = 24. If loadSize
- * exceeds available data to load, i.e. loadSize = 5 with only items in index[0, 1, 2] are
- * available, the `endIndex` is clipped to the value of [key]. Reusing example with [key] = 2
- * and items[0, 1, 2] available, the `startIndex` = 0 and the `endIndex` of `4` is clipped
- * to `2` such that only items [0, 1, 2] are loaded.
+ * Prepend: The [key] represents the index before the first loaded Page's first index, i.e. if
+ * first loaded page contains items in index[25, 26, 27], then [key] = 24. If loadSize exceeds
+ * available data to load, i.e. loadSize = 5 with only items in index[0, 1, 2] are available,
+ * the `endIndex` is clipped to the value of [key]. Reusing example with [key] = 2 and items[0,
+ * 1, 2] available, the `startIndex` = 0 and the `endIndex` of `4` is clipped to `2` such that
+ * only items [0, 1, 2] are loaded.
*
- * Refresh: The [key] may derive from either [getRefreshKey] or from the `initialKey` passed
- * in to [Pager]. As long as the `endIndex` is within bounds of [dataList] indices, `endIndex`
- * will load the maximum amount of available data as requested by [LoadParams.loadSize]. If
- * the [key] is out of bounds, it will be clipped to a maximum value of [dataList].lastIndex.
+ * Refresh: The [key] may derive from either [getRefreshKey] or from the `initialKey` passed in
+ * to [Pager]. As long as the `endIndex` is within bounds of [dataList] indices, `endIndex` will
+ * load the maximum amount of available data as requested by [LoadParams.loadSize]. If the [key]
+ * is out of bounds, it will be clipped to a maximum value of [dataList].lastIndex.
*/
private fun computeIndexEnd(params: LoadParams<Int>, key: Int, startIndex: Int): Int {
val defaultOffset = startIndex + params.loadSize - 1
@@ -107,11 +105,11 @@
*
* It is unknown whether anchorPosition represents the item at the top of the screen or item at
* the bottom of the screen. To ensure the number of items loaded is enough to fill up the
- * screen, half of loadSize is loaded before the anchorPosition and the other half is
- * loaded after the anchorPosition -- anchorPosition becomes the middle item.
+ * screen, half of loadSize is loaded before the anchorPosition and the other half is loaded
+ * after the anchorPosition -- anchorPosition becomes the middle item.
*
* Negative refreshKeys are clipped to 0.
- */
+ */
override fun getRefreshKey(state: PagingState<Int, Value>): Int? {
return when (val anchorPosition = state.anchorPosition) {
null -> null
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
index fc5ea4d..8458def 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
@@ -34,9 +34,9 @@
* should be reused within the lifetime of a ViewModel.
*
* Extension method on a [Flow] of list that represents the data source, with each static list
- * representing a generation of data from which a [PagingSource] will load from. With every
- * emission to the flow, the current [PagingSource] will be invalidated, thereby triggering
- * a new generation of Paged data.
+ * representing a generation of data from which a [PagingSource] will load from. With every emission
+ * to the flow, the current [PagingSource] will be invalidated, thereby triggering a new generation
+ * of Paged data.
*
* Supports multiple factories and thus multiple collection on the same flow.
*
@@ -52,8 +52,7 @@
val factory = InvalidatingPagingSourceFactory {
val dataSource = data ?: emptyList()
- @Suppress("UNCHECKED_CAST")
- StaticListPagingSource(dataSource)
+ @Suppress("UNCHECKED_CAST") StaticListPagingSource(dataSource)
}
coroutineScope.launch {
@@ -74,9 +73,11 @@
*
* Extension method on a [List] of data from which a [PagingSource] will load from. While this
* factory supports multi-generational operations such as [REFRESH], it does not support updating
- * the data source. This means any PagingSources generated by the same factory will load from
- * the exact same list of data.
+ * the data source. This means any PagingSources generated by the same factory will load from the
+ * exact same list of data.
*/
@VisibleForTesting
public fun <Value : Any> List<Value>.asPagingSourceFactory(): PagingSourceFactory<Int, Value> =
- PagingSourceFactory { StaticListPagingSource(this) }
+ PagingSourceFactory {
+ StaticListPagingSource(this)
+ }
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
index 6cca56c..2c2918f 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
@@ -36,12 +36,12 @@
/**
* A fake [Pager] class to simulate how a real Pager and UI would load data from a PagingSource.
*
- * As Paging's first load is always of type [LoadType.REFRESH], the first load operation of
- * the [TestPager] must be a call to [refresh].
+ * As Paging's first load is always of type [LoadType.REFRESH], the first load operation of the
+ * [TestPager] must be a call to [refresh].
*
* This class only supports loads from a single instance of PagingSource. To simulate
- * multi-generational Paging behavior, you must create a new [TestPager] by supplying a
- * new instance of [PagingSource].
+ * multi-generational Paging behavior, you must create a new [TestPager] by supplying a new instance
+ * of [PagingSource].
*
* @param config the [PagingConfig] to configure this TestPager's loading behavior.
* @param pagingSource the [PagingSource] to load data from.
@@ -62,29 +62,30 @@
*
* If initialKey != null, refresh will start loading from the supplied key.
*
- * Since Paging's first load is always of [LoadType.REFRESH], this method must be the very
- * first load operation to be called on the TestPager before either [append] or [prepend]
- * can be called. However, other non-loading operations can still be invoked. For example,
- * you can call [getLastLoadedPage] before any load operations.
+ * Since Paging's first load is always of [LoadType.REFRESH], this method must be the very first
+ * load operation to be called on the TestPager before either [append] or [prepend] can be
+ * called. However, other non-loading operations can still be invoked. For example, you can call
+ * [getLastLoadedPage] before any load operations.
*
* Returns the LoadResult upon refresh on the [PagingSource].
*
* @param initialKey the [Key] to start loading data from on initial refresh.
- *
* @throws IllegalStateException TestPager does not support multi-generational paging behavior.
- * As such, multiple calls to refresh() on this TestPager is illegal. The [PagingSource] passed
- * in to this [TestPager] will also be invalidated to prevent reuse of this pager for loads.
- * However, other [TestPager] methods that does not invoke loads can still be called,
- * such as [getLastLoadedPage].
+ * As such, multiple calls to refresh() on this TestPager is illegal. The [PagingSource]
+ * passed in to this [TestPager] will also be invalidated to prevent reuse of this pager for
+ * loads. However, other [TestPager] methods that does not invoke loads can still be called,
+ * such as [getLastLoadedPage].
*/
public suspend fun refresh(
initialKey: Key? = null
): @JvmSuppressWildcards LoadResult<Key, Value> {
if (!hasRefreshed.compareAndSet(false, true)) {
pagingSource.invalidate()
- throw IllegalStateException("TestPager does not support multi-generational access " +
- "and refresh() can only be called once per TestPager. To start a new generation," +
- "create a new TestPager with a new PagingSource.")
+ throw IllegalStateException(
+ "TestPager does not support multi-generational access " +
+ "and refresh() can only be called once per TestPager. To start a new generation," +
+ "create a new TestPager with a new PagingSource."
+ )
}
return doInitialLoad(initialKey)
}
@@ -121,112 +122,106 @@
return doLoad(PREPEND)
}
- /**
- * Helper to perform REFRESH loads.
- */
+ /** Helper to perform REFRESH loads. */
private suspend fun doInitialLoad(
initialKey: Key?
): @JvmSuppressWildcards LoadResult<Key, Value> {
return lock.withLock {
- pagingSource.load(
- LoadParams.Refresh(initialKey, config.initialLoadSize, config.enablePlaceholders)
- ).also { result ->
- if (result is LoadResult.Page) {
- pages.addLast(result)
+ pagingSource
+ .load(
+ LoadParams.Refresh(
+ initialKey,
+ config.initialLoadSize,
+ config.enablePlaceholders
+ )
+ )
+ .also { result ->
+ if (result is LoadResult.Page) {
+ pages.addLast(result)
+ }
}
- }
}
}
- /**
- * Helper to perform APPEND or PREPEND loads.
- */
+ /** Helper to perform APPEND or PREPEND loads. */
private suspend fun doLoad(loadType: LoadType): LoadResult<Key, Value>? {
return lock.withLock {
if (!hasRefreshed.get()) {
- throw IllegalStateException("TestPager's first load operation must be a refresh. " +
- "Please call refresh() once before calling ${loadType.name.lowercase()}().")
+ throw IllegalStateException(
+ "TestPager's first load operation must be a refresh. " +
+ "Please call refresh() once before calling ${loadType.name.lowercase()}()."
+ )
}
when (loadType) {
- REFRESH -> throw IllegalArgumentException(
- "For LoadType.REFRESH use doInitialLoad()"
- )
+ REFRESH ->
+ throw IllegalArgumentException("For LoadType.REFRESH use doInitialLoad()")
APPEND -> {
val key = pages.lastOrNull()?.nextKey ?: return null
- pagingSource.load(
- LoadParams.Append(key, config.pageSize, config.enablePlaceholders)
- ).also { result ->
- if (result is LoadResult.Page) {
- pages.addLast(result)
+ pagingSource
+ .load(LoadParams.Append(key, config.pageSize, config.enablePlaceholders))
+ .also { result ->
+ if (result is LoadResult.Page) {
+ pages.addLast(result)
+ }
+ dropPagesOrNoOp(PREPEND)
}
- dropPagesOrNoOp(PREPEND)
- }
- } PREPEND -> {
+ }
+ PREPEND -> {
val key = pages.firstOrNull()?.prevKey ?: return null
- pagingSource.load(
- LoadParams.Prepend(key, config.pageSize, config.enablePlaceholders)
- ).also { result ->
- if (result is LoadResult.Page) {
- pages.addFirst(result)
+ pagingSource
+ .load(LoadParams.Prepend(key, config.pageSize, config.enablePlaceholders))
+ .also { result ->
+ if (result is LoadResult.Page) {
+ pages.addFirst(result)
+ }
+ dropPagesOrNoOp(APPEND)
}
- dropPagesOrNoOp(APPEND)
- }
}
}
}
}
/**
- * Returns the most recent [LoadResult.Page] loaded from the [PagingSource]. Null if
- * no pages have been returned from [PagingSource]. For example, if PagingSource has
- * only returned [LoadResult.Error] or [LoadResult.Invalid].
+ * Returns the most recent [LoadResult.Page] loaded from the [PagingSource]. Null if no pages
+ * have been returned from [PagingSource]. For example, if PagingSource has only returned
+ * [LoadResult.Error] or [LoadResult.Invalid].
*/
public suspend fun getLastLoadedPage(): @JvmSuppressWildcards LoadResult.Page<Key, Value>? {
- return lock.withLock {
- pages.lastOrNull()
- }
+ return lock.withLock { pages.lastOrNull() }
}
- /**
- * Returns the current list of [LoadResult.Page] loaded so far from the [PagingSource].
- */
+ /** Returns the current list of [LoadResult.Page] loaded so far from the [PagingSource]. */
public suspend fun getPages(): @JvmSuppressWildcards List<LoadResult.Page<Key, Value>> {
- return lock.withLock {
- pages.toList()
- }
+ return lock.withLock { pages.toList() }
}
/**
* Returns a [PagingState] to generate a [LoadParams.key] by supplying it to
- * [PagingSource.getRefreshKey]. The key returned from [PagingSource.getRefreshKey]
- * should be used as the [LoadParams.Refresh.key] when calling [refresh] on a new generation of
- * TestPager.
+ * [PagingSource.getRefreshKey]. The key returned from [PagingSource.getRefreshKey] should be
+ * used as the [LoadParams.Refresh.key] when calling [refresh] on a new generation of TestPager.
*
- * The anchorPosition must be within index of loaded items, which can include
- * placeholders if [PagingConfig.enablePlaceholders] is true. For example:
- * - No placeholders: If 40 items have been loaded so far , anchorPosition must be
- * in [0 .. 39].
- * - With placeholders: If there are a total of 100 loadable items, the anchorPosition
- * must be in [0..99].
+ * The anchorPosition must be within index of loaded items, which can include placeholders if
+ * [PagingConfig.enablePlaceholders] is true. For example:
+ * - No placeholders: If 40 items have been loaded so far , anchorPosition must be in [0 .. 39].
+ * - With placeholders: If there are a total of 100 loadable items, the anchorPosition must be
+ * in [0..99].
*
- * The [anchorPosition] should be the index that the user has hypothetically
- * scrolled to on the UI. Since the [PagingState.anchorPosition] in Paging can be based
- * on any item or placeholder currently visible on the screen, the actual
- * value of [PagingState.anchorPosition] may not exactly match the [anchorPosition] passed
- * to this function even if viewing the same page of data.
+ * The [anchorPosition] should be the index that the user has hypothetically scrolled to on the
+ * UI. Since the [PagingState.anchorPosition] in Paging can be based on any item or placeholder
+ * currently visible on the screen, the actual value of [PagingState.anchorPosition] may not
+ * exactly match the [anchorPosition] passed to this function even if viewing the same page of
+ * data.
*
- * Note that when `[PagingConfig.enablePlaceholders] = false`, the
- * [PagingState.anchorPosition] returned from this function references the absolute index
- * within all loadable data. For example, with items[0 - 99]:
- * If items[20 - 30] were loaded without placeholders, anchorPosition 0 references item[20].
- * But once translated into [PagingState.anchorPosition], anchorPosition 0 references item[0].
- * The [PagingSource] is expected to handle this correctly within [PagingSource.getRefreshKey]
- * when [PagingConfig.enablePlaceholders] = false.
+ * Note that when `[PagingConfig.enablePlaceholders] = false`, the [PagingState.anchorPosition]
+ * returned from this function references the absolute index within all loadable data. For
+ * example, with items[0 - 99]: If items[20 - 30] were loaded without placeholders,
+ * anchorPosition 0 references item[20]. But once translated into [PagingState.anchorPosition],
+ * anchorPosition 0 references item[0]. The [PagingSource] is expected to handle this correctly
+ * within [PagingSource.getRefreshKey] when [PagingConfig.enablePlaceholders] = false.
*
- * @param anchorPosition the index representing the last accessed item within the
- * items presented on the UI, which may be a placeholder if
- * [PagingConfig.enablePlaceholders] is true.
- *
+ * @param anchorPosition the index representing the last accessed item within the items
+ * presented on the UI, which may be a placeholder if [PagingConfig.enablePlaceholders] is
+ * true.
* @throws IllegalStateException if anchorPosition is out of bounds.
*/
public suspend fun getPagingState(
@@ -245,50 +240,47 @@
/**
* Returns a [PagingState] to generate a [LoadParams.key] by supplying it to
- * [PagingSource.getRefreshKey]. The key returned from [PagingSource.getRefreshKey]
- * should be used as the [LoadParams.Refresh.key] when calling [refresh] on a new generation of
- * TestPager.
+ * [PagingSource.getRefreshKey]. The key returned from [PagingSource.getRefreshKey] should be
+ * used as the [LoadParams.Refresh.key] when calling [refresh] on a new generation of TestPager.
*
* The [anchorPositionLookup] lambda should return an item that the user has hypothetically
- * scrolled to on the UI. The item must have already been loaded prior to using this helper.
- * To generate a PagingState anchored to a placeholder, use the overloaded [getPagingState]
- * function instead. Since the [PagingState.anchorPosition] in Paging can be based
- * on any item or placeholder currently visible on the screen, the actual
- * value of [PagingState.anchorPosition] may not exactly match the anchorPosition returned
- * from this function even if viewing the same page of data.
+ * scrolled to on the UI. The item must have already been loaded prior to using this helper. To
+ * generate a PagingState anchored to a placeholder, use the overloaded [getPagingState]
+ * function instead. Since the [PagingState.anchorPosition] in Paging can be based on any item
+ * or placeholder currently visible on the screen, the actual value of
+ * [PagingState.anchorPosition] may not exactly match the anchorPosition returned from this
+ * function even if viewing the same page of data.
*
- * Note that when `[PagingConfig.enablePlaceholders] = false`, the
- * [PagingState.anchorPosition] returned from this function references the absolute index
- * within all loadable data. For example, with items[0 - 99]:
- * If items[20 - 30] were loaded without placeholders, anchorPosition 0 references item[20].
- * But once translated into [PagingState.anchorPosition], anchorPosition 0 references item[0].
- * The [PagingSource] is expected to handle this correctly within [PagingSource.getRefreshKey]
- * when [PagingConfig.enablePlaceholders] = false.
+ * Note that when `[PagingConfig.enablePlaceholders] = false`, the [PagingState.anchorPosition]
+ * returned from this function references the absolute index within all loadable data. For
+ * example, with items[0 - 99]: If items[20 - 30] were loaded without placeholders,
+ * anchorPosition 0 references item[20]. But once translated into [PagingState.anchorPosition],
+ * anchorPosition 0 references item[0]. The [PagingSource] is expected to handle this correctly
+ * within [PagingSource.getRefreshKey] when [PagingConfig.enablePlaceholders] = false.
*
* @param anchorPositionLookup the predicate to match with an item which will serve as the basis
- * for generating the [PagingState].
- *
+ * for generating the [PagingState].
* @throws IllegalArgumentException if the given predicate fails to match with an item.
*/
public suspend fun getPagingState(
anchorPositionLookup: (item: @JvmSuppressWildcards Value) -> Boolean
): @JvmSuppressWildcards PagingState<Key, Value> {
lock.withLock {
- val indexInPages = pages.flatten().indexOfFirst {
- anchorPositionLookup(it)
- }
+ val indexInPages = pages.flatten().indexOfFirst { anchorPositionLookup(it) }
return when {
- indexInPages < 0 -> throw IllegalArgumentException(
- "The given predicate has returned false for every loaded item. To generate a" +
- "PagingState anchored to an item, the expected item must have already " +
- "been loaded."
- )
+ indexInPages < 0 ->
+ throw IllegalArgumentException(
+ "The given predicate has returned false for every loaded item. To generate a" +
+ "PagingState anchored to an item, the expected item must have already " +
+ "been loaded."
+ )
else -> {
- val finalIndex = if (config.enablePlaceholders) {
- indexInPages + (pages.firstOrNull()?.itemsBefore ?: 0)
- } else {
- indexInPages
- }
+ val finalIndex =
+ if (config.enablePlaceholders) {
+ indexInPages + (pages.firstOrNull()?.itemsBefore ?: 0)
+ } else {
+ indexInPages
+ }
PagingState(
pages = pages.toList(),
anchorPosition = finalIndex,
@@ -313,12 +305,14 @@
*/
private fun checkWithinBoundary(anchorPosition: Int) {
val loadedSize = pages.flatten().size
- val maxBoundary = if (config.enablePlaceholders) {
- (pages.firstOrNull()?.itemsBefore ?: 0) + loadedSize +
- (pages.lastOrNull()?.itemsAfter ?: 0) - 1
- } else {
- loadedSize - 1
- }
+ val maxBoundary =
+ if (config.enablePlaceholders) {
+ (pages.firstOrNull()?.itemsBefore ?: 0) +
+ loadedSize +
+ (pages.lastOrNull()?.itemsAfter ?: 0) - 1
+ } else {
+ loadedSize - 1
+ }
check(anchorPosition in 0..maxBoundary) {
"anchorPosition $anchorPosition is out of bounds between [0..$maxBoundary]. Please " +
"provide a valid anchorPosition."
@@ -335,11 +329,12 @@
// 3. COUNT_UNDEFINED if not implemented
val itemsBefore: Int? = pages.firstOrNull()?.itemsBefore
// finalItemsBefore is `null` if it is either case 2. or 3.
- val finalItemsBefore = if (itemsBefore == null || itemsBefore == COUNT_UNDEFINED) {
- null
- } else {
- itemsBefore
- }
+ val finalItemsBefore =
+ if (itemsBefore == null || itemsBefore == COUNT_UNDEFINED) {
+ null
+ } else {
+ itemsBefore
+ }
// This will ultimately return 0 if user didn't implement itemsBefore or if pages
// are empty, i.e. user called getPagingState before any loads.
finalItemsBefore ?: 0
@@ -349,9 +344,7 @@
}
private fun dropPagesOrNoOp(dropType: LoadType) {
- require(dropType != REFRESH) {
- "Drop loadType must be APPEND or PREPEND but got $dropType"
- }
+ require(dropType != REFRESH) { "Drop loadType must be APPEND or PREPEND but got $dropType" }
// check if maxSize has been set
if (config.maxSize == PagingConfig.MAX_SIZE_UNBOUNDED) return
@@ -360,21 +353,21 @@
if (itemCount < config.maxSize) return
// represents the max droppable amount of items
- val presentedItemsBeforeOrAfter = when (dropType) {
- PREPEND -> pages.take(pages.lastIndex)
- else -> pages.takeLast(pages.lastIndex)
- }.fold(0) { acc, page ->
- acc + page.data.size
- }
+ val presentedItemsBeforeOrAfter =
+ when (dropType) {
+ PREPEND -> pages.take(pages.lastIndex)
+ else -> pages.takeLast(pages.lastIndex)
+ }.fold(0) { acc, page -> acc + page.data.size }
var itemsDropped = 0
// mirror Paging requirement to never drop below 2 pages
while (pages.size > 2 && itemCount - itemsDropped > config.maxSize) {
- val pageSize = when (dropType) {
- PREPEND -> pages.first().data.size
- else -> pages.last().data.size
- }
+ val pageSize =
+ when (dropType) {
+ PREPEND -> pages.first().data.size
+ else -> pages.last().data.size
+ }
val itemsAfterDrop = presentedItemsBeforeOrAfter - itemsDropped - pageSize
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt
index 8aed900..824cb00 100644
--- a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt
@@ -18,17 +18,22 @@
internal expect class AtomicInt(initialValue: Int) {
fun get(): Int
+
fun set(value: Int)
}
internal expect class AtomicBoolean(initialValue: Boolean) {
fun get(): Boolean
+
fun set(value: Boolean)
+
fun compareAndSet(expect: Boolean, update: Boolean): Boolean
}
internal expect class AtomicRef<T>(initialValue: T) {
fun get(): T
+
fun set(value: T)
+
fun getAndSet(value: T): T
}
diff --git a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
index f14079d..700f914 100644
--- a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
@@ -53,16 +53,11 @@
)
private fun createSingleGenFactory(data: List<Int>, loadDelay: Long) =
- WrappedPagingSourceFactory(
- data.asPagingSourceFactory(),
- loadDelay
- )
+ WrappedPagingSourceFactory(data.asPagingSourceFactory(), loadDelay)
- @Test
- fun initialRefresh_loadDelay0() = initialRefresh(0)
+ @Test fun initialRefresh_loadDelay0() = initialRefresh(0)
- @Test
- fun initialRefresh_loadDelay10000() = initialRefresh(10000)
+ @Test fun initialRefresh_loadDelay10000() = initialRefresh(10000)
private fun initialRefresh(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
@@ -70,17 +65,13 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
- @Test
- fun initialRefreshSingleGen_loadDelay0() = initialRefreshSingleGen(0)
+ @Test fun initialRefreshSingleGen_loadDelay0() = initialRefreshSingleGen(0)
- @Test
- fun initialRefreshSingleGen_loadDelay10000() = initialRefreshSingleGen(10000)
+ @Test fun initialRefreshSingleGen_loadDelay10000() = initialRefreshSingleGen(10000)
private fun initialRefreshSingleGen(loadDelay: Long) {
val data = List(30) { it }
@@ -88,18 +79,14 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
- @Test
- fun initialRefresh_emptyOperations_loadDelay0() = initialRefresh_emptyOperations(0)
+ @Test fun initialRefresh_emptyOperations_loadDelay0() = initialRefresh_emptyOperations(0)
@Test
- fun initialRefresh_emptyOperations_loadDelay10000() =
- initialRefresh_emptyOperations(10000)
+ fun initialRefresh_emptyOperations_loadDelay10000() = initialRefresh_emptyOperations(10000)
private fun initialRefresh_emptyOperations(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
@@ -107,36 +94,31 @@
testScope.runTest {
val snapshot = pager.asSnapshot {}
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
- @Test
- fun initialRefresh_withSeparators_loadDelay0() = initialRefresh_withSeparators(0)
+ @Test fun initialRefresh_withSeparators_loadDelay0() = initialRefresh_withSeparators(0)
- @Test
- fun initialRefresh_withSeparators_loadDelay10000() = initialRefresh_withSeparators(10000)
+ @Test fun initialRefresh_withSeparators_loadDelay10000() = initialRefresh_withSeparators(10000)
private fun initialRefresh_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
- val pager = createPager(dataFlow, loadDelay).map { pagingData ->
- pagingData.insertSeparators { before: Int?, after: Int? ->
- if (before != null && after != null) "sep" else null
+ val pager =
+ createPager(dataFlow, loadDelay).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, after: Int? ->
+ if (before != null && after != null) "sep" else null
+ }
}
- }
testScope.runTest {
val snapshot = pager.asSnapshot()
// loads 8[initial 5 + prefetch 3] items total, including separators
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, "sep", 1, "sep", 2, "sep", 3, "sep", 4)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, "sep", 1, "sep", 2, "sep", 3, "sep", 4))
}
}
- @Test
- fun initialRefresh_withoutPrefetch_loadDelay0() = initialRefresh_withoutPrefetch(0)
+ @Test fun initialRefresh_withoutPrefetch_loadDelay0() = initialRefresh_withoutPrefetch(0)
@Test
fun initialRefresh_withoutPrefetch_loadDelay10000() = initialRefresh_withoutPrefetch(10000)
@@ -147,17 +129,13 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
}
}
- @Test
- fun initialRefresh_withInitialKey_loadDelay0() = initialRefresh_withInitialKey(0)
+ @Test fun initialRefresh_withInitialKey_loadDelay0() = initialRefresh_withInitialKey(0)
- @Test
- fun initialRefresh_withInitialKey_loadDelay10000() = initialRefresh_withInitialKey(10000)
+ @Test fun initialRefresh_withInitialKey_loadDelay10000() = initialRefresh_withInitialKey(10000)
private fun initialRefresh_withInitialKey(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
@@ -165,9 +143,8 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17))
}
}
@@ -185,9 +162,7 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(10, 11, 12, 13, 14)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(10, 11, 12, 13, 14))
}
}
@@ -198,34 +173,34 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
}
}
@Test
fun initialRefresh_PagingDataFrom_withLoadStates() {
val data = List(10) { it }
- val pager = flowOf(PagingData.from(data, LoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
- )))
+ val pager =
+ flowOf(
+ PagingData.from(
+ data,
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
+ )
+ )
testScope.runTest {
val snapshot = pager.asSnapshot()
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
}
}
- @Test
- fun emptyInitialRefresh_loadDelay0() = emptyInitialRefresh(0)
+ @Test fun emptyInitialRefresh_loadDelay0() = emptyInitialRefresh(0)
- @Test
- fun emptyInitialRefresh_loadDelay10000() = emptyInitialRefresh(10000)
+ @Test fun emptyInitialRefresh_loadDelay10000() = emptyInitialRefresh(10000)
private fun emptyInitialRefresh(loadDelay: Long) {
val dataFlow = emptyFlow<List<Int>>()
@@ -233,17 +208,13 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot).containsExactlyElementsIn(emptyList<Int>())
}
}
- @Test
- fun emptyInitialRefreshSingleGen_loadDelay0() = emptyInitialRefreshSingleGen(0)
+ @Test fun emptyInitialRefreshSingleGen_loadDelay0() = emptyInitialRefreshSingleGen(0)
- @Test
- fun emptyInitialRefreshSingleGen_loadDelay10000() = emptyInitialRefreshSingleGen(10000)
+ @Test fun emptyInitialRefreshSingleGen_loadDelay10000() = emptyInitialRefreshSingleGen(10000)
private fun emptyInitialRefreshSingleGen(loadDelay: Long) {
val data = emptyList<Int>()
@@ -251,9 +222,7 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot).containsExactlyElementsIn(emptyList<Int>())
}
}
@@ -270,47 +239,39 @@
testScope.runTest {
val snapshot = pager.asSnapshot()
- assertThat(snapshot).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot).containsExactlyElementsIn(emptyList<Int>())
}
}
- @Test
- fun manualRefresh_loadDelay0() = manualRefresh(0)
+ @Test fun manualRefresh_loadDelay0() = manualRefresh(0)
- @Test
- fun manualRefresh_loadDelay10000() = manualRefresh(10000)
+ @Test fun manualRefresh_loadDelay10000() = manualRefresh(10000)
private fun manualRefresh(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh()
- }
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4),
- )
+ val snapshot = pager.asSnapshot { refresh() }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4),
+ )
}
}
- @Test
- fun manualRefreshSingleGen_loadDelay0() = manualRefreshSingleGen(0)
+ @Test fun manualRefreshSingleGen_loadDelay0() = manualRefreshSingleGen(0)
- @Test
- fun manualRefreshSingleGen_loadDelay10000() = manualRefreshSingleGen(10000)
+ @Test fun manualRefreshSingleGen_loadDelay10000() = manualRefreshSingleGen(10000)
private fun manualRefreshSingleGen(loadDelay: Long) {
val data = List(30) { it }
val pager = createPager(data, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh()
- }
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7),
- )
+ val snapshot = pager.asSnapshot { refresh() }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7),
+ )
}
}
@@ -319,14 +280,14 @@
val data = List(30) { it }
val sources = mutableListOf<PagingSource<Int, Int>>()
val factory = data.asPagingSourceFactory()
- val pager = Pager(
- config = PagingConfig(pageSize = 3, initialLoadSize = 5),
- pagingSourceFactory = { factory().also { sources.add(it) } },
- ).flow
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+ pagingSourceFactory = { factory().also { sources.add(it) } },
+ )
+ .flow
testScope.runTest {
- pager.asSnapshot {
- refresh()
- }
+ pager.asSnapshot { refresh() }
assertThat(sources.first().invalid).isTrue()
}
}
@@ -336,158 +297,151 @@
val data = List(10) { it }
val pager = flowOf(PagingData.from(data))
testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh()
- }
+ val snapshot = pager.asSnapshot { refresh() }
// first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
}
}
@Test
fun manualRefresh_PagingDataFrom_withLoadStates() {
val data = List(10) { it }
- val pager = flowOf(PagingData.from(data, LoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
- )))
- testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh()
- }
- // first page + prefetched page
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+ val pager =
+ flowOf(
+ PagingData.from(
+ data,
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
+ )
)
+ testScope.runTest {
+ val snapshot = pager.asSnapshot { refresh() }
+ // first page + prefetched page
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
}
}
- @Test
- fun manualEmptyRefresh_loadDelay0() = manualEmptyRefresh(0)
+ @Test fun manualEmptyRefresh_loadDelay0() = manualEmptyRefresh(0)
- @Test
- fun manualEmptyRefresh_loadDelay10000() = manualEmptyRefresh(10000)
+ @Test fun manualEmptyRefresh_loadDelay10000() = manualEmptyRefresh(10000)
private fun manualEmptyRefresh(loadDelay: Long) {
val dataFlow = emptyFlow<List<Int>>()
val pager = createPagerNoPrefetch(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh()
- }
- assertThat(snapshot).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ val snapshot = pager.asSnapshot { refresh() }
+ assertThat(snapshot).containsExactlyElementsIn(emptyList<Int>())
}
}
- @Test
- fun appendWhile_loadDelay0() = appendWhile(0)
+ @Test fun appendWhile_loadDelay0() = appendWhile(0)
- @Test
- fun appendWhile_loadDelay10000() = appendWhile(10000)
+ @Test fun appendWhile_loadDelay10000() = appendWhile(10000)
private fun appendWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 14
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [0-4]
- // prefetched [5-7]
- // appended [8-16]
- // prefetched [17-19]
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item < 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [0-4]
+ // prefetched [5-7]
+ // appended [8-16]
+ // prefetched [17-19]
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+ )
}
}
- @Test
- fun appendWhile_withDrops_loadDelay0() = appendWhile_withDrops(0)
+ @Test fun appendWhile_withDrops_loadDelay0() = appendWhile_withDrops(0)
- @Test
- fun appendWhile_withDrops_loadDelay10000() = appendWhile_withDrops(10000)
+ @Test fun appendWhile_withDrops_loadDelay10000() = appendWhile_withDrops(10000)
private fun appendWhile_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item ->
- item < 14
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // dropped [0-10]
- listOf(11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ val snapshot = pager.asSnapshot { appendScrollWhile { item -> item < 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // dropped [0-10]
+ listOf(11, 12, 13, 14, 15, 16, 17, 18, 19)
+ )
}
}
- @Test
- fun appendWhile_withSeparators_loadDelay0() = appendWhile_withSeparators(0)
+ @Test fun appendWhile_withSeparators_loadDelay0() = appendWhile_withSeparators(0)
- @Test
- fun appendWhile_withSeparators_loadDelay10000() = appendWhile_withSeparators(10000)
+ @Test fun appendWhile_withSeparators_loadDelay10000() = appendWhile_withSeparators(10000)
private fun appendWhile_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
- val pager = createPager(dataFlow, loadDelay).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 9 || before == 12) "sep" else null
- }
- }
- testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item ->
- item !is Int || item < 14
+ val pager =
+ createPager(dataFlow, loadDelay).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 9 || before == 12) "sep" else null
}
}
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [0-4]
- // prefetched [5-7]
- // appended [8-16]
- // prefetched [17-19]
- listOf(
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "sep", 10, 11, 12, "sep", 13, 14, 15,
- 16, 17, 18, 19
+ testScope.runTest {
+ val snapshot =
+ pager.asSnapshot { appendScrollWhile { item -> item !is Int || item < 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [0-4]
+ // prefetched [5-7]
+ // appended [8-16]
+ // prefetched [17-19]
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ "sep",
+ 10,
+ 11,
+ 12,
+ "sep",
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19
+ )
)
- )
}
}
- @Test
- fun appendWhile_withoutPrefetch_loadDelay0() = appendWhile_withoutPrefetch(0)
+ @Test fun appendWhile_withoutPrefetch_loadDelay0() = appendWhile_withoutPrefetch(0)
- @Test
- fun appendWhile_withoutPrefetch_loadDelay10000() = appendWhile_withoutPrefetch(10000)
+ @Test fun appendWhile_withoutPrefetch_loadDelay10000() = appendWhile_withoutPrefetch(10000)
private fun appendWhile_withoutPrefetch(loadDelay: Long) {
val dataFlow = flowOf(List(50) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 14
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [0-4]
- // appended [5-16]
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item < 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [0-4]
+ // appended [5-16]
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
}
}
- @Test
- fun appendWhile_withoutPlaceholders_loadDelay0() = appendWhile_withoutPlaceholders(0)
+ @Test fun appendWhile_withoutPlaceholders_loadDelay0() = appendWhile_withoutPlaceholders(0)
@Test
fun appendWhile_withoutPlaceholders_loadDelay10000() = appendWhile_withoutPlaceholders(10000)
@@ -496,125 +450,118 @@
val dataFlow = flowOf(List(50) { it })
val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item != 14
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [0-4]
- // prefetched [5-7]
- // appended [8-16]
- // prefetched [17-19]
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item != 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [0-4]
+ // prefetched [5-7]
+ // appended [8-16]
+ // prefetched [17-19]
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+ )
}
}
- @Test
- fun prependWhile_loadDelay0() = prependWhile(0)
+ @Test fun prependWhile_loadDelay0() = prependWhile(0)
- @Test
- fun prependWhile_loadDelay10000() = prependWhile(10000)
+ @Test fun prependWhile_loadDelay10000() = prependWhile(10000)
private fun prependWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay, 20)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 14
- }
- }
+ val snapshot = pager.asSnapshot { prependScrollWhile { item: Int -> item > 14 } }
// initial load [20-24]
// prefetched [17-19], [25-27]
// prepended [14-16]
// prefetched [11-13]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27)
+ )
}
}
- @Test
- fun prependWhile_withDrops_loadDelay0() = prependWhile_withDrops(0)
+ @Test fun prependWhile_withDrops_loadDelay0() = prependWhile_withDrops(0)
- @Test
- fun prependWhile_withDrops_loadDelay10000() = prependWhile_withDrops(10000)
+ @Test fun prependWhile_withDrops_loadDelay10000() = prependWhile_withDrops(10000)
private fun prependWhile_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay, 20)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 14
- }
- }
+ val snapshot = pager.asSnapshot { prependScrollWhile { item: Int -> item > 14 } }
// dropped [20-27]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(11, 12, 13, 14, 15, 16, 17, 18, 19))
}
}
- @Test
- fun prependWhile_withSeparators_loadDelay0() = prependWhile_withSeparators(0)
+ @Test fun prependWhile_withSeparators_loadDelay0() = prependWhile_withSeparators(0)
- @Test
- fun prependWhile_withSeparators_loadDelay10000() = prependWhile_withSeparators(10000)
+ @Test fun prependWhile_withSeparators_loadDelay10000() = prependWhile_withSeparators(10000)
private fun prependWhile_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
- val pager = createPager(dataFlow, loadDelay, 20).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 14 || before == 18) "sep" else null
- }
- }
- testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item ->
- item !is Int || item > 14
+ val pager =
+ createPager(dataFlow, loadDelay, 20).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 14 || before == 18) "sep" else null
}
}
+ testScope.runTest {
+ val snapshot =
+ pager.asSnapshot { prependScrollWhile { item -> item !is Int || item > 14 } }
// initial load [20-24]
// prefetched [17-19], no append prefetch because separator fulfilled prefetchDistance
// prepended [14-16]
// prefetched [11-13]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 11, 12, 13, 14, "sep", 15, 16, 17, 18, "sep", 19, 20, 21, 22, 23,
- 24, 25, 26, 27
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 11,
+ 12,
+ 13,
+ 14,
+ "sep",
+ 15,
+ 16,
+ 17,
+ 18,
+ "sep",
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27
+ )
)
- )
}
}
- @Test
- fun prependWhile_withoutPrefetch_loadDelay0() = prependWhile_withoutPrefetch(0)
+ @Test fun prependWhile_withoutPrefetch_loadDelay0() = prependWhile_withoutPrefetch(0)
- @Test
- fun prependWhile_withoutPrefetch_loadDelay10000() = prependWhile_withoutPrefetch(10000)
+ @Test fun prependWhile_withoutPrefetch_loadDelay10000() = prependWhile_withoutPrefetch(10000)
private fun prependWhile_withoutPrefetch(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay, 20)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 14
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [20-24]
- // prepended [14-19]
- listOf(14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
- )
+ val snapshot = pager.asSnapshot { prependScrollWhile { item: Int -> item > 14 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [20-24]
+ // prepended [14-19]
+ listOf(14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
+ )
}
}
- @Test
- fun prependWhile_withoutPlaceholders_loadDelay0() = prependWhile_withoutPlaceholders(0)
+ @Test fun prependWhile_withoutPlaceholders_loadDelay0() = prependWhile_withoutPlaceholders(0)
@Test
fun prependWhile_withoutPlaceholders_loadDelay10000() = prependWhile_withoutPlaceholders(10000)
@@ -623,45 +570,56 @@
val dataFlow = flowOf(List(50) { it })
val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 30)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item != 22
- }
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [30-34]
- // prefetched [27-29], [35-37]
- // prepended [21-26]
- // prefetched [18-20]
- listOf(
- 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37
+ val snapshot = pager.asSnapshot { prependScrollWhile { item: Int -> item != 22 } }
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [30-34]
+ // prefetched [27-29], [35-37]
+ // prepended [21-26]
+ // prefetched [18-20]
+ listOf(
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37
+ )
)
- )
}
}
- @Test
- fun appendWhile_withInitialKey_loadDelay0() = appendWhile_withInitialKey(0)
+ @Test fun appendWhile_withInitialKey_loadDelay0() = appendWhile_withInitialKey(0)
- @Test
- fun appendWhile_withInitialKey_loadDelay10000() = appendWhile_withInitialKey(10000)
+ @Test fun appendWhile_withInitialKey_loadDelay10000() = appendWhile_withInitialKey(10000)
private fun appendWhile_withInitialKey(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay, 10)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 18
- }
- }
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item < 18 } }
// initial load [10-14]
// prefetched [7-9], [15-17]
// appended [18-20]
// prefetched [21-23]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
+ )
}
}
@@ -677,18 +635,15 @@
val dataFlow = flowOf(List(30) { it })
val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 10)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item != 19
- }
- }
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item != 19 } }
// initial load [10-14]
// prefetched [7-9], [15-17]
// appended [18-20]
// prefetched [21-23]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
+ )
}
}
@@ -704,21 +659,15 @@
val dataFlow = flowOf(List(30) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 18
- }
- }
+ val snapshot = pager.asSnapshot { appendScrollWhile { item: Int -> item < 18 } }
// initial load [10-14]
// appended [15-20]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))
}
}
- @Test
- fun prependWhile_withoutInitialKey_loadDelay0() = prependWhile_withoutInitialKey(0)
+ @Test fun prependWhile_withoutInitialKey_loadDelay0() = prependWhile_withoutInitialKey(0)
@Test
fun prependWhile_withoutInitialKey_loadDelay10000() = prependWhile_withoutInitialKey(10000)
@@ -727,80 +676,49 @@
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > -3
- }
- }
+ val snapshot = pager.asSnapshot { prependScrollWhile { item: Int -> item > -3 } }
// initial load [0-4]
// prefetched [5-7]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
- @Test
- fun consecutiveAppendWhile_loadDelay0() = consecutiveAppendWhile(0)
+ @Test fun consecutiveAppendWhile_loadDelay0() = consecutiveAppendWhile(0)
- @Test
- fun consecutiveAppendWhile_loadDelay10000() = consecutiveAppendWhile(10000)
+ @Test fun consecutiveAppendWhile_loadDelay10000() = consecutiveAppendWhile(10000)
private fun consecutiveAppendWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot1 = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 7
- }
- }
+ val snapshot1 = pager.asSnapshot { appendScrollWhile { item: Int -> item < 7 } }
- val snapshot2 = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 22
- }
- }
+ val snapshot2 = pager.asSnapshot { appendScrollWhile { item: Int -> item < 22 } }
// includes initial load, 1st page, 2nd page (from prefetch)
- assertThat(snapshot1).containsExactlyElementsIn(
- List(11) { it }
- )
+ assertThat(snapshot1).containsExactlyElementsIn(List(11) { it })
// includes extra page from prefetch
- assertThat(snapshot2).containsExactlyElementsIn(
- List(26) { it }
- )
+ assertThat(snapshot2).containsExactlyElementsIn(List(26) { it })
}
}
- @Test
- fun consecutivePrependWhile_loadDelay0() = consecutivePrependWhile(0)
+ @Test fun consecutivePrependWhile_loadDelay0() = consecutivePrependWhile(0)
- @Test
- fun consecutivePrependWhile_loadDelay10000() = consecutivePrependWhile(10000)
+ @Test fun consecutivePrependWhile_loadDelay10000() = consecutivePrependWhile(10000)
private fun consecutivePrependWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
- val pager = createPagerNoPrefetch(dataFlow, loadDelay, 20)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPrefetch(dataFlow, loadDelay, 20).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot1 = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 17
- }
- }
- assertThat(snapshot1).containsExactlyElementsIn(
- listOf(17, 18, 19, 20, 21, 22, 23, 24)
- )
- val snapshot2 = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 11
- }
- }
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
- )
+ val snapshot1 = pager.asSnapshot { prependScrollWhile { item: Int -> item > 17 } }
+ assertThat(snapshot1).containsExactlyElementsIn(listOf(17, 18, 19, 20, 21, 22, 23, 24))
+ val snapshot2 = pager.asSnapshot { prependScrollWhile { item: Int -> item > 11 } }
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
+ )
}
}
@@ -816,17 +734,16 @@
val dataFlow = flowOf(List(10) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- // condition scrolls till end of data since we only have 10 items
- item < 18
+ val snapshot =
+ pager.asSnapshot {
+ appendScrollWhile { item: Int ->
+ // condition scrolls till end of data since we only have 10 items
+ item < 18
+ }
}
- }
// returns the items loaded before index becomes out of bounds
- assertThat(snapshot).containsExactlyElementsIn(
- List(10) { it }
- )
+ assertThat(snapshot).containsExactlyElementsIn(List(10) { it })
}
}
@@ -842,128 +759,119 @@
val dataFlow = flowOf(List(20) { it })
val pager = createPager(dataFlow, loadDelay, 10)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- // condition scrolls till index = 0
- item > -3
+ val snapshot =
+ pager.asSnapshot {
+ prependScrollWhile { item: Int ->
+ // condition scrolls till index = 0
+ item > -3
+ }
}
- }
// returns the items loaded before index becomes out of bounds
- assertThat(snapshot).containsExactlyElementsIn(
- // initial load [10-14]
- // prefetched [7-9], [15-17]
- // prepended [0-6]
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // initial load [10-14]
+ // prefetched [7-9], [15-17]
+ // prepended [0-6]
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
+ )
}
}
- @Test
- fun refreshAndAppendWhile_loadDelay0() = refreshAndAppendWhile(0)
+ @Test fun refreshAndAppendWhile_loadDelay0() = refreshAndAppendWhile(0)
- @Test
- fun refreshAndAppendWhile_loadDelay10000() = refreshAndAppendWhile(10000)
+ @Test fun refreshAndAppendWhile_loadDelay10000() = refreshAndAppendWhile(10000)
private fun refreshAndAppendWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- refresh() // triggers second gen
- appendScrollWhile { item: Int ->
- item < 10
+ val snapshot =
+ pager.asSnapshot {
+ refresh() // triggers second gen
+ appendScrollWhile { item: Int -> item < 10 }
}
- }
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))
}
}
- @Test
- fun refreshAndPrependWhile_loadDelay0() = refreshAndPrependWhile(0)
+ @Test fun refreshAndPrependWhile_loadDelay0() = refreshAndPrependWhile(0)
- @Test
- fun refreshAndPrependWhile_loadDelay10000() = refreshAndPrependWhile(10000)
+ @Test fun refreshAndPrependWhile_loadDelay10000() = refreshAndPrependWhile(10000)
private fun refreshAndPrependWhile(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay, 20).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // this prependScrollWhile does not cause paging to load more items
- // but it helps this test register a non-null anchorPosition so the upcoming
- // refresh doesn't start at index 0
- prependScrollWhile { item -> item > 20 }
- // triggers second gen
- refresh()
- prependScrollWhile { item: Int ->
- item > 12
+ val snapshot =
+ pager.asSnapshot {
+ // this prependScrollWhile does not cause paging to load more items
+ // but it helps this test register a non-null anchorPosition so the upcoming
+ // refresh doesn't start at index 0
+ prependScrollWhile { item -> item > 20 }
+ // triggers second gen
+ refresh()
+ prependScrollWhile { item: Int -> item > 12 }
}
- }
// second gen initial load, anchorPos = 20, refreshKey = 18, loaded
// initial load [18-22]
// prefetched [15-17], [23-25]
// prepended [12-14]
// prefetched [9-11]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
+ )
}
}
- @Test
- fun appendWhileAndRefresh_loadDelay0() = appendWhileAndRefresh(0)
+ @Test fun appendWhileAndRefresh_loadDelay0() = appendWhileAndRefresh(0)
- @Test
- fun appendWhileAndRefresh_loadDelay10000() = appendWhileAndRefresh(10000)
+ @Test fun appendWhileAndRefresh_loadDelay10000() = appendWhileAndRefresh(10000)
private fun appendWhileAndRefresh(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- appendScrollWhile { item: Int ->
- item < 10
+ val snapshot =
+ pager.asSnapshot {
+ appendScrollWhile { item: Int -> item < 10 }
+ refresh()
}
- refresh()
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // second gen initial load, anchorPos = 10, refreshKey = 8
- // initial load [8-12]
- // prefetched [5-7], [13-15]
- listOf(5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // second gen initial load, anchorPos = 10, refreshKey = 8
+ // initial load [8-12]
+ // prefetched [5-7], [13-15]
+ listOf(5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
+ )
}
}
- @Test
- fun prependWhileAndRefresh_loadDelay0() = prependWhileAndRefresh(0)
+ @Test fun prependWhileAndRefresh_loadDelay0() = prependWhileAndRefresh(0)
- @Test
- fun prependWhileAndRefresh_loadDelay10000() = prependWhileAndRefresh(10000)
+ @Test fun prependWhileAndRefresh_loadDelay10000() = prependWhileAndRefresh(10000)
private fun prependWhileAndRefresh(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val pager = createPager(dataFlow, loadDelay, 15).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- prependScrollWhile { item: Int ->
- item > 8
+ val snapshot =
+ pager.asSnapshot {
+ prependScrollWhile { item: Int -> item > 8 }
+ refresh()
}
- refresh()
- }
- assertThat(snapshot).containsExactlyElementsIn(
- // second gen initial load, anchorPos = 8, refreshKey = 6
- // initial load [6-10]
- // prefetched [3-5], [11-13]
- listOf(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ // second gen initial load, anchorPos = 8, refreshKey = 6
+ // initial load [6-10]
+ // prefetched [3-5], [11-13]
+ listOf(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
+ )
}
}
- @Test
- fun consecutiveGenerations_fromFlow_loadDelay0() = consecutiveGenerations_fromFlow(0)
+ @Test fun consecutiveGenerations_fromFlow_loadDelay0() = consecutiveGenerations_fromFlow(0)
@Test
fun consecutiveGenerations_fromFlow_loadDelay10000() = consecutiveGenerations_fromFlow(10000)
@@ -982,23 +890,17 @@
val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
val snapshot1 = pager.asSnapshot()
- assertThat(snapshot1).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot1).containsExactlyElementsIn(emptyList<Int>())
delay(500)
val snapshot2 = pager.asSnapshot()
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
delay(500)
val snapshot3 = pager.asSnapshot()
- assertThat(snapshot3).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
+ assertThat(snapshot3).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
}
}
@@ -1024,9 +926,8 @@
testScope.runTest {
val snapshot1 = pager.asSnapshot()
assertWithMessage("Only the last generation should be loaded without LoadStates")
- .that(snapshot1).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34, 35, 36, 37, 38, 39)
- )
+ .that(snapshot1)
+ .containsExactlyElementsIn(listOf(30, 31, 32, 33, 34, 35, 36, 37, 38, 39))
}
}
@@ -1040,46 +941,57 @@
private fun consecutiveGenerations_PagingDataFrom_withLoadStates(loadDelay: Long) {
// wait for 500 + loadDelay between each emission
- val pager = flow {
- emit(PagingData.empty(LoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
- )))
- delay(500 + loadDelay)
+ val pager =
+ flow {
+ emit(
+ PagingData.empty(
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
+ )
+ )
+ delay(500 + loadDelay)
- emit(PagingData.from(List(10) { it }, LoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
- )))
- delay(500 + loadDelay)
+ emit(
+ PagingData.from(
+ List(10) { it },
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
+ )
+ )
+ delay(500 + loadDelay)
- emit(PagingData.from(List(10) { it + 30 }, LoadStates(
- refresh = LoadState.NotLoading(true),
- prepend = LoadState.NotLoading(true),
- append = LoadState.NotLoading(true)
- )))
- }.cachedIn(testScope.backgroundScope)
+ emit(
+ PagingData.from(
+ List(10) { it + 30 },
+ LoadStates(
+ refresh = LoadState.NotLoading(true),
+ prepend = LoadState.NotLoading(true),
+ append = LoadState.NotLoading(true)
+ )
+ )
+ )
+ }
+ .cachedIn(testScope.backgroundScope)
testScope.runTest {
val snapshot1 = pager.asSnapshot()
- assertThat(snapshot1).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot1).containsExactlyElementsIn(emptyList<Int>())
delay(500 + loadDelay)
val snapshot2 = pager.asSnapshot()
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
- )
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
delay(500 + loadDelay)
val snapshot3 = pager.asSnapshot()
- assertThat(snapshot3).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34, 35, 36, 37, 38, 39)
- )
+ assertThat(snapshot3)
+ .containsExactlyElementsIn(listOf(30, 31, 32, 33, 34, 35, 36, 37, 38, 39))
}
}
@@ -1096,23 +1008,13 @@
val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
val snapshot1 = pager.asSnapshot()
- assertThat(snapshot1).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot1).containsExactlyElementsIn(emptyList<Int>())
- val snapshot2 = pager.asSnapshot {
- dataFlow.emit(List(30) { it })
- }
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ val snapshot2 = pager.asSnapshot { dataFlow.emit(List(30) { it }) }
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
- val snapshot3 = pager.asSnapshot {
- dataFlow.emit(List(30) { it + 30 })
- }
- assertThat(snapshot3).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
+ val snapshot3 = pager.asSnapshot { dataFlow.emit(List(30) { it + 30 }) }
+ assertThat(snapshot3).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
}
}
@@ -1130,31 +1032,21 @@
testScope.runTest {
dataFlow.emit(emptyList())
val snapshot1 = pager.asSnapshot()
- assertThat(snapshot1).containsExactlyElementsIn(
- emptyList<Int>()
- )
+ assertThat(snapshot1).containsExactlyElementsIn(emptyList<Int>())
dataFlow.emit(List(30) { it })
val snapshot2 = pager.asSnapshot()
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
dataFlow.emit(List(30) { it + 30 })
val snapshot3 = pager.asSnapshot()
- assertThat(snapshot3).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
+ assertThat(snapshot3).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
}
}
- @Test
- fun consecutiveGenerations_nonNullRefreshKey_loadDelay0() =
- consecutiveGenerations(0)
+ @Test fun consecutiveGenerations_nonNullRefreshKey_loadDelay0() = consecutiveGenerations(0)
- @Test
- fun consecutiveGenerations_loadDelay10000() =
- consecutiveGenerations(10000)
+ @Test fun consecutiveGenerations_loadDelay10000() = consecutiveGenerations(10000)
private fun consecutiveGenerations(loadDelay: Long) {
val dataFlow = flow {
@@ -1167,22 +1059,17 @@
}
val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot1 = pager.asSnapshot {
- // we scroll to register a non-null anchorPos
- appendScrollWhile { item: Int ->
- item < 5
+ val snapshot1 =
+ pager.asSnapshot {
+ // we scroll to register a non-null anchorPos
+ appendScrollWhile { item: Int -> item < 5 }
}
- }
- assertThat(snapshot1).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot1).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
delay(1000)
val snapshot2 = pager.asSnapshot()
// anchorPos = 5, refreshKey = 3
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(3, 4, 5, 6, 7)
- )
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(3, 4, 5, 6, 7))
}
}
@@ -1203,127 +1090,172 @@
// second gen
emit(List(20) { it })
}
- val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPrefetch(dataFlow, loadDelay, 10).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot1 = pager.asSnapshot {
- // we scroll to register a non-null anchorPos
- appendScrollWhile { item: Int ->
- item < 15
+ val snapshot1 =
+ pager.asSnapshot {
+ // we scroll to register a non-null anchorPos
+ appendScrollWhile { item: Int -> item < 15 }
}
- }
- assertThat(snapshot1).containsExactlyElementsIn(
- listOf(10, 11, 12, 13, 14, 15, 16, 17)
- )
+ assertThat(snapshot1).containsExactlyElementsIn(listOf(10, 11, 12, 13, 14, 15, 16, 17))
delay(1000)
val snapshot2 = pager.asSnapshot()
// anchorPos = 15, refreshKey = 13
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(13, 14, 15, 16, 17)
- )
+ assertThat(snapshot2).containsExactlyElementsIn(listOf(13, 14, 15, 16, 17))
}
}
- @Test
- fun prependScroll_loadDelay0() = prependScroll(0)
+ @Test fun prependScroll_loadDelay0() = prependScroll(0)
- @Test
- fun prependScroll_loadDelay10000() = prependScroll(10000)
+ @Test fun prependScroll_loadDelay10000() = prependScroll(10000)
private fun prependScroll(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- }
+ val snapshot = pager.asSnapshot { scrollTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun prependScroll_withDrops_loadDelay0() = prependScroll_withDrops(0)
+ @Test fun prependScroll_withDrops_loadDelay0() = prependScroll_withDrops(0)
- @Test
- fun prependScroll_withDrops_loadDelay10000() = prependScroll_withDrops(10000)
+ @Test fun prependScroll_withDrops_loadDelay10000() = prependScroll_withDrops(10000)
private fun prependScroll_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- }
+ val snapshot = pager.asSnapshot { scrollTo(42) }
// dropped [47-57]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(38, 39, 40, 41, 42, 43, 44, 45, 46)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(38, 39, 40, 41, 42, 43, 44, 45, 46))
}
}
- @Test
- fun prependScroll_withSeparators_loadDelay0() = prependScroll_withSeparators(0)
+ @Test fun prependScroll_withSeparators_loadDelay0() = prependScroll_withSeparators(0)
- @Test
- fun prependScroll_withSeparators_loadDelay10000() = prependScroll_withSeparators(10000)
+ @Test fun prependScroll_withSeparators_loadDelay10000() = prependScroll_withSeparators(10000)
private fun prependScroll_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPager(dataFlow, loadDelay, 50).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 42 || before == 49) "sep" else null
+ val pager =
+ createPager(dataFlow, loadDelay, 50).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 42 || before == 49) "sep" else null
+ }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- }
+ val snapshot = pager.asSnapshot { scrollTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, "sep", 43, 44, 45, 46, 47, 48, 49, "sep", 50, 51, 52,
- 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ "sep",
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ "sep",
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun consecutivePrependScroll_loadDelay0() = consecutivePrependScroll(0)
+ @Test fun consecutivePrependScroll_loadDelay0() = consecutivePrependScroll(0)
- @Test
- fun consecutivePrependScroll_loadDelay10000() = consecutivePrependScroll(10000)
+ @Test fun consecutivePrependScroll_loadDelay10000() = consecutivePrependScroll(10000)
private fun consecutivePrependScroll(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- scrollTo(38)
- }
+ val snapshot =
+ pager.asSnapshot {
+ scrollTo(42)
+ scrollTo(38)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [38-46]
// prefetched [35-37]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
- 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -1339,34 +1271,71 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- }
+ val snapshot = pager.asSnapshot { scrollTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
- val snapshot2 = pager.asSnapshot {
- scrollTo(38)
- }
+ val snapshot2 = pager.asSnapshot { scrollTo(38) }
// prefetched [35-37]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
- 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun prependScroll_indexOutOfBounds_loadDelay0() = prependScroll_indexOutOfBounds(0)
+ @Test fun prependScroll_indexOutOfBounds_loadDelay0() = prependScroll_indexOutOfBounds(0)
@Test
fun prependScroll_indexOutOfBounds_loadDelay10000() = prependScroll_indexOutOfBounds(10000)
@@ -1375,21 +1344,17 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 5).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(-5)
- }
+ val snapshot = pager.asSnapshot { scrollTo(-5) }
// ensure index is capped when no more data to load
// initial load [5-9]
// prefetched [2-4], [10-12]
// scrollTo prepended [0-1]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
}
}
- @Test
- fun prependScroll_accessPageBoundary_loadDelay0() = prependScroll_accessPageBoundary(0)
+ @Test fun prependScroll_accessPageBoundary_loadDelay0() = prependScroll_accessPageBoundary(0)
@Test
fun prependScroll_accessPageBoundary_loadDelay10000() = prependScroll_accessPageBoundary(10000)
@@ -1398,42 +1363,37 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(47)
- }
+ val snapshot = pager.asSnapshot { scrollTo(47) }
// ensure that SnapshotLoader waited for last prefetch before returning
// initial load [50-54]
// prefetched [47-49], [55-57] - expect only one extra page to be prefetched after this
// scrollTo prepended [44-46]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
}
}
- @Test
- fun prependScroll_withoutPrefetch_loadDelay0() = prependScroll_withoutPrefetch(0)
+ @Test fun prependScroll_withoutPrefetch_loadDelay0() = prependScroll_withoutPrefetch(0)
- @Test
- fun prependScroll_withoutPrefetch_loadDelay10000() = prependScroll_withoutPrefetch(10000)
+ @Test fun prependScroll_withoutPrefetch_loadDelay10000() = prependScroll_withoutPrefetch(10000)
private fun prependScroll_withoutPrefetch(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(42)
- }
+ val snapshot = pager.asSnapshot { scrollTo(42) }
// initial load [50-54]
// prepended [41-49]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54)
+ )
}
}
- @Test
- fun prependScroll_withoutPlaceholders_loadDelay0() = prependScroll_withoutPlaceholders(0)
+ @Test fun prependScroll_withoutPlaceholders_loadDelay0() = prependScroll_withoutPlaceholders(0)
@Test
fun prependScroll_withoutPlaceholders_loadDelay10000() =
@@ -1441,19 +1401,21 @@
private fun prependScroll_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(0)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(0)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [44-46]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
}
}
@@ -1467,22 +1429,40 @@
private fun prependScroll_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(-5)
- }
+ val snapshot = pager.asSnapshot { scrollTo(-5) }
// ensure it honors negative indices starting with index[0] = item[47]
// initial load [50-54]
// prefetched [47-49], [55-57]
// scrollTo prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -1496,19 +1476,16 @@
private fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 5)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 5).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(-5)
- }
+ val snapshot = pager.asSnapshot { scrollTo(-5) }
// ensure index is capped when no more data to load
// initial load [5-9]
// prefetched [2-4], [10-12]
// scrollTo prepended [0-1]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
}
}
@@ -1522,27 +1499,53 @@
private fun consecutivePrependScroll_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(-1)
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(-5)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(-1)
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(-5)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// first scrollTo prepended [41-46]
// index[0] is now anchored to [41]
// second scrollTo prepended [35-40]
// prefetched [32-34]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
- 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -1556,33 +1559,61 @@
private fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(-1)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(-1)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// scrollTo prepended [44-46]
// prefetched [41-43]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
- val snapshot2 = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(-5)
- }
+ val snapshot2 =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(-5)
+ }
// scrollTo prepended [35-40]
// prefetched [32-34]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(
- 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
- 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -1596,127 +1627,144 @@
private fun prependScroll_withoutPlaceholders_noPrefetchTriggered(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = Pager(
- config = PagingConfig(
- pageSize = 4,
- initialLoadSize = 8,
- enablePlaceholders = false,
- // a small prefetchDistance to prevent prefetch until we scroll to boundary
- prefetchDistance = 1
- ),
- initialKey = 50,
- pagingSourceFactory = createFactory(dataFlow, loadDelay),
- ).flow.cachedIn(testScope.backgroundScope)
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 4,
+ initialLoadSize = 8,
+ enablePlaceholders = false,
+ // a small prefetchDistance to prevent prefetch until we scroll to
+ // boundary
+ prefetchDistance = 1
+ ),
+ initialKey = 50,
+ pagingSourceFactory = createFactory(dataFlow, loadDelay),
+ )
+ .flow
+ .cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- scrollTo(0)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ scrollTo(0)
+ }
// initial load [50-57]
// no prefetch after initial load because it didn't hit prefetch distance
// scrollTo prepended [46-49]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57))
}
}
- @Test
- fun appendScroll_loadDelay0() = appendScroll(0)
+ @Test fun appendScroll_loadDelay0() = appendScroll(0)
- @Test
- fun appendScroll_loadDelay10000() = appendScroll(10000)
+ @Test fun appendScroll_loadDelay10000() = appendScroll(10000)
private fun appendScroll(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- }
+ val snapshot = pager.asSnapshot { scrollTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
}
}
- @Test
- fun appendScroll_withDrops_loadDelay0() = appendScroll_withDrops(0)
+ @Test fun appendScroll_withDrops_loadDelay0() = appendScroll_withDrops(0)
- @Test
- fun appendScroll_withDrops_loadDelay10000() = appendScroll_withDrops(10000)
+ @Test fun appendScroll_withDrops_loadDelay10000() = appendScroll_withDrops(10000)
private fun appendScroll_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- }
+ val snapshot = pager.asSnapshot { scrollTo(12) }
// dropped [0-7]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(8, 9, 10, 11, 12, 13, 14, 15, 16))
}
}
- @Test
- fun appendScroll_withSeparators_loadDelay0() = appendScroll_withSeparators(0)
+ @Test fun appendScroll_withSeparators_loadDelay0() = appendScroll_withSeparators(0)
- @Test
- fun appendScroll_withSeparators_loadDelay10000() = appendScroll_withSeparators(10000)
+ @Test fun appendScroll_withSeparators_loadDelay10000() = appendScroll_withSeparators(10000)
private fun appendScroll_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPager(dataFlow, loadDelay).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 0 || before == 14) "sep" else null
+ val pager =
+ createPager(dataFlow, loadDelay).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 0 || before == 14) "sep" else null
+ }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- }
+ val snapshot = pager.asSnapshot { scrollTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, "sep", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, "sep", 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, "sep", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, "sep", 15, 16)
+ )
}
}
- @Test
- fun consecutiveAppendScroll_loadDelay0() = consecutiveAppendScroll(0)
+ @Test fun consecutiveAppendScroll_loadDelay0() = consecutiveAppendScroll(0)
- @Test
- fun consecutiveAppendScroll_loadDelay10000() = consecutiveAppendScroll(10000)
+ @Test fun consecutiveAppendScroll_loadDelay10000() = consecutiveAppendScroll(10000)
private fun consecutiveAppendScroll(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- scrollTo(18)
- }
+ val snapshot =
+ pager.asSnapshot {
+ scrollTo(12)
+ scrollTo(18)
+ }
// initial load [0-4]
// prefetched [5-7]
// appended [8-19]
// prefetched [20-22]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
- @Test
+ @Test
fun consecutiveAppendScroll_multiSnapshots_loadDelay0() =
consecutiveAppendScroll_multiSnapshots(0)
@@ -1728,55 +1776,72 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- }
+ val snapshot = pager.asSnapshot { scrollTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
- val snapshot2 = pager.asSnapshot {
- scrollTo(18)
- }
+ val snapshot2 = pager.asSnapshot { scrollTo(18) }
// appended [17-19]
// prefetched [20-22]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
- 18, 19, 20, 21, 22
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
)
- )
}
}
- @Test
- fun appendScroll_indexOutOfBounds_loadDelay0() = appendScroll_indexOutOfBounds(0)
+ @Test fun appendScroll_indexOutOfBounds_loadDelay0() = appendScroll_indexOutOfBounds(0)
- @Test
- fun appendScroll_indexOutOfBounds_loadDelay10000() = appendScroll_indexOutOfBounds(10000)
+ @Test fun appendScroll_indexOutOfBounds_loadDelay10000() = appendScroll_indexOutOfBounds(10000)
private fun appendScroll_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(15) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // index out of bounds
- scrollTo(50)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // index out of bounds
+ scrollTo(50)
+ }
// initial load [0-4]
// prefetched [5-7]
// scrollTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
}
}
- @Test
- fun appendScroll_accessPageBoundary_loadDelay0() = appendScroll_accessPageBoundary(0)
+ @Test fun appendScroll_accessPageBoundary_loadDelay0() = appendScroll_accessPageBoundary(0)
@Test
fun appendScroll_accessPageBoundary_loadDelay10000() = appendScroll_accessPageBoundary(10000)
@@ -1785,62 +1850,53 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // after initial Load and prefetch, max loaded index is 7
- scrollTo(7)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // after initial Load and prefetch, max loaded index is 7
+ scrollTo(7)
+ }
// ensure that SnapshotLoader waited for last prefetch before returning
// initial load [0-4]
// prefetched [5-7] - expect only one extra page to be prefetched after this
// scrollTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
- @Test
- fun appendScroll_withoutPrefetch_loadDelay0() = appendScroll_withoutPrefetch(0)
+ @Test fun appendScroll_withoutPrefetch_loadDelay0() = appendScroll_withoutPrefetch(0)
- @Test
- fun appendScroll_withoutPrefetch_loadDelay10000() = appendScroll_withoutPrefetch(10000)
+ @Test fun appendScroll_withoutPrefetch_loadDelay10000() = appendScroll_withoutPrefetch(10000)
private fun appendScroll_withoutPrefetch(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerNoPrefetch(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(10)
- }
+ val snapshot = pager.asSnapshot { scrollTo(10) }
// initial load [0-4]
// appended [5-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
- @Test
- fun appendScroll_withoutPlaceholders_loadDelay0() = appendScroll_withoutPlaceholders(0)
+ @Test fun appendScroll_withoutPlaceholders_loadDelay0() = appendScroll_withoutPlaceholders(0)
@Test
fun appendScroll_withoutPlaceholders_loadDelay10000() = appendScroll_withoutPlaceholders(10000)
private fun appendScroll_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // scroll to max loaded index
- scrollTo(7)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // scroll to max loaded index
+ scrollTo(7)
+ }
// initial load [0-4]
// prefetched [5-7]
// scrollTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
@@ -1854,21 +1910,23 @@
private fun appendScroll_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(20) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // 12 is larger than differ.size = 8 after initial refresh
- scrollTo(12)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // 12 is larger than differ.size = 8 after initial refresh
+ scrollTo(12)
+ }
// ensure it honors scrollTo indices >= differ.size
// initial load [0-4]
// prefetched [5-7]
// scrollTo appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
}
}
@@ -1882,19 +1940,18 @@
private fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
val dataFlow = flowOf(List(20) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(50)
- }
+ val snapshot = pager.asSnapshot { scrollTo(50) }
// ensure index is still capped to max index available
// initial load [0-4]
// prefetched [5-7]
// scrollTo appended [8-19]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+ )
}
}
@@ -1908,22 +1965,47 @@
private fun consecutiveAppendScroll_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- scrollTo(17)
- }
+ val snapshot =
+ pager.asSnapshot {
+ scrollTo(12)
+ scrollTo(17)
+ }
// initial load [0-4]
// prefetched [5-7]
// first scrollTo appended [8-13]
// second scrollTo appended [14-19]
// prefetched [19-22]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
@@ -1937,32 +2019,53 @@
private fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(12)
- }
+ val snapshot = pager.asSnapshot { scrollTo(12) }
// initial load [0-4]
// prefetched [5-7]
// scrollTo appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
- val snapshot2 = pager.asSnapshot {
- scrollTo(17)
- }
+ val snapshot2 = pager.asSnapshot { scrollTo(17) }
// initial load [0-4]
// prefetched [5-7]
// first scrollTo appended [8-13]
// second scrollTo appended [14-19]
// prefetched [19-22]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
@@ -1976,135 +2079,178 @@
private fun scrollTo_indexAccountsForSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
- val pagerWithSeparator = pager.map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 6) "sep" else null
+ val pagerWithSeparator =
+ pager.map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ -> if (before == 6) "sep" else null }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(8)
- }
+ val snapshot = pager.asSnapshot { scrollTo(8) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-10]
// prefetched [11-13]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))
- val snapshotWithSeparator = pagerWithSeparator.asSnapshot {
- scrollTo(8)
- }
+ val snapshotWithSeparator = pagerWithSeparator.asSnapshot { scrollTo(8) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-10]
// no prefetch on [11-13] because separator fulfilled prefetchDistance
- assertThat(snapshotWithSeparator).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, "sep", 7, 8, 9, 10)
- )
+ assertThat(snapshotWithSeparator)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, "sep", 7, 8, 9, 10))
}
}
- @Test
- fun prependFling_loadDelay0() = prependFling(0)
+ @Test fun prependFling_loadDelay0() = prependFling(0)
- @Test
- fun prependFling_loadDelay10000() = prependFling(10000)
+ @Test fun prependFling_loadDelay10000() = prependFling(10000)
private fun prependFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(42)
- }
+ val snapshot = pager.asSnapshot { flingTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun prependFling_withDrops_loadDelay0() = prependFling_withDrops(0)
+ @Test fun prependFling_withDrops_loadDelay0() = prependFling_withDrops(0)
- @Test
- fun prependFling_withDrops_loadDelay10000() = prependFling_withDrops(10000)
+ @Test fun prependFling_withDrops_loadDelay10000() = prependFling_withDrops(10000)
private fun prependFling_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(42)
- }
+ val snapshot = pager.asSnapshot { flingTo(42) }
// dropped [47-57]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(38, 39, 40, 41, 42, 43, 44, 45, 46)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(38, 39, 40, 41, 42, 43, 44, 45, 46))
}
}
- @Test
- fun prependFling_withSeparators_loadDelay0() = prependFling_withSeparators(0)
+ @Test fun prependFling_withSeparators_loadDelay0() = prependFling_withSeparators(0)
- @Test
- fun prependFling_withSeparators_loadDelay10000() = prependFling_withSeparators(10000)
+ @Test fun prependFling_withSeparators_loadDelay10000() = prependFling_withSeparators(10000)
private fun prependFling_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPager(dataFlow, loadDelay, 50).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 42 || before == 49) "sep" else null
+ val pager =
+ createPager(dataFlow, loadDelay, 50).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 42 || before == 49) "sep" else null
+ }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(42)
- }
+ val snapshot = pager.asSnapshot { flingTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, "sep", 43, 44, 45, 46, 47, 48, 49, "sep", 50, 51, 52,
- 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ "sep",
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ "sep",
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun consecutivePrependFling_loadDelay0() = consecutivePrependFling(0)
+ @Test fun consecutivePrependFling_loadDelay0() = consecutivePrependFling(0)
- @Test
- fun consecutivePrependFling_loadDelay10000() = consecutivePrependFling(10000)
+ @Test fun consecutivePrependFling_loadDelay10000() = consecutivePrependFling(10000)
private fun consecutivePrependFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(42)
- flingTo(38)
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(42)
+ flingTo(38)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [38-46]
// prefetched [35-37]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
- 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -2120,126 +2266,156 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(42)
- }
+ val snapshot = pager.asSnapshot { flingTo(42) }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [41-46]
// prefetched [38-40]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
- val snapshot2 = pager.asSnapshot {
- flingTo(38)
- }
+ val snapshot2 = pager.asSnapshot { flingTo(38) }
// prefetched [35-37]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
- 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
- @Test
- fun prependFling_jump_loadDelay0() = prependFling_jump(0)
+ @Test fun prependFling_jump_loadDelay0() = prependFling_jump(0)
- @Test
- fun prependFling_jump_loadDelay10000() = prependFling_jump(10000)
+ @Test fun prependFling_jump_loadDelay10000() = prependFling_jump(10000)
private fun prependFling_jump(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(30)
- // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(30)
+ // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+ }
// initial load [28-32]
// prefetched [25-27], [33-35]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35))
}
}
- @Test
- fun prependFling_scrollThenJump_loadDelay0() = prependFling_scrollThenJump(0)
+ @Test fun prependFling_scrollThenJump_loadDelay0() = prependFling_scrollThenJump(0)
- @Test
- fun prependFling_scrollThenJump_loadDelay10000() = prependFling_scrollThenJump(10000)
+ @Test fun prependFling_scrollThenJump_loadDelay10000() = prependFling_scrollThenJump(10000)
private fun prependFling_scrollThenJump(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(43)
- flingTo(30)
- // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
- }
+ val snapshot =
+ pager.asSnapshot {
+ scrollTo(43)
+ flingTo(30)
+ // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+ }
// initial load [28-32]
// prefetched [25-27], [33-35]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35))
}
}
- @Test
- fun prependFling_jumpThenFling_loadDelay0() = prependFling_jumpThenFling(0)
+ @Test fun prependFling_jumpThenFling_loadDelay0() = prependFling_jumpThenFling(0)
- @Test
- fun prependFling_jumpThenFling_loadDelay10000() = prependFling_jumpThenFling(10000)
+ @Test fun prependFling_jumpThenFling_loadDelay10000() = prependFling_jumpThenFling(10000)
private fun prependFling_jumpThenFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(30)
- // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
- flingTo(22)
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(30)
+ // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+ flingTo(22)
+ }
// initial load [28-32]
// prefetched [25-27], [33-35]
// flingTo prepended [22-24]
// prefetched [19-21]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
+ )
}
}
- @Test
- fun prependFling_indexOutOfBounds_loadDelay0() = prependFling_indexOutOfBounds(0)
+ @Test fun prependFling_indexOutOfBounds_loadDelay0() = prependFling_indexOutOfBounds(0)
- @Test
- fun prependFling_indexOutOfBounds_loadDelay10000() = prependFling_indexOutOfBounds(10000)
+ @Test fun prependFling_indexOutOfBounds_loadDelay10000() = prependFling_indexOutOfBounds(10000)
private fun prependFling_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 10)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(-3)
- }
+ val snapshot = pager.asSnapshot { flingTo(-3) }
// initial load [10-14]
// prefetched [7-9], [15-17]
// flingTo prepended [0-6]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
+ )
}
}
- @Test
- fun prependFling_accessPageBoundary_loadDelay0() = prependFling_accessPageBoundary(0)
+ @Test fun prependFling_accessPageBoundary_loadDelay0() = prependFling_accessPageBoundary(0)
@Test
fun prependFling_accessPageBoundary_loadDelay10000() = prependFling_accessPageBoundary(10000)
@@ -2248,42 +2424,45 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // page boundary
- flingTo(44)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // page boundary
+ flingTo(44)
+ }
// ensure that SnapshotLoader waited for last prefetch before returning
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [44-46] - expect only one extra page to be prefetched after this
// prefetched [41-43]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
}
}
- @Test
- fun prependFling_withoutPlaceholders_loadDelay0() = prependFling_withoutPlaceholders(0)
+ @Test fun prependFling_withoutPlaceholders_loadDelay0() = prependFling_withoutPlaceholders(0)
@Test
fun prependFling_withoutPlaceholders_loadDelay10000() = prependFling_withoutPlaceholders(10000)
private fun prependFling_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- flingTo(0)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ flingTo(0)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [44-46]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
}
}
@@ -2299,20 +2478,40 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(-8)
- }
+ val snapshot = pager.asSnapshot { flingTo(-8) }
// ensure we honor negative indices if there is data to load
// initial load [50-54]
// prefetched [47-49], [55-57]
// prepended [38-46]
// prefetched [35-37]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -2326,19 +2525,16 @@
private fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 5)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 5).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(-20)
- }
+ val snapshot = pager.asSnapshot { flingTo(-20) }
// ensure index is capped when no more data to load
// initial load [5-9]
// prefetched [2-4], [10-12]
// flingTo prepended [0-1]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
}
}
@@ -2352,27 +2548,53 @@
private fun consecutivePrependFling_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- flingTo(-1)
- // Without placeholders, first loaded page always starts at index[0]
- flingTo(-5)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ flingTo(-1)
+ // Without placeholders, first loaded page always starts at index[0]
+ flingTo(-5)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// first flingTo prepended [41-46]
// index[0] is now anchored to [41]
// second flingTo prepended [35-40]
// prefetched [32-34]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(
- 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
- 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -2386,33 +2608,61 @@
private fun consecutivePrependFling_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- flingTo(-1)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ flingTo(-1)
+ }
// initial load [50-54]
// prefetched [47-49], [55-57]
// flingTo prepended [44-46]
// prefetched [41-43]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+ )
- val snapshot2 = pager.asSnapshot {
- // Without placeholders, first loaded page always starts at index[0]
- flingTo(-5)
- }
+ val snapshot2 =
+ pager.asSnapshot {
+ // Without placeholders, first loaded page always starts at index[0]
+ flingTo(-5)
+ }
// flingTo prepended [35-40]
// prefetched [32-34]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(
- 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
- 50, 51, 52, 53, 54, 55, 56, 57
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57
+ )
)
- )
}
}
@@ -2427,123 +2677,136 @@
private fun prependFling_withoutPlaceholders_indexPrecision(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
// load sizes and prefetch set to 1 to test precision of flingTo indexing
- val pager = Pager(
- config = PagingConfig(
- pageSize = 1,
- initialLoadSize = 1,
- enablePlaceholders = false,
- prefetchDistance = 1
- ),
- initialKey = 50,
- pagingSourceFactory = createFactory(dataFlow, loadDelay),
- )
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 1,
+ initialLoadSize = 1,
+ enablePlaceholders = false,
+ prefetchDistance = 1
+ ),
+ initialKey = 50,
+ pagingSourceFactory = createFactory(dataFlow, loadDelay),
+ )
testScope.runTest {
- val snapshot = pager.flow.asSnapshot {
- // after refresh, lastAccessedIndex == index[2] == item(9)
- flingTo(-1)
- }
+ val snapshot =
+ pager.flow.asSnapshot {
+ // after refresh, lastAccessedIndex == index[2] == item(9)
+ flingTo(-1)
+ }
// initial load [50]
// prefetched [49], [51]
// prepended [48]
// prefetched [47]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(47, 48, 49, 50, 51)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(47, 48, 49, 50, 51))
}
}
- @Test
- fun appendFling_loadDelay0() = appendFling(0)
+ @Test fun appendFling_loadDelay0() = appendFling(0)
- @Test
- fun appendFling_loadDelay10000() = appendFling(10000)
+ @Test fun appendFling_loadDelay10000() = appendFling(10000)
private fun appendFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- }
+ val snapshot = pager.asSnapshot { flingTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
}
}
- @Test
- fun appendFling_withDrops_loadDelay0() = appendFling_withDrops(0)
+ @Test fun appendFling_withDrops_loadDelay0() = appendFling_withDrops(0)
- @Test
- fun appendFling_withDrops_loadDelay10000() = appendFling_withDrops(10000)
+ @Test fun appendFling_withDrops_loadDelay10000() = appendFling_withDrops(10000)
private fun appendFling_withDrops(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithDrops(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- }
+ val snapshot = pager.asSnapshot { flingTo(12) }
// dropped [0-7]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(8, 9, 10, 11, 12, 13, 14, 15, 16))
}
}
- @Test
- fun appendFling_withSeparators_loadDelay0() = appendFling_withSeparators(0)
+ @Test fun appendFling_withSeparators_loadDelay0() = appendFling_withSeparators(0)
- @Test
- fun appendFling_withSeparators_loadDelay10000() = appendFling_withSeparators(10000)
+ @Test fun appendFling_withSeparators_loadDelay10000() = appendFling_withSeparators(10000)
private fun appendFling_withSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPager(dataFlow, loadDelay).map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 0 || before == 14) "sep" else null
+ val pager =
+ createPager(dataFlow, loadDelay).map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ ->
+ if (before == 0 || before == 14) "sep" else null
+ }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- }
+ val snapshot = pager.asSnapshot { flingTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, "sep", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, "sep", 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, "sep", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, "sep", 15, 16)
+ )
}
}
- @Test
- fun consecutiveAppendFling_loadDelay0() = consecutiveAppendFling(0)
+ @Test fun consecutiveAppendFling_loadDelay0() = consecutiveAppendFling(0)
- @Test
- fun consecutiveAppendFling_loadDelay10000() = consecutiveAppendFling(10000)
+ @Test fun consecutiveAppendFling_loadDelay10000() = consecutiveAppendFling(10000)
private fun consecutiveAppendFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- flingTo(18)
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(12)
+ flingTo(18)
+ }
// initial load [0-4]
// prefetched [5-7]
// appended [8-19]
// prefetched [20-22]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
@@ -2559,125 +2822,138 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- }
+ val snapshot = pager.asSnapshot { flingTo(12) }
// initial load [0-4]
// prefetched [5-7]
// appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
- val snapshot2 = pager.asSnapshot {
- flingTo(18)
- }
+ val snapshot2 = pager.asSnapshot { flingTo(18) }
// appended [17-19]
// prefetched [20-22]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
- 18, 19, 20, 21, 22
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
)
- )
}
}
- @Test
- fun appendFling_jump_loadDelay0() = appendFling_jump(0)
+ @Test fun appendFling_jump_loadDelay0() = appendFling_jump(0)
- @Test
- fun appendFling_jump_loadDelay10000() = appendFling_jump(10000)
+ @Test fun appendFling_jump_loadDelay10000() = appendFling_jump(10000)
private fun appendFling_jump(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(30)
- // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(30)
+ // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+ }
// initial load [28-32]
// prefetched [25-27], [33-35]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35))
}
}
- @Test
- fun appendFling_scrollThenJump_loadDelay0() = appendFling_scrollThenJump(0)
+ @Test fun appendFling_scrollThenJump_loadDelay0() = appendFling_scrollThenJump(0)
- @Test
- fun appendFling_scrollThenJump_loadDelay10000() = appendFling_scrollThenJump(10000)
+ @Test fun appendFling_scrollThenJump_loadDelay10000() = appendFling_scrollThenJump(10000)
private fun appendFling_scrollThenJump(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- scrollTo(30)
- flingTo(43)
- // jump triggered when flingTo registered lastAccessedIndex[43], refreshKey[41]
- }
+ val snapshot =
+ pager.asSnapshot {
+ scrollTo(30)
+ flingTo(43)
+ // jump triggered when flingTo registered lastAccessedIndex[43], refreshKey[41]
+ }
// initial load [41-45]
// prefetched [38-40], [46-48]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48))
}
}
- @Test
- fun appendFling_jumpThenFling_loadDelay0() = appendFling_jumpThenFling(0)
+ @Test fun appendFling_jumpThenFling_loadDelay0() = appendFling_jumpThenFling(0)
- @Test
- fun appendFling_jumpThenFling_loadDelay10000() = appendFling_jumpThenFling(10000)
+ @Test fun appendFling_jumpThenFling_loadDelay10000() = appendFling_jumpThenFling(10000)
private fun appendFling_jumpThenFling(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
val pager = createPagerWithJump(dataFlow, loadDelay)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(30)
- // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
- flingTo(38)
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(30)
+ // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+ flingTo(38)
+ }
// initial load [28-32]
// prefetched [25-27], [33-35]
// flingTo appended [36-38]
// prefetched [39-41]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41)
+ )
}
}
- @Test
- fun appendFling_indexOutOfBounds_loadDelay0() = appendFling_indexOutOfBounds(0)
+ @Test fun appendFling_indexOutOfBounds_loadDelay0() = appendFling_indexOutOfBounds(0)
- @Test
- fun appendFling_indexOutOfBounds_loadDelay10000() = appendFling_indexOutOfBounds(10000)
+ @Test fun appendFling_indexOutOfBounds_loadDelay10000() = appendFling_indexOutOfBounds(10000)
private fun appendFling_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(15) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // index out of bounds
- flingTo(50)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // index out of bounds
+ flingTo(50)
+ }
// initial load [0-4]
// prefetched [5-7]
// flingTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
}
}
- @Test
- fun appendFling_accessPageBoundary_loadDelay0() = appendFling_accessPageBoundary(0)
+ @Test fun appendFling_accessPageBoundary_loadDelay0() = appendFling_accessPageBoundary(0)
@Test
fun appendFling_accessPageBoundary_loadDelay10000() = appendFling_accessPageBoundary(10000)
@@ -2686,41 +2962,38 @@
val dataFlow = flowOf(List(100) { it })
val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // after initial Load and prefetch, max loaded index is 7
- flingTo(7)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // after initial Load and prefetch, max loaded index is 7
+ flingTo(7)
+ }
// ensure that SnapshotLoader waited for last prefetch before returning
// initial load [0-4]
// prefetched [5-7] - expect only one extra page to be prefetched after this
// flingTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
- @Test
- fun appendFling_withoutPlaceholders_loadDelay0() = appendFling_withoutPlaceholders(0)
+ @Test fun appendFling_withoutPlaceholders_loadDelay0() = appendFling_withoutPlaceholders(0)
@Test
fun appendFling_withoutPlaceholders_loadDelay10000() = appendFling_withoutPlaceholders(10000)
private fun appendFling_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // scroll to max loaded index
- flingTo(7)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // scroll to max loaded index
+ flingTo(7)
+ }
// initial load [0-4]
// prefetched [5-7]
// flingTo appended [8-10]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
@@ -2734,21 +3007,23 @@
private fun appendFling_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
val dataFlow = flowOf(List(20) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- // 12 is larger than differ.size = 8 after initial refresh
- flingTo(12)
- }
+ val snapshot =
+ pager.asSnapshot {
+ // 12 is larger than differ.size = 8 after initial refresh
+ flingTo(12)
+ }
// ensure it honors scrollTo indices >= differ.size
// initial load [0-4]
// prefetched [5-7]
// flingTo appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
}
}
@@ -2762,19 +3037,18 @@
private fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
val dataFlow = flowOf(List(20) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(50)
- }
+ val snapshot = pager.asSnapshot { flingTo(50) }
// ensure index is still capped to max index available
// initial load [0-4]
// prefetched [5-7]
// flingTo appended [8-19]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+ )
}
}
@@ -2788,22 +3062,47 @@
private fun consecutiveAppendFling_withoutPlaceholders(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- flingTo(17)
- }
+ val snapshot =
+ pager.asSnapshot {
+ flingTo(12)
+ flingTo(17)
+ }
// initial load [0-4]
// prefetched [5-7]
// first flingTo appended [8-13]
// second flingTo appended [14-19]
// prefetched [19-22]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
@@ -2817,32 +3116,53 @@
private fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
- .cachedIn(testScope.backgroundScope)
+ val pager =
+ createPagerNoPlaceholders(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(12)
- }
+ val snapshot = pager.asSnapshot { flingTo(12) }
// initial load [0-4]
// prefetched [5-7]
// flingTo appended [8-13]
// prefetched [14-16]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
- )
+ assertThat(snapshot)
+ .containsExactlyElementsIn(
+ listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ )
- val snapshot2 = pager.asSnapshot {
- flingTo(17)
- }
+ val snapshot2 = pager.asSnapshot { flingTo(17) }
// initial load [0-4]
// prefetched [5-7]
// first flingTo appended [8-13]
// second flingTo appended [14-19]
// prefetched [19-22]
- assertThat(snapshot2).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22)
- )
+ assertThat(snapshot2)
+ .containsExactlyElementsIn(
+ listOf(
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22
+ )
+ )
}
}
@@ -2857,27 +3177,28 @@
private fun appendFling_withoutPlaceholders_indexPrecision(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
// load sizes and prefetch set to 1 to test precision of flingTo indexing
- val pager = Pager(
- config = PagingConfig(
- pageSize = 1,
- initialLoadSize = 1,
- enablePlaceholders = false,
- prefetchDistance = 1
- ),
- pagingSourceFactory = createFactory(dataFlow, loadDelay),
- )
+ val pager =
+ Pager(
+ config =
+ PagingConfig(
+ pageSize = 1,
+ initialLoadSize = 1,
+ enablePlaceholders = false,
+ prefetchDistance = 1
+ ),
+ pagingSourceFactory = createFactory(dataFlow, loadDelay),
+ )
testScope.runTest {
- val snapshot = pager.flow.asSnapshot {
- // after refresh, lastAccessedIndex == index[2] == item(9)
- flingTo(2)
- }
+ val snapshot =
+ pager.flow.asSnapshot {
+ // after refresh, lastAccessedIndex == index[2] == item(9)
+ flingTo(2)
+ }
// initial load [0]
// prefetched [1]
// appended [2]
// prefetched [3]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3))
}
}
@@ -2890,188 +3211,174 @@
private fun flingTo_indexAccountsForSeparators(loadDelay: Long) {
val dataFlow = flowOf(List(100) { it })
- val pager = createPager(
- dataFlow,
- PagingConfig(
- pageSize = 1,
- initialLoadSize = 1,
- prefetchDistance = 1
- ),
- loadDelay,
- 50
- )
- val pagerWithSeparator = pager.map { pagingData ->
- pagingData.insertSeparators { before: Int?, _ ->
- if (before == 49) "sep" else null
+ val pager =
+ createPager(
+ dataFlow,
+ PagingConfig(pageSize = 1, initialLoadSize = 1, prefetchDistance = 1),
+ loadDelay,
+ 50
+ )
+ val pagerWithSeparator =
+ pager.map { pagingData ->
+ pagingData.insertSeparators { before: Int?, _ -> if (before == 49) "sep" else null }
}
- }
testScope.runTest {
- val snapshot = pager.asSnapshot {
- flingTo(51)
- }
+ val snapshot = pager.asSnapshot { flingTo(51) }
// initial load [50]
// prefetched [49], [51]
// flingTo [51] accessed item[51]prefetched [52]
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(49, 50, 51, 52)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(49, 50, 51, 52))
- val snapshotWithSeparator = pagerWithSeparator.asSnapshot {
- flingTo(51)
- }
+ val snapshotWithSeparator = pagerWithSeparator.asSnapshot { flingTo(51) }
// initial load [50]
// prefetched [49], [51]
// flingTo [51] accessed item[50], no prefetch triggered
- assertThat(snapshotWithSeparator).containsExactlyElementsIn(
- listOf(49, "sep", 50, 51)
- )
+ assertThat(snapshotWithSeparator).containsExactlyElementsIn(listOf(49, "sep", 50, 51))
}
}
- @Test
- fun errorHandler_throw_loadDelay0() = errorHandler_throw(0)
+ @Test fun errorHandler_throw_loadDelay0() = errorHandler_throw(0)
- @Test
- fun errorHandler_throw_loadDelay10000() = errorHandler_throw(10000)
+ @Test fun errorHandler_throw_loadDelay10000() = errorHandler_throw(10000)
private fun errorHandler_throw(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val factory = createFactory(dataFlow, loadDelay)
val pagingSources = mutableListOf<TestPagingSource>()
- val pager = Pager(
- config = PagingConfig(pageSize = 3, initialLoadSize = 5),
- pagingSourceFactory = {
- factory.invoke().also { pagingSources.add(it as TestPagingSource) }
- },
- ).flow
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+ pagingSourceFactory = {
+ factory.invoke().also { pagingSources.add(it as TestPagingSource) }
+ },
+ )
+ .flow
testScope.runTest {
- val error = assertFailsWith(IllegalArgumentException::class) {
- pager.asSnapshot(onError = { ErrorRecovery.THROW }) {
- val source = pagingSources.first()
- source.errorOnNextLoad = true
- scrollTo(12)
+ val error =
+ assertFailsWith(IllegalArgumentException::class) {
+ pager.asSnapshot(onError = { ErrorRecovery.THROW }) {
+ val source = pagingSources.first()
+ source.errorOnNextLoad = true
+ scrollTo(12)
+ }
}
- }
assertThat(error.message).isEqualTo("PagingSource load error")
}
}
- @Test
- fun errorHandler_retry_loadDelay0() = errorHandler_retry(0)
+ @Test fun errorHandler_retry_loadDelay0() = errorHandler_retry(0)
- @Test
- fun errorHandler_retry_loadDelay10000() = errorHandler_retry(10000)
+ @Test fun errorHandler_retry_loadDelay10000() = errorHandler_retry(10000)
private fun errorHandler_retry(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val factory = createFactory(dataFlow, loadDelay)
val pagingSources = mutableListOf<TestPagingSource>()
- val pager = Pager(
- config = PagingConfig(pageSize = 3, initialLoadSize = 5),
- pagingSourceFactory = {
- factory.invoke().also { pagingSources.add(it as TestPagingSource) }
- },
- ).flow
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+ pagingSourceFactory = {
+ factory.invoke().also { pagingSources.add(it as TestPagingSource) }
+ },
+ )
+ .flow
testScope.runTest {
- val snapshot = pager.asSnapshot(onError = { ErrorRecovery.RETRY }) {
- val source = pagingSources.first()
- // should have two loads to far - refresh and append(prefetch)
- assertThat(source.loads.size).isEqualTo(2)
+ val snapshot =
+ pager.asSnapshot(onError = { ErrorRecovery.RETRY }) {
+ val source = pagingSources.first()
+ // should have two loads to far - refresh and append(prefetch)
+ assertThat(source.loads.size).isEqualTo(2)
- // throw error on next load, should trigger a retry
- source.errorOnNextLoad = true
- scrollTo(7)
+ // throw error on next load, should trigger a retry
+ source.errorOnNextLoad = true
+ scrollTo(7)
- // make sure it did retry
- assertThat(source.loads.size).isEqualTo(4)
- // failed load
- val failedLoad = source.loads[2]
- assertThat(failedLoad is LoadParams.Append).isTrue()
- assertThat(failedLoad.key).isEqualTo(8)
- // retry load
- val retryLoad = source.loads[3]
- assertThat(retryLoad is LoadParams.Append).isTrue()
- assertThat(retryLoad.key).isEqualTo(8)
- }
+ // make sure it did retry
+ assertThat(source.loads.size).isEqualTo(4)
+ // failed load
+ val failedLoad = source.loads[2]
+ assertThat(failedLoad is LoadParams.Append).isTrue()
+ assertThat(failedLoad.key).isEqualTo(8)
+ // retry load
+ val retryLoad = source.loads[3]
+ assertThat(retryLoad is LoadParams.Append).isTrue()
+ assertThat(retryLoad.key).isEqualTo(8)
+ }
// retry success
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
}
- @Test
- fun errorHandler_retryFails_loadDelay0() = errorHandler_retryFails(0)
+ @Test fun errorHandler_retryFails_loadDelay0() = errorHandler_retryFails(0)
- @Test
- fun errorHandler_retryFails_loadDelay10000() = errorHandler_retryFails(10000)
+ @Test fun errorHandler_retryFails_loadDelay10000() = errorHandler_retryFails(10000)
private fun errorHandler_retryFails(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val factory = createFactory(dataFlow, loadDelay)
val pagingSources = mutableListOf<TestPagingSource>()
- val pager = Pager(
- config = PagingConfig(pageSize = 3, initialLoadSize = 5),
- pagingSourceFactory = {
- factory.invoke().also { pagingSources.add(it as TestPagingSource) }
- },
- ).flow
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+ pagingSourceFactory = {
+ factory.invoke().also { pagingSources.add(it as TestPagingSource) }
+ },
+ )
+ .flow
var retryCount = 0
testScope.runTest {
- val snapshot = pager.asSnapshot(
- onError = {
- // retry twice
- if (retryCount < 2) {
- retryCount++
- ErrorRecovery.RETRY
- } else {
- ErrorRecovery.RETURN_CURRENT_SNAPSHOT
+ val snapshot =
+ pager.asSnapshot(
+ onError = {
+ // retry twice
+ if (retryCount < 2) {
+ retryCount++
+ ErrorRecovery.RETRY
+ } else {
+ ErrorRecovery.RETURN_CURRENT_SNAPSHOT
+ }
}
+ ) {
+ val source = pagingSources.first()
+ // should have two loads to far - refresh and append(prefetch)
+ assertThat(source.loads.size).isEqualTo(2)
+
+ source.errorOnLoads = true
+ scrollTo(8)
+
+ // additional failed load + two retries
+ assertThat(source.loads.size).isEqualTo(5)
}
- ) {
- val source = pagingSources.first()
- // should have two loads to far - refresh and append(prefetch)
- assertThat(source.loads.size).isEqualTo(2)
-
- source.errorOnLoads = true
- scrollTo(8)
-
- // additional failed load + two retries
- assertThat(source.loads.size).isEqualTo(5)
- }
// retry failed, returned existing snapshot
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
- @Test
- fun errorHandler_returnSnapshot_loadDelay0() = errorHandler_returnSnapshot(0)
+ @Test fun errorHandler_returnSnapshot_loadDelay0() = errorHandler_returnSnapshot(0)
- @Test
- fun errorHandler_returnSnapshot_loadDelay10000() = errorHandler_returnSnapshot(10000)
+ @Test fun errorHandler_returnSnapshot_loadDelay10000() = errorHandler_returnSnapshot(10000)
private fun errorHandler_returnSnapshot(loadDelay: Long) {
val dataFlow = flowOf(List(30) { it })
val factory = createFactory(dataFlow, loadDelay)
val pagingSources = mutableListOf<TestPagingSource>()
- val pager = Pager(
- config = PagingConfig(pageSize = 3, initialLoadSize = 5),
- pagingSourceFactory = {
- factory.invoke().also { pagingSources.add(it as TestPagingSource) }
- },
- ).flow
+ val pager =
+ Pager(
+ config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+ pagingSourceFactory = {
+ factory.invoke().also { pagingSources.add(it as TestPagingSource) }
+ },
+ )
+ .flow
testScope.runTest {
- val snapshot = pager.asSnapshot(onError = { ErrorRecovery.RETURN_CURRENT_SNAPSHOT }) {
- val source = pagingSources.first()
- source.errorOnNextLoad = true
- scrollTo(12)
- }
+ val snapshot =
+ pager.asSnapshot(onError = { ErrorRecovery.RETURN_CURRENT_SNAPSHOT }) {
+ val source = pagingSources.first()
+ source.errorOnNextLoad = true
+ scrollTo(12)
+ }
// snapshot items before scrollTo
- assertThat(snapshot).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ assertThat(snapshot).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
}
}
@@ -3085,10 +3392,11 @@
private fun createPager(data: List<Int>, loadDelay: Long, initialKey: Int = 0) =
Pager(
- PagingConfig(pageSize = 3, initialLoadSize = 5),
- initialKey,
- createSingleGenFactory(data, loadDelay),
- ).flow
+ PagingConfig(pageSize = 3, initialLoadSize = 5),
+ initialKey,
+ createSingleGenFactory(data, loadDelay),
+ )
+ .flow
private fun createPagerNoPlaceholders(
dataFlow: Flow<List<Int>>,
@@ -3104,7 +3412,8 @@
prefetchDistance = 3
),
loadDelay,
- initialKey)
+ initialKey
+ )
private fun createPagerNoPrefetch(
dataFlow: Flow<List<Int>>,
@@ -3147,11 +3456,13 @@
config: PagingConfig,
loadDelay: Long,
initialKey: Int = 0,
- ) = Pager(
- config = config,
- initialKey = initialKey,
- pagingSourceFactory = createFactory(dataFlow, loadDelay),
- ).flow
+ ) =
+ Pager(
+ config = config,
+ initialKey = initialKey,
+ pagingSourceFactory = createFactory(dataFlow, loadDelay),
+ )
+ .flow
}
private class WrappedPagingSourceFactory(
@@ -3173,10 +3484,9 @@
get() = _loads.toList()
init {
- originalSource.registerInvalidatedCallback {
- invalidate()
- }
+ originalSource.registerInvalidatedCallback { invalidate() }
}
+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
delay(loadDelay)
_loads.add(params)
diff --git a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
index 0419b4f..c6fbae8 100644
--- a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
@@ -37,10 +37,7 @@
class StaticListPagingSourceFactoryTest {
private val testScope = TestScope(UnconfinedTestDispatcher())
- private val CONFIG = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5
- )
+ private val CONFIG = PagingConfig(pageSize = 3, initialLoadSize = 5)
@Test
fun emptyFlow() {
@@ -57,214 +54,194 @@
@Test
fun simpleCollect_singleGen() {
- val flow = flowOf(
- List(20) { it }
- )
+ val flow = flowOf(List(20) { it })
- val factory: PagingSourceFactory<Int, Int> =
- flow.asPagingSourceFactory(testScope)
+ val factory: PagingSourceFactory<Int, Int> = flow.asPagingSourceFactory(testScope)
val pagingSource = factory()
val pager = TestPager(CONFIG, pagingSource)
runTest {
val result = pager.refresh() as Page
- assertThat(result.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
}
}
@Test
- fun simpleCollect_multiGeneration() = testScope.runTest {
- val flow = flow {
- emit(List(20) { it }) // first gen
- delay(1500)
- emit(List(15) { it + 30 }) // second gen
+ fun simpleCollect_multiGeneration() =
+ testScope.runTest {
+ val flow = flow {
+ emit(List(20) { it }) // first gen
+ delay(1500)
+ emit(List(15) { it + 30 }) // second gen
+ }
+
+ val factory: PagingSourceFactory<Int, Int> = flow.asPagingSourceFactory(testScope)
+
+ advanceTimeBy(1000)
+
+ // first gen
+ val pagingSource1 = factory()
+ val pager1 = TestPager(CONFIG, pagingSource1)
+ val result1 = pager1.refresh() as Page
+ assertThat(result1.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
+
+ // second list emits -- this should invalidate original pagingSource and trigger new gen
+ advanceUntilIdle()
+
+ assertThat(pagingSource1.invalid).isTrue()
+
+ // second gen
+ val pagingSource2 = factory()
+ val pager2 = TestPager(CONFIG, pagingSource2)
+ val result2 = pager2.refresh() as Page
+ assertThat(result2.data).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
}
- val factory: PagingSourceFactory<Int, Int> =
- flow.asPagingSourceFactory(testScope)
+ @Test
+ fun collection_cancellation() =
+ testScope.runTest {
+ val mutableFlow = MutableSharedFlow<List<Int>>()
+ val collectionScope = this.backgroundScope
- advanceTimeBy(1000)
+ val factory: PagingSourceFactory<Int, Int> =
+ mutableFlow.asPagingSourceFactory(collectionScope)
- // first gen
- val pagingSource1 = factory()
- val pager1 = TestPager(CONFIG, pagingSource1)
- val result1 = pager1.refresh() as Page
- assertThat(result1.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ mutableFlow.emit(List(10) { it })
- // second list emits -- this should invalidate original pagingSource and trigger new gen
- advanceUntilIdle()
+ advanceUntilIdle()
- assertThat(pagingSource1.invalid).isTrue()
+ val pagingSource = factory()
+ val pager = TestPager(CONFIG, pagingSource)
+ val result = pager.refresh() as Page
+ assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
- // second gen
- val pagingSource2 = factory()
- val pager2 = TestPager(CONFIG, pagingSource2)
- val result2 = pager2.refresh() as Page
- assertThat(result2.data).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
- }
+ // cancel collection scope inside the pagingSourceFactory
+ collectionScope.cancel()
+
+ mutableFlow.emit(List(10) { it })
+
+ advanceUntilIdle()
+
+ // new list should not be collected, meaning the previous generation should still be
+ // valid
+ assertThat(pagingSource.invalid).isFalse()
+ }
@Test
- fun collection_cancellation() = testScope.runTest {
- val mutableFlow = MutableSharedFlow<List<Int>>()
- val collectionScope = this.backgroundScope
+ fun multipleFactories_fromSameFlow() =
+ testScope.runTest {
+ val mutableFlow = MutableSharedFlow<List<Int>>()
- val factory: PagingSourceFactory<Int, Int> =
- mutableFlow.asPagingSourceFactory(collectionScope)
+ val factory1: PagingSourceFactory<Int, Int> =
+ mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
- mutableFlow.emit(List(10) { it })
+ val factory2: PagingSourceFactory<Int, Int> =
+ mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
- advanceUntilIdle()
+ mutableFlow.emit(List(10) { it })
- val pagingSource = factory()
- val pager = TestPager(CONFIG, pagingSource)
- val result = pager.refresh() as Page
- assertThat(result.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ advanceUntilIdle()
- // cancel collection scope inside the pagingSourceFactory
- collectionScope.cancel()
+ // factory 1 first gen
+ val pagingSource = factory1()
+ val pager = TestPager(CONFIG, pagingSource)
+ val result = pager.refresh() as Page
+ assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
- mutableFlow.emit(List(10) { it })
+ // factory 2 first gen
+ val pagingSource2 = factory2()
+ val pager2 = TestPager(CONFIG, pagingSource2)
+ val result2 = pager2.refresh() as Page
+ assertThat(result2.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
- advanceUntilIdle()
+ // trigger second generation
+ mutableFlow.emit(List(10) { it + 30 })
- // new list should not be collected, meaning the previous generation should still be valid
- assertThat(pagingSource.invalid).isFalse()
- }
+ advanceUntilIdle()
+
+ assertThat(pagingSource.invalid).isTrue()
+ assertThat(pagingSource2.invalid).isTrue()
+
+ // factory 1 second gen
+ val pagingSource3 = factory1()
+ val pager3 = TestPager(CONFIG, pagingSource3)
+ val result3 = pager3.refresh() as Page
+ assertThat(result3.data).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
+
+ // factory 2 second gen
+ val pagingSource4 = factory2()
+ val pager4 = TestPager(CONFIG, pagingSource4)
+ val result4 = pager4.refresh() as Page
+ assertThat(result4.data).containsExactlyElementsIn(listOf(30, 31, 32, 33, 34))
+ }
@Test
- fun multipleFactories_fromSameFlow() = testScope.runTest {
- val mutableFlow = MutableSharedFlow<List<Int>>()
+ fun singleListFactory_refresh() =
+ testScope.runTest {
+ val data = List(20) { it }
+ val factory = data.asPagingSourceFactory()
- val factory1: PagingSourceFactory<Int, Int> =
- mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
+ val pagingSource1 = factory()
+ val pager1 = TestPager(CONFIG, pagingSource1)
+ val refresh1 = pager1.refresh() as Page
+ assertThat(refresh1.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
- val factory2: PagingSourceFactory<Int, Int> =
- mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
-
- mutableFlow.emit(List(10) { it })
-
- advanceUntilIdle()
-
- // factory 1 first gen
- val pagingSource = factory1()
- val pager = TestPager(CONFIG, pagingSource)
- val result = pager.refresh() as Page
- assertThat(result.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
-
- // factory 2 first gen
- val pagingSource2 = factory2()
- val pager2 = TestPager(CONFIG, pagingSource2)
- val result2 = pager2.refresh() as Page
- assertThat(result2.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
-
- // trigger second generation
- mutableFlow.emit(List(10) { it + 30 })
-
- advanceUntilIdle()
-
- assertThat(pagingSource.invalid).isTrue()
- assertThat(pagingSource2.invalid).isTrue()
-
- // factory 1 second gen
- val pagingSource3 = factory1()
- val pager3 = TestPager(CONFIG, pagingSource3)
- val result3 = pager3.refresh() as Page
- assertThat(result3.data).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
-
- // factory 2 second gen
- val pagingSource4 = factory2()
- val pager4 = TestPager(CONFIG, pagingSource4)
- val result4 = pager4.refresh() as Page
- assertThat(result4.data).containsExactlyElementsIn(
- listOf(30, 31, 32, 33, 34)
- )
- }
+ val pagingSource2 = factory()
+ val pager2 = TestPager(CONFIG, pagingSource2)
+ val refresh2 = pager2.refresh() as Page
+ assertThat(refresh2.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
+ }
@Test
- fun singleListFactory_refresh() = testScope.runTest {
- val data = List(20) { it }
- val factory = data.asPagingSourceFactory()
+ fun singleListFactory_empty() =
+ testScope.runTest {
+ val data = emptyList<Int>()
+ val factory = data.asPagingSourceFactory()
- val pagingSource1 = factory()
- val pager1 = TestPager(CONFIG, pagingSource1)
- val refresh1 = pager1.refresh() as Page
- assertThat(refresh1.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ val pagingSource1 = factory()
+ val pager1 = TestPager(CONFIG, pagingSource1)
+ val refresh1 = pager1.refresh() as Page
+ assertThat(refresh1.data).isEmpty()
- val pagingSource2 = factory()
- val pager2 = TestPager(CONFIG, pagingSource2)
- val refresh2 = pager2.refresh() as Page
- assertThat(refresh2.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
- }
+ val pagingSource2 = factory()
+ val pager2 = TestPager(CONFIG, pagingSource2)
+ val refresh2 = pager2.refresh() as Page
+ assertThat(refresh2.data).isEmpty()
+ }
@Test
- fun singleListFactory_empty() = testScope.runTest {
- val data = emptyList<Int>()
- val factory = data.asPagingSourceFactory()
+ fun singleListFactory_append() =
+ testScope.runTest {
+ val data = List(20) { it }
+ val factory = data.asPagingSourceFactory()
+ val pagingSource1 = factory()
+ val pager1 = TestPager(CONFIG, pagingSource1)
- val pagingSource1 = factory()
- val pager1 = TestPager(CONFIG, pagingSource1)
- val refresh1 = pager1.refresh() as Page
- assertThat(refresh1.data).isEmpty()
+ pager1.refresh() as Page
+ pager1.append()
+ assertThat(pager1.getPages().flatMap { it.data })
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7))
- val pagingSource2 = factory()
- val pager2 = TestPager(CONFIG, pagingSource2)
- val refresh2 = pager2.refresh() as Page
- assertThat(refresh2.data).isEmpty()
- }
+ pager1.append()
+ assertThat(pager1.getPages().flatMap { it.data })
+ .containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
+ }
@Test
- fun singleListFactory_append() = testScope.runTest {
- val data = List(20) { it }
- val factory = data.asPagingSourceFactory()
- val pagingSource1 = factory()
- val pager1 = TestPager(CONFIG, pagingSource1)
+ fun singleListFactory_prepend() =
+ testScope.runTest {
+ val data = List(20) { it }
+ val factory = data.asPagingSourceFactory()
+ val pagingSource1 = factory()
+ val pager1 = TestPager(CONFIG, pagingSource1)
- pager1.refresh() as Page
- pager1.append()
- assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7)
- )
+ pager1.refresh(initialKey = 10) as Page
+ pager1.prepend()
+ assertThat(pager1.getPages().flatMap { it.data })
+ .containsExactlyElementsIn(listOf(7, 8, 9, 10, 11, 12, 13, 14))
- pager1.append()
- assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- )
- }
-
- @Test
- fun singleListFactory_prepend() = testScope.runTest {
- val data = List(20) { it }
- val factory = data.asPagingSourceFactory()
- val pagingSource1 = factory()
- val pager1 = TestPager(CONFIG, pagingSource1)
-
- pager1.refresh(initialKey = 10) as Page
- pager1.prepend()
- assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
- listOf(7, 8, 9, 10, 11, 12, 13, 14)
- )
-
- pager1.prepend()
- assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
- listOf(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
- )
- }
+ pager1.prepend()
+ assertThat(pager1.getPages().flatMap { it.data })
+ .containsExactlyElementsIn(listOf(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
+ }
}
diff --git a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
index ae8ce3b..1d6964d 100644
--- a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
@@ -26,88 +26,86 @@
class StaticListPagingSourceTest {
private val DATA = List(100) { it }
- private val CONFIG = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- )
+ private val CONFIG =
+ PagingConfig(
+ pageSize = 3,
+ initialLoadSize = 5,
+ )
@Test
fun refresh() = runPagingSourceTest { _, pager ->
val result = pager.refresh() as LoadResult.Page
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = listOf(0, 1, 2, 3, 4),
- prevKey = null,
- nextKey = 5,
- itemsBefore = 0,
- itemsAfter = 95
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = listOf(0, 1, 2, 3, 4),
+ prevKey = null,
+ nextKey = 5,
+ itemsBefore = 0,
+ itemsAfter = 95
+ )
)
- )
}
@Test
- fun refresh_withEmptyData() = runPagingSourceTest(StaticListPagingSource(emptyList())) {
- _, pager ->
-
- val result = pager.refresh() as LoadResult.Page
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = emptyList(),
- prevKey = null,
- nextKey = null,
- itemsBefore = 0,
- itemsAfter = 0
- )
- )
- }
+ fun refresh_withEmptyData() =
+ runPagingSourceTest(StaticListPagingSource(emptyList())) { _, pager ->
+ val result = pager.refresh() as LoadResult.Page
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = emptyList(),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = 0,
+ itemsAfter = 0
+ )
+ )
+ }
@Test
fun refresh_initialKey() = runPagingSourceTest { _, pager ->
val result = pager.refresh(initialKey = 20) as LoadResult.Page
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = listOf(20, 21, 22, 23, 24),
- prevKey = 19,
- nextKey = 25,
- itemsBefore = 20,
- itemsAfter = 75
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = listOf(20, 21, 22, 23, 24),
+ prevKey = 19,
+ nextKey = 25,
+ itemsBefore = 20,
+ itemsAfter = 75
+ )
)
- )
}
@Test
- fun refresh_initialKey_withEmptyData() = runPagingSourceTest(
- StaticListPagingSource(emptyList())
- ) { _, pager ->
-
- val result = pager.refresh(initialKey = 20) as LoadResult.Page
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = emptyList(),
- prevKey = null,
- nextKey = null,
- itemsBefore = 0,
- itemsAfter = 0
- )
- )
- }
+ fun refresh_initialKey_withEmptyData() =
+ runPagingSourceTest(StaticListPagingSource(emptyList())) { _, pager ->
+ val result = pager.refresh(initialKey = 20) as LoadResult.Page
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = emptyList(),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = 0,
+ itemsAfter = 0
+ )
+ )
+ }
@Test
fun refresh_negativeKeyClippedToZero() = runPagingSourceTest { _, pager ->
val result = pager.refresh(initialKey = -1) as LoadResult.Page
// loads first page
- assertThat(result).isEqualTo(
- listOf(0, 1, 2, 3, 4).asPage()
- )
+ assertThat(result).isEqualTo(listOf(0, 1, 2, 3, 4).asPage())
}
@Test
fun refresh_KeyLargerThanDataSize_loadsLastPage() = runPagingSourceTest { _, pager ->
val result = pager.refresh(initialKey = 140) as LoadResult.Page
// loads last page
- assertThat(result).isEqualTo(
- listOf(95, 96, 97, 98, 99).asPage()
- )
+ assertThat(result).isEqualTo(listOf(95, 96, 97, 98, 99).asPage())
}
@Test
@@ -116,24 +114,26 @@
refresh()
append()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- LoadResult.Page(
- data = listOf(0, 1, 2, 3, 4),
- prevKey = null,
- nextKey = 5,
- itemsBefore = 0,
- itemsAfter = 95
- ),
- LoadResult.Page(
- data = listOf(5, 6, 7),
- prevKey = 4,
- nextKey = 8,
- itemsBefore = 5,
- itemsAfter = 92
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ LoadResult.Page(
+ data = listOf(0, 1, 2, 3, 4),
+ prevKey = null,
+ nextKey = 5,
+ itemsBefore = 0,
+ itemsAfter = 95
+ ),
+ LoadResult.Page(
+ data = listOf(5, 6, 7),
+ prevKey = 4,
+ nextKey = 8,
+ itemsBefore = 5,
+ itemsAfter = 92
+ )
)
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -144,31 +144,35 @@
append()
append()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(0, 1, 2, 3, 4).asPage(),
- listOf(5, 6, 7).asPage(),
- listOf(8, 9, 10).asPage(),
- listOf(11, 12, 13).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(0, 1, 2, 3, 4).asPage(),
+ listOf(5, 6, 7).asPage(),
+ listOf(8, 9, 10).asPage(),
+ listOf(11, 12, 13).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
fun append_loadSizeLargerThanAvailableData() = runPagingSourceTest { _, pager ->
- val result = pager.run {
- refresh(initialKey = 94)
- append() as LoadResult.Page
- }
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = listOf(99),
- prevKey = 98,
- nextKey = null,
- itemsBefore = 99,
- itemsAfter = 0
+ val result =
+ pager.run {
+ refresh(initialKey = 94)
+ append() as LoadResult.Page
+ }
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = listOf(99),
+ prevKey = 98,
+ nextKey = null,
+ itemsBefore = 99,
+ itemsAfter = 0
+ )
)
- )
}
@Test
@@ -177,24 +181,26 @@
refresh(20)
prepend()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- LoadResult.Page(
- data = listOf(17, 18, 19),
- prevKey = 16,
- nextKey = 20,
- itemsBefore = 17,
- itemsAfter = 80
- ),
- LoadResult.Page(
- data = listOf(20, 21, 22, 23, 24),
- prevKey = 19,
- nextKey = 25,
- itemsBefore = 20,
- itemsAfter = 75
- ),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ LoadResult.Page(
+ data = listOf(17, 18, 19),
+ prevKey = 16,
+ nextKey = 20,
+ itemsBefore = 17,
+ itemsAfter = 80
+ ),
+ LoadResult.Page(
+ data = listOf(20, 21, 22, 23, 24),
+ prevKey = 19,
+ nextKey = 25,
+ itemsBefore = 20,
+ itemsAfter = 75
+ ),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -205,31 +211,35 @@
prepend()
prepend()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(41, 42, 43).asPage(),
- listOf(44, 45, 46).asPage(),
- listOf(47, 48, 49).asPage(),
- listOf(50, 51, 52, 53, 54).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(41, 42, 43).asPage(),
+ listOf(44, 45, 46).asPage(),
+ listOf(47, 48, 49).asPage(),
+ listOf(50, 51, 52, 53, 54).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
fun prepend_loadSizeLargerThanAvailableData() = runPagingSourceTest { _, pager ->
- val result = pager.run {
- refresh(initialKey = 2)
- prepend()
- }
- assertThat(result).isEqualTo(
- LoadResult.Page(
- data = listOf(0, 1),
- prevKey = null,
- nextKey = 2,
- itemsBefore = 0,
- itemsAfter = 98
+ val result =
+ pager.run {
+ refresh(initialKey = 2)
+ prepend()
+ }
+ assertThat(result)
+ .isEqualTo(
+ LoadResult.Page(
+ data = listOf(0, 1),
+ prevKey = null,
+ nextKey = 2,
+ itemsBefore = 0,
+ itemsAfter = 98
+ )
)
- )
}
@Test
@@ -240,12 +250,13 @@
@Test
fun refreshKey() = runPagingSourceTest { pagingSource, pager ->
- val state = pager.run {
- refresh() // [0, 1, 2, 3, 4]
- append() // [5, 6, 7]
- // the anchorPos should be 7
- getPagingState(anchorPosition = 7)
- }
+ val state =
+ pager.run {
+ refresh() // [0, 1, 2, 3, 4]
+ append() // [5, 6, 7]
+ // the anchorPos should be 7
+ getPagingState(anchorPosition = 7)
+ }
val refreshKey = pagingSource.getRefreshKey(state)
val expected = 7 - (CONFIG.initialLoadSize / 2)
@@ -255,11 +266,12 @@
@Test
fun refreshKey_negativeKeyClippedToZero() = runPagingSourceTest { pagingSource, pager ->
- val state = pager.run {
- refresh(2) // [2, 3, 4, 5, 6]
- prepend() // [0, 1]
- getPagingState(anchorPosition = 1)
- }
+ val state =
+ pager.run {
+ refresh(2) // [2, 3, 4, 5, 6]
+ prepend() // [0, 1]
+ getPagingState(anchorPosition = 1)
+ }
// before clipping, refreshKey = 1 - (CONFIG.initialLoadSize / 2) = -1
val refreshKey = pagingSource.getRefreshKey(state)
assertThat(refreshKey).isEqualTo(0)
@@ -271,9 +283,7 @@
pager: TestPager<Int, Int> = TestPager(CONFIG, source),
block: suspend (pagingSource: PagingSource<Int, Int>, pager: TestPager<Int, Int>) -> Unit
) {
- runTest {
- block(source, pager)
- }
+ runTest { block(source, pager) }
}
private fun List<Int>.asPage(): LoadResult.Page<Int, Int> {
@@ -281,12 +291,12 @@
val indexEnd = lastOrNull()
return LoadResult.Page(
data = this,
- prevKey = indexStart?.let {
- if (indexStart <= 0 || isEmpty()) null else indexStart - 1
- },
- nextKey = indexEnd?.let {
- if (indexEnd >= DATA.lastIndex || isEmpty()) null else indexEnd + 1
- },
+ prevKey =
+ indexStart?.let { if (indexStart <= 0 || isEmpty()) null else indexStart - 1 },
+ nextKey =
+ indexEnd?.let {
+ if (indexEnd >= DATA.lastIndex || isEmpty()) null else indexEnd + 1
+ },
itemsBefore = indexStart ?: -1,
itemsAfter = if (indexEnd == null) -1 else DATA.lastIndex - indexEnd
)
diff --git a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
index b534856..53c69eb 100644
--- a/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
@@ -108,10 +108,11 @@
val pager = TestPager(CONFIG, source)
runTest {
- val page: LoadResult.Page<Int, Int>? = pager.run {
- refresh()
- getLastLoadedPage()
- }
+ val page: LoadResult.Page<Int, Int>? =
+ pager.run {
+ refresh()
+ getLastLoadedPage()
+ }
assertThat(page).isNotNull()
assertThat(page?.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
}
@@ -123,13 +124,14 @@
val pager = TestPager(CONFIG, source)
runTest {
- val page = pager.run {
- refresh()
- append() // page should be this appended page
- source.invalidate()
- assertTrue(source.invalid)
- getLastLoadedPage()
- }
+ val page =
+ pager.run {
+ refresh()
+ append() // page should be this appended page
+ source.invalidate()
+ assertTrue(source.invalid)
+ getLastLoadedPage()
+ }
assertThat(page).isNotNull()
assertThat(page?.data).containsExactlyElementsIn(listOf(5, 6, 7)).inOrder()
}
@@ -141,16 +143,15 @@
val pager = TestPager(CONFIG, source)
runTest {
- val pages = pager.run {
- refresh()
- getPages()
- }
+ val pages =
+ pager.run {
+ refresh()
+ getPages()
+ }
assertThat(pages).hasSize(1)
- assertThat(pages).containsExactlyElementsIn(
- listOf(
- listOf(0, 1, 2, 3, 4).asPage()
- )
- ).inOrder()
+ assertThat(pages)
+ .containsExactlyElementsIn(listOf(listOf(0, 1, 2, 3, 4).asPage()))
+ .inOrder()
}
}
@@ -163,14 +164,16 @@
refresh(20)
prepend()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // prepend
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // prepend
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -187,19 +190,19 @@
val pager = TestPager(CONFIG, source)
runTest {
- val pages = pager.run {
- refresh()
- append()
- source.invalidate()
- assertTrue(source.invalid)
- getPages()
- }
- assertThat(pages).containsExactlyElementsIn(
- listOf(
- listOf(0, 1, 2, 3, 4).asPage(),
- listOf(5, 6, 7).asPage()
+ val pages =
+ pager.run {
+ refresh()
+ append()
+ source.invalidate()
+ assertTrue(source.invalid)
+ getPages()
+ }
+ assertThat(pages)
+ .containsExactlyElementsIn(
+ listOf(listOf(0, 1, 2, 3, 4).asPage(), listOf(5, 6, 7).asPage())
)
- ).inOrder()
+ .inOrder()
}
}
@@ -218,30 +221,35 @@
}
job.start()
assertTrue(job.isActive)
- val pages2 = pager.run {
- delay(200) // let launch start first
- append() // second
- prepend() // fourth
- getPages() // sixth
- }
+ val pages2 =
+ pager.run {
+ delay(200) // let launch start first
+ append() // second
+ prepend() // fourth
+ getPages() // sixth
+ }
advanceUntilIdle()
- assertThat(pages).containsExactlyElementsIn(
- listOf(
- // should contain first and second load
- listOf(20, 21, 22, 23, 24).asPage(), // refresh
- listOf(25, 26, 27).asPage(), // append
+ assertThat(pages)
+ .containsExactlyElementsIn(
+ listOf(
+ // should contain first and second load
+ listOf(20, 21, 22, 23, 24).asPage(), // refresh
+ listOf(25, 26, 27).asPage(), // append
+ )
)
- ).inOrder()
- assertThat(pages2).containsExactlyElementsIn(
- // should contain all loads
- listOf(
- listOf(14, 15, 16).asPage(),
- listOf(17, 18, 19).asPage(),
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
+ .inOrder()
+ assertThat(pages2)
+ .containsExactlyElementsIn(
+ // should contain all loads
+ listOf(
+ listOf(14, 15, 16).asPage(),
+ listOf(17, 18, 19).asPage(),
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -269,9 +277,7 @@
val pager1 = TestPager(CONFIG, source1)
// first gen
- val result1 = pager1.run {
- refresh()
- } as LoadResult.Page
+ val result1 = pager1.run { refresh() } as LoadResult.Page
assertThat(result1.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
@@ -279,9 +285,7 @@
val source2 = TestPagingSource()
val pager2 = TestPager(CONFIG, source2)
- val result2 = pager2.run {
- refresh()
- } as LoadResult.Page
+ val result2 = pager2.run { refresh() } as LoadResult.Page
assertThat(result2.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
}
@@ -291,18 +295,18 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val result = pager.run {
- refresh(null)
- append()
- } as LoadResult.Page
+ val result =
+ pager.run {
+ refresh(null)
+ append()
+ } as LoadResult.Page
assertThat(result.data).containsExactlyElementsIn(listOf(5, 6, 7)).inOrder()
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(0, 1, 2, 3, 4).asPage(),
- listOf(5, 6, 7).asPage()
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(listOf(0, 1, 2, 3, 4).asPage(), listOf(5, 6, 7).asPage())
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -310,39 +314,38 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val result = pager.run {
- refresh(30)
- prepend()
- } as LoadResult.Page
+ val result =
+ pager.run {
+ refresh(30)
+ prepend()
+ } as LoadResult.Page
assertThat(result.data).containsExactlyElementsIn(listOf(27, 28, 29)).inOrder()
// prepended pages should be inserted before refresh
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // prepend
- listOf(27, 28, 29).asPage(),
- // refresh
- listOf(30, 31, 32, 33, 34).asPage()
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // prepend
+ listOf(27, 28, 29).asPage(),
+ // refresh
+ listOf(30, 31, 32, 33, 34).asPage()
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
fun append_beforeRefresh_throws() = runTest {
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- assertFailsWith<IllegalStateException> {
- pager.append()
- }
+ assertFailsWith<IllegalStateException> { pager.append() }
}
@Test
fun prepend_beforeRefresh_throws() = runTest {
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- assertFailsWith<IllegalStateException> {
- pager.prepend()
- }
+ assertFailsWith<IllegalStateException> { pager.prepend() }
}
@Test
@@ -350,14 +353,15 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val result = pager.run {
- refresh()
- source.invalidate()
- assertThat(source.invalid).isTrue()
- // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated
- source.nextLoadResult = LoadResult.Invalid()
- append()
- }
+ val result =
+ pager.run {
+ refresh()
+ source.invalidate()
+ assertThat(source.invalid).isTrue()
+ // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated
+ source.nextLoadResult = LoadResult.Invalid()
+ append()
+ }
assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>()
}
@@ -366,14 +370,15 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val result = pager.run {
- refresh(initialKey = 20)
- source.invalidate()
- assertThat(source.invalid).isTrue()
- // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated
- source.nextLoadResult = LoadResult.Invalid()
- prepend()
- }
+ val result =
+ pager.run {
+ refresh(initialKey = 20)
+ source.invalidate()
+ assertThat(source.invalid).isTrue()
+ // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated
+ source.nextLoadResult = LoadResult.Invalid()
+ prepend()
+ }
assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>()
}
@@ -388,13 +393,15 @@
append()
} as LoadResult.Page
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
- listOf(28, 29, 30).asPage()
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ listOf(28, 29, 30).asPage()
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -409,16 +416,18 @@
} as LoadResult.Page
// prepended pages should be ordered before the refresh
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // 2nd prepend
- listOf(14, 15, 16).asPage(),
- // 1st prepend
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // 2nd prepend
+ listOf(14, 15, 16).asPage(),
+ // 1st prepend
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -432,16 +441,18 @@
prepend()
} as LoadResult.Page
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // prepend
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
- // append
- listOf(25, 26, 27).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // prepend
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ // append
+ listOf(25, 26, 27).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -455,16 +466,18 @@
append()
} as LoadResult.Page
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // prepend
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
- // append
- listOf(25, 26, 27).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // prepend
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ // append
+ listOf(25, 26, 27).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -494,15 +507,17 @@
advanceUntilIdle()
assertThat(loadOrder).containsExactlyElementsIn(listOf(1, 2, 3, 4, 5)).inOrder()
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(14, 15, 16).asPage(),
- listOf(17, 18, 19).asPage(),
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
- listOf(28, 29, 30).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(14, 15, 16).asPage(),
+ listOf(17, 18, 19).asPage(),
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ listOf(28, 29, 30).asPage(),
+ )
)
- ).inOrder()
+ .inOrder()
}
@Test
@@ -526,31 +541,33 @@
job.start()
assertTrue(job.isActive)
- val pages = pager.run {
- // give some time for job to start first
- delay(200)
- append().also { loadOrder.add(2) } // second operation
- prepend().also { loadOrder.add(4) } // fourth operation
- // sixth operation, should return 4 pages
- getPages().also { loadOrder.add(6) }
- }
+ val pages =
+ pager.run {
+ // give some time for job to start first
+ delay(200)
+ append().also { loadOrder.add(2) } // second operation
+ prepend().also { loadOrder.add(4) } // fourth operation
+ // sixth operation, should return 4 pages
+ getPages().also { loadOrder.add(6) }
+ }
advanceUntilIdle()
- assertThat(loadOrder).containsExactlyElementsIn(
- listOf(1, 2, 3, 4, 5, 6, 7)
- ).inOrder()
- assertThat(lastLoadedPage).isEqualTo(
- listOf(25, 26, 27).asPage(),
- )
- // should not contain the second prepend, with a total of 4 pages
- assertThat(pages).containsExactlyElementsIn(
- listOf(
- listOf(17, 18, 19).asPage(), // first prepend
- listOf(20, 21, 22, 23, 24).asPage(), // refresh
- listOf(25, 26, 27).asPage(), // first append
- listOf(28, 29, 30).asPage(), // second append
+ assertThat(loadOrder).containsExactlyElementsIn(listOf(1, 2, 3, 4, 5, 6, 7)).inOrder()
+ assertThat(lastLoadedPage)
+ .isEqualTo(
+ listOf(25, 26, 27).asPage(),
)
- ).inOrder()
+ // should not contain the second prepend, with a total of 4 pages
+ assertThat(pages)
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(17, 18, 19).asPage(), // first prepend
+ listOf(20, 21, 22, 23, 24).asPage(), // refresh
+ listOf(25, 26, 27).asPage(), // first append
+ listOf(28, 29, 30).asPage(), // second append
+ )
+ )
+ .inOrder()
}
@Test
@@ -558,70 +575,68 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val state = pager.run {
- refresh(20)
- prepend()
- append()
- getPagingState(7)
- }
+ val state =
+ pager.run {
+ refresh(20)
+ prepend()
+ append()
+ getPagingState(7)
+ }
// in this case anchorPos is a placeholder at index 7
- assertThat(state).isEqualTo(
- PagingState(
- pages = listOf(
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
- // append
- listOf(25, 26, 27).asPage(),
- ),
- anchorPosition = 7,
- config = CONFIG,
- leadingPlaceholderCount = 17
+ assertThat(state)
+ .isEqualTo(
+ PagingState(
+ pages =
+ listOf(
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ // append
+ listOf(25, 26, 27).asPage(),
+ ),
+ anchorPosition = 7,
+ config = CONFIG,
+ leadingPlaceholderCount = 17
+ )
)
- )
val source2 = TestPagingSource()
val pager2 = TestPager(CONFIG, source)
- val page = pager2.run {
- refresh(source2.getRefreshKey(state))
- }
+ val page = pager2.run { refresh(source2.getRefreshKey(state)) }
assertThat(page).isEqualTo(listOf(7, 8, 9, 10, 11).asPage())
}
@Test
fun getPagingStateWithAnchorPosition_placeHoldersDisabled() = runTest {
val source = TestPagingSource(placeholdersEnabled = false)
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false
- )
+ val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false)
val pager = TestPager(config, source)
- val state = pager.run {
- refresh(20)
- prepend()
- append()
- getPagingState(7)
- }
- assertThat(state).isEqualTo(
- PagingState(
- pages = listOf(
- listOf(17, 18, 19).asPage(placeholdersEnabled = false),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false),
- // append
- listOf(25, 26, 27).asPage(placeholdersEnabled = false),
- ),
- anchorPosition = 7,
- config = config,
- leadingPlaceholderCount = 0
+ val state =
+ pager.run {
+ refresh(20)
+ prepend()
+ append()
+ getPagingState(7)
+ }
+ assertThat(state)
+ .isEqualTo(
+ PagingState(
+ pages =
+ listOf(
+ listOf(17, 18, 19).asPage(placeholdersEnabled = false),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false),
+ // append
+ listOf(25, 26, 27).asPage(placeholdersEnabled = false),
+ ),
+ anchorPosition = 7,
+ config = config,
+ leadingPlaceholderCount = 0
+ )
)
- )
val source2 = TestPagingSource()
val pager2 = TestPager(CONFIG, source)
- val page = pager2.run {
- refresh(source2.getRefreshKey(state))
- }
+ val page = pager2.run { refresh(source2.getRefreshKey(state)) }
// without placeholders, Paging currently has no way to translate item[7] within loaded
// pages into its absolute position within available data. Hence anchorPosition 7 will
// reference item[7] within available data.
@@ -633,59 +648,61 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val msg = assertFailsWith<IllegalStateException> {
- pager.run {
- refresh()
- append()
- getPagingState(-1)
- }
- }.message
- assertThat(msg).isEqualTo(
- "anchorPosition -1 is out of bounds between [0..${ITEM_COUNT - 1}]. Please " +
- "provide a valid anchorPosition."
- )
+ val msg =
+ assertFailsWith<IllegalStateException> {
+ pager.run {
+ refresh()
+ append()
+ getPagingState(-1)
+ }
+ }
+ .message
+ assertThat(msg)
+ .isEqualTo(
+ "anchorPosition -1 is out of bounds between [0..${ITEM_COUNT - 1}]. Please " +
+ "provide a valid anchorPosition."
+ )
- val msg2 = assertFailsWith<IllegalStateException> {
- pager.getPagingState(ITEM_COUNT)
- }.message
- assertThat(msg2).isEqualTo(
- "anchorPosition $ITEM_COUNT is out of bounds between [0..${ITEM_COUNT - 1}]. " +
- "Please provide a valid anchorPosition."
- )
+ val msg2 =
+ assertFailsWith<IllegalStateException> { pager.getPagingState(ITEM_COUNT) }.message
+ assertThat(msg2)
+ .isEqualTo(
+ "anchorPosition $ITEM_COUNT is out of bounds between [0..${ITEM_COUNT - 1}]. " +
+ "Please provide a valid anchorPosition."
+ )
}
@Test
fun getPagingStateWithAnchorPosition_indexOutOfBoundsWithoutPlaceholders() = runTest {
val source = TestPagingSource()
- val pager = TestPager(
- PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false
- ),
- source
- )
+ val pager =
+ TestPager(
+ PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false),
+ source
+ )
- val msg = assertFailsWith<IllegalStateException> {
- pager.run {
- refresh()
- append()
- getPagingState(-1)
- }
- }.message
- assertThat(msg).isEqualTo(
- "anchorPosition -1 is out of bounds between [0..7]. Please " +
- "provide a valid anchorPosition."
- )
+ val msg =
+ assertFailsWith<IllegalStateException> {
+ pager.run {
+ refresh()
+ append()
+ getPagingState(-1)
+ }
+ }
+ .message
+ assertThat(msg)
+ .isEqualTo(
+ "anchorPosition -1 is out of bounds between [0..7]. Please " +
+ "provide a valid anchorPosition."
+ )
// total loaded items = 8, anchorPos with index 8 should be out of bounds
- val msg2 = assertFailsWith<IllegalStateException> {
- pager.getPagingState(8)
- }.message
- assertThat(msg2).isEqualTo(
- "anchorPosition 8 is out of bounds between [0..7]. Please " +
- "provide a valid anchorPosition."
- )
+ val msg2 = assertFailsWith<IllegalStateException> { pager.getPagingState(8) }.message
+ assertThat(msg2)
+ .isEqualTo(
+ "anchorPosition 8 is out of bounds between [0..7]. Please " +
+ "provide a valid anchorPosition."
+ )
}
@Test
@@ -693,71 +710,69 @@
val source = TestPagingSource()
val pager = TestPager(CONFIG, source)
- val state = pager.run {
- refresh(20)
- prepend()
- append()
- getPagingState { it == TestPagingSource.ITEMS[22] }
- }
- assertThat(state).isEqualTo(
- PagingState(
- pages = listOf(
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
- // append
- listOf(25, 26, 27).asPage(),
- ),
- anchorPosition = 22,
- config = CONFIG,
- leadingPlaceholderCount = 17
+ val state =
+ pager.run {
+ refresh(20)
+ prepend()
+ append()
+ getPagingState { it == TestPagingSource.ITEMS[22] }
+ }
+ assertThat(state)
+ .isEqualTo(
+ PagingState(
+ pages =
+ listOf(
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ // append
+ listOf(25, 26, 27).asPage(),
+ ),
+ anchorPosition = 22,
+ config = CONFIG,
+ leadingPlaceholderCount = 17
+ )
)
- )
// use state to getRefreshKey
val source2 = TestPagingSource()
val pager2 = TestPager(CONFIG, source)
- val page = pager2.run {
- refresh(source2.getRefreshKey(state))
- }
+ val page = pager2.run { refresh(source2.getRefreshKey(state)) }
assertThat(page).isEqualTo(listOf(22, 23, 24, 25, 26).asPage())
}
@Test
fun getPagingStateWithAnchorLookup_placeHoldersDisabled() = runTest {
val source = TestPagingSource(placeholdersEnabled = false)
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false
- )
+ val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false)
val pager = TestPager(config, source)
- val state = pager.run {
- refresh(20)
- prepend()
- append()
- getPagingState { it == TestPagingSource.ITEMS[22] } // item 22 in this case
- }
- assertThat(state).isEqualTo(
- PagingState(
- pages = listOf(
- listOf(17, 18, 19).asPage(placeholdersEnabled = false),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false),
- // append
- listOf(25, 26, 27).asPage(placeholdersEnabled = false),
- ),
- anchorPosition = 5,
- config = config,
- leadingPlaceholderCount = 0
+ val state =
+ pager.run {
+ refresh(20)
+ prepend()
+ append()
+ getPagingState { it == TestPagingSource.ITEMS[22] } // item 22 in this case
+ }
+ assertThat(state)
+ .isEqualTo(
+ PagingState(
+ pages =
+ listOf(
+ listOf(17, 18, 19).asPage(placeholdersEnabled = false),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false),
+ // append
+ listOf(25, 26, 27).asPage(placeholdersEnabled = false),
+ ),
+ anchorPosition = 5,
+ config = config,
+ leadingPlaceholderCount = 0
+ )
)
- )
// use state to getRefreshKey
val source2 = TestPagingSource()
val pager2 = TestPager(CONFIG, source)
- val page = pager2.run {
- refresh(source2.getRefreshKey(state))
- }
+ val page = pager2.run { refresh(source2.getRefreshKey(state)) }
// without placeholders, Paging currently has no way to translate item[5] within loaded
// pages into its absolute position within available data. anchorPosition 5 will reference
// item[5] within available data.
@@ -767,139 +782,149 @@
@Test
fun getPagingStateWithAnchorLookup_itemNotFoundThrows() = runTest {
val source = TestPagingSource(placeholdersEnabled = false)
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false
- )
+ val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false)
val pager = TestPager(config, source)
- val msg = assertFailsWith<IllegalArgumentException> {
- pager.run {
- refresh(20)
- prepend()
- append()
- getPagingState { it == TestPagingSource.ITEMS[10] }
- }
- }.message
- assertThat(msg).isEqualTo(
- "The given predicate has returned false for every loaded item. To generate a" +
- "PagingState anchored to an item, the expected item must have already " +
- "been loaded."
- )
+ val msg =
+ assertFailsWith<IllegalArgumentException> {
+ pager.run {
+ refresh(20)
+ prepend()
+ append()
+ getPagingState { it == TestPagingSource.ITEMS[10] }
+ }
+ }
+ .message
+ assertThat(msg)
+ .isEqualTo(
+ "The given predicate has returned false for every loaded item. To generate a" +
+ "PagingState anchored to an item, the expected item must have already " +
+ "been loaded."
+ )
}
@Test
fun dropPrependedPage() = runTest {
val source = TestPagingSource()
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false,
- maxSize = 10
- )
+ val config =
+ PagingConfig(
+ pageSize = 3,
+ initialLoadSize = 5,
+ enablePlaceholders = false,
+ maxSize = 10
+ )
val pager = TestPager(config, source)
pager.run {
refresh(20)
prepend()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(17, 18, 19).asPage(),
- // refresh
- listOf(20, 21, 22, 23, 24).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(17, 18, 19).asPage(),
+ // refresh
+ listOf(20, 21, 22, 23, 24).asPage(),
+ )
)
- )
// this append should trigger paging to drop the prepended page
pager.append()
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ )
)
- )
}
@Test
fun dropAppendedPage() = runTest {
val source = TestPagingSource()
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false,
- maxSize = 10
- )
+ val config =
+ PagingConfig(
+ pageSize = 3,
+ initialLoadSize = 5,
+ enablePlaceholders = false,
+ maxSize = 10
+ )
val pager = TestPager(config, source)
pager.run {
refresh(20)
append()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ )
)
- )
// this prepend should trigger paging to drop the prepended page
pager.prepend()
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(17, 18, 19).asPage(),
- listOf(20, 21, 22, 23, 24).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(17, 18, 19).asPage(),
+ listOf(20, 21, 22, 23, 24).asPage(),
+ )
)
- )
}
@Test
fun dropInitialRefreshedPage() = runTest {
val source = TestPagingSource()
- val config = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- enablePlaceholders = false,
- maxSize = 10
- )
+ val config =
+ PagingConfig(
+ pageSize = 3,
+ initialLoadSize = 5,
+ enablePlaceholders = false,
+ maxSize = 10
+ )
val pager = TestPager(config, source)
pager.run {
refresh(20)
append()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(20, 21, 22, 23, 24).asPage(),
- listOf(25, 26, 27).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(20, 21, 22, 23, 24).asPage(),
+ listOf(25, 26, 27).asPage(),
+ )
)
- )
// this append should trigger paging to drop the first page which is the initial refresh
pager.append()
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(25, 26, 27).asPage(),
- listOf(28, 29, 30).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(25, 26, 27).asPage(),
+ listOf(28, 29, 30).asPage(),
+ )
)
- )
}
@Test
fun dropRespectsPrefetchDistance_InDroppedDirection() = runTest {
val source = TestPagingSource()
- val config = PagingConfig(
- pageSize = 1,
- initialLoadSize = 10,
- enablePlaceholders = false,
- maxSize = 5,
- prefetchDistance = 2
- )
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ initialLoadSize = 10,
+ enablePlaceholders = false,
+ maxSize = 5,
+ prefetchDistance = 2
+ )
val pager = TestPager(config, source)
pager.refresh(20)
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(),
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(),
+ )
)
- )
// these appends should would normally trigger paging to drop first page, but it won't
// in this case due to prefetchDistance
@@ -907,56 +932,57 @@
append()
append()
}
- assertThat(pager.getPages()).containsExactlyElementsIn(
- listOf(
- // second page counted towards prefetch distance
- listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(),
- // first page counted towards prefetch distance
- listOf(30).asPage(),
- listOf(31).asPage()
+ assertThat(pager.getPages())
+ .containsExactlyElementsIn(
+ listOf(
+ // second page counted towards prefetch distance
+ listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(),
+ // first page counted towards prefetch distance
+ listOf(30).asPage(),
+ listOf(31).asPage()
+ )
)
- )
}
@Test
fun drop_noOpUnderTwoPages() = runTest {
val source = TestPagingSource()
- val config = PagingConfig(
- pageSize = 1,
- initialLoadSize = 5,
- enablePlaceholders = false,
- maxSize = 3,
- prefetchDistance = 1
- )
+ val config =
+ PagingConfig(
+ pageSize = 1,
+ initialLoadSize = 5,
+ enablePlaceholders = false,
+ maxSize = 3,
+ prefetchDistance = 1
+ )
val pager = TestPager(config, source)
val result = pager.refresh() as LoadResult.Page
- assertThat(result.data).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4)
- )
+ assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4))
pager.append()
// data size exceeds maxSize but no data should be dropped
- assertThat(pager.getPages().flatten()).containsExactlyElementsIn(
- listOf(0, 1, 2, 3, 4, 5)
- )
+ assertThat(pager.getPages().flatten()).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5))
}
- private val CONFIG = PagingConfig(
- pageSize = 3,
- initialLoadSize = 5,
- )
+ private val CONFIG =
+ PagingConfig(
+ pageSize = 3,
+ initialLoadSize = 5,
+ )
private fun List<Int>.asPage(placeholdersEnabled: Boolean = true): LoadResult.Page<Int, Int> {
- val itemsBefore = if (placeholdersEnabled) {
- if (first() == 0) 0 else first()
- } else {
- Int.MIN_VALUE
- }
- val itemsAfter = if (placeholdersEnabled) {
- if (last() == ITEM_COUNT - 1) 0 else ITEM_COUNT - 1 - last()
- } else {
- Int.MIN_VALUE
- }
+ val itemsBefore =
+ if (placeholdersEnabled) {
+ if (first() == 0) 0 else first()
+ } else {
+ Int.MIN_VALUE
+ }
+ val itemsAfter =
+ if (placeholdersEnabled) {
+ if (last() == ITEM_COUNT - 1) 0 else ITEM_COUNT - 1 - last()
+ } else {
+ Int.MIN_VALUE
+ }
return LoadResult.Page(
data = this,
prevKey = if (first() == 0) null else first() - 1,
diff --git a/paging/samples/src/main/java/androidx/paging/samples/BasePagingAdapter.kt b/paging/samples/src/main/java/androidx/paging/samples/BasePagingAdapter.kt
index f762d5b..e6cbc12 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/BasePagingAdapter.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/BasePagingAdapter.kt
@@ -21,20 +21,19 @@
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
-/**
- * No-op PagingAdapter interface impl to be used by sample code that doesn't show adapter impl
- */
-internal open class BasePagingAdapter<T : Any> : PagingDataAdapter<T, RecyclerView.ViewHolder>(
- object : DiffUtil.ItemCallback<T>() {
- override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
- return true
- }
+/** No-op PagingAdapter interface impl to be used by sample code that doesn't show adapter impl */
+internal open class BasePagingAdapter<T : Any> :
+ PagingDataAdapter<T, RecyclerView.ViewHolder>(
+ object : DiffUtil.ItemCallback<T>() {
+ override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
+ return true
+ }
- override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
- return true
+ override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
+ return true
+ }
}
- }
-) {
+ ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
throw NotImplementedError()
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/BaseViewModel.kt b/paging/samples/src/main/java/androidx/paging/samples/BaseViewModel.kt
index 9662758..d4e499fd 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/BaseViewModel.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/BaseViewModel.kt
@@ -27,16 +27,12 @@
import androidx.paging.rxjava2.flowable
import kotlinx.coroutines.ExperimentalCoroutinesApi
-/**
- * No-op ViewModel base class to be used by sample code that doesn't show ViewModel impl
- */
+/** No-op ViewModel base class to be used by sample code that doesn't show ViewModel impl */
open class BaseViewModel<T : Any> : ViewModel() {
private lateinit var pagingSourceFactory: () -> PagingSource<String, T>
- private val pager = Pager(
- config = PagingConfig(pageSize = 40),
- pagingSourceFactory = pagingSourceFactory
- )
+ private val pager =
+ Pager(config = PagingConfig(pageSize = 40), pagingSourceFactory = pagingSourceFactory)
val pagingFlow = pager.flow.cachedIn(viewModelScope)
diff --git a/paging/samples/src/main/java/androidx/paging/samples/CachedInSample.kt b/paging/samples/src/main/java/androidx/paging/samples/CachedInSample.kt
index 7de466d..1783da6 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/CachedInSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/CachedInSample.kt
@@ -35,7 +35,9 @@
import kotlinx.coroutines.launch
private class UiModel(@Suppress("UNUSED_PARAMETER") string: String)
+
private class MyPagingAdapter : BasePagingAdapter<UiModel>()
+
private class MyViewModel : BaseViewModel<UiModel>()
private lateinit var pagingSourceFactory: () -> PagingSource<String, String>
@@ -43,14 +45,13 @@
@Sampled
fun cachedInSample() {
class MyViewModel : ViewModel() {
- val flow = Pager(
- config = PagingConfig(pageSize = 40),
- pagingSourceFactory = pagingSourceFactory
- ).flow
- // Loads and transformations before the cachedIn operation will be cached, so that
- // multiple observers get the same data. This is true either for simultaneous
- // observers, or e.g. an Activity re-subscribing after being recreated
- .cachedIn(viewModelScope)
+ val flow =
+ Pager(config = PagingConfig(pageSize = 40), pagingSourceFactory = pagingSourceFactory)
+ .flow
+ // Loads and transformations before the cachedIn operation will be cached, so that
+ // multiple observers get the same data. This is true either for simultaneous
+ // observers, or e.g. an Activity re-subscribing after being recreated
+ .cachedIn(viewModelScope)
}
class MyActivity : AppCompatActivity() {
@@ -68,9 +69,7 @@
// example un-cached transformation
pagingData.map { UiModel(it) }
}
- .collectLatest {
- pagingAdapter.submitData(it)
- }
+ .collectLatest { pagingAdapter.submitData(it) }
}
}
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsSample.kt b/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsSample.kt
index da92c7b..d94246d 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsSample.kt
@@ -75,14 +75,16 @@
// map outer stream, so we can perform transformations on each paging generation
pagingData.insertSeparatorsAsync { before: String?, after: String? ->
Maybe.fromCallable<String> {
- if (after != null && before?.first() != after.first()) {
- // separator - after is first item that starts with its first letter
- after.first().uppercaseChar().toString()
- } else {
- // no separator - either end of list, or first letters of before/after are the same
- null
+ if (after != null && before?.first() != after.first()) {
+ // separator - after is first item that starts with its first letter
+ after.first().uppercaseChar().toString()
+ } else {
+ // no separator - either end of list, or first letters of before/after are
+ // the same
+ null
+ }
}
- }.subscribeOn(Schedulers.computation())
+ .subscribeOn(Schedulers.computation())
}
}
}
@@ -112,7 +114,8 @@
// separator - after is first item that starts with its first letter
after.first().uppercaseChar().toString()
} else {
- // no separator - either end of list, or first letters of before/after are the same
+ // no separator - either end of list, or first letters of before/after
+ // are the same
null
}
},
diff --git a/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsUiModelSample.kt b/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsUiModelSample.kt
index 9a2bd7f..78a0d81 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsUiModelSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/InsertSeparatorsUiModelSample.kt
@@ -34,12 +34,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-class Item(
- @JvmField
- val id: String,
- @JvmField
- val label: String
-)
+class Item(@JvmField val id: String, @JvmField val label: String)
private lateinit var pagingDataStream: Flow<PagingData<Item>>
@@ -69,7 +64,8 @@
// separator - after is first item that starts with its first letter
SeparatorUiModel(after.item.label.first().uppercaseChar())
} else {
- // no separator - either end of list, or first letters of before/after are the same
+ // no separator - either end of list, or first letters of before/after are the
+ // same
null
}
}
@@ -99,14 +95,19 @@
}
.insertSeparatorsAsync { before: ItemUiModel?, after: ItemUiModel? ->
Maybe.fromCallable<UiModel> {
- if (after != null && before?.item?.label?.first() != after.item.label.first()) {
- // separator - after is first item that starts with its first letter
- SeparatorUiModel(after.item.label.first().uppercaseChar())
- } else {
- // no separator - either end of list, or first letters of before/after are the same
- null
+ if (
+ after != null &&
+ before?.item?.label?.first() != after.item.label.first()
+ ) {
+ // separator - after is first item that starts with its first letter
+ SeparatorUiModel(after.item.label.first().uppercaseChar())
+ } else {
+ // no separator - either end of list, or first letters of before/after
+ // are the same
+ null
+ }
}
- }.subscribeOn(Schedulers.computation())
+ .subscribeOn(Schedulers.computation())
}
}
}
@@ -140,13 +141,15 @@
Futures.submit(
Callable<UiModel> {
val (before, after) = it!!
- if (after != null &&
- before?.item?.label?.first() != after.item.label.first()
+ if (
+ after != null &&
+ before?.item?.label?.first() != after.item.label.first()
) {
// separator - after is first item that starts with its first letter
SeparatorUiModel(after.item.label.first().uppercaseChar())
} else {
- // no separator - either end of list, or first letters of before/after are the same
+ // no separator - either end of list, or first letters of
+ // before/after are the same
null
}
},
diff --git a/paging/samples/src/main/java/androidx/paging/samples/ListenableFuturePagingSourceSample.kt b/paging/samples/src/main/java/androidx/paging/samples/ListenableFuturePagingSourceSample.kt
index a13a3e6..234d68e 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/ListenableFuturePagingSourceSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/ListenableFuturePagingSourceSample.kt
@@ -27,11 +27,7 @@
import java.util.concurrent.Executor
import retrofit2.HttpException
-data class RemoteResult(
- val items: List<Item>,
- val prev: String,
- val next: String
-)
+data class RemoteResult(val items: List<Item>, val prev: String, val next: String)
private class GuavaBackendService {
@Suppress("UNUSED_PARAMETER")
@@ -52,10 +48,7 @@
params: LoadParams<String>
): ListenableFuture<LoadResult<String, Item>> {
return myBackend
- .searchUsers(
- searchTerm = searchTerm,
- pageKey = params.key
- )
+ .searchUsers(searchTerm = searchTerm, pageKey = params.key)
.transform<LoadResult<String, Item>>(
{ response ->
LoadResult.Page(
diff --git a/paging/samples/src/main/java/androidx/paging/samples/LoadStateAdapterSample.kt b/paging/samples/src/main/java/androidx/paging/samples/LoadStateAdapterSample.kt
index 7c5e617..efcd8cb 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/LoadStateAdapterSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/LoadStateAdapterSample.kt
@@ -31,17 +31,16 @@
@Sampled
fun loadStateAdapterSample() {
- class LoadStateViewHolder(
- parent: ViewGroup,
- retry: () -> Unit
- ) : RecyclerView.ViewHolder(
- LayoutInflater.from(parent.context)
- .inflate(R.layout.load_state_item, parent, false)
- ) {
+ class LoadStateViewHolder(parent: ViewGroup, retry: () -> Unit) :
+ RecyclerView.ViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.load_state_item, parent, false)
+ ) {
private val progressBar: ProgressBar = itemView.findViewById(R.id.progress_bar)
private val errorMsg: TextView = itemView.findViewById(R.id.error_msg)
- private val retry: Button = itemView.findViewById<Button>(R.id.retry_button)
- .also { it.setOnClickListener { retry.invoke() } }
+ private val retry: Button =
+ itemView.findViewById<Button>(R.id.retry_button).also {
+ it.setOnClickListener { retry.invoke() }
+ }
fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
@@ -52,20 +51,20 @@
errorMsg.visibility = toVisibility(loadState !is LoadState.Loading)
}
- private fun toVisibility(constraint: Boolean): Int = if (constraint) {
- View.VISIBLE
- } else {
- View.GONE
- }
+ private fun toVisibility(constraint: Boolean): Int =
+ if (constraint) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
}
/**
* Adapter which displays a loading spinner when `state = LoadState.Loading`, and an error
* message and retry button when `state is LoadState.Error`.
*/
- class MyLoadStateAdapter(
- private val retry: () -> Unit
- ) : LoadStateAdapter<LoadStateViewHolder>() {
+ class MyLoadStateAdapter(private val retry: () -> Unit) :
+ LoadStateAdapter<LoadStateViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState) =
LoadStateViewHolder(parent, retry)
diff --git a/paging/samples/src/main/java/androidx/paging/samples/PagingDataAdapterSample.kt b/paging/samples/src/main/java/androidx/paging/samples/PagingDataAdapterSample.kt
index 91fd531..3c063ed 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/PagingDataAdapterSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/PagingDataAdapterSample.kt
@@ -40,11 +40,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-data class User(
- val userId: String,
- val favoriteFood: String,
- val isFavorite: Boolean
-)
+data class User(val userId: String, val favoriteFood: String, val isFavorite: Boolean)
// TODO: consider adding a fleshed out ViewHolder as part of the sample
@Suppress("UNUSED_PARAMETER")
@@ -61,15 +57,16 @@
@Suppress("LocalVariableName") // We're pretending local val is global
@Sampled
fun pagingDataAdapterSample() {
- val USER_COMPARATOR = object : DiffUtil.ItemCallback<User>() {
- override fun areItemsTheSame(oldItem: User, newItem: User): Boolean =
- // User ID serves as unique ID
- oldItem.userId == newItem.userId
+ val USER_COMPARATOR =
+ object : DiffUtil.ItemCallback<User>() {
+ override fun areItemsTheSame(oldItem: User, newItem: User): Boolean =
+ // User ID serves as unique ID
+ oldItem.userId == newItem.userId
- override fun areContentsTheSame(oldItem: User, newItem: User): Boolean =
- // Compare full contents (note: Java users should call .equals())
- oldItem == newItem
- }
+ override fun areContentsTheSame(oldItem: User, newItem: User): Boolean =
+ // Compare full contents (note: Java users should call .equals())
+ oldItem == newItem
+ }
class UserAdapter : PagingDataAdapter<User, UserViewHolder>(USER_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
@@ -85,6 +82,7 @@
}
internal class UserPagingAdapter : BasePagingAdapter<User>()
+
internal class UserListViewModel : BaseViewModel<User>()
internal class MyActivityBinding {
@@ -116,9 +114,7 @@
binding.swipeRefreshLayout.isRefreshing = loadStates.refresh is LoadState.Loading
}
- binding.swipeRefreshLayout.setOnRefreshListener {
- pagingAdapter.refresh()
- }
+ binding.swipeRefreshLayout.setOnRefreshListener { pagingAdapter.refresh() }
}
}
}
@@ -182,12 +178,11 @@
val viewModel by viewModels<UserListViewModel>()
lifecycleScope.launch {
- viewModel.pagingFlow
- .collectLatest { pagingData ->
- // submitData suspends until loading this generation of data stops
- // so be sure to use collectLatest {} when presenting a Flow<PagingData>
- pagingAdapter.submitData(pagingData)
- }
+ viewModel.pagingFlow.collectLatest { pagingData ->
+ // submitData suspends until loading this generation of data stops
+ // so be sure to use collectLatest {} when presenting a Flow<PagingData>
+ pagingAdapter.submitData(pagingData)
+ }
}
}
}
@@ -227,9 +222,7 @@
viewModel.pagingFlowable
.autoDispose(this) // Using AutoDispose to handle subscription lifecycle
- .subscribe { pagingData ->
- pagingAdapter.submitData(lifecycle, pagingData)
- }
+ .subscribe { pagingData -> pagingAdapter.submitData(lifecycle, pagingData) }
}
}
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/PagingSourceSample.kt b/paging/samples/src/main/java/androidx/paging/samples/PagingSourceSample.kt
index 273996e..970381f 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/PagingSourceSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/PagingSourceSample.kt
@@ -26,11 +26,7 @@
import retrofit2.HttpException
internal class MyBackendService {
- data class RemoteResult(
- val items: List<Item>,
- val prev: String?,
- val next: String?
- )
+ data class RemoteResult(val items: List<Item>, val prev: String?, val next: String?)
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
suspend fun searchItems(pageKey: String?): RemoteResult {
@@ -55,9 +51,7 @@
*
* Loads Items from network requests via Retrofit to a backend service.
*/
- class MyPagingSource(
- val myBackend: MyBackendService
- ) : PagingSource<String, Item>() {
+ class MyPagingSource(val myBackend: MyBackendService) : PagingSource<String, Item>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Item> {
// Retrofit calls that return the body type throw either IOException for network
// failures, or HttpException for any non-2xx HTTP status codes. This code reports all
@@ -94,9 +88,7 @@
*
* Note that the key type is Int, since we're using page number to load a page.
*/
- class MyPagingSource(
- val myBackend: MyBackendService
- ) : PagingSource<Int, Item>() {
+ class MyPagingSource(val myBackend: MyBackendService) : PagingSource<Int, Item>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
// Retrofit calls that return the body type throw either IOException for network
@@ -119,11 +111,7 @@
// This API defines that it's out of data when a page returns empty. When out of
// data, we return `null` to signify no more pages should be loaded
val nextKey = if (response.items.isNotEmpty()) pageNumber + 1 else null
- LoadResult.Page(
- data = response.items,
- prevKey = prevKey,
- nextKey = nextKey
- )
+ LoadResult.Page(data = response.items, prevKey = prevKey, nextKey = nextKey)
} catch (e: IOException) {
LoadResult.Error(e)
} catch (e: HttpException) {
@@ -152,20 +140,19 @@
// The following shows how you use convert such a response loaded in PagingSource.load() to
// a Page, which can be returned from that method
- fun NetworkResponseObject.toPage() = LoadResult.Page(
- data = items,
- prevKey = null, // this implementation can only append, can't load a prepend
- nextKey = next, // next token will be the params.key of a subsequent append load
- itemsAfter = approximateItemsRemaining
- )
+ fun NetworkResponseObject.toPage() =
+ LoadResult.Page(
+ data = items,
+ prevKey = null, // this implementation can only append, can't load a prepend
+ nextKey = next, // next token will be the params.key of a subsequent append load
+ itemsAfter = approximateItemsRemaining
+ )
}
@Sampled
fun pageIndexedPage() {
// If you load by page number, the response may not define how to load the next page.
- data class NetworkResponseObject(
- val items: List<Item>
- )
+ data class NetworkResponseObject(val items: List<Item>)
// The following shows how you use the current page number (e.g., the current key in
// PagingSource.load() to convert a response into a Page, which can be returned from that method
diff --git a/paging/samples/src/main/java/androidx/paging/samples/RemoteMediatorSample.kt b/paging/samples/src/main/java/androidx/paging/samples/RemoteMediatorSample.kt
index 6cbefcb..b7d31f6 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/RemoteMediatorSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/RemoteMediatorSample.kt
@@ -34,15 +34,21 @@
private interface ItemDao {
fun withTransaction(block: () -> Any)
+
fun insertAll(items: List<Item>)
+
fun removeAll()
+
fun lastUpdated(): Long
}
private interface ItemKeyDao {
fun withTransaction(block: () -> Any)
+
fun queryKey(item: Item): String
+
fun insertKey(item: Item, key: String): String
+
fun removeAll()
}
@@ -86,25 +92,29 @@
// The network load method takes an optional `after=<user.id>` parameter. For every
// page after the first, we pass the last user ID to let it continue from where it
// left off. For REFRESH, pass `null` to load the first page.
- val loadKey = when (loadType) {
- LoadType.REFRESH -> null
- // In this example, we never need to prepend, since REFRESH will always load the
- // first page in the list. Immediately return, reporting end of pagination.
- LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
- LoadType.APPEND -> {
- val lastItem = state.lastItemOrNull()
-
- // We must explicitly check if the last item is `null` when appending,
- // since passing `null` to networkService is only valid for initial load.
- // If lastItem is `null` it means no items were loaded after the initial
- // REFRESH and there are no more items to load.
- if (lastItem == null) {
+ val loadKey =
+ when (loadType) {
+ LoadType.REFRESH -> null
+ // In this example, we never need to prepend, since REFRESH will always load
+ // the
+ // first page in the list. Immediately return, reporting end of pagination.
+ LoadType.PREPEND ->
return MediatorResult.Success(endOfPaginationReached = true)
- }
+ LoadType.APPEND -> {
+ val lastItem = state.lastItemOrNull()
- lastItem.id
+ // We must explicitly check if the last item is `null` when appending,
+ // since passing `null` to networkService is only valid for initial
+ // load.
+ // If lastItem is `null` it means no items were loaded after the initial
+ // REFRESH and there are no more items to load.
+ if (lastItem == null) {
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+
+ lastItem.id
+ }
}
- }
// Suspending network load via Retrofit. This doesn't need to be wrapped in a
// withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter
@@ -173,28 +183,30 @@
// after the first, we pass the [String] token returned from the previous page to
// let it continue from where it left off. For REFRESH, pass `null` to load the
// first page.
- val loadKey = when (loadType) {
- LoadType.REFRESH -> null
- // In this example, we never need to prepend, since REFRESH will always load the
- // first page in the list. Immediately return, reporting end of pagination.
- LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
- // Query remoteKeyDao for the next RemoteKey.
- LoadType.APPEND -> {
- val remoteKey = database.withTransaction {
- remoteKeyDao.remoteKeyByQuery(query)
- }
-
- // We must explicitly check if the page key is `null` when appending,
- // since `null` is only valid for initial load. If we receive `null`
- // for APPEND, that means we have reached the end of pagination and
- // there are no more items to load.
- if (remoteKey.nextKey == null) {
+ val loadKey =
+ when (loadType) {
+ LoadType.REFRESH -> null
+ // In this example, we never need to prepend, since REFRESH will always load
+ // the
+ // first page in the list. Immediately return, reporting end of pagination.
+ LoadType.PREPEND ->
return MediatorResult.Success(endOfPaginationReached = true)
- }
+ // Query remoteKeyDao for the next RemoteKey.
+ LoadType.APPEND -> {
+ val remoteKey =
+ database.withTransaction { remoteKeyDao.remoteKeyByQuery(query) }
- remoteKey.nextKey
+ // We must explicitly check if the page key is `null` when appending,
+ // since `null` is only valid for initial load. If we receive `null`
+ // for APPEND, that means we have reached the end of pagination and
+ // there are no more items to load.
+ if (remoteKey.nextKey == null) {
+ return MediatorResult.Success(endOfPaginationReached = true)
+ }
+
+ remoteKey.nextKey
+ }
}
- }
// Suspending network load via Retrofit. This doesn't need to be wrapped in a
// withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter
diff --git a/paging/samples/src/main/java/androidx/paging/samples/RxPagingSourceSample.kt b/paging/samples/src/main/java/androidx/paging/samples/RxPagingSourceSample.kt
index 6665998..2f1e4b0 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/RxPagingSourceSample.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/RxPagingSourceSample.kt
@@ -27,11 +27,7 @@
import retrofit2.HttpException
private class RxBackendService {
- data class RemoteResult(
- val items: List<Item>,
- val prev: String,
- val next: String
- )
+ data class RemoteResult(val items: List<Item>, val prev: String, val next: String)
@Suppress("UNUSED_PARAMETER")
fun searchUsers(searchTerm: String, pageKey: String?): Single<RemoteResult> {
@@ -45,10 +41,8 @@
* Sample RxPagingSource which loads `Item`s from network requests via Retrofit to a backend
* service, which uses String tokens to load pages (each response has a next/previous token).
*/
- class MyPagingSource(
- val myBackend: RxBackendService,
- val searchTerm: String
- ) : RxPagingSource<String, Item>() {
+ class MyPagingSource(val myBackend: RxBackendService, val searchTerm: String) :
+ RxPagingSource<String, Item>() {
override fun loadSingle(params: LoadParams<String>): Single<LoadResult<String, Item>> {
return myBackend
// Single-based network load
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleGuavaBackendService.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleGuavaBackendService.kt
index 4432451..e7c3747 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleGuavaBackendService.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleGuavaBackendService.kt
@@ -20,5 +20,6 @@
interface ExampleGuavaBackendService {
fun searchUsers(query: String, after: String?): ListenableFuture<SearchUserResponse>
+
fun searchUsers(query: String, pageNumber: Int?): ListenableFuture<SearchUserResponse>
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleRxBackendService.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleRxBackendService.kt
index b92aaab..bd3653e 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleRxBackendService.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/ExampleRxBackendService.kt
@@ -20,5 +20,6 @@
interface ExampleRxBackendService {
fun searchUsers(query: String, after: String?): Single<SearchUserResponse>
+
fun searchUsers(query: String, pageNumber: Int?): Single<SearchUserResponse>
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKey.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKey.kt
index 88a8523d..72c0c07 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKey.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKey.kt
@@ -18,5 +18,4 @@
import androidx.room.Entity
-@Entity(tableName = "remote_keys")
-data class RemoteKey(val label: String, val nextKey: String?)
+@Entity(tableName = "remote_keys") data class RemoteKey(val label: String, val nextKey: String?)
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKeyDao.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKeyDao.kt
index ef13266..7f96d54 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKeyDao.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/RemoteKeyDao.kt
@@ -27,8 +27,7 @@
interface RemoteKeyDao {
// Normally suspend when using Kotlin Coroutines, but sync version allows this Dao to be used
// in both Java and Kotlin samples.
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertOrReplace(remoteKey: RemoteKey)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplace(remoteKey: RemoteKey)
@Query("SELECT * FROM remote_keys WHERE label = :query")
fun remoteKeyByQuery(query: String): RemoteKey
@@ -41,6 +40,5 @@
// Normally suspend when using Kotlin Coroutines, but sync version allows this Dao to be used
// in both Java and Kotlin samples.
- @Query("DELETE FROM remote_keys WHERE label = :query")
- fun deleteByQuery(query: String)
+ @Query("DELETE FROM remote_keys WHERE label = :query") fun deleteByQuery(query: String)
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/RoomDb.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/RoomDb.kt
index eb2626c..074553c 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/RoomDb.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/RoomDb.kt
@@ -21,11 +21,7 @@
import androidx.room.Room
import androidx.room.RoomDatabase
-@Database(
- entities = [User::class, RemoteKey::class],
- version = 1,
- exportSchema = false
-)
+@Database(entities = [User::class, RemoteKey::class], version = 1, exportSchema = false)
abstract class RoomDb : RoomDatabase() {
companion object {
fun create(context: Context): RoomDb {
@@ -37,5 +33,6 @@
}
abstract fun userDao(): UserDao
+
abstract fun remoteKeyDao(): RemoteKeyDao
}
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/User.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/User.kt
index a4d341a..ff281e4 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/User.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/User.kt
@@ -18,5 +18,4 @@
import androidx.room.Entity
-@Entity(tableName = "users")
-data class User(val id: String, val label: String)
+@Entity(tableName = "users") data class User(val id: String, val label: String)
diff --git a/paging/samples/src/main/java/androidx/paging/samples/shared/UserDao.kt b/paging/samples/src/main/java/androidx/paging/samples/shared/UserDao.kt
index b5aca9f..343d199 100644
--- a/paging/samples/src/main/java/androidx/paging/samples/shared/UserDao.kt
+++ b/paging/samples/src/main/java/androidx/paging/samples/shared/UserDao.kt
@@ -28,15 +28,13 @@
interface UserDao {
// Normally suspend when using Kotlin Coroutines, but sync version allows this Dao to be used
// in both Java and Kotlin samples.
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertAll(users: List<User>)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(users: List<User>)
fun pagingSource(): PagingSource<Int, User>
// Normally suspend when using Kotlin Coroutines, but sync version allows this Dao to be used
// in both Java and Kotlin samples.
- @Query("DELETE FROM users WHERE label = :query")
- fun deleteByQuery(query: String)
+ @Query("DELETE FROM users WHERE label = :query") fun deleteByQuery(query: String)
suspend fun lastUpdated(): Long