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.AnyThread
20 import androidx.annotation.VisibleForTesting
21 import androidx.annotation.WorkerThread
22 import androidx.arch.core.util.Function
23 import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
24 import kotlinx.coroutines.CoroutineDispatcher
25 import kotlinx.coroutines.Dispatchers
26 
27 /**
28  * Base class for loading pages of snapshot data into a [PagedList].
29  *
30  * DataSource is queried to load pages of content into a [PagedList]. A PagedList can grow as it
31  * loads more data, but the data loaded cannot be updated. If the underlying data set is modified, a
32  * new PagedList / DataSource pair must be created to represent the new data.
33  *
34  * ### Loading Pages
35  *
36  * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter calls
37  * [PagedList.loadAround] to load content as the user scrolls in a RecyclerView.
38  *
39  * To control how and when a PagedList queries data from its DataSource, see [PagedList.Config]. The
40  * Config object defines things like load sizes and prefetch distance.
41  *
42  * ### Updating Paged Data
43  *
44  * A PagedList / DataSource pair are a snapshot of the data set. A new pair of PagedList /
45  * DataSource must be created if an update occurs, such as a reorder, insert, delete, or content
46  * update occurs. A DataSource must detect that it cannot continue loading its snapshot (for
47  * instance, when Database query notices a table being invalidated), and call [invalidate]. Then a
48  * new PagedList / DataSource pair would be created to load data from the new state of the Database
49  * query.
50  *
51  * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
52  * PagedList. For example, loading from network when the network's paging API doesn't provide
53  * updates.
54  *
55  * To page in data from a source that does provide updates, you can create a [DataSource.Factory],
56  * where each DataSource created is invalidated when an update to the data set occurs that makes the
57  * current snapshot invalid. For example, when paging a query from the Database, and the table being
58  * queried inserts or removes items. You can also use a DataSource.Factory to provide multiple
59  * versions of network-paged lists. If reloading all content (e.g. in response to an action like
60  * swipe-to-refresh) is required to get a new version of data, you can connect an explicit refresh
61  * signal to call [invalidate] on the current [DataSource].
62  *
63  * If you have more granular update signals, such as a network API signaling an update to a single
64  * item in the list, it's recommended to load data from network into memory. Then present that data
65  * to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy
66  * changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot
67  * can be created.
68  *
69  * ### Implementing a DataSource
70  *
71  * To implement, extend one of the subclasses: [PageKeyedDataSource], [ItemKeyedDataSource], or
72  * [PositionalDataSource].
73  *
74  * Use [PageKeyedDataSource] if pages you load embed keys for loading adjacent pages. For example a
75  * network response that returns some items, and a next/previous page links.
76  *
77  * Use [ItemKeyedDataSource] if you need to use data from item `N-1` to load item `N`. For example,
78  * if requesting the backend for the next comments in the list requires the ID or timestamp of the
79  * most recent loaded comment, or if querying the next users from a name-sorted database query
80  * requires the name and unique ID of the previous.
81  *
82  * Use [PositionalDataSource] if you can load pages of a requested size at arbitrary positions, and
83  * provide a fixed item count. PositionalDataSource supports querying pages at arbitrary positions,
84  * so can provide data to PagedLists in arbitrary order. Note that PositionalDataSource is required
85  * to respect page size for efficient tiling. If you want to override page size (e.g. when network
86  * page size constraints are only known at runtime), use one of the other DataSource classes.
87  *
88  * Because a `null` item indicates a placeholder in [PagedList], DataSource may not return `null`
89  * items in lists that it loads. This is so that users of the PagedList can differentiate unloaded
90  * placeholder items from content that has been paged in.
91  *
92  * @param Key Unique identifier for item loaded from DataSource. Often an integer to represent
93  *   position in data set. Note - this is distinct from e.g. Room's `<Value>` Value type loaded by
94  *   the DataSource.
95  */
96 public abstract class DataSource<Key : Any, Value : Any>
97 // Since we currently rely on implementation details of two implementations, prevent external
98 // subclassing, except through exposed subclasses.
99 internal constructor(internal val type: KeyType) {
100 
101     private val invalidateCallbackTracker =
102         InvalidateCallbackTracker<InvalidatedCallback>(
103             callbackInvoker = { it.onInvalidated() },
104             invalidGetter = { isInvalid },
105         )
106 
107     internal val invalidateCallbackCount: Int
108         @VisibleForTesting get() = invalidateCallbackTracker.callbackCount()
109 
110     /** @return `true` if the data source is invalid, and can no longer be queried for data. */
111     public open val isInvalid: Boolean
112         @WorkerThread get() = invalidateCallbackTracker.invalid
113 
114     /**
115      * Factory for DataSources.
116      *
117      * Data-loading systems of an application or library can implement this interface to allow
118      * `LiveData<PagedList>`s to be created. For example, Room can provide a [DataSource.Factory]
119      * for a given SQL query:
120      * ```
121      * @Dao
122      * interface UserDao {
123      *     @Query("SELECT * FROM user ORDER BY lastName ASC")
124      *     public abstract DataSource.Factory<Integer, User> usersByLastName();
125      * }
126      * ```
127      *
128      * In the above sample, `Integer` is used because it is the `Key` type of PositionalDataSource.
129      * Currently, Room uses the `LIMIT`/`OFFSET` SQL keywords to page a large query with a
130      * PositionalDataSource.
131      *
132      * @param Key Key identifying items in DataSource.
133      * @param Value Type of items in the list loaded by the DataSources.
134      */
135     public abstract class Factory<Key : Any, Value : Any> {
136         /**
137          * Create a [DataSource].
138          *
139          * The [DataSource] should invalidate itself if the snapshot is no longer valid. If a
140          * [DataSource] becomes invalid, the only way to query more data is to create a new
141          * [DataSource] from the Factory.
142          *
143          * [androidx.paging.LivePagedListBuilder] for example will construct a new PagedList and
144          * DataSource when the current DataSource is invalidated, and pass the new PagedList through
145          * the `LiveData<PagedList>` to observers.
146          *
147          * @return the new DataSource.
148          */
149         public abstract fun create(): DataSource<Key, Value>
150 
151         /**
152          * Applies the given function to each value emitted by DataSources produced by this Factory.
153          *
154          * Same as [mapByPage], but operates on individual items.
155          *
156          * @param function Function that runs on each loaded item, returning items of a potentially
157          *   new type.
158          * @param ToValue Type of items produced by the new [DataSource], from the passed function.
159          * @return A new [DataSource.Factory], which transforms items using the given function.
160          * @see mapByPage
161          * @see DataSource.map
162          * @see DataSource.mapByPage
163          */
164         public open fun <ToValue : Any> map(
165             function: Function<Value, ToValue>
166         ): Factory<Key, ToValue> {
167             return mapByPage(Function { list -> list.map { function.apply(it) } })
168         }
169 
170         /**
171          * Applies the given function to each value emitted by DataSources produced by this Factory.
172          *
173          * An overload of [map] that accepts a kotlin function type.
174          *
175          * Same as [mapByPage], but operates on individual items.
176          *
177          * @param function Function that runs on each loaded item, returning items of a potentially
178          *   new type.
179          * @param ToValue Type of items produced by the new [DataSource], from the passed function.
180          * @return A new [DataSource.Factory], which transforms items using the given function.
181          * @see mapByPage
182          * @see DataSource.map
183          * @see DataSource.mapByPage
184          */
185         @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
186         public open fun <ToValue : Any> map(function: (Value) -> ToValue): Factory<Key, ToValue> {
187             return mapByPage(Function { list -> list.map(function) })
188         }
189 
190         /**
191          * Applies the given function to each value emitted by DataSources produced by this Factory.
192          *
193          * Same as [map], but allows for batch conversions.
194          *
195          * @param function Function that runs on each loaded page, returning items of a potentially
196          *   new type.
197          * @param ToValue Type of items produced by the new [DataSource], from the passed function.
198          * @return A new [DataSource.Factory], which transforms items using the given function.
199          * @see map
200          * @see DataSource.map
201          * @see DataSource.mapByPage
202          */
203         public open fun <ToValue : Any> mapByPage(
204             function: Function<List<Value>, List<ToValue>>
205         ): Factory<Key, ToValue> =
206             object : Factory<Key, ToValue>() {
207                 override fun create(): DataSource<Key, ToValue> =
208                     this@Factory.create().mapByPage(function)
209             }
210 
211         /**
212          * Applies the given function to each value emitted by DataSources produced by this Factory.
213          *
214          * An overload of [mapByPage] that accepts a kotlin function type.
215          *
216          * Same as [map], but allows for batch conversions.
217          *
218          * @param function Function that runs on each loaded page, returning items of a potentially
219          *   new type.
220          * @param ToValue Type of items produced by the new [DataSource], from the passed function.
221          * @return A new [DataSource.Factory], which transforms items using the given function.
222          * @see map
223          * @see DataSource.map
224          * @see DataSource.mapByPage
225          */
226         @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
227         public open fun <ToValue : Any> mapByPage(
228             function: (List<Value>) -> List<ToValue>
229         ): Factory<Key, ToValue> = mapByPage(Function { function(it) })
230 
231         @JvmOverloads
232         public fun asPagingSourceFactory(
233             fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
234         ): () -> PagingSource<Key, Value> =
235             SuspendingPagingSourceFactory(
236                 delegate = { LegacyPagingSource(fetchDispatcher, create()) },
237                 dispatcher = fetchDispatcher
238             )
239     }
240 
241     /**
242      * Applies the given function to each value emitted by the DataSource.
243      *
244      * Same as [map], but allows for batch conversions.
245      *
246      * @param function Function that runs on each loaded page, returning items of a potentially new
247      *   type.
248      * @param ToValue Type of items produced by the new DataSource, from the passed function.
249      * @return A new DataSource, which transforms items using the given function.
250      * @see map
251      * @see DataSource.Factory.map
252      * @see DataSource.Factory.mapByPage
253      */
254     public open fun <ToValue : Any> mapByPage(
255         function: Function<List<Value>, List<ToValue>>
256     ): DataSource<Key, ToValue> = WrapperDataSource(this, function)
257 
258     /**
259      * Applies the given function to each value emitted by the DataSource.
260      *
261      * An overload of [mapByPage] that accepts a kotlin function type.
262      *
263      * Same as [map], but allows for batch conversions.
264      *
265      * @param function Function that runs on each loaded page, returning items of a potentially new
266      *   type.
267      * @param ToValue Type of items produced by the new DataSource, from the passed function.
268      * @return A new [DataSource], which transforms items using the given function.
269      * @see map
270      * @see DataSource.Factory.map
271      * @see DataSource.Factory.mapByPage
272      */
273     @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
274     public open fun <ToValue : Any> mapByPage(
275         function: (List<Value>) -> List<ToValue>
276     ): DataSource<Key, ToValue> = mapByPage(Function { function(it) })
277 
278     /**
279      * Applies the given function to each value emitted by the DataSource.
280      *
281      * Same as [mapByPage], but operates on individual items.
282      *
283      * @param function Function that runs on each loaded item, returning items of a potentially new
284      *   type.
285      * @param ToValue Type of items produced by the new DataSource, from the passed function.
286      * @return A new DataSource, which transforms items using the given function.
287      * @see mapByPage
288      * @see DataSource.Factory.map
289      * @see DataSource.Factory.mapByPage
290      */
291     public open fun <ToValue : Any> map(
292         function: Function<Value, ToValue>
293     ): DataSource<Key, ToValue> {
294         return mapByPage { list -> list.map { function.apply(it) } }
295     }
296 
297     /**
298      * Applies the given function to each value emitted by the DataSource.
299      *
300      * An overload of [map] that accepts a kotlin function type.
301      *
302      * Same as [mapByPage], but operates on individual items.
303      *
304      * @param function Function that runs on each loaded item, returning items of a potentially new
305      *   type.
306      * @param ToValue Type of items produced by the new DataSource, from the passed function.
307      * @return A new DataSource, which transforms items using the given function.
308      * @see mapByPage
309      * @see DataSource.Factory.map
310      */
311     @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
312     public open fun <ToValue : Any> map(function: (Value) -> ToValue): DataSource<Key, ToValue> =
313         map(Function { function(it) })
314 
315     /**
316      * Returns true if the data source guaranteed to produce a contiguous set of items, never
317      * producing gaps.
318      */
319     internal open val isContiguous = true
320 
321     internal open val supportsPageDropping = true
322 
323     /**
324      * Invalidation callback for [DataSource].
325      *
326      * Used to signal when a [DataSource] a data source has become invalid, and that a new data
327      * source is needed to continue loading data.
328      */
329     public fun interface InvalidatedCallback {
330         /**
331          * Called when the data backing the list has become invalid. This callback is typically used
332          * to signal that a new data source is needed.
333          *
334          * This callback will be invoked on the thread that calls [invalidate]. It is valid for the
335          * data source to invalidate itself during its load methods, or for an outside source to
336          * invalidate it.
337          */
338         @AnyThread public fun onInvalidated()
339     }
340 
341     /**
342      * Add a callback to invoke when the DataSource is first invalidated.
343      *
344      * Once invalidated, a data source will not become valid again.
345      *
346      * A data source will only invoke its callbacks once - the first time [invalidate] is called, on
347      * that thread.
348      *
349      * If this [DataSource] is already invalid, the provided [onInvalidatedCallback] will be
350      * triggered immediately.
351      *
352      * @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
353      *   [DataSource].
354      */
355     @AnyThread
356     @Suppress("RegistrationName")
357     public open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
358         invalidateCallbackTracker.registerInvalidatedCallback(onInvalidatedCallback)
359     }
360 
361     /**
362      * Remove a previously added invalidate callback.
363      *
364      * @param onInvalidatedCallback The previously added callback.
365      */
366     @AnyThread
367     @Suppress("RegistrationName")
368     public open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
369         invalidateCallbackTracker.unregisterInvalidatedCallback(onInvalidatedCallback)
370     }
371 
372     /**
373      * Signal the data source to stop loading, and notify its callback.
374      *
375      * If invalidate has already been called, this method does nothing.
376      */
377     @AnyThread
378     public open fun invalidate() {
379         invalidateCallbackTracker.invalidate()
380     }
381 
382     /**
383      * @param K Type of the key used to query the [DataSource].
384      * @property key Can be `null` for init, otherwise non-null
385      */
386     internal class Params<K : Any>
387     internal constructor(
388         internal val type: LoadType,
389         val key: K?,
390         val initialLoadSize: Int,
391         val placeholdersEnabled: Boolean,
392         val pageSize: Int
393     ) {
394         init {
395             if (type != LoadType.REFRESH && key == null) {
396                 throw IllegalArgumentException("Key must be non-null for prepend/append")
397             }
398         }
399     }
400 
401     /** @param Value Type of the data produced by a [DataSource]. */
402     internal class BaseResult<Value : Any>
403     internal constructor(
404         @JvmField val data: List<Value>,
405         val prevKey: Any?,
406         val nextKey: Any?,
407         val itemsBefore: Int = COUNT_UNDEFINED,
408         val itemsAfter: Int = COUNT_UNDEFINED
409     ) {
410         init {
411             if (itemsBefore < 0 && itemsBefore != COUNT_UNDEFINED) {
412                 throw IllegalArgumentException("Position must be non-negative")
413             }
414             if (data.isEmpty() && (itemsBefore > 0 || itemsAfter > 0)) {
415                 // If non-initial, itemsBefore, itemsAfter are COUNT_UNDEFINED
416                 throw IllegalArgumentException(
417                     "Initial result cannot be empty if items are present in data set."
418                 )
419             }
420             if (itemsAfter < 0 && itemsAfter != COUNT_UNDEFINED) {
421                 throw IllegalArgumentException(
422                     "List size + position too large, last item in list beyond totalCount."
423                 )
424             }
425         }
426 
427         /**
428          * While it may seem unnecessary to do this validation now that tiling is gone, we do this
429          * to ensure consistency with 2.1, and to ensure all loadRanges have the same page size.
430          */
431         internal fun validateForInitialTiling(pageSize: Int) {
432             if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
433                 throw IllegalStateException(
434                     "Placeholders requested, but totalCount not provided. Please call the" +
435                         " three-parameter onResult method, or disable placeholders in the" +
436                         " PagedList.Config"
437                 )
438             }
439 
440             if (itemsAfter > 0 && data.size % pageSize != 0) {
441                 val totalCount = itemsBefore + data.size + itemsAfter
442                 throw IllegalArgumentException(
443                     "PositionalDataSource requires initial load size to be a multiple of page" +
444                         " size to support internal tiling. loadSize ${data.size}, position" +
445                         " $itemsBefore, totalCount $totalCount, pageSize $pageSize"
446                 )
447             }
448             if (itemsBefore % pageSize != 0) {
449                 throw IllegalArgumentException(
450                     "Initial load must be pageSize aligned.Position = $itemsBefore, pageSize =" +
451                         " $pageSize"
452                 )
453             }
454         }
455 
456         override fun equals(other: Any?) =
457             when (other) {
458                 is BaseResult<*> ->
459                     data == other.data &&
460                         prevKey == other.prevKey &&
461                         nextKey == other.nextKey &&
462                         itemsBefore == other.itemsBefore &&
463                         itemsAfter == other.itemsAfter
464                 else -> false
465             }
466 
467         internal companion object {
468             internal fun <T : Any> empty() = BaseResult(emptyList<T>(), null, null, 0, 0)
469 
470             internal fun <ToValue : Any, Value : Any> convert(
471                 result: BaseResult<ToValue>,
472                 function: Function<List<ToValue>, List<Value>>
473             ) =
474                 BaseResult(
475                     data = convert(function, result.data),
476                     prevKey = result.prevKey,
477                     nextKey = result.nextKey,
478                     itemsBefore = result.itemsBefore,
479                     itemsAfter = result.itemsAfter
480                 )
481         }
482     }
483 
484     internal enum class KeyType {
485         POSITIONAL,
486         PAGE_KEYED,
487         ITEM_KEYED
488     }
489 
490     internal abstract suspend fun load(params: Params<Key>): BaseResult<Value>
491 
492     internal abstract fun getKeyInternal(item: Value): Key
493 
494     internal companion object {
495         internal fun <A, B> convert(
496             function: Function<List<A>, List<B>>,
497             source: List<A>
498         ): List<B> {
499             val dest = function.apply(source)
500             if (dest.size != source.size) {
501                 throw IllegalStateException(
502                     "Invalid Function $function changed return size. This is not supported."
503                 )
504             }
505             return dest
506         }
507     }
508 }
509