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