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.RestrictTo 20 import androidx.paging.DataSource.KeyType.ITEM_KEYED 21 import androidx.paging.DataSource.KeyType.PAGE_KEYED 22 import androidx.paging.DataSource.KeyType.POSITIONAL 23 import androidx.paging.DataSource.Params 24 import androidx.paging.LoadType.APPEND 25 import androidx.paging.LoadType.PREPEND 26 import androidx.paging.LoadType.REFRESH 27 import androidx.paging.internal.BUGANIZER_URL 28 import kotlin.coroutines.CoroutineContext 29 import kotlinx.coroutines.DelicateCoroutinesApi 30 import kotlinx.coroutines.withContext 31 32 /** A wrapper around [DataSource] which adapts it to the [PagingSource] API. */ 33 @OptIn(DelicateCoroutinesApi::class) 34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 35 public class LegacyPagingSource<Key : Any, Value : Any>( 36 private val fetchContext: CoroutineContext, 37 internal val dataSource: DataSource<Key, Value> 38 ) : PagingSource<Key, Value>(), CompatLegacyPagingSource { 39 private var pageSize: Int = PAGE_SIZE_NOT_SET 40 41 init { 42 dataSource.addInvalidatedCallback(::invalidate) 43 // technically, there is a possibly race where data source might call back our invalidate. 44 // in practice, it is fine because all fields are initialized at this point. 45 registerInvalidatedCallback { 46 dataSource.removeInvalidatedCallback(::invalidate) 47 dataSource.invalidate() 48 } 49 } 50 51 /** */ 52 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 53 public override fun setPageSize(pageSize: Int) { 54 check(this.pageSize == PAGE_SIZE_NOT_SET || pageSize == this.pageSize) { 55 "Page size is already set to ${this.pageSize}." 56 } 57 this.pageSize = pageSize 58 } 59 60 /** 61 * This only ever happens in testing if Pager / PagedList is not used hence we'll not get the 62 * page size. For those cases, guess :). 63 */ 64 private fun guessPageSize(params: LoadParams<Key>): Int { 65 if (params is LoadParams.Refresh) { 66 if (params.loadSize % PagingConfig.DEFAULT_INITIAL_PAGE_MULTIPLIER == 0) { 67 return params.loadSize / PagingConfig.DEFAULT_INITIAL_PAGE_MULTIPLIER 68 } 69 } 70 return params.loadSize 71 } 72 73 override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> { 74 val type = 75 when (params) { 76 is LoadParams.Refresh -> REFRESH 77 is LoadParams.Append -> APPEND 78 is LoadParams.Prepend -> PREPEND 79 } 80 if (pageSize == PAGE_SIZE_NOT_SET) { 81 // println because we don't have android logger here 82 println( 83 """ 84 WARNING: pageSize on the LegacyPagingSource is not set. 85 When using legacy DataSource / DataSourceFactory with Paging3, page size 86 should've been set by the paging library but it is not set yet. 87 88 If you are seeing this message in tests where you are testing DataSource 89 in isolation (without a Pager), it is expected and page size will be estimated 90 based on parameters. 91 92 If you are seeing this message despite using a Pager, please file a bug: 93 $BUGANIZER_URL 94 """ 95 .trimIndent() 96 ) 97 pageSize = guessPageSize(params) 98 } 99 val dataSourceParams = 100 Params(type, params.key, params.loadSize, params.placeholdersEnabled, pageSize) 101 102 return withContext(fetchContext) { 103 dataSource.load(dataSourceParams).run { 104 LoadResult.Page( 105 data, 106 @Suppress("UNCHECKED_CAST") 107 if (data.isEmpty() && params is LoadParams.Prepend) null else prevKey as Key?, 108 @Suppress("UNCHECKED_CAST") 109 if (data.isEmpty() && params is LoadParams.Append) null else nextKey as Key?, 110 itemsBefore, 111 itemsAfter 112 ) 113 } 114 } 115 } 116 117 @Suppress("UNCHECKED_CAST") 118 override fun getRefreshKey(state: PagingState<Key, Value>): Key? { 119 return when (dataSource.type) { 120 POSITIONAL -> 121 state.anchorPosition?.let { anchorPosition -> 122 state.anchorPositionToPagedIndices(anchorPosition) { _, indexInPage -> 123 val offset = state.closestPageToPosition(anchorPosition)?.prevKey ?: 0 124 (offset as Int).plus(indexInPage) as Key? 125 } 126 } 127 PAGE_KEYED -> null 128 ITEM_KEYED -> 129 state.anchorPosition 130 ?.let { anchorPosition -> state.closestItemToPosition(anchorPosition) } 131 ?.let { item -> dataSource.getKeyInternal(item) } 132 } 133 } 134 135 override val jumpingSupported: Boolean 136 get() = dataSource.type == POSITIONAL 137 138 private companion object { 139 private const val PAGE_SIZE_NOT_SET = Int.MIN_VALUE 140 } 141 } 142