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