1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.paging
18 
19 import androidx.arch.core.executor.ArchTaskExecutor
20 import androidx.lifecycle.LiveData
21 import java.util.concurrent.Executor
22 import kotlinx.coroutines.CoroutineScope
23 import kotlinx.coroutines.DelicateCoroutinesApi
24 import kotlinx.coroutines.GlobalScope
25 import kotlinx.coroutines.asCoroutineDispatcher
26 
27 /**
28  * Builder for `LiveData<PagedList>` for Java users, given a [androidx.paging.DataSource.Factory]
29  * and a [androidx.paging.PagedList.Config].
30  *
31  * The required parameters are in the constructor, so you can simply construct and build, or
32  * optionally enable extra features (such as initial load key, or BoundaryCallback).
33  *
34  * @param Key Type of input valued used to load data from the [DataSource]. Must be [Int] if you're
35  *   using [PositionalDataSource].
36  * @param Value Item type being presented.
37  * @see toLiveData
38  */
39 @Deprecated("PagedList is deprecated and has been replaced by PagingData")
40 class LivePagedListBuilder<Key : Any, Value : Any> {
41     private val pagingSourceFactory: (() -> PagingSource<Key, Value>)?
42     private val dataSourceFactory: DataSource.Factory<Key, Value>?
43 
44     @Suppress("DEPRECATION") private val config: PagedList.Config
45     @OptIn(DelicateCoroutinesApi::class) private var coroutineScope: CoroutineScope = GlobalScope
46     private var initialLoadKey: Key? = null
47 
48     @Suppress("DEPRECATION") private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
49     private var fetchDispatcher = ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher()
50 
51     /**
52      * Creates a [LivePagedListBuilder] with required parameters.
53      *
54      * @param dataSourceFactory [DataSource] factory providing DataSource generations.
55      * @param config Paging configuration.
56      */
57     @Deprecated(
58         message = "PagedList is deprecated and has been replaced by PagingData",
59         replaceWith =
60             ReplaceWith(
61                 """Pager(
62                 PagingConfig(
63                     config.pageSize,
64                     config.prefetchDistance,
65                     config.enablePlaceholders,
66                     config.initialLoadSizeHint,
67                     config.maxSize
68                 ),
69                 initialLoadKey,
70                 dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
71             ).liveData""",
72                 "androidx.paging.Pager",
73                 "androidx.paging.PagingConfig",
74                 "androidx.paging.liveData",
75                 "kotlinx.coroutines.Dispatchers"
76             )
77     )
78     constructor(
79         dataSourceFactory: DataSource.Factory<Key, Value>,
80         @Suppress("DEPRECATION") config: PagedList.Config
81     ) {
82         this.pagingSourceFactory = null
83         this.dataSourceFactory = dataSourceFactory
84         this.config = config
85     }
86 
87     /**
88      * Creates a [LivePagedListBuilder] with required parameters.
89      *
90      * This method is a convenience for:
91      * ```
92      * LivePagedListBuilder(dataSourceFactory,
93      *         new PagedList.Config.Builder().setPageSize(pageSize).build())
94      * ```
95      *
96      * @param dataSourceFactory [DataSource.Factory] providing DataSource generations.
97      * @param pageSize Size of pages to load.
98      */
99     @Suppress("DEPRECATION")
100     @Deprecated(
101         message = "PagedList is deprecated and has been replaced by PagingData",
102         replaceWith =
103             ReplaceWith(
104                 """Pager(
105                 PagingConfig(pageSize),
106                 initialLoadKey,
107                 dataSourceFactory.asPagingSourceFactory(Dispatchers.IO)
108             ).liveData""",
109                 "androidx.paging.Pager",
110                 "androidx.paging.PagingConfig",
111                 "androidx.paging.liveData",
112                 "kotlinx.coroutines.Dispatchers"
113             )
114     )
115     constructor(
116         dataSourceFactory: DataSource.Factory<Key, Value>,
117         pageSize: Int
118     ) : this(dataSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
119 
120     /**
121      * Creates a [LivePagedListBuilder] with required parameters.
122      *
123      * @param pagingSourceFactory [PagingSource] factory providing [PagingSource] generations.
124      *
125      * The returned [PagingSource] should invalidate itself if the snapshot is no longer valid. If a
126      * [PagingSource] becomes invalid, the only way to query more data is to create a new
127      * [PagingSource] by invoking the supplied [pagingSourceFactory].
128      *
129      * [pagingSourceFactory] will invoked to construct a new [PagedList] and [PagingSource] when the
130      * current [PagingSource] is invalidated, and pass the new [PagedList] through the
131      * `LiveData<PagedList>` to observers.
132      *
133      * @param config Paging configuration.
134      */
135     @Deprecated(
136         message = "PagedList is deprecated and has been replaced by PagingData",
137         replaceWith =
138             ReplaceWith(
139                 """Pager(
140                 PagingConfig(
141                     config.pageSize,
142                     config.prefetchDistance,
143                     config.enablePlaceholders,
144                     config.initialLoadSizeHint,
145                     config.maxSize
146                 ),
147                 initialLoadKey,
148                 this
149             ).liveData""",
150                 "androidx.paging.Pager",
151                 "androidx.paging.PagingConfig",
152                 "androidx.paging.liveData"
153             )
154     )
155     constructor(
156         pagingSourceFactory: () -> PagingSource<Key, Value>,
157         @Suppress("DEPRECATION") config: PagedList.Config
158     ) {
159         this.pagingSourceFactory = pagingSourceFactory
160         this.dataSourceFactory = null
161         this.config = config
162     }
163 
164     /**
165      * Creates a [LivePagedListBuilder] with required parameters.
166      *
167      * This method is a convenience for:
168      * ```
169      * LivePagedListBuilder(pagingSourceFactory,
170      *         new PagedList.Config.Builder().setPageSize(pageSize).build())
171      * ```
172      *
173      * @param pagingSourceFactory [PagingSource] factory providing [PagingSource] generations.
174      *
175      * The returned [PagingSource] should invalidate itself if the snapshot is no longer valid. If a
176      * [PagingSource] becomes invalid, the only way to query more data is to create a new
177      * [PagingSource] by invoking the supplied [pagingSourceFactory].
178      *
179      * [pagingSourceFactory] will invoked to construct a new [PagedList] and [PagingSource] when the
180      * current [PagingSource] is invalidated, and pass the new [PagedList] through the
181      * `LiveData<PagedList>` to observers.
182      *
183      * @param pageSize Size of pages to load.
184      */
185     @Suppress("DEPRECATION")
186     @Deprecated(
187         message = "PagedList is deprecated and has been replaced by PagingData",
188         replaceWith =
189             ReplaceWith(
190                 """Pager(
191                 PagingConfig(pageSize),
192                 initialLoadKey,
193                 this
194             ).liveData""",
195                 "androidx.paging.Pager",
196                 "androidx.paging.PagingConfig",
197                 "androidx.paging.liveData"
198             )
199     )
200     constructor(
201         pagingSourceFactory: () -> PagingSource<Key, Value>,
202         pageSize: Int
203     ) : this(pagingSourceFactory, PagedList.Config.Builder().setPageSize(pageSize).build())
204 
205     /**
206      * Set the [CoroutineScope] that page loads should be launched within. The set [coroutineScope]
207      * allows a [PagingSource] to cancel running load operations when the results are no longer
208      * needed - for example, when the containing activity is destroyed.
209      *
210      * Defaults to [GlobalScope].
211      *
212      * @param coroutineScope
213      * @return this
214      */
215     @Suppress("unused") // Public API
setCoroutineScopenull216     fun setCoroutineScope(coroutineScope: CoroutineScope) =
217         this.apply { this.coroutineScope = coroutineScope }
218 
219     /**
220      * First loading key passed to the first PagedList/DataSource.
221      *
222      * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
223      * the previous generation so that data is loaded around the position already being observed.
224      *
225      * @param key Initial load key passed to the first PagedList/DataSource.
226      * @return this
227      */
<lambda>null228     fun setInitialLoadKey(key: Key?) = this.apply { initialLoadKey = key }
229 
230     /**
231      * Sets a [androidx.paging.PagedList.BoundaryCallback] on each PagedList created, typically used
232      * to load additional data from network when paging from local storage.
233      *
234      * Pass a [PagedList.BoundaryCallback] to listen to when the PagedList runs out of data to load.
235      * If this method is not called, or `null` is passed, you will not be notified when each
236      * [PagingSource] runs out of data to provide to its [PagedList].
237      *
238      * If you are paging from a DataSource.Factory backed by local storage, you can set a
239      * BoundaryCallback to know when there is no more information to page from local storage. This
240      * is useful to page from the network when local storage is a cache of network data.
241      *
242      * Note that when using a BoundaryCallback with a `LiveData<PagedList>`, method calls on the
243      * callback may be dispatched multiple times - one for each PagedList/DataSource pair. If
244      * loading network data from a BoundaryCallback, you should prevent multiple dispatches of the
245      * same method from triggering multiple simultaneous network loads.
246      *
247      * @param boundaryCallback The boundary callback for listening to PagedList load state.
248      * @return this
249      */
setBoundaryCallbacknull250     fun setBoundaryCallback(
251         @Suppress("DEPRECATION") boundaryCallback: PagedList.BoundaryCallback<Value>?
252     ) = this.apply { this.boundaryCallback = boundaryCallback }
253 
254     /**
255      * Sets [Executor] used for background fetching of [PagedList]s, and the pages within.
256      *
257      * The library will wrap this as a [kotlinx.coroutines.CoroutineDispatcher].
258      *
259      * If not set, defaults to a
260      * [ExecutorCoroutineDispatcher][kotlinx.coroutines.ExecutorCoroutineDispatcher] backed by
261      * [ArchTaskExecutor.getIOThreadExecutor].
262      *
263      * @param fetchExecutor [Executor] for fetching data from [PagingSource]s.
264      * @return this
265      */
setFetchExecutornull266     fun setFetchExecutor(fetchExecutor: Executor) =
267         this.apply { this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher() }
268 
269     /**
270      * Constructs the `LiveData<PagedList>`.
271      *
272      * No work (such as loading) is done immediately, the creation of the first [PagedList] is
273      * deferred until the [LiveData] is observed.
274      *
275      * @return The [LiveData] of [PagedList]s
276      */
277     @Suppress("DEPRECATION")
buildnull278     fun build(): LiveData<PagedList<Value>> {
279         val pagingSourceFactory =
280             pagingSourceFactory ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
281 
282         check(pagingSourceFactory != null) {
283             "LivePagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
284         }
285 
286         return LivePagedList(
287             coroutineScope,
288             initialLoadKey,
289             config,
290             boundaryCallback,
291             pagingSourceFactory,
292             ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
293             fetchDispatcher
294         )
295     }
296 }
297