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.IntRange
20 import androidx.annotation.MainThread
21 import androidx.annotation.RestrictTo
22 import java.lang.ref.WeakReference
23 import java.util.AbstractList
24 import java.util.concurrent.Executor
25 import kotlinx.coroutines.CoroutineDispatcher
26 import kotlinx.coroutines.CoroutineScope
27 import kotlinx.coroutines.DelicateCoroutinesApi
28 import kotlinx.coroutines.Dispatchers
29 import kotlinx.coroutines.GlobalScope
30 import kotlinx.coroutines.asCoroutineDispatcher
31 import kotlinx.coroutines.launch
32 import kotlinx.coroutines.runBlocking
33 
34 /**
35  * Lazy loading list that pages in immutable content from a [PagingSource].
36  *
37  * A [PagedList] is a [List] which loads its data in chunks (pages) from a [PagingSource]. Items can
38  * be accessed with [get], and further loading can be triggered with [loadAround]. To display a
39  * [PagedList], see [androidx.paging.PagedListAdapter], which enables the binding of a [PagedList]
40  * to a [androidx.recyclerview.widget.RecyclerView].
41  *
42  * ### Loading Data
43  *
44  * All data in a [PagedList] is loaded from its [PagingSource]. Creating a [PagedList] loads the
45  * first chunk of data from the [PagingSource] immediately, and should for this reason be done on a
46  * background thread. The constructed [PagedList] may then be passed to and used on the UI thread.
47  * This is done to prevent passing a list with no loaded content to the UI thread, which should
48  * generally not be presented to the user.
49  *
50  * A [PagedList] initially presents this first partial load as its content, and expands over time as
51  * content is loaded in. When [loadAround] is called, items will be loaded in near the passed list
52  * index. If placeholder `null`s are present in the list, they will be replaced as content is
53  * loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
54  *
55  * [PagedList] can present data for an unbounded, infinite scrolling list, or a very large but
56  * countable list. Use [PagedList.Config] to control how many items a [PagedList] loads, and when.
57  *
58  * If you use [androidx.paging.LivePagedListBuilder] to get a [androidx.lifecycle.LiveData], it will
59  * initialize [PagedList]s on a background thread for you.
60  *
61  * ### Placeholders
62  *
63  * There are two ways that [PagedList] can represent its not-yet-loaded data - with or without
64  * `null` placeholders.
65  *
66  * With placeholders, the [PagedList] is always the full size of the data set. `get(N)` returns the
67  * `N`th item in the data set, or `null` if it's not yet loaded.
68  *
69  * Without `null` placeholders, the [PagedList] is the sublist of data that has already been loaded.
70  * The size of the [PagedList] is the number of currently loaded items, and `get(N)` returns the
71  * `N`th *loaded* item. This is not necessarily the `N`th item in the data set.
72  *
73  * Placeholders have several benefits:
74  * * They express the full sized list to the presentation layer (often a
75  *   [androidx.paging.PagedListAdapter]), and so can support scrollbars (without jumping as pages
76  *   are loaded or dropped) and fast-scrolling to any position, loaded or not.
77  * * They avoid the need for a loading spinner at the end of the loaded list, since the list is
78  *   always full sized.
79  *
80  * They also have drawbacks:
81  * * Your Adapter needs to account for `null` items. This often means providing default values in
82  *   data you bind to a [androidx.recyclerview.widget.RecyclerView.ViewHolder].
83  * * They don't work well if your item views are of different sizes, as this will prevent loading
84  *   items from cross-fading nicely.
85  * * They require you to count your data set, which can be expensive or impossible, depending on
86  *   your [PagingSource].
87  *
88  * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
89  * [PagingSource] does not count its data set in its initial load, or if `false` is passed to
90  * [PagedList.Config.Builder.setEnablePlaceholders] when building a [PagedList.Config].
91  *
92  * ### Mutability and Snapshots
93  *
94  * A [PagedList] is *mutable* while loading, or ready to load from its [PagingSource]. As loads
95  * succeed, a mutable [PagedList] will be updated via Runnables on the main thread. You can listen
96  * to these updates with a [PagedList.Callback]. (Note that [androidx.paging .PagedListAdapter] will
97  * listen to these to signal RecyclerView about the updates/changes).
98  *
99  * If a [PagedList] attempts to load from an invalid [PagingSource], it will [detach] from the
100  * [PagingSource], meaning that it will no longer attempt to load data. It will return true from
101  * [isImmutable], and a new [PagingSource] / [PagedList] pair must be created to load further data.
102  *
103  * See [PagingSource] and [androidx.paging.LivePagedListBuilder] for how new [PagedList]s are
104  * created to represent changed data.
105  *
106  * A [PagedList] snapshot is simply an immutable shallow copy of the current state of the
107  * [PagedList] as a `List`. It will reference the same inner items, and contain the same `null`
108  * placeholders, if present.
109  *
110  * @param T The type of the entries in the list.
111  */
112 @Suppress("DEPRECATION")
113 @Deprecated("PagedList is deprecated and has been replaced by PagingData")
114 public abstract class PagedList<T : Any>
115 internal constructor(
116     /** The [PagingSource] that provides data to this [PagedList]. */
117     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
118     public open val pagingSource: PagingSource<*, T>,
119     internal val coroutineScope: CoroutineScope,
120     internal val notifyDispatcher: CoroutineDispatcher,
121     internal val storage: PagedStorage<T>,
122 
123     /**
124      * Return the Config used to construct this PagedList.
125      *
126      * @return the Config of this PagedList
127      */
128     public val config: Config
129 ) : AbstractList<T>() {
130     public companion object {
131         /**
132          * Create a [PagedList] which loads data from the provided data source on a background
133          * thread, posting updates to the main thread.
134          *
135          * @param pagingSource [PagingSource] providing data to the [PagedList]
136          * @param notifyDispatcher [CoroutineDispatcher] that will use and consume data from the
137          *   [PagedList]. Generally, this is the UI/main thread.
138          * @param fetchDispatcher Data loading jobs will be dispatched to this
139          *   [CoroutineDispatcher] - should be a background thread.
140          * @param boundaryCallback Optional boundary callback to attach to the list.
141          * @param config [PagedList.Config], which defines how the [PagedList] will load data.
142          * @param K Key type that indicates to the [PagingSource] what data to load.
143          * @param T Type of items to be held and loaded by the [PagedList].
144          * @return The newly created [PagedList], which will page in data from the [PagingSource] as
145          *   needed.
146          */
147         @JvmStatic
148         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
149         public fun <K : Any, T : Any> create(
150             pagingSource: PagingSource<K, T>,
151             initialPage: PagingSource.LoadResult.Page<K, T>?,
152             coroutineScope: CoroutineScope,
153             notifyDispatcher: CoroutineDispatcher,
154             fetchDispatcher: CoroutineDispatcher,
155             boundaryCallback: BoundaryCallback<T>?,
156             config: Config,
157             key: K?
158         ): PagedList<T> {
159             val resolvedInitialPage =
160                 when (initialPage) {
161                     null -> {
162                         // Compatibility codepath - perform the initial load immediately, since
163                         // caller
164                         // hasn't done it. We block in this case, but it's only used in the legacy
165                         // path.
166                         val params =
167                             PagingSource.LoadParams.Refresh(
168                                 key,
169                                 config.initialLoadSizeHint,
170                                 config.enablePlaceholders,
171                             )
172                         runBlocking {
173                             val initialResult = pagingSource.load(params)
174                             when (initialResult) {
175                                 is PagingSource.LoadResult.Page -> initialResult
176                                 is PagingSource.LoadResult.Error -> throw initialResult.throwable
177                                 is PagingSource.LoadResult.Invalid ->
178                                     throw IllegalStateException(
179                                         "Failed to create PagedList. The provided PagingSource " +
180                                             "returned LoadResult.Invalid, but a LoadResult.Page was " +
181                                             "expected. To use a PagingSource which supports " +
182                                             "invalidation, use a PagedList builder that accepts a " +
183                                             "factory method for PagingSource or DataSource.Factory, " +
184                                             "such as LivePagedList."
185                                     )
186                             }
187                         }
188                     }
189                     else -> initialPage
190                 }
191             return ContiguousPagedList(
192                 pagingSource,
193                 coroutineScope,
194                 notifyDispatcher,
195                 fetchDispatcher,
196                 boundaryCallback,
197                 config,
198                 resolvedInitialPage,
199                 key
200             )
201         }
202 
203         /**
204          * Extremely naive diff dispatch: mark entire list as modified (essentially,
205          * notifyDataSetChanged). We do this because previous logic was incorrect, and could
206          * dispatch invalid diffs when pages are dropped. Instead of passing a snapshot, we now
207          * recommend to strictly use the addWeakCallback variant that only accepts a callback.
208          */
209         internal fun dispatchNaiveUpdatesSinceSnapshot(
210             currentSize: Int,
211             snapshotSize: Int,
212             callback: Callback
213         ) {
214             if (snapshotSize < currentSize) {
215                 if (snapshotSize > 0) {
216                     callback.onChanged(0, snapshotSize)
217                 }
218                 val diffCount = currentSize - snapshotSize
219                 if (diffCount > 0) {
220                     callback.onInserted(snapshotSize, diffCount)
221                 }
222             } else {
223                 if (currentSize > 0) {
224                     callback.onChanged(0, currentSize)
225                 }
226                 val diffCount = snapshotSize - currentSize
227                 if (diffCount != 0) {
228                     callback.onRemoved(currentSize, diffCount)
229                 }
230             }
231         }
232     }
233 
234     /**
235      * Builder class for [PagedList].
236      *
237      * [pagingSource], [config], [notifyDispatcher] and [fetchDispatcher] must all be provided.
238      *
239      * A [PagedList] queries initial data from its [PagingSource] during construction, to avoid
240      * empty [PagedList]s being presented to the UI when possible. It's preferred to present initial
241      * data, so that the UI doesn't show an empty list, or placeholders for a few frames, just
242      * before showing initial content.
243      *
244      * [LivePagedListBuilder][androidx.paging.LivePagedListBuilder] does this creation on a
245      * background thread automatically, if you want to receive a `LiveData<PagedList<...>>`.
246      *
247      * @param Key Type of key used to load data from the [PagingSource].
248      * @param Value Type of items held and loaded by the [PagedList].
249      */
250     @Deprecated(
251         message =
252             "PagedList is deprecated and has been replaced by PagingData, which no " +
253                 "longer supports constructing snapshots of loaded data manually.",
254         replaceWith = ReplaceWith("Pager.flow", "androidx.paging.Pager")
255     )
256     public class Builder<Key : Any, Value : Any> {
257         private val pagingSource: PagingSource<Key, Value>?
258         private var dataSource: DataSource<Key, Value>?
259         private val initialPage: PagingSource.LoadResult.Page<Key, Value>?
260         private val config: Config
261         @OptIn(DelicateCoroutinesApi::class)
262         private var coroutineScope: CoroutineScope = GlobalScope
263         private var notifyDispatcher: CoroutineDispatcher? = null
264         private var fetchDispatcher: CoroutineDispatcher? = null
265         private var boundaryCallback: BoundaryCallback<Value>? = null
266         private var initialKey: Key? = null
267 
268         /**
269          * Create a [Builder][PagedList.Builder] with the provided [DataSource] and
270          * [Config][PagedList.Config].
271          *
272          * @param dataSource [DataSource] the [PagedList] will load from.
273          * @param config [PagedList.Config] that defines how the [PagedList] loads data from its
274          *   [DataSource].
275          */
276         public constructor(dataSource: DataSource<Key, Value>, config: Config) {
277             this.pagingSource = null
278             this.dataSource = dataSource
279             this.initialPage = null
280             this.config = config
281         }
282 
283         /**
284          * Create a [PagedList.Builder] with the provided [DataSource] and [pageSize].
285          *
286          * This method is a convenience for:
287          * ```
288          * PagedList.Builder(dataSource,
289          *     new PagedList.Config.Builder().setPageSize(pageSize).build());
290          * ```
291          *
292          * @param dataSource [DataSource] the [PagedList] will load from.
293          * @param pageSize Size of loaded pages when the [PagedList] loads data from its
294          *   [DataSource].
295          */
296         public constructor(
297             dataSource: DataSource<Key, Value>,
298             pageSize: Int
299         ) : this(dataSource = dataSource, config = Config(pageSize))
300 
301         /**
302          * Create a [PagedList.Builder] with the provided [PagingSource], initial
303          * [PagingSource.LoadResult.Page], and [PagedList.Config].
304          *
305          * @param pagingSource [PagingSource] the [PagedList] will load from.
306          * @param initialPage Initial page loaded from the [PagingSource].
307          * @param config [PagedList.Config] that defines how the [PagedList] loads data from its
308          *   [PagingSource].
309          */
310         public constructor(
311             pagingSource: PagingSource<Key, Value>,
312             initialPage: PagingSource.LoadResult.Page<Key, Value>,
313             config: Config
314         ) {
315             this.pagingSource = pagingSource
316             this.dataSource = null
317             this.initialPage = initialPage
318             this.config = config
319         }
320 
321         /**
322          * Create a [PagedList.Builder] with the provided [PagingSource], initial
323          * [PagingSource.LoadResult.Page], and [pageSize].
324          *
325          * This method is a convenience for:
326          * ```
327          * PagedList.Builder(
328          *     pagingSource,
329          *     page,
330          *     PagedList.Config.Builder().setPageSize(pageSize).build()
331          * )
332          * ```
333          *
334          * @param pagingSource [PagingSource] the [PagedList] will load from.
335          * @param initialPage Initial page loaded from the [PagingSource].
336          * @param pageSize Size of loaded pages when the [PagedList] loads data from its
337          *   [PagingSource].
338          */
339         public constructor(
340             pagingSource: PagingSource<Key, Value>,
341             initialPage: PagingSource.LoadResult.Page<Key, Value>,
342             pageSize: Int
343         ) : this(pagingSource = pagingSource, initialPage = initialPage, config = Config(pageSize))
344 
345         /**
346          * Set the [CoroutineScope] that page loads should be launched within.
347          *
348          * The set [coroutineScope] allows a [PagingSource] to cancel running load operations when
349          * the results are no longer needed - for example, when the containing Activity is
350          * destroyed.
351          *
352          * Defaults to [GlobalScope].
353          *
354          * @param coroutineScope
355          * @return this
356          */
357         public fun setCoroutineScope(coroutineScope: CoroutineScope): Builder<Key, Value> = apply {
358             this.coroutineScope = coroutineScope
359         }
360 
361         /**
362          * The [Executor] defining where page loading updates are dispatched.
363          *
364          * @param notifyExecutor [Executor] that receives [PagedList] updates, and where
365          *   [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
366          * @return this
367          */
368         @Deprecated(
369             message =
370                 "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
371                     "consider passing a CoroutineDispatcher directly",
372             replaceWith =
373                 ReplaceWith(
374                     "setNotifyDispatcher(fetchExecutor.asCoroutineDispatcher())",
375                     "kotlinx.coroutines.asCoroutineDispatcher"
376                 )
377         )
378         public fun setNotifyExecutor(notifyExecutor: Executor): Builder<Key, Value> = apply {
379             this.notifyDispatcher = notifyExecutor.asCoroutineDispatcher()
380         }
381 
382         /**
383          * The [CoroutineDispatcher] defining where page loading updates are dispatched.
384          *
385          * @param notifyDispatcher [CoroutineDispatcher] that receives [PagedList] updates, and
386          *   where [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
387          * @return this
388          */
389         public fun setNotifyDispatcher(notifyDispatcher: CoroutineDispatcher): Builder<Key, Value> =
390             apply {
391                 this.notifyDispatcher = notifyDispatcher
392             }
393 
394         /**
395          * The [Executor] used to fetch additional pages from the [PagingSource].
396          *
397          * Does not affect initial load, which will be done immediately on whichever thread the
398          * [PagedList] is created on.
399          *
400          * @param fetchExecutor [Executor] used to fetch from [PagingSource]s, generally a
401          *   background thread pool for e.g. I/O or network loading.
402          * @return this
403          */
404         @Deprecated(
405             message =
406                 "Passing an executor will cause it get wrapped as a CoroutineDispatcher, " +
407                     "consider passing a CoroutineDispatcher directly",
408             replaceWith =
409                 ReplaceWith(
410                     "setFetchDispatcher(fetchExecutor.asCoroutineDispatcher())",
411                     "kotlinx.coroutines.asCoroutineDispatcher"
412                 )
413         )
414         public fun setFetchExecutor(fetchExecutor: Executor): Builder<Key, Value> = apply {
415             this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
416         }
417 
418         /**
419          * The [CoroutineDispatcher] used to fetch additional pages from the [PagingSource].
420          *
421          * Does not affect initial load, which will be done immediately on whichever thread the
422          * [PagedList] is created on.
423          *
424          * @param fetchDispatcher [CoroutineDispatcher] used to fetch from [PagingSource]s,
425          *   generally a background thread pool for e.g. I/O or network loading.
426          * @return this
427          */
428         public fun setFetchDispatcher(fetchDispatcher: CoroutineDispatcher): Builder<Key, Value> =
429             apply {
430                 this.fetchDispatcher = fetchDispatcher
431             }
432 
433         /**
434          * The [BoundaryCallback] for out of data events.
435          *
436          * Pass a [BoundaryCallback] to listen to when the [PagedList] runs out of data to load.
437          *
438          * @param boundaryCallback [BoundaryCallback] for listening to out-of-data events.
439          * @return this
440          */
441         public fun setBoundaryCallback(
442             boundaryCallback: BoundaryCallback<Value>?
443         ): Builder<Key, Value> = apply { this.boundaryCallback = boundaryCallback }
444 
445         /**
446          * Sets the initial key the [PagingSource] should load around as part of initialization.
447          *
448          * @param initialKey Key the [PagingSource] should load around as part of initialization.
449          * @return this
450          */
451         public fun setInitialKey(initialKey: Key?): Builder<Key, Value> = apply {
452             this.initialKey = initialKey
453         }
454 
455         /**
456          * Creates a [PagedList] with the given parameters.
457          *
458          * This call will dispatch the [androidx.paging.PagingSource]'s loadInitial method
459          * immediately on the current thread, and block the current on the result. This method
460          * should always be called on a worker thread to prevent blocking the main thread.
461          *
462          * It's fine to create a [PagedList] with an async [PagingSource] on the main thread, such
463          * as in the constructor of a ViewModel. An async network load won't block the initial call
464          * to the Load function. For a synchronous [PagingSource] such as one created from a Room
465          * database, a `LiveData<PagedList>` can be safely constructed with
466          * [androidx.paging.LivePagedListBuilder] on the main thread, since actual construction work
467          * is deferred, and done on a background thread.
468          *
469          * While [build] will always return a [PagedList], it's important to note that the
470          * [PagedList] initial load may fail to acquire data from the [PagingSource]. This can
471          * happen for example if the [PagingSource] is invalidated during its initial load. If this
472          * happens, the [PagedList] will be immediately [detached][PagedList.isDetached], and you
473          * can retry construction (including setting a new [PagingSource]).
474          *
475          * @return The newly constructed [PagedList]
476          * @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
477          */
478         public fun build(): PagedList<Value> {
479             val fetchDispatcher = fetchDispatcher ?: Dispatchers.IO
480             val pagingSource =
481                 pagingSource
482                     ?: dataSource?.let { dataSource ->
483                         LegacyPagingSource(fetchContext = fetchDispatcher, dataSource = dataSource)
484                     }
485 
486             if (pagingSource is LegacyPagingSource) {
487                 pagingSource.setPageSize(config.pageSize)
488             }
489 
490             check(pagingSource != null) {
491                 "PagedList cannot be built without a PagingSource or DataSource"
492             }
493 
494             return create(
495                 pagingSource,
496                 initialPage,
497                 coroutineScope,
498                 notifyDispatcher ?: Dispatchers.Main.immediate,
499                 fetchDispatcher,
500                 boundaryCallback,
501                 config,
502                 initialKey
503             )
504         }
505     }
506 
507     /**
508      * Callback signaling when content is loaded into the list.
509      *
510      * Can be used to listen to items being paged in and out. These calls will be dispatched on the
511      * dispatcher defined by [PagedList.Builder.setNotifyDispatcher], which is generally the main/UI
512      * thread.
513      */
514     public abstract class Callback {
515         /**
516          * Called when null padding items have been loaded to signal newly available data, or when
517          * data that hasn't been used in a while has been dropped, and swapped back to null.
518          *
519          * @param position Position of first newly loaded items, out of total number of items
520          *   (including padded nulls).
521          * @param count Number of items loaded.
522          */
523         public abstract fun onChanged(position: Int, count: Int)
524 
525         /**
526          * Called when new items have been loaded at the end or beginning of the list.
527          *
528          * @param position Position of the first newly loaded item (in practice, either `0` or
529          *   `size - 1`.
530          * @param count Number of items loaded.
531          */
532         public abstract fun onInserted(position: Int, count: Int)
533 
534         /**
535          * Called when items have been removed at the end or beginning of the list, and have not
536          * been replaced by padded nulls.
537          *
538          * @param position Position of the first newly loaded item (in practice, either `0` or
539          *   `size - 1`.
540          * @param count Number of items loaded.
541          */
542         public abstract fun onRemoved(position: Int, count: Int)
543     }
544 
545     /**
546      * Configures how a [PagedList] loads content from its [PagingSource].
547      *
548      * Use [PagedList.Config.Builder] to construct and define custom loading behavior, such as
549      * [setPageSize][PagedList.Config.Builder.setPageSize], which defines number of items loaded at
550      * a time.
551      */
552     public class Config
553     internal constructor(
554         /** Size of each page loaded by the PagedList. */
555         @JvmField public val pageSize: Int,
556         /**
557          * Prefetch distance which defines how far ahead to load.
558          *
559          * If this value is set to 50, the paged list will attempt to load 50 items in advance of
560          * data that's already been accessed.
561          *
562          * @see PagedList.loadAround
563          */
564         @JvmField public val prefetchDistance: Int,
565         /**
566          * Defines whether the [PagedList] may display null placeholders, if the [PagingSource]
567          * provides them.
568          */
569         @JvmField public val enablePlaceholders: Boolean,
570         /** Size hint for initial load of PagedList, often larger than a regular page. */
571         @JvmField public val initialLoadSizeHint: Int,
572         /**
573          * Defines the maximum number of items that may be loaded into this pagedList before pages
574          * should be dropped.
575          *
576          * If set to [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED], pages will never be dropped.
577          *
578          * @see PagedList.Config.Companion.MAX_SIZE_UNBOUNDED
579          * @see PagedList.Config.Builder.setMaxSize
580          */
581         @JvmField public val maxSize: Int
582     ) {
583         /**
584          * Builder class for [PagedList.Config].
585          *
586          * You must at minimum specify page size with [setPageSize].
587          */
588         public class Builder {
589             private var pageSize = -1
590             private var prefetchDistance = -1
591             private var initialLoadSizeHint = -1
592             private var enablePlaceholders = true
593             private var maxSize = MAX_SIZE_UNBOUNDED
594 
595             /**
596              * Defines the number of items loaded at once from the [PagingSource].
597              *
598              * Should be several times the number of visible items onscreen.
599              *
600              * Configuring your page size depends on how your data is being loaded and used. Smaller
601              * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
602              * improve loading throughput, to a point (avoid loading more than 2MB from SQLite at
603              * once, since it incurs extra cost).
604              *
605              * If you're loading data for very large, social-media style cards that take up most of
606              * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
607              * displaying dozens of items in a tiled grid, which can present items during a scroll
608              * much more quickly, consider closer to 100.
609              *
610              * @param pageSize Number of items loaded at once from the [PagingSource].
611              * @return this
612              * @throws IllegalArgumentException if pageSize is < `1`.
613              */
614             public fun setPageSize(@IntRange(from = 1) pageSize: Int): Builder = apply {
615                 if (pageSize < 1) {
616                     throw IllegalArgumentException("Page size must be a positive number")
617                 }
618                 this.pageSize = pageSize
619             }
620 
621             /**
622              * Defines how far from the edge of loaded content an access must be to trigger further
623              * loading.
624              *
625              * Should be several times the number of visible items onscreen.
626              *
627              * If not set, defaults to page size.
628              *
629              * A value of 0 indicates that no list items will be loaded until they are specifically
630              * requested. This is generally not recommended, so that users don't observe a
631              * placeholder item (with placeholders) or end of list (without) while scrolling.
632              *
633              * @param prefetchDistance Distance the [PagedList] should prefetch.
634              * @return this
635              */
636             public fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int): Builder =
637                 apply {
638                     this.prefetchDistance = prefetchDistance
639                 }
640 
641             /**
642              * Pass false to disable null placeholders in [PagedList]s using this
643              * [PagedList.Config].
644              *
645              * If not set, defaults to true.
646              *
647              * A [PagedList] will present null placeholders for not-yet-loaded content if two
648              * conditions are met:
649              * 1) Its [PagingSource] can count all unloaded items (so that the number of nulls to
650              *    present is known).
651              * 2) placeholders are not disabled on the [PagedList.Config].
652              *
653              * Call `setEnablePlaceholders(false)` to ensure the receiver of the PagedList (often a
654              * [androidx.paging.PagedListAdapter]) doesn't need to account for null items.
655              *
656              * If placeholders are disabled, not-yet-loaded content will not be present in the list.
657              * Paging will still occur, but as items are loaded or removed, they will be signaled as
658              * inserts to the [PagedList.Callback].
659              *
660              * [PagedList.Callback.onChanged] will not be issued as part of loading, though a
661              * [androidx.paging.PagedListAdapter] may still receive change events as a result of
662              * [PagedList] diffing.
663              *
664              * @param enablePlaceholders `false` if null placeholders should be disabled.
665              * @return this
666              */
667             public fun setEnablePlaceholders(enablePlaceholders: Boolean): Builder = apply {
668                 this.enablePlaceholders = enablePlaceholders
669             }
670 
671             /**
672              * Defines how many items to load when first load occurs.
673              *
674              * This value is typically larger than page size, so on first load data there's a large
675              * enough range of content loaded to cover small scrolls.
676              *
677              * If not set, defaults to three times page size.
678              *
679              * @param initialLoadSizeHint Number of items to load while initializing the [PagedList]
680              * @return this
681              */
682             public fun setInitialLoadSizeHint(
683                 @IntRange(from = 1) initialLoadSizeHint: Int
684             ): Builder = apply { this.initialLoadSizeHint = initialLoadSizeHint }
685 
686             /**
687              * Defines how many items to keep loaded at once.
688              *
689              * This can be used to cap the number of items kept in memory by dropping pages. This
690              * value is typically many pages so old pages are cached in case the user scrolls back.
691              *
692              * This value must be at least two times the [prefetchDistance][setPrefetchDistance]
693              * plus the [pageSize][setPageSize]). This constraint prevent loads from being
694              * continuously fetched and discarded due to prefetching.
695              *
696              * The max size specified here best effort, not a guarantee. In practice, if [maxSize]
697              * is many times the page size, the number of items held by the [PagedList] will not
698              * grow above this number. Exceptions are made as necessary to guarantee:
699              * * Pages are never dropped until there are more than two pages loaded. Note that a
700              *   [PagingSource] may not be held strictly to
701              *   [requested pageSize][PagedList.Config.pageSize], so two pages may be larger than
702              *   expected.
703              * * Pages are never dropped if they are within a prefetch window (defined to be
704              *   `pageSize + (2 * prefetchDistance)`) of the most recent load.
705              *
706              * If not set, defaults to [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED], which
707              * disables page dropping.
708              *
709              * @param maxSize Maximum number of items to keep in memory, or
710              *   [PagedList.Config.Companion.MAX_SIZE_UNBOUNDED] to disable page dropping.
711              * @return this
712              * @see Config.MAX_SIZE_UNBOUNDED
713              * @see Config.maxSize
714              */
715             public fun setMaxSize(@IntRange(from = 2) maxSize: Int): Builder = apply {
716                 this.maxSize = maxSize
717             }
718 
719             /**
720              * Creates a [PagedList.Config] with the given parameters.
721              *
722              * @return A new [PagedList.Config].
723              * @throws IllegalArgumentException if placeholders are disabled and prefetchDistance is
724              *   set to 0
725              * @throws IllegalArgumentException if maximum size is less than pageSize +
726              *   2*prefetchDistance
727              */
728             public fun build(): Config {
729                 if (prefetchDistance < 0) {
730                     prefetchDistance = pageSize
731                 }
732                 if (initialLoadSizeHint < 0) {
733                     initialLoadSizeHint = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
734                 }
735                 if (!enablePlaceholders && prefetchDistance == 0) {
736                     throw IllegalArgumentException(
737                         "Placeholders and prefetch are the only ways" +
738                             " to trigger loading of more data in the PagedList, so either" +
739                             " placeholders must be enabled, or prefetch distance must be > 0."
740                     )
741                 }
742                 if (maxSize != MAX_SIZE_UNBOUNDED && maxSize < pageSize + prefetchDistance * 2) {
743                     throw IllegalArgumentException(
744                         "Maximum size must be at least pageSize + 2*prefetchDist" +
745                             ", pageSize=$pageSize, prefetchDist=$prefetchDistance" +
746                             ", maxSize=$maxSize"
747                     )
748                 }
749 
750                 return Config(
751                     pageSize,
752                     prefetchDistance,
753                     enablePlaceholders,
754                     initialLoadSizeHint,
755                     maxSize
756                 )
757             }
758 
759             internal companion object {
760                 internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
761             }
762         }
763 
764         internal companion object {
765             /**
766              * When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
767              * unbounded, and pages will never be dropped.
768              */
769             @Suppress("MinMaxConstant") const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
770         }
771     }
772 
773     /**
774      * Signals when a PagedList has reached the end of available data.
775      *
776      * When local storage is a cache of network data, it's common to set up a streaming pipeline:
777      * Network data is paged into the database, database is paged into UI. Paging from the database
778      * to UI can be done with a `LiveData<PagedList>`, but it's still necessary to know when to
779      * trigger network loads.
780      *
781      * [BoundaryCallback] does this signaling - when a [PagingSource] runs out of data at the end of
782      * the list, [onItemAtEndLoaded] is called, and you can start an async network load that will
783      * write the result directly to the database. Because the database is being observed, the UI
784      * bound to the `LiveData<PagedList>` will update automatically to account for the new items.
785      *
786      * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
787      * [androidx.paging.LivePagedListBuilder.setBoundaryCallback], the callbacks may be issued
788      * multiple times. If for example [onItemAtEndLoaded] triggers a network load, it should avoid
789      * triggering it again while the load is ongoing.
790      *
791      * The database + network Repository in the
792      * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
793      * shows how to implement a network BoundaryCallback using
794      * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network
795      * errors, and retry.
796      *
797      * ### Requesting Network Data
798      * [BoundaryCallback] only passes the item at front or end of the list when out of data. This
799      * makes it an easy fit for item-keyed network requests, where you can use the item passed to
800      * the [BoundaryCallback] to request more data from the network. In these cases, the source of
801      * truth for next page to load is coming from local storage, based on what's already loaded.
802      *
803      * If you aren't using an item-keyed network API, you may be using page-keyed, or page-indexed.
804      * If this is the case, the paging library doesn't know about the page key or index used in the
805      * [BoundaryCallback], so you need to track it yourself. You can do this in one of two ways:
806      *
807      * <h5>Local storage Page key</h5> If you want to perfectly resume your query, even if the app
808      * is killed and resumed, you can store the key on disk. Note that with a positional/page index
809      * network API, there's a simple way to do this, by using the `listSize` as an input to the next
810      * load (or `listSize / NETWORK_PAGE_SIZE`, for page indexing).
811      *
812      * The current list size isn't passed to the BoundaryCallback though. This is because the
813      * PagedList doesn't necessarily know the number of items in local storage. Placeholders may be
814      * disabled, or the [PagingSource] may not count total number of items.
815      *
816      * Instead, for these positional cases, you can query the database for the number of items, and
817      * pass that to the network. <h5>In-Memory Page key</h5> Often it doesn't make sense to query
818      * the next page from network if the last page you fetched was loaded many hours or days before.
819      * If you keep the key in memory, you can refresh any time you start paging from a network
820      * source.
821      *
822      * Store the next key in memory, inside your BoundaryCallback. When you create a new
823      * BoundaryCallback when creating a new `LiveData`/`Observable` of `PagedList`, refresh data.
824      * For example,
825      * [in the Paging Codelab](https://codelabs.developers.google.com/codelabs/android-paging/index.html#8),
826      * the GitHub network page index is stored in memory.
827      *
828      * @param T Type loaded by the [PagedList].
829      */
830     @MainThread
831     public abstract class BoundaryCallback<T : Any> {
832         /**
833          * Called when zero items are returned from an initial load of the PagedList's data source.
834          */
835         public open fun onZeroItemsLoaded() {}
836 
837         /**
838          * Called when the item at the front of the PagedList has been loaded, and access has
839          * occurred within [PagedList.Config.prefetchDistance] of it.
840          *
841          * No more data will be prepended to the PagedList before this item.
842          *
843          * @param itemAtFront The first item of PagedList
844          */
845         public open fun onItemAtFrontLoaded(itemAtFront: T) {}
846 
847         /**
848          * Called when the item at the end of the PagedList has been loaded, and access has occurred
849          * within [PagedList.Config.prefetchDistance] of it.
850          *
851          * No more data will be appended to the [PagedList] after this item.
852          *
853          * @param itemAtEnd The first item of [PagedList]
854          */
855         public open fun onItemAtEndLoaded(itemAtEnd: T) {}
856     }
857 
858     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
859     public abstract class LoadStateManager {
860         public var refreshState: LoadState = LoadState.NotLoading.Incomplete
861         public var startState: LoadState = LoadState.NotLoading.Incomplete
862         public var endState: LoadState = LoadState.NotLoading.Incomplete
863 
864         public fun setState(type: LoadType, state: LoadState) {
865             // deduplicate signals
866             when (type) {
867                 LoadType.REFRESH -> {
868                     if (refreshState == state) return
869                     refreshState = state
870                 }
871                 LoadType.PREPEND -> {
872                     if (startState == state) return
873                     startState = state
874                 }
875                 LoadType.APPEND -> {
876                     if (endState == state) return
877                     endState = state
878                 }
879             }
880 
881             onStateChanged(type, state)
882         }
883 
884         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise.
885         public abstract fun onStateChanged(type: LoadType, state: LoadState)
886 
887         public fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {
888             callback(LoadType.REFRESH, refreshState)
889             callback(LoadType.PREPEND, startState)
890             callback(LoadType.APPEND, endState)
891         }
892     }
893 
894     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise
895     public fun getPlaceholderPaddedList(): PlaceholderPaddedList<T> = storage
896 
897     internal var refreshRetryCallback: Runnable? = null
898 
899     /**
900      * Last access location in list.
901      *
902      * Used by list diffing to re-initialize loading near viewport.
903      */
904     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
905     public fun lastLoad(): Int = storage.lastLoadAroundIndex
906 
907     internal val requiredRemainder = config.prefetchDistance * 2 + config.pageSize
908 
909     private val callbacks = mutableListOf<WeakReference<Callback>>()
910 
911     private val loadStateListeners = mutableListOf<WeakReference<(LoadType, LoadState) -> Unit>>()
912 
913     /**
914      * Size of the list, including any placeholders (not-yet-loaded null padding).
915      *
916      * To get the number of loaded items, not counting placeholders, use [loadedCount].
917      *
918      * @see loadedCount
919      */
920     override val size: Int
921         get() = storage.size
922 
923     /**
924      * @throws IllegalStateException if this [PagedList] was instantiated without a wrapping a
925      *   backing [DataSource]
926      */
927     @Deprecated(
928         message =
929             "DataSource is deprecated and has been replaced by PagingSource. PagedList " +
930                 "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
931                 "you should not need to access the DataSource/PagingSource."
932     )
933     public val dataSource: DataSource<*, T>
934         @Suppress("DocumentExceptions")
935         get() {
936             val pagingSource = pagingSource
937             @Suppress("UNCHECKED_CAST")
938             if (pagingSource is LegacyPagingSource<*, *>) {
939                 return pagingSource.dataSource as DataSource<*, T>
940             }
941             throw IllegalStateException(
942                 "Attempt to access dataSource on a PagedList that was instantiated with a " +
943                     "${pagingSource::class.java.simpleName} instead of a DataSource"
944             )
945         }
946 
947     /**
948      * Return the key for the position passed most recently to [loadAround].
949      *
950      * When a PagedList is invalidated, you can pass the key returned by this function to initialize
951      * the next PagedList. This ensures (depending on load times) that the next PagedList that
952      * arrives will have data that overlaps. If you use androidx.paging.LivePagedListBuilder, it
953      * will do this for you.
954      *
955      * @return Key of position most recently passed to [loadAround].
956      */
957     public abstract val lastKey: Any?
958 
959     /**
960      * True if the [PagedList] has detached the [PagingSource] it was loading from, and will no
961      * longer load new data.
962      *
963      * A detached list is [immutable][isImmutable].
964      *
965      * @return `true` if the data source is detached.
966      */
967     public abstract val isDetached: Boolean
968 
969     @RestrictTo(RestrictTo.Scope.LIBRARY)
970     public abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
971 
972     @RestrictTo(RestrictTo.Scope.LIBRARY) public abstract fun loadAroundInternal(index: Int)
973 
974     /**
975      * Detach the [PagedList] from its [PagingSource], and attempt to load no more data.
976      *
977      * This is called automatically when a [PagingSource] is observed to be invalid, which is a
978      * signal to stop loading. The [PagedList] will continue to present existing data, but will not
979      * initiate new loads.
980      */
981     public abstract fun detach()
982 
983     /**
984      * Returns the number of items loaded in the [PagedList].
985      *
986      * Unlike [size] this counts only loaded items, not placeholders.
987      *
988      * If placeholders are [disabled][PagedList.Config.enablePlaceholders], this method is
989      * equivalent to [size].
990      *
991      * @return Number of items currently loaded, not counting placeholders.
992      * @see size
993      */
994     public val loadedCount: Int
995         get() = storage.dataCount
996 
997     /**
998      * Returns whether the list is immutable.
999      *
1000      * Immutable lists may not become mutable again, and may safely be accessed from any thread.
1001      *
1002      * In the future, this method may return true when a PagedList has completed loading from its
1003      * [PagingSource]. Currently, it is equivalent to [isDetached].
1004      *
1005      * @return `true` if the [PagedList] is immutable.
1006      */
1007     public open val isImmutable: Boolean
1008         get() = isDetached
1009 
1010     /**
1011      * Position offset of the data in the list.
1012      *
1013      * If the PagingSource backing this PagedList is counted, the item returned from `get(i)` has a
1014      * position in the original data set of `i + getPositionOffset()`.
1015      *
1016      * If placeholders are enabled, this value is always `0`, since `get(i)` will return either the
1017      * data in its original index, or null if it is not loaded.
1018      */
1019     public val positionOffset: Int
1020         get() = storage.positionOffset
1021 
1022     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
1023     public open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {}
1024 
1025     /**
1026      * Retry any errors associated with this [PagedList].
1027      *
1028      * If for example a network [PagingSource] append timed out, calling this method will retry the
1029      * failed append load.
1030      *
1031      * You can observe loading state via [addWeakLoadStateListener], though generally this is done
1032      * through the [PagedListAdapter][androidx.paging.PagedListAdapter] or
1033      * [AsyncPagedListDiffer][androidx.paging.AsyncPagedListDiffer].
1034      *
1035      * @see addWeakLoadStateListener
1036      * @see removeWeakLoadStateListener
1037      */
1038     public open fun retry() {}
1039 
1040     @RestrictTo(RestrictTo.Scope.LIBRARY)
1041     public fun setRetryCallback(refreshRetryCallback: Runnable?) {
1042         this.refreshRetryCallback = refreshRetryCallback
1043     }
1044 
1045     internal fun dispatchStateChangeAsync(type: LoadType, state: LoadState) {
1046         coroutineScope.launch(notifyDispatcher) {
1047             loadStateListeners.removeAll { it.get() == null }
1048             loadStateListeners.forEach { it.get()?.invoke(type, state) }
1049         }
1050     }
1051 
1052     /**
1053      * Get the item in the list of loaded items at the provided index.
1054      *
1055      * @param index Index in the loaded item list. Must be >= 0, and < [size]
1056      * @return The item at the passed index, or `null` if a `null` placeholder is at the specified
1057      *   position.
1058      * @see size
1059      */
1060     public override fun get(index: Int): T? = storage[index]
1061 
1062     /**
1063      * Load adjacent items to passed index.
1064      *
1065      * @param index Index at which to load.
1066      * @throws IndexOutOfBoundsException if index is not within bounds.
1067      */
1068     public fun loadAround(index: Int) {
1069         if (index < 0 || index >= size) {
1070             throw IndexOutOfBoundsException("Index: $index, Size: $size")
1071         }
1072         storage.lastLoadAroundIndex = index
1073         loadAroundInternal(index)
1074     }
1075 
1076     /**
1077      * Returns an immutable snapshot of the [PagedList] in its current state.
1078      *
1079      * If this [PagedList] [is immutable][isImmutable] due to its [PagingSource] being invalid, it
1080      * will be returned.
1081      *
1082      * @return Immutable snapshot of [PagedList] data.
1083      */
1084     public fun snapshot(): List<T> =
1085         when {
1086             isImmutable -> this
1087             else -> SnapshotPagedList(this)
1088         }
1089 
1090     /**
1091      * Add a listener to observe the loading state of the [PagedList].
1092      *
1093      * @param listener Listener to receive updates.
1094      * @see removeWeakLoadStateListener
1095      */
1096     public fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
1097         // Clean up any empty weak refs.
1098         loadStateListeners.removeAll { it.get() == null }
1099 
1100         // Add the new one.
1101         loadStateListeners.add(WeakReference(listener))
1102         dispatchCurrentLoadState(listener)
1103     }
1104 
1105     /**
1106      * Remove a previously registered load state listener.
1107      *
1108      * @param listener Previously registered listener.
1109      * @see addWeakLoadStateListener
1110      */
1111     public fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
1112         loadStateListeners.removeAll { it.get() == null || it.get() === listener }
1113     }
1114 
1115     /**
1116      * Adds a callback, and issues updates since the [previousSnapshot] was created.
1117      *
1118      * If [previousSnapshot] is passed, the [callback] will also immediately be dispatched any
1119      * differences between the previous snapshot, and the current state. For example, if the
1120      * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls, 12
1121      * items, 3 nulls, the callback would immediately receive a call of`onChanged(14, 2)`.
1122      *
1123      * This allows an observer that's currently presenting a snapshot to catch up to the most recent
1124      * version, including any changes that may have been made.
1125      *
1126      * The callback is internally held as weak reference, so [PagedList] doesn't hold a strong
1127      * reference to its observer, such as a [PagedListAdapter][androidx.paging.PagedListAdapter]. If
1128      * an adapter were held with a strong reference, it would be necessary to clear its [PagedList]
1129      * observer before it could be GC'd.
1130      *
1131      * @param previousSnapshot Snapshot previously captured from this List, or `null`.
1132      * @param callback [PagedList.Callback] to dispatch to.
1133      * @see removeWeakCallback
1134      */
1135     @Deprecated(
1136         "Dispatching a diff since snapshot created is behavior that can be instead " +
1137             "tracked by attaching a Callback to the PagedList that is mutating, and tracking " +
1138             "changes since calling PagedList.snapshot()."
1139     )
1140     public fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
1141         if (previousSnapshot != null && previousSnapshot !== this) {
1142             dispatchNaiveUpdatesSinceSnapshot(size, previousSnapshot.size, callback)
1143         }
1144         addWeakCallback(callback)
1145     }
1146 
1147     /**
1148      * Adds a callback.
1149      *
1150      * The callback is internally held as weak reference, so [PagedList] doesn't hold a strong
1151      * reference to its observer, such as a [androidx.paging.PagedListAdapter]. If an adapter were
1152      * held with a strong reference, it would be necessary to clear its [PagedList] observer before
1153      * it could be GC'd.
1154      *
1155      * @param callback Callback to dispatch to.
1156      * @see removeWeakCallback
1157      */
1158     @Suppress("RegistrationName")
1159     public fun addWeakCallback(callback: Callback) {
1160         // first, clean up any empty weak refs
1161         callbacks.removeAll { it.get() == null }
1162 
1163         // then add the new one
1164         callbacks.add(WeakReference(callback))
1165     }
1166 
1167     /**
1168      * Removes a previously added callback.
1169      *
1170      * @param callback Callback, previously added.
1171      * @see addWeakCallback
1172      */
1173     @Suppress("RegistrationName")
1174     public fun removeWeakCallback(callback: Callback) {
1175         callbacks.removeAll { it.get() == null || it.get() === callback }
1176     }
1177 
1178     internal fun notifyInserted(position: Int, count: Int) {
1179         if (count == 0) return
1180         callbacks.reversed().forEach { it.get()?.onInserted(position, count) }
1181     }
1182 
1183     @RestrictTo(RestrictTo.Scope.LIBRARY)
1184     public fun notifyChanged(position: Int, count: Int) {
1185         if (count == 0) return
1186         callbacks.reversed().forEach { it.get()?.onChanged(position, count) }
1187     }
1188 
1189     @RestrictTo(RestrictTo.Scope.LIBRARY)
1190     public fun notifyRemoved(position: Int, count: Int) {
1191         if (count == 0) return
1192         callbacks.reversed().forEach { it.get()?.onRemoved(position, count) }
1193     }
1194 }
1195 
1196 /**
1197  * Constructs a [PagedList], convenience for [PagedList.Builder].
1198  *
1199  * @param Key Type of key used to load data from the [DataSource].
1200  * @param Value Type of items held and loaded by the [PagedList].
1201  * @param dataSource [DataSource] the [PagedList] will load from.
1202  * @param config Config that defines how the [PagedList] loads data from its [DataSource].
1203  * @param notifyExecutor [Executor] that receives [PagedList] updates, and where
1204  *   [PagedList.Callback] calls are dispatched. Generally, this is the UI/main thread.
1205  * @param fetchExecutor [Executor] used to fetch from [DataSource]s, generally a background thread
1206  *   pool for e.g. I/O or network loading.
1207  * @param boundaryCallback [PagedList.BoundaryCallback] for listening to out-of-data events.
1208  * @param initialKey [Key] the [DataSource] should load around as part of initialization.
1209  */
1210 @Suppress("FunctionName", "DEPRECATION")
1211 @JvmSynthetic
1212 @Deprecated("DataSource is deprecated and has been replaced by PagingSource")
PagedListnull1213 public fun <Key : Any, Value : Any> PagedList(
1214     dataSource: DataSource<Key, Value>,
1215     config: PagedList.Config,
1216     notifyExecutor: Executor,
1217     fetchExecutor: Executor,
1218     boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
1219     initialKey: Key? = null
1220 ): PagedList<Value> {
1221     return PagedList.Builder(dataSource, config)
1222         .setNotifyExecutor(notifyExecutor)
1223         .setFetchExecutor(fetchExecutor)
1224         .setBoundaryCallback(boundaryCallback)
1225         .setInitialKey(initialKey)
1226         .build()
1227 }
1228 
1229 @Suppress("DEPRECATION")
1230 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toRefreshLoadParamsnull1231 public fun <Key : Any> PagedList.Config.toRefreshLoadParams(
1232     key: Key?
1233 ): PagingSource.LoadParams<Key> =
1234     PagingSource.LoadParams.Refresh(
1235         key,
1236         initialLoadSizeHint,
1237         enablePlaceholders,
1238     )
1239