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.paging.LoadType.APPEND 20 import androidx.paging.LoadType.PREPEND 21 import androidx.paging.LoadType.REFRESH 22 import androidx.paging.internal.appendMediatorStatesIfNotNull 23 24 /** 25 * Events in the stream from paging fetch logic to UI. 26 * 27 * Every event sent to the UI is a PageEvent, and will be processed atomically. 28 */ 29 internal sealed class PageEvent<T : Any> { 30 /** 31 * Represents a fully-terminal, static list of data. 32 * 33 * This event should always be the first and only emission in a Flow<PageEvent> within a 34 * generation. 35 * 36 * @param sourceLoadStates source [LoadStates] to emit if non-null, ignored otherwise, allowing 37 * the presenter receiving this event to maintain the previous state. 38 * @param mediatorLoadStates mediator [LoadStates] to emit if non-null, ignored otherwise, 39 * allowing the presenter receiving this event to maintain its previous state. 40 */ 41 data class StaticList<T : Any>( 42 val data: List<T>, 43 val sourceLoadStates: LoadStates? = null, 44 val mediatorLoadStates: LoadStates? = null 45 ) : PageEvent<T>() { 46 override suspend fun <R : Any> map(transform: suspend (T) -> R): PageEvent<R> { 47 return StaticList( 48 data = data.map { transform(it) }, 49 sourceLoadStates = sourceLoadStates, 50 mediatorLoadStates = mediatorLoadStates, 51 ) 52 } 53 54 override suspend fun <R : Any> flatMap( 55 transform: suspend (T) -> Iterable<R> 56 ): PageEvent<R> { 57 return StaticList( 58 data = data.flatMap { transform(it) }, 59 sourceLoadStates = sourceLoadStates, 60 mediatorLoadStates = mediatorLoadStates, 61 ) 62 } 63 64 override suspend fun filter(predicate: suspend (T) -> Boolean): PageEvent<T> { 65 return StaticList( 66 data = data.filter { predicate(it) }, 67 sourceLoadStates = sourceLoadStates, 68 mediatorLoadStates = mediatorLoadStates, 69 ) 70 } 71 72 override fun toString(): String { 73 return appendMediatorStatesIfNotNull(mediatorLoadStates) { 74 """PageEvent.StaticList with ${data.size} items ( 75 | first item: ${data.firstOrNull()} 76 | last item: ${data.lastOrNull()} 77 | sourceLoadStates: $sourceLoadStates 78 """ 79 } 80 } 81 } 82 83 // Intentional to prefer Refresh, Prepend, Append constructors from Companion. 84 @Suppress("DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING") 85 data class Insert<T : Any> 86 private constructor( 87 val loadType: LoadType, 88 val pages: List<TransformablePage<T>>, 89 val placeholdersBefore: Int, 90 val placeholdersAfter: Int, 91 val sourceLoadStates: LoadStates, 92 val mediatorLoadStates: LoadStates? = null 93 ) : PageEvent<T>() { 94 init { 95 require(loadType == APPEND || placeholdersBefore >= 0) { 96 "Prepend insert defining placeholdersBefore must be > 0, but was" + 97 " $placeholdersBefore" 98 } 99 require(loadType == PREPEND || placeholdersAfter >= 0) { 100 "Append insert defining placeholdersAfter must be > 0, but was" + 101 " $placeholdersAfter" 102 } 103 require(loadType != REFRESH || pages.isNotEmpty()) { 104 "Cannot create a REFRESH Insert event with no TransformablePages as this could " + 105 "permanently stall pagination. Note that this check does not prevent empty " + 106 "LoadResults and is instead usually an indication of an internal error in " + 107 "Paging itself." 108 } 109 } 110 111 private inline fun <R : Any> mapPages( 112 transform: (TransformablePage<T>) -> TransformablePage<R> 113 ) = transformPages { it.map(transform) } 114 115 internal inline fun <R : Any> transformPages( 116 transform: (List<TransformablePage<T>>) -> List<TransformablePage<R>> 117 ): Insert<R> = 118 Insert( 119 loadType = loadType, 120 pages = transform(pages), 121 placeholdersBefore = placeholdersBefore, 122 placeholdersAfter = placeholdersAfter, 123 sourceLoadStates = sourceLoadStates, 124 mediatorLoadStates = mediatorLoadStates, 125 ) 126 127 override suspend fun <R : Any> map(transform: suspend (T) -> R): PageEvent<R> = mapPages { 128 TransformablePage( 129 originalPageOffsets = it.originalPageOffsets, 130 data = it.data.map { item -> transform(item) }, 131 hintOriginalPageOffset = it.hintOriginalPageOffset, 132 hintOriginalIndices = it.hintOriginalIndices 133 ) 134 } 135 136 override suspend fun <R : Any> flatMap( 137 transform: suspend (T) -> Iterable<R> 138 ): PageEvent<R> = mapPages { 139 val data = mutableListOf<R>() 140 val originalIndices = mutableListOf<Int>() 141 it.data.forEachIndexed { index, t -> 142 data += transform(t) 143 val indexToStore = it.hintOriginalIndices?.get(index) ?: index 144 while (originalIndices.size < data.size) { 145 originalIndices.add(indexToStore) 146 } 147 } 148 TransformablePage( 149 originalPageOffsets = it.originalPageOffsets, 150 data = data, 151 hintOriginalPageOffset = it.hintOriginalPageOffset, 152 hintOriginalIndices = originalIndices 153 ) 154 } 155 156 override suspend fun filter(predicate: suspend (T) -> Boolean): PageEvent<T> = mapPages { 157 val data = mutableListOf<T>() 158 val originalIndices = mutableListOf<Int>() 159 it.data.forEachIndexed { index, t -> 160 if (predicate(t)) { 161 data.add(t) 162 originalIndices.add(it.hintOriginalIndices?.get(index) ?: index) 163 } 164 } 165 TransformablePage( 166 originalPageOffsets = it.originalPageOffsets, 167 data = data, 168 hintOriginalPageOffset = it.hintOriginalPageOffset, 169 hintOriginalIndices = originalIndices 170 ) 171 } 172 173 companion object { 174 fun <T : Any> Refresh( 175 pages: List<TransformablePage<T>>, 176 placeholdersBefore: Int, 177 placeholdersAfter: Int, 178 sourceLoadStates: LoadStates, 179 mediatorLoadStates: LoadStates? = null 180 ) = 181 Insert( 182 REFRESH, 183 pages, 184 placeholdersBefore, 185 placeholdersAfter, 186 sourceLoadStates, 187 mediatorLoadStates, 188 ) 189 190 fun <T : Any> Prepend( 191 pages: List<TransformablePage<T>>, 192 placeholdersBefore: Int, 193 sourceLoadStates: LoadStates, 194 mediatorLoadStates: LoadStates? = null 195 ) = 196 Insert( 197 PREPEND, 198 pages, 199 placeholdersBefore, 200 -1, 201 sourceLoadStates, 202 mediatorLoadStates, 203 ) 204 205 fun <T : Any> Append( 206 pages: List<TransformablePage<T>>, 207 placeholdersAfter: Int, 208 sourceLoadStates: LoadStates, 209 mediatorLoadStates: LoadStates? = null 210 ) = 211 Insert( 212 APPEND, 213 pages, 214 -1, 215 placeholdersAfter, 216 sourceLoadStates, 217 mediatorLoadStates, 218 ) 219 220 /** 221 * Empty refresh, used to convey initial state. 222 * 223 * Note - has no remote state, so remote state may be added over time 224 */ 225 val EMPTY_REFRESH_LOCAL: Insert<Any> = 226 Refresh( 227 pages = listOf(TransformablePage.EMPTY_INITIAL_PAGE), 228 placeholdersBefore = 0, 229 placeholdersAfter = 0, 230 sourceLoadStates = 231 LoadStates( 232 refresh = LoadState.NotLoading.Incomplete, 233 prepend = LoadState.NotLoading.Complete, 234 append = LoadState.NotLoading.Complete, 235 ), 236 ) 237 } 238 239 override fun toString(): String { 240 val itemCount = pages.fold(0) { total, page -> total + page.data.size } 241 val placeholdersBefore = if (placeholdersBefore != -1) "$placeholdersBefore" else "none" 242 val placeholdersAfter = if (placeholdersAfter != -1) "$placeholdersAfter" else "none" 243 return appendMediatorStatesIfNotNull(mediatorLoadStates) { 244 """PageEvent.Insert for $loadType, with $itemCount items ( 245 | first item: ${pages.firstOrNull()?.data?.firstOrNull()} 246 | last item: ${pages.lastOrNull()?.data?.lastOrNull()} 247 | placeholdersBefore: $placeholdersBefore 248 | placeholdersAfter: $placeholdersAfter 249 | sourceLoadStates: $sourceLoadStates 250 """ 251 } 252 } 253 } 254 255 // TODO: b/195658070 consider refactoring Drop events to carry full source/mediator states. 256 data class Drop<T : Any>( 257 val loadType: LoadType, 258 /** Smallest [TransformablePage.originalPageOffsets] to drop; inclusive. */ 259 val minPageOffset: Int, 260 /** Largest [TransformablePage.originalPageOffsets] to drop; inclusive */ 261 val maxPageOffset: Int, 262 val placeholdersRemaining: Int 263 ) : PageEvent<T>() { 264 265 init { 266 require(loadType != REFRESH) { "Drop load type must be PREPEND or APPEND" } 267 require(pageCount > 0) { "Drop count must be > 0, but was $pageCount" } 268 require(placeholdersRemaining >= 0) { 269 "Invalid placeholdersRemaining $placeholdersRemaining" 270 } 271 } 272 273 val pageCount 274 get() = maxPageOffset - minPageOffset + 1 275 276 override fun toString(): String { 277 val direction = 278 when (loadType) { 279 APPEND -> "end" 280 PREPEND -> "front" 281 else -> 282 throw IllegalArgumentException("Drop load type must be PREPEND or APPEND") 283 } 284 return """PageEvent.Drop from the $direction ( 285 | minPageOffset: $minPageOffset 286 | maxPageOffset: $maxPageOffset 287 | placeholdersRemaining: $placeholdersRemaining 288 |)""" 289 .trimMargin() 290 } 291 } 292 293 /** 294 * A [PageEvent] to notify presenter layer of changes in local and remote LoadState. 295 * 296 * Uses two LoadStates objects instead of CombinedLoadStates so that consumers like 297 * PagingDataPresenter can define behavior of convenience properties 298 */ 299 data class LoadStateUpdate<T : Any>( 300 val source: LoadStates, 301 val mediator: LoadStates? = null, 302 ) : PageEvent<T>() { 303 304 override fun toString(): String { 305 return appendMediatorStatesIfNotNull(mediator) { 306 """PageEvent.LoadStateUpdate ( 307 | sourceLoadStates: $source 308 """ 309 } 310 } 311 } 312 313 @Suppress("UNCHECKED_CAST") 314 open suspend fun <R : Any> map(transform: suspend (T) -> R): PageEvent<R> = this as PageEvent<R> 315 316 @Suppress("UNCHECKED_CAST") 317 open suspend fun <R : Any> flatMap(transform: suspend (T) -> Iterable<R>): PageEvent<R> { 318 return this as PageEvent<R> 319 } 320 321 open suspend fun filter(predicate: suspend (T) -> Boolean): PageEvent<T> = this 322 } 323