1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.paging 18 19 import androidx.paging.LoadType.APPEND 20 import androidx.paging.LoadType.PREPEND 21 import androidx.paging.LoadType.REFRESH 22 import androidx.paging.PagingSource.LoadResult 23 import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH 24 import kotlin.jvm.JvmName 25 26 /** 27 * Defines a set of callbacks used to incrementally load data from a remote source into a local 28 * source wrapped by a [PagingSource], e.g., loading data from network into a local db cache. 29 * 30 * A [RemoteMediator] is registered by passing it to [Pager]'s constructor. 31 * 32 * [RemoteMediator] allows hooking into the following events: 33 * * Stream initialization 34 * * [REFRESH] signal driven from UI 35 * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most recent 36 * [LoadResult.Page] in the [PREPEND] or [APPEND] direction has [LoadResult.Page.prevKey] or 37 * [LoadResult.Page.nextKey] set to `null` respectively. 38 * 39 * @sample androidx.paging.samples.remoteMediatorItemKeyedSample 40 * @sample androidx.paging.samples.remoteMediatorPageKeyedSample 41 */ 42 @ExperimentalPagingApi 43 public abstract class RemoteMediator<Key : Any, Value : Any> { 44 /** 45 * Callback triggered when Paging needs to request more data from a remote source due to any of 46 * the following events: 47 * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH] 48 * * [REFRESH] signal driven from UI 49 * * [PagingSource] returns a [LoadResult] which signals a boundary condition, i.e., the most 50 * recent [LoadResult.Page] in the [PREPEND] or [APPEND] direction has 51 * [LoadResult.Page.prevKey] or [LoadResult.Page.nextKey] set to `null` respectively. 52 * 53 * It is the responsibility of this method to update the backing dataset and trigger 54 * [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new items 55 * found by [load]. 56 * 57 * The runtime and result of this method defines the remote [LoadState] behavior sent to the UI 58 * via [CombinedLoadStates]. 59 * 60 * This method is never called concurrently *unless* [Pager.flow] has multiple collectors. Note 61 * that Paging might cancel calls to this function if it is currently executing a [PREPEND] or 62 * [APPEND] and a [REFRESH] is requested. In that case, [REFRESH] has higher priority and will 63 * be executed after the previous call is cancelled. If the [load] call with [REFRESH] returns 64 * an error, Paging will call [load] with the previously cancelled [APPEND] or [PREPEND] 65 * request. If [REFRESH] succeeds, it won't make the [APPEND] or [PREPEND] requests unless they 66 * are necessary again after the [REFRESH] is applied to the UI. 67 * 68 * @param loadType [LoadType] of the condition which triggered this callback. 69 * * [PREPEND] indicates the end of pagination in the [PREPEND] direction was reached. This 70 * occurs when [PagingSource.load] returns a [LoadResult.Page] with 71 * [LoadResult.Page.prevKey] == `null`. 72 * * [APPEND] indicates the end of pagination in the [APPEND] direction was reached. This 73 * occurs when [PagingSource.load] returns a [LoadResult.Page] with 74 * [LoadResult.Page.nextKey] == `null`. 75 * * [REFRESH] indicates this method was triggered due to a requested refresh. Generally, 76 * this means that a request to load remote data and **replace** all local data was made. 77 * This can happen when: 78 * * Stream initialization if [initialize] returns [LAUNCH_INITIAL_REFRESH] 79 * * An explicit call to refresh driven by the UI 80 * 81 * @param state A copy of the state including the list of pages currently held in memory of the 82 * currently presented [PagingData] at the time of starting the load. E.g. for load(loadType = 83 * APPEND), you can use the page or item at the end as input for what to load from the 84 * network. 85 * @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether 86 * there's more data available. 87 */ loadnull88 public abstract suspend fun load( 89 loadType: LoadType, 90 state: PagingState<Key, Value> 91 ): MediatorResult 92 93 /** 94 * Callback fired during initialization of a [PagingData] stream, before initial load. 95 * 96 * This function runs to completion before any loading is performed. 97 * 98 * @return [InitializeAction] used to control whether [load] with load type [REFRESH] will be 99 * immediately dispatched when the first [PagingData] is submitted: 100 * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch [load] asynchronously with load type 101 * [REFRESH], to update paginated content when the stream is initialized. Note: This also 102 * prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until [REFRESH] 103 * succeeds. 104 * * [SKIP_INITIAL_REFRESH] to wait for a refresh request from the UI before dispatching 105 * [load] asynchronously with load type [REFRESH]. 106 */ 107 public open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH 108 109 /** Return type of [load], which determines [LoadState]. */ 110 public sealed class MediatorResult { 111 /** Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error]. */ 112 public class Error(public val throwable: Throwable) : MediatorResult() 113 114 /** 115 * Success signaling that [LoadState] should be set to [LoadState.NotLoading] if 116 * [endOfPaginationReached] is `true`, otherwise [LoadState] is kept at [LoadState.Loading] 117 * to await invalidation. 118 * 119 * NOTE: It is the responsibility of [load] to update the backing dataset and trigger 120 * [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new 121 * items found by [load]. 122 */ 123 public class Success( 124 @get:JvmName("endOfPaginationReached") public val endOfPaginationReached: Boolean 125 ) : MediatorResult() 126 } 127 128 /** 129 * Return type of [initialize], which signals the action to take after [initialize] completes. 130 */ 131 public enum class InitializeAction { 132 /** 133 * Immediately dispatch a [load] asynchronously with load type [REFRESH], to update 134 * paginated content when the stream is initialized. 135 * 136 * Note: This also prevents [RemoteMediator] from triggering [PREPEND] or [APPEND] until 137 * [REFRESH] succeeds. 138 */ 139 LAUNCH_INITIAL_REFRESH, 140 141 /** 142 * Wait for a refresh request from the UI before dispatching [load] with load type [REFRESH] 143 */ 144 SKIP_INITIAL_REFRESH 145 } 146 } 147