/*
* Copyright 2020 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.LoadType.APPEND
import androidx.paging.LoadType.PREPEND
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
/**
* Defines a set of callbacks used to incrementally load data from a remote source into a local
* source wrapped by a [PagingSource], e.g., loading data from network into a local db cache.
*
* 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.
*
* @sample androidx.paging.samples.remoteMediatorItemKeyedSample
* @sample androidx.paging.samples.remoteMediatorPageKeyedSample
*/
@ExperimentalPagingApi
public abstract class RemoteMediator<Key : Any, Value : Any> {
/**
* 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.
*
* 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].
*
* 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.
*
* @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.
*
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
* there's more data available.
*/
public abstract suspend fun load(
loadType: LoadType,
state: PagingState<Key, Value>
): MediatorResult
/**
* Callback fired during initialization of a [PagingData] stream, before initial load.
*
* 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].
*/
public open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
/**
* Return type of [load], which determines [LoadState].
*/
public sealed class MediatorResult {
/**
* Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error].
*/
public class Error(public val throwable: Throwable) : MediatorResult()
/**
* Success signaling that [LoadState] should be set to [LoadState.NotLoading] if
* [endOfPaginationReached] is `true`, otherwise [LoadState] is kept at [LoadState.Loading]
* to await invalidation.
*
* NOTE: It is the responsibility of [load] to update the backing dataset and trigger
* [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new
* items found by [load].
*/
public class Success(
@get:JvmName("endOfPaginationReached") public val endOfPaginationReached: Boolean
) : MediatorResult()
}
/**
* Return type of [initialize], which signals the action to take after [initialize] completes.
*/
public enum class InitializeAction {
/**
* Immediately dispatch a [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.
*/
LAUNCH_INITIAL_REFRESH,
/**
* Wait for a refresh request from the UI before dispatching [load] with load type [REFRESH]
*/
SKIP_INITIAL_REFRESH
}
}