1 /*
2  * Copyright 2020 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.paging.PagingConfig.Companion.MAX_SIZE_UNBOUNDED
21 import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
22 import kotlin.jvm.JvmField
23 import kotlin.jvm.JvmOverloads
24 
25 /**
26  * An object used to configure loading behavior within a [Pager], as it loads content from a
27  * [PagingSource].
28  */
29 public class PagingConfig
30 @JvmOverloads
31 public constructor(
32     /**
33      * Defines the number of items loaded at once from the [PagingSource].
34      *
35      * Should be several times the number of visible items onscreen.
36      *
37      * Configuring your page size depends on how your data is being loaded and used. Smaller page
38      * sizes improve memory usage, latency, and avoid GC churn. Larger pages generally improve
39      * loading throughput, to a point (avoid loading more than 2MB from SQLite at once, since it
40      * incurs extra cost).
41      *
42      * If you're loading data for very large, social-media style cards that take up most of a
43      * screen, and your database isn't a bottleneck, 10-20 may make sense. If you're displaying
44      * dozens of items in a tiled grid, which can present items during a scroll much more quickly,
45      * consider closer to 100.
46      *
47      * Note: [pageSize] is used to inform [PagingSource.LoadParams.loadSize], but is not enforced. A
48      * [PagingSource] may completely ignore this value and still return a valid
49      * [Page][PagingSource.LoadResult.Page].
50      */
51     @JvmField public val pageSize: Int,
52 
53     /**
54      * Prefetch distance which defines how far from the edge of loaded content an access must be to
55      * trigger further loading. Typically should be set several times the number of visible items
56      * onscreen.
57      *
58      * E.g., If this value is set to 50, a [PagingData] will attempt to load 50 items in advance of
59      * data that's already been accessed.
60      *
61      * A value of 0 indicates that no list items will be loaded until they are specifically
62      * requested. This is generally not recommended, so that users don't observe a placeholder item
63      * (with placeholders) or end of list (without) while scrolling.
64      */
65     @JvmField @IntRange(from = 0) public val prefetchDistance: Int = pageSize,
66 
67     /**
68      * Defines whether [PagingData] may display `null` placeholders, if the [PagingSource] provides
69      * them.
70      *
71      * [PagingData] will present `null` placeholders for not-yet-loaded content if two conditions
72      * are met:
73      * 1) Its [PagingSource] can count all unloaded items (so that the number of nulls to present is
74      *    known).
75      * 2) [enablePlaceholders] is set to `true`
76      */
77     @JvmField public val enablePlaceholders: Boolean = true,
78 
79     /**
80      * Defines requested load size for initial load from [PagingSource], typically larger than
81      * [pageSize], so on first load data there's a large enough range of content loaded to cover
82      * small scrolls.
83      *
84      * Note: [initialLoadSize] is used to inform [PagingSource.LoadParams.loadSize], but is not
85      * enforced. A [PagingSource] may completely ignore this value and still return a valid initial
86      * [Page][PagingSource.LoadResult.Page].
87      */
88     @JvmField
89     @IntRange(from = 1)
90     public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
91     /**
92      * Defines the maximum number of items that may be loaded into [PagingData] before pages should
93      * be dropped.
94      *
95      * If set to [MAX_SIZE_UNBOUNDED], pages will never be dropped.
96      *
97      * This can be used to cap the number of items kept in memory by dropping pages. This value is
98      * typically many pages so old pages are cached in case the user scrolls back.
99      *
100      * This value must be at least two times the [prefetchDistance] plus the [pageSize]). This
101      * constraint prevent loads from being continuously fetched and discarded due to prefetching.
102      *
103      * [maxSize] is best effort, not a guarantee. In practice, if [maxSize] is many times
104      * [pageSize], the number of items held by [PagingData] will not grow above this number.
105      * Exceptions are made as necessary to guarantee:
106      * * Pages are never dropped until there are more than two pages loaded. Note that a
107      *   [PagingSource] may not be held strictly to [requested pageSize][PagingConfig.pageSize], so
108      *   two pages may be larger than expected.
109      * * Pages are never dropped if they are within a prefetch window (defined to be `pageSize +
110      *   (2 * prefetchDistance)`) of the most recent load.
111      *
112      * @see PagingConfig.MAX_SIZE_UNBOUNDED
113      */
114     @JvmField @IntRange(from = 2) public val maxSize: Int = MAX_SIZE_UNBOUNDED,
115 
116     /**
117      * Defines a threshold for the number of items scrolled outside the bounds of loaded items
118      * before Paging should give up on loading pages incrementally, and instead jump to the user's
119      * position by triggering REFRESH via invalidate.
120      *
121      * Defaults to [COUNT_UNDEFINED], which disables invalidation due to scrolling large distances.
122      *
123      * Note: In order to allow [PagingSource] to resume from the user's current scroll position
124      * after invalidation, [PagingSource.getRefreshKey] must be implemented.
125      *
126      * @see PagingSource.getRefreshKey
127      * @see PagingSource.jumpingSupported
128      */
129     @JvmField public val jumpThreshold: Int = COUNT_UNDEFINED
130 ) {
131     init {
132         if (!enablePlaceholders && prefetchDistance == 0) {
133             throw IllegalArgumentException(
134                 "Placeholders and prefetch are the only ways" +
135                     " to trigger loading of more data in PagingData, so either placeholders" +
136                     " must be enabled, or prefetch distance must be > 0."
137             )
138         }
139         if (maxSize != MAX_SIZE_UNBOUNDED && maxSize < pageSize + prefetchDistance * 2) {
140             throw IllegalArgumentException(
141                 "Maximum size must be at least pageSize + 2*prefetchDist" +
142                     ", pageSize=$pageSize, prefetchDist=$prefetchDistance" +
143                     ", maxSize=$maxSize"
144             )
145         }
146 
<lambda>null147         require(jumpThreshold == COUNT_UNDEFINED || jumpThreshold > 0) {
148             "jumpThreshold must be positive to enable jumps or COUNT_UNDEFINED to disable jumping."
149         }
150     }
151 
152     public companion object {
153         /**
154          * When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
155          * unbounded, and pages will never be dropped.
156          */
157         @Suppress("MinMaxConstant") public const val MAX_SIZE_UNBOUNDED: Int = Int.MAX_VALUE
158         internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
159     }
160 }
161