1 /* <lambda>null2 * 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.PagingSource.LoadResult.Page 21 22 /** 23 * Snapshot state of Paging system including the loaded [pages], the last accessed [anchorPosition], 24 * and the [config] used. 25 */ 26 public class PagingState<Key : Any, Value : Any> 27 constructor( 28 /** Loaded pages of data in the list. */ 29 public val pages: List<Page<Key, Value>>, 30 /** 31 * Most recently accessed index in the list, including placeholders. 32 * 33 * `null` if no access in the [PagingData] has been made yet. E.g., if this snapshot was 34 * generated before or during the first load. 35 */ 36 public val anchorPosition: Int?, 37 /** [PagingConfig] that was given when initializing the [PagingData] stream. */ 38 public val config: PagingConfig, 39 /** 40 * Number of placeholders before the first loaded item if placeholders are enabled, otherwise 0. 41 */ 42 @IntRange(from = 0) private val leadingPlaceholderCount: Int 43 ) { 44 45 override fun equals(other: Any?): Boolean { 46 return other is PagingState<*, *> && 47 pages == other.pages && 48 anchorPosition == other.anchorPosition && 49 config == other.config && 50 leadingPlaceholderCount == other.leadingPlaceholderCount 51 } 52 53 override fun hashCode(): Int { 54 return pages.hashCode() + 55 anchorPosition.hashCode() + 56 config.hashCode() + 57 leadingPlaceholderCount.hashCode() 58 } 59 60 /** 61 * Coerces [anchorPosition] to closest loaded value in [pages]. 62 * 63 * This function can be called with [anchorPosition] to fetch the loaded item that is closest to 64 * the last accessed index in the list. 65 * 66 * @param anchorPosition Index in the list, including placeholders. 67 * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if all 68 * loaded [pages] are empty. 69 */ 70 public fun closestItemToPosition(anchorPosition: Int): Value? { 71 if (pages.all { it.data.isEmpty() }) return null 72 73 anchorPositionToPagedIndices(anchorPosition) { pageIndex, index -> 74 val firstNonEmptyPage = pages.first { it.data.isNotEmpty() } 75 val lastNonEmptyPage = pages.last { it.data.isNotEmpty() } 76 return when { 77 index < 0 -> firstNonEmptyPage.data.first() 78 pageIndex == pages.lastIndex && index > pages.last().data.lastIndex -> { 79 lastNonEmptyPage.data.last() 80 } 81 else -> pages[pageIndex].data[index] 82 } 83 } 84 } 85 86 /** 87 * Coerces an index in the list, including placeholders, to closest loaded page in [pages]. 88 * 89 * This function can be called with [anchorPosition] to fetch the loaded page that is closest to 90 * the last accessed index in the list. 91 * 92 * @param anchorPosition Index in the list, including placeholders. 93 * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if all 94 * loaded [pages] are empty. 95 */ 96 public fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? { 97 if (pages.all { it.data.isEmpty() }) return null 98 99 anchorPositionToPagedIndices(anchorPosition) { pageIndex, index -> 100 return when { 101 index < 0 -> pages.first() 102 else -> pages[pageIndex] 103 } 104 } 105 } 106 107 /** 108 * @return `true` if all loaded pages are empty or no pages were loaded when this [PagingState] 109 * was created, `false` otherwise. 110 */ 111 public fun isEmpty(): Boolean = pages.all { it.data.isEmpty() } 112 113 /** 114 * @return The first loaded item in the list or `null` if all loaded pages are empty or no pages 115 * were loaded when this [PagingState] was created. 116 */ 117 public fun firstItemOrNull(): Value? { 118 return pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() 119 } 120 121 /** 122 * @return The last loaded item in the list or `null` if all loaded pages are empty or no pages 123 * were loaded when this [PagingState] was created. 124 */ 125 public fun lastItemOrNull(): Value? { 126 return pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() 127 } 128 129 override fun toString(): String { 130 return "PagingState(pages=$pages, anchorPosition=$anchorPosition, config=$config, " + 131 "leadingPlaceholderCount=$leadingPlaceholderCount)" 132 } 133 134 internal inline fun <T> anchorPositionToPagedIndices( 135 anchorPosition: Int, 136 block: (pageIndex: Int, index: Int) -> T 137 ): T { 138 var pageIndex = 0 139 var index = anchorPosition - leadingPlaceholderCount 140 while (pageIndex < pages.lastIndex && index > pages[pageIndex].data.lastIndex) { 141 index -= pages[pageIndex].data.size 142 pageIndex++ 143 } 144 145 return block(pageIndex, index) 146 } 147 } 148