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