1 /* <lambda>null2 * Copyright 2019 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.arch.core.util.Function 20 import androidx.paging.DataSource.KeyType.PAGE_KEYED 21 import kotlin.coroutines.resume 22 import kotlinx.coroutines.CancellableContinuation 23 import kotlinx.coroutines.suspendCancellableCoroutine 24 25 /** 26 * Incremental data loader for page-keyed content, where requests return keys for next/previous 27 * pages. 28 * 29 * Implement a [DataSource] using [PageKeyedDataSource] if you need to use data from page `N - 1` to 30 * load page `N`. This is common, for example, in network APIs that include a next/previous link or 31 * key with each page load. 32 * 33 * The `InMemoryByPageRepository` in the 34 * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md) 35 * shows how to implement a network PageKeyedDataSource using 36 * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network errors, 37 * and retry. 38 * 39 * @param Key Type of data used to query Value types out of the [DataSource]. 40 * @param Value Type of items being loaded by the [DataSource]. 41 */ 42 @Deprecated( 43 message = "PageKeyedDataSource is deprecated and has been replaced by PagingSource", 44 replaceWith = ReplaceWith("PagingSource<Key, Value>", "androidx.paging.PagingSource") 45 ) 46 public abstract class PageKeyedDataSource<Key : Any, Value : Any> : 47 DataSource<Key, Value>(PAGE_KEYED) { 48 49 /** 50 * Holder object for inputs to [loadInitial]. 51 * 52 * @param Key Type of data used to query pages. 53 * @param requestedLoadSize Requested number of items to load. 54 * 55 * Note that this may be larger than available data. 56 * 57 * @param placeholdersEnabled Defines whether placeholders are enabled, and whether the loaded 58 * total count will be ignored. 59 */ 60 public open class LoadInitialParams<Key : Any>( 61 @JvmField public val requestedLoadSize: Int, 62 @JvmField public val placeholdersEnabled: Boolean 63 ) 64 65 /** 66 * Holder object for inputs to [loadBefore] and [loadAfter]. 67 * 68 * @param Key Type of data used to query pages. 69 * @param key Load items before/after this key. 70 * 71 * Returned data must begin directly adjacent to this position. 72 * 73 * @param requestedLoadSize Requested number of items to load. 74 * 75 * Returned page can be of this size, but it may be altered if that is easier, e.g. a network 76 * data source where the backend defines page size. 77 */ 78 public open class LoadParams<Key : Any>( 79 @JvmField public val key: Key, 80 @JvmField public val requestedLoadSize: Int 81 ) 82 83 /** 84 * Callback for [loadInitial] to return data and, optionally, position/count information. 85 * 86 * A callback can be called only once, and will throw if called again. 87 * 88 * If you can compute the number of items in the data set before and after the loaded range, 89 * call the five parameter [onResult] to pass that information. You can skip passing this 90 * information by calling the three parameter [onResult], either if it's difficult to compute, 91 * or if [LoadInitialParams.placeholdersEnabled] is `false`, so the positioning information will 92 * be ignored. 93 * 94 * It is always valid for a DataSource loading method that takes a callback to stash the 95 * callback and call it later. This enables DataSources to be fully asynchronous, and to handle 96 * temporary, recoverable error states (such as a network error that can be retried). 97 * 98 * @param Key Type of data used to query pages. 99 * @param Value Type of items being loaded. 100 */ 101 public abstract class LoadInitialCallback<Key, Value> { 102 /** 103 * Called to pass initial load state from a DataSource. 104 * 105 * Call this method from your DataSource's `loadInitial` function to return data, and inform 106 * how many placeholders should be shown before and after. If counting is cheap to compute 107 * (for example, if a network load returns the information regardless), it's recommended to 108 * pass data back through this method. 109 * 110 * It is always valid to pass a different amount of data than what is requested. Pass an 111 * empty list if there is no more data to load. 112 * 113 * @param data List of items loaded from the [DataSource]. If this is empty, the 114 * [DataSource] is treated as empty, and no further loads will occur. 115 * @param position Position of the item at the front of the list. If there are `N` items 116 * before the items in data that can be loaded from this DataSource, pass `N`. 117 * @param totalCount Total number of items that may be returned from this DataSource. 118 * Includes the number in the initial `data` parameter as well as any items that can be 119 * loaded in front or behind of `data`. 120 * @param previousPageKey Key for page before the initial load result, or `null` if no more 121 * data can be loaded before. 122 * @param nextPageKey Key for page after the initial load result, or `null` if no more data 123 * can be loaded after. 124 */ 125 public abstract fun onResult( 126 data: List<Value>, 127 position: Int, 128 totalCount: Int, 129 previousPageKey: Key?, 130 nextPageKey: Key? 131 ) 132 133 /** 134 * Called to pass loaded data from a DataSource. 135 * 136 * Call this from [loadInitial] to initialize without counting available data, or supporting 137 * placeholders. 138 * 139 * It is always valid to pass a different amount of data than what is requested. Pass an 140 * empty list if there is no more data to load. 141 * 142 * @param data List of items loaded from the [PageKeyedDataSource]. 143 * @param previousPageKey Key for page before the initial load result, or `null` if no more 144 * data can be loaded before. 145 * @param nextPageKey Key for page after the initial load result, or `null` if no more data 146 * can be loaded after. 147 */ 148 public abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) 149 } 150 151 /** 152 * Callback for [loadBefore] and [loadAfter] to return data. 153 * 154 * A callback can be called only once, and will throw if called again. 155 * 156 * It is always valid for a DataSource loading method that takes a callback to stash the 157 * callback and call it later. This enables DataSources to be fully asynchronous, and to handle 158 * temporary, recoverable error states (such as a network error that can be retried). 159 * 160 * @param Key Type of data used to query pages. 161 * @param Value Type of items being loaded. 162 */ 163 public abstract class LoadCallback<Key, Value> { 164 /** 165 * Called to pass loaded data from a [DataSource]. 166 * 167 * Call this method from your PageKeyedDataSource's [loadBefore] and [loadAfter] methods to 168 * return data. 169 * 170 * It is always valid to pass a different amount of data than what is requested. Pass an 171 * empty list if there is no more data to load. 172 * 173 * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've 174 * loaded a page in [loadBefore], pass the key for the previous page, or `null` if the 175 * loaded page is the first. If in [loadAfter], pass the key for the next page, or `null` if 176 * the loaded page is the last. 177 * 178 * @param data List of items loaded from the PageKeyedDataSource. 179 * @param adjacentPageKey Key for subsequent page load (previous page in [loadBefore] / next 180 * page in [loadAfter]), or `null` if there are no more pages to load in the current load 181 * direction. 182 */ 183 public abstract fun onResult(data: List<Value>, adjacentPageKey: Key?) 184 } 185 186 /** @throws [IllegalArgumentException] when passed an unsupported load type. */ 187 @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly. 188 internal final override suspend fun load(params: Params<Key>): BaseResult<Value> = 189 when { 190 params.type == LoadType.REFRESH -> 191 loadInitial(LoadInitialParams(params.initialLoadSize, params.placeholdersEnabled)) 192 params.key == null -> BaseResult.empty() 193 params.type == LoadType.PREPEND -> loadBefore(LoadParams(params.key, params.pageSize)) 194 params.type == LoadType.APPEND -> loadAfter(LoadParams(params.key, params.pageSize)) 195 else -> throw IllegalArgumentException("Unsupported type " + params.type.toString()) 196 } 197 198 private suspend fun loadInitial(params: LoadInitialParams<Key>) = 199 suspendCancellableCoroutine<BaseResult<Value>> { cont -> 200 loadInitial( 201 params, 202 object : LoadInitialCallback<Key, Value>() { 203 override fun onResult( 204 data: List<Value>, 205 position: Int, 206 totalCount: Int, 207 previousPageKey: Key?, 208 nextPageKey: Key? 209 ) { 210 cont.resume( 211 BaseResult( 212 data = data, 213 prevKey = previousPageKey, 214 nextKey = nextPageKey, 215 itemsBefore = position, 216 itemsAfter = totalCount - data.size - position 217 ) 218 ) 219 } 220 221 override fun onResult( 222 data: List<Value>, 223 previousPageKey: Key?, 224 nextPageKey: Key? 225 ) { 226 cont.resume(BaseResult(data, previousPageKey, nextPageKey)) 227 } 228 } 229 ) 230 } 231 232 private suspend fun loadBefore(params: LoadParams<Key>) = 233 suspendCancellableCoroutine<BaseResult<Value>> { cont -> 234 loadBefore(params, continuationAsCallback(cont, false)) 235 } 236 237 private suspend fun loadAfter(params: LoadParams<Key>) = 238 suspendCancellableCoroutine<BaseResult<Value>> { cont -> 239 loadAfter(params, continuationAsCallback(cont, true)) 240 } 241 242 // Possible workaround for b/161464680; issue was reported when built with Kotlin 1.3.71 243 @Suppress("RemoveRedundantQualifierName") 244 private fun continuationAsCallback( 245 continuation: CancellableContinuation<BaseResult<Value>>, 246 isAppend: Boolean 247 ): LoadCallback<Key, Value> { 248 return object : LoadCallback<Key, Value>() { 249 override fun onResult(data: List<Value>, adjacentPageKey: Key?) { 250 continuation.resume( 251 BaseResult( 252 data = data, 253 prevKey = if (isAppend) null else adjacentPageKey, 254 nextKey = if (isAppend) adjacentPageKey else null 255 ) 256 ) 257 } 258 } 259 } 260 261 /** 262 * Load initial data. 263 * 264 * This method is called first to initialize a PagedList with data. If it's possible to count 265 * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to 266 * the callback via the three-parameter [LoadInitialCallback.onResult]. This enables PagedLists 267 * presenting data from this source to display placeholders to represent unloaded items. 268 * 269 * [LoadInitialParams.requestedLoadSize] is a hint, not a requirement, so it may be may be 270 * altered or ignored. 271 * 272 * @param params Parameters for initial load, including requested load size. 273 * @param callback Callback that receives initial load data. 274 */ 275 public abstract fun loadInitial( 276 params: LoadInitialParams<Key>, 277 callback: LoadInitialCallback<Key, Value> 278 ) 279 280 /** 281 * Prepend page with the key specified by [LoadParams.key]. 282 * 283 * It's valid to return a different list size than the page size if it's easier, e.g. if your 284 * backend defines page sizes. It is generally preferred to increase the number loaded than 285 * reduce. 286 * 287 * Data may be passed synchronously during the load method, or deferred and called at a later 288 * time. Further loads going down will be blocked until the callback is called. 289 * 290 * If data cannot be loaded (for example, if the request is invalid, or the data would be stale 291 * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and 292 * prevent further loading. 293 * 294 * @param params Parameters for the load, including the key for the new page, and requested load 295 * size. 296 * @param callback Callback that receives loaded data. 297 */ 298 public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>) 299 300 /** 301 * Append page with the key specified by [LoadParams.key]. 302 * 303 * It's valid to return a different list size than the page size if it's easier, e.g. if your 304 * backend defines page sizes. It is generally preferred to increase the number loaded than 305 * reduce. 306 * 307 * Data may be passed synchronously during the load method, or deferred and called at a later 308 * time. Further loads going down will be blocked until the callback is called. 309 * 310 * If data cannot be loaded (for example, if the request is invalid, or the data would be stale 311 * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and 312 * prevent further loading. 313 * 314 * @param params Parameters for the load, including the key for the new page, and requested load 315 * size. 316 * @param callback Callback that receives loaded data. 317 */ 318 public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>) 319 320 @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly. 321 internal override fun getKeyInternal(item: Value): Key = 322 throw IllegalStateException("Cannot get key by item in pageKeyedDataSource") 323 324 @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly. 325 internal override val supportsPageDropping = false 326 327 @Suppress("DEPRECATION") 328 final override fun <ToValue : Any> mapByPage( 329 function: Function<List<Value>, List<ToValue>> 330 ): PageKeyedDataSource<Key, ToValue> = WrapperPageKeyedDataSource(this, function) 331 332 @Suppress("DEPRECATION") 333 final override fun <ToValue : Any> mapByPage( 334 function: (List<Value>) -> List<ToValue> 335 ): PageKeyedDataSource<Key, ToValue> = mapByPage(Function { function(it) }) 336 337 @Suppress("DEPRECATION") 338 final override fun <ToValue : Any> map( 339 function: Function<Value, ToValue> 340 ): PageKeyedDataSource<Key, ToValue> = 341 mapByPage(Function { list -> list.map { function.apply(it) } }) 342 343 @Suppress("DEPRECATION") 344 final override fun <ToValue : Any> map( 345 function: (Value) -> ToValue 346 ): PageKeyedDataSource<Key, ToValue> = mapByPage(Function { list -> list.map(function) }) 347 } 348