[go: nahoru, domu]

blob: 788273841c9372721a9a78e5af2ad6219ea60e01 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.paging
import androidx.paging.PagingSource.LoadParams
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
internal class LegacyPager<K : Any, V : Any>(
private val pagedListScope: CoroutineScope,
@Suppress("DEPRECATION")
val config: PagedList.Config,
val source: PagingSource<K, V>,
private val notifyDispatcher: CoroutineDispatcher,
private val fetchDispatcher: CoroutineDispatcher,
val pageConsumer: PageConsumer<V>,
private val keyProvider: KeyProvider<K>
) {
private val detached = AtomicBoolean(false)
@Suppress("DEPRECATION")
var loadStateManager = object : PagedList.LoadStateManager() {
override fun onStateChanged(type: LoadType, state: LoadState) {
pageConsumer.onStateChanged(type, state)
}
}
val isDetached
get() = detached.get()
private fun scheduleLoad(type: LoadType, params: LoadParams<K>) {
// Listen on the BG thread if the paged source is invalid, since it can be expensive.
pagedListScope.launch(fetchDispatcher) {
val value = source.load(params)
// if invalid, drop result on the floor
if (source.invalid) {
detach()
return@launch
}
// Source has been verified to be valid after producing data, so sent data to UI
launch(notifyDispatcher) {
when (value) {
is PagingSource.LoadResult.Page -> onLoadSuccess(type, value)
is PagingSource.LoadResult.Error -> onLoadError(type, value.throwable)
}
}
}
}
private fun onLoadSuccess(type: LoadType, value: PagingSource.LoadResult.Page<K, V>) {
if (isDetached) return // abort!
if (pageConsumer.onPageResult(type, value)) {
when (type) {
LoadType.START -> schedulePrepend()
LoadType.END -> scheduleAppend()
else -> throw IllegalStateException("Can only fetch more during append/prepend")
}
} else {
if (value.data.isEmpty()) {
loadStateManager.setState(type, LoadState.Done)
} else {
loadStateManager.setState(type, LoadState.Idle)
}
}
}
private fun onLoadError(type: LoadType, throwable: Throwable) {
if (isDetached) return // abort!
val state = LoadState.Error(throwable)
loadStateManager.setState(type, state)
}
fun trySchedulePrepend() {
if (loadStateManager.startState is LoadState.Idle) schedulePrepend()
}
fun tryScheduleAppend() {
if (loadStateManager.endState is LoadState.Idle) scheduleAppend()
}
private fun schedulePrepend() {
val key = keyProvider.prevKey
if (key == null) {
onLoadSuccess(LoadType.START, PagingSource.LoadResult.Page.empty())
return
}
loadStateManager.setState(LoadType.START, LoadState.Loading)
val loadParams = LoadParams(
LoadType.START,
key,
config.pageSize,
config.enablePlaceholders,
config.pageSize
)
scheduleLoad(LoadType.START, loadParams)
}
private fun scheduleAppend() {
val key = keyProvider.nextKey
if (key == null) {
onLoadSuccess(LoadType.END, PagingSource.LoadResult.Page.empty())
return
}
loadStateManager.setState(LoadType.END, LoadState.Loading)
val loadParams = LoadParams(
LoadType.END,
key,
config.pageSize,
config.enablePlaceholders,
config.pageSize
)
scheduleLoad(LoadType.END, loadParams)
}
fun retry() {
loadStateManager.startState.run {
if (this is LoadState.Error) schedulePrepend()
}
loadStateManager.endState.run {
if (this is LoadState.Error) scheduleAppend()
}
}
fun detach() = detached.set(true)
internal interface PageConsumer<V : Any> {
/**
* @return `true` if we need to fetch more
*/
fun onPageResult(type: LoadType, page: PagingSource.LoadResult.Page<*, V>): Boolean
fun onStateChanged(type: LoadType, state: LoadState)
}
internal interface KeyProvider<K : Any> {
val prevKey: K?
val nextKey: K?
}
}