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