Okhttp3 retroift2 GET-вызов отменяется в реализации androidx paging 3 с реализацией приостановленной нагрузки
Я работал с реализацией androidx paging 3 и столкнулся со странной проблемой.
В некоторых случаях мои "запросы GET okhttp3 retrofit2 https" непреднамеренно отменяются.
Мой простой вопрос: когда OkHttpClient непреднамеренно отменяет запросы? (Я не отправляю запросы на отмену ниоткуда).
- Это когда один и тот же запрос отправляется второй раз (скорее всего, это произойдет, если я сделаю запрос на "обновление". IE отправляет тот же запрос на сервер второй раз из-за ожидаемых изменений на моем сервере, но никаких изменений не было сделано, так что результат был такой же).
- Есть ли ограничение на количество одновременных запросов к серверу.
- Может ли удаленный сервер выполнить отмену?
- Может ли он отменить запрос, если запрос выполняется из потока, который умирает в ответе на запрос, если да, то как мне его предотвратить (это странно..., запрос отменяется в течение 70 мс и выполняется из вызова приостановки kotlin...)?
Моя фабрика клиентов API:
object APIFamappClientFactory{
private var apiFamappInterfaceService: APIFamappInterfaceService ?= null
fun makeAPIFamappInterfaceService(): APIFamappInterfaceService{
if(apiFamappInterfaceService == null) {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient
.Builder()
.addInterceptor(interceptor)
.readTimeout(30,TimeUnit.SECONDS)
.build()
apiFamappInterfaceService = Builder()
.baseUrl("https://www.famapp.no")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(APIFamappInterfaceService::class.java)
}
return apiFamappInterfaceService!!
}
}
Один из запросов, которые отменяются.
interface APIFamappInterfaceService{
//---
@GET("/fuelpump/liststations_fp3_v6.php")
suspend fun getSimpleStationsGlobalNearest(
@Query("userid") userid: String,
@Query("passfrase") passfrase: String,
@Query("latitude") latitude: String,
@Query("longitude") longitude: String,
@Query("limit") limit: String,
@Query("offset") offset: String,
@Query("lastversion") lastversion: String,
@Query("killed") killed: String,
@Query("range") range:String
): List<APISimpleStation>
//---
}
Вставляя строку запроса в браузер, я не нахожу ошибки, результаты в порядке, поэтому я не подозреваю, что на стороне сервера...
где-то во фрагменте реализация потока для заполнения recyclerview
viewModelJob = lifecycleScope.launch {
viewModel.getStationsFlow().collectLatest {stationWithKindPricePagingData ->
adapter.submitData(stationWithKindPricePagingData)
}
}
реализация потока в модели просмотра (хотя и простая)
class StationListViewModel internal constructor(
private val stationListRepository: StationListRepository
/*unsignificants omitted*/
) : ViewModel()
{
//---
fun getStationsFlow() = stationListRepository.getStationsFlow()
//---
}
В репозитории я использую реализацию Pager с RemoteMediator
class StationListRepository private constructor(
private val appDatabase: AppDatabase
)
{
//----
fun getStationsFlow(): Flow<PagingData<StationWithKindPrice>>
{
val pagingSourceFactory = {
stationListPagingSource = appDatabase
.stationDao()
.getLivePagingSourceStationsWithKindPriceUser("user")
stationListPagingSource
}
return Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = StationListRemoteMediator2(appDatabase),
pagingSourceFactory = pagingSourceFactory
).flow
}
}
RemoteMediator довольно сложный, поэтому я исключил его все...
private const val STATION_LIST_STARTING_PAGE_INDEX = 1
@OptIn(ExperimentalPagingApi::class)
class StationListRemoteMediator2(
private val appDatabase: AppDatabase
): RemoteMediator<Int, StationWithKindPrice>(){
private val TAG by lazy { this::class.java.simpleName }
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, StationWithKindPrice>
): MediatorResult {
Log.i(TAG, "load: ( loadType = $loadType )")
var page = when(loadType){
LoadType.REFRESH -> {
Log.i(TAG, "load: LoadType.REFRESH returning null (initial load)")
null
}
LoadType.PREPEND -> {
Log.i(TAG,
"load: LoadType.PREPEND state.lastItemOrNull()?.stationId = " +
"${state.lastItemOrNull()?.stationId}")
Log.i(TAG, "load: LoadType.PREPEND returning endOfPaginationReached = true, returning")
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> {
Log.i(TAG, "load: LoadType.APPEND preparing lastItem")
val lastItem = state.lastItemOrNull()?.let {
Log.i(TAG, "load: LoadType.APPEND state.lastItem.stationId = ${it.stationId}")
return@let it
} ?: let {
Log.i(TAG, "load: LoadType.APPEND state.lastItem = null, no data to append, returning")
return MediatorResult.Success(endOfPaginationReached = true)
}
Log.i(TAG, "load: LoadType.APPEND preparing remoteKey")
val remoteKey = appDatabase.withTransaction {
Log.i(
TAG, "load: LoadType.APPEND querying remoteKeyFromStationId " +
"lastItem.stationID = ${lastItem.stationId}"
)
val key = appDatabase.stationListRemoteKeyDao()
.remoteKeyFromStationId(lastItem.stationId)
Log.i(TAG, "load: LoadType.APPEND returning key = $key")
key
}?.let {
Log.i(TAG, "load: LoadType.APPEND remoteKeyFromStationId found = $it")
return@let it
} ?: let {
Log.w(TAG, "load: LoadType.APPEND remoteKeyFromStationID found null, returning")
return MediatorResult.Success(endOfPaginationReached = true)
}
Log.i(
TAG,
"load: LoadType.APPEND returning remoteKey.nextKey = ${remoteKey.nextKey}"
)
remoteKey.nextKey ?: return MediatorResult.Success(endOfPaginationReached = true)
}
}
Log.i(TAG, "load: Begin try api")
try {
if(page==null) page=1
val apiService = APIFamappClientFactory.makeAPIFamappInterfaceService()
var endOfPaginationReached = false
val deviceLocation =
appDatabase.deviceLocationDao().getNullableDeviceLocation()?.let {
Log.i(TAG, "load: getNullableDeviceLocation = $it")
return@let it
} ?:let{
Log.w(TAG, "load: getNullableDeviceLocation == null", )
return MediatorResult.Error(Throwable("No location data!!"))
}
Log.i(TAG, "load: deviceLocation = $deviceLocation")
val user = appDatabase.userDao().getUser()
?:return MediatorResult.Error(Throwable("No user registered yet !!"))
Log.i(TAG, "load: user = $user")
val userSelection = appDatabase.userSelectionDao().getNullableUserSelection()
?:return MediatorResult.Error(Throwable("No user selections !!"))
Log.i(TAG, "load: userSelection = $userSelection")
var range:String? = if(page <= 1){
Log.i(TAG, "load: resets range to 1.0")
CoroutineScope(Dispatchers.IO).launch {
appDatabase.userSelectionDao().updateCurrentRange("1.0")
}
"1.0"
} else userSelection.currentRange;
Log.i(TAG, "load: range = $range")
val countryCode: String
countryCode = if(userSelection.selectionAutoCountry)
{
deviceLocation.countrycode
?:user.tmsNWCountryISO?.toUpperCase(Locale.ROOT)
?:user.tmsSIMCountryISO?.toUpperCase(Locale.ROOT)
?:userSelection.selectionCountry?:"--"
}
else
{
userSelection.selectionCountry
?:user.tmsNWCountryISO?.toUpperCase(Locale.ROOT)
?:user.tmsSIMCountryISO?.toUpperCase(Locale.ROOT)
?:deviceLocation.countrycode?:"--"
}
Log.i(TAG,"load: countryCode = $countryCode")
Log.i(TAG,"load: userSelection.selectionArea = ${userSelection.selectionArea}")
Log.i(TAG,"load: userSelection.selectionSorting = ${userSelection.selectionSorting}")
var req: List<APISimpleStation>?=null
if((userSelection.selectionArea == "Country" )
&&(userSelection.selectionSorting == "Nearest"))
{
do {
if (userSelection.selectionAutoCountry) {
Log.i(TAG, "load: country, nearest, selectionAutoCountry = true, range = $range")
req = apiService.getSimpleStationsCountryNearest(
userid = user.userId,
passfrase = user.passfrase,
country = countryCode,
latitude = deviceLocation.latitude.toString()
.format(Locale.ROOT, "%s"),
longitude = deviceLocation.longitude.toString()
.format(Locale.ROOT, "%s"),
limit = state.config.pageSize.toString(),
offset = ((page - 1) * state.config.pageSize).toString(),
lastversion = user.currentVersion,
killed = if (userSelection.showHidden) "1" else "0",
range = range ?: "1.0" //may be changed
)
} else {
Log.i(TAG, "load: country, nearest, selectionAutoCountry = false, range = $range")
val countryData = appDatabase.countryDao().getCountryByCode(countryCode)
Log.i(TAG, "load: autoCountry not auto set = ${countryData?.isoCC2}")
req = apiService.getSimpleStationsCountryNearest(
userid = user.userId,
passfrase = user.passfrase,
country = countryCode,
latitude = (countryData?.latitude_center ?: 0.0F).toString()
.format(Locale.ROOT, "%s"),
longitude = (countryData?.longitude_center ?: 0.0F).toString()
.format(Locale.ROOT, "%s"),
limit = state.config.pageSize.toString(),
offset = ((page - 1) * state.config.pageSize).toString(),
lastversion = user.currentVersion,
killed = if (userSelection.showHidden) "1" else "0",
range = range ?: "1.0" //must be changed
)
}
Log.i(TAG, "load: req.size = ${req.size}")
if(req.isEmpty()) range = increaseRange(range)
else break
}while (range != null)
}else if((userSelection.selectionArea == "Global")
&&(userSelection.selectionSorting == "Nearest"))
{
do{
Log.i(TAG, "load: global, nearest, range = $range")
req = apiService.getSimpleStationsGlobalNearest(
userid = user.userId,
passfrase = user.passfrase,
latitude = deviceLocation.latitude.toString().format(Locale.ROOT,"%s"),
longitude = deviceLocation.longitude.toString().format(Locale.ROOT,"%s"),
limit = state.config.pageSize.toString(),
offset = ((page-1)*state.config.pageSize).toString(),
lastversion = user.currentVersion,
killed = if(userSelection.showHidden) "1" else "0",
range = range?:"1.0"
)
Log.i(TAG, "load: req.size = ${req.size}")
if(req.isEmpty()) range = increaseRange(range)
else break
}while (range != null)
}else if((userSelection.selectionArea == "Country")
&&(userSelection.selectionSorting == "Latest"))
{
Log.i(TAG, "load: country, latest")
req = apiService.getSimpleStationsCountryLatest(
userid = user.userId,
passfrase = user.passfrase,
country = countryCode,
limit = state.config.pageSize.toString(),
offset = ((page-1)*state.config.pageSize).toString(),
lastversion = user.currentVersion,
killed = if(userSelection.showHidden) "1" else "0"
)
}else if((userSelection.selectionArea == "Global")
&&(userSelection.selectionSorting == "Latest"))
{
Log.i(TAG, "load: global, latest")
req = apiService.getSimpleStationsGlobalLatest(
userid = user.userId,
passfrase = user.passfrase,
limit = state.config.pageSize.toString(),
offset = ((page-1)*state.config.pageSize).toString(),
lastversion = user.currentVersion,
killed = if(userSelection.showHidden) "1" else "0"
)
}
//TODO: Add rest
endOfPaginationReached = req?.isEmpty()
?:let {
Log.i(TAG, "load: req is empty, returning MediatorResult.Error")
return MediatorResult.Error(Throwable("No data could be read from backend...")) }
if(loadType == LoadType.REFRESH)
{
Log.i(TAG, "load: start clear appDatabase.withTransaction")
appDatabase.withTransaction {
appDatabase.stationListRemoteKeyDao().clearStationListRemoteKeys()
appDatabase.stationDao().nukeTable() //Nuke station table
}
}
Log.i(TAG, "load: start appDatabase.withTransaction")
appDatabase.withTransaction {
val prevKey = if(page == STATION_LIST_STARTING_PAGE_INDEX) null else page -1
val nextKey = if(endOfPaginationReached) null else page + 1
val keys = req.map {
StationListRemoteKey(
stationId = it.idsite,
prevKey = prevKey,
nextKey = nextKey)
}
val stations = req.map{
Log.i(
TAG,
"load: station map ${it.idsite}: ${it.stationname}: ${it.latitude}, ${it.longitude} -> ${deviceLocation.latitude}, ${deviceLocation.longitude}"
)
Station(
stationId = it.idsite,
stationName = it.stationname,
longitude = it.longitude.toDoubleOrNull(),
latitude = it.latitude.toDoubleOrNull(),
airDistance = (locationDistance(
it.latitude.toDoubleOrNull(),
it.longitude.toDoubleOrNull(),
deviceLocation.latitude,
deviceLocation.longitude
)?:0.0)/1000.0,
enterpriseId = it.enterpriseid,
googlePlaceId = it.googleplaceid,
stationAddress = it.stationaddress,
stationPlace = it.stationplace,
stationZip = it.stationzip,
stationCountryCode = it.countrycode,
killed = it.killed?.equals("1")?:false,
dateCreated =it.datecreated,
stationCountry = it.stationcountry
)
}
appDatabase.stationListRemoteKeyDao().insertAll(keys)
appDatabase.stationDao().insertAll(stations)
}
Log.i(TAG, "load: appDatabase.withTransaction end returing endofPaginationReached = $endOfPaginationReached")
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
}catch (exception: IOException)
{
return MediatorResult.Error(exception)
}
catch (exception: HttpException)
{
return MediatorResult.Error(exception)
}
}
fun increaseRange(inRange: String?): String? {
var fRange:Float = inRange?.toFloatOrNull()?:1.0F
fRange += 1.0F
Log.i(TAG, "increaseRange: fRange = $fRange")
val rRange = if(fRange>180.0F) null
else fRange.toString().format(Locale.ROOT,"%0.1f")
CoroutineScope(Dispatchers.IO).launch {
appDatabase.userSelectionDao().updateCurrentRange(rRange)
}
return rRange
}
fun locationDistance(
fromLatitude: Double?,
fromLongitude: Double?,
toLatitude: Double?,
toLongitude : Double?
):Double?{
fromLatitude?:return null
fromLongitude?:return null
toLatitude?:return null
toLongitude?:return null
val fromLocation = Location("FromLocation")
val toLocation = Location("ToLocation")
fromLocation.latitude = fromLatitude
fromLocation.longitude = fromLongitude
toLocation.latitude = toLatitude
toLocation.longitude = toLongitude
return fromLocation.distanceTo(toLocation).toDouble()
}
}
Если вы погрузитесь в это, я не увижу никакой отмены с моей стороны...
2020-08-16 10:31:35.918 30686-30686/no.rogo.emptyfuel I/MainActivity: createLocationCallback: onLocationResult locationCallback last location registered: Location[fused 61.890727,6.676107 hAcc=5 et=+4d1h19m58s848ms alt=0.0 vel=0.0 bear=90.0 vAcc=1 sAcc=1 bAcc=30 {Bundle[mParcelledData.dataSize=52]}]
2020-08-16 10:31:35.918 30686-30686/no.rogo.emptyfuel I/StationListRepository: upd: updateAirDistance2():
2020-08-16 10:31:35.923 30686-31093/no.rogo.emptyfuel I/StationListRepository: countrycode = NO (Norway)
2020-08-16 10:31:35.924 30686-31093/no.rogo.emptyfuel I/StationListRepository: deviceLocation altered !
2020-08-16 10:31:35.928 30686-31093/no.rogo.emptyfuel I/StationListRepository: oldcountrycode = NO, countrycode = NO
2020-08-16 10:31:35.929 30686-31093/no.rogo.emptyfuel I/StationListRepository: upd: Waiting for staions
2020-08-16 10:31:35.930 30686-30686/no.rogo.emptyfuel I/StationListFragment: location accuracy 5.0
2020-08-16 10:31:35.931 30686-30686/no.rogo.emptyfuel I/StationListFragment: viewModel.liveDeviceLocation.observing 61.8907267 6.6761067
2020-08-16 10:31:35.938 30686-31093/no.rogo.emptyfuel I/StationListRepository: upd: Stations read: 80
2020-08-16 10:31:35.938 30686-31093/no.rogo.emptyfuel I/StationListRepository: updateAirDistance2: location = Location[fused 61.890727,6.676107 hAcc=5 et=+4d1h19m58s848ms alt=0.0 vel=0.0 bear=90.0 vAcc=1 sAcc=1 bAcc=30 {Bundle[mParcelledData.dataSize=52]}]
2020-08-16 10:31:35.948 30686-30686/no.rogo.emptyfuel I/BA isGone: Visible
2020-08-16 10:31:35.948 30686-30686/no.rogo.emptyfuel I/BA isGone: Gone
2020-08-16 10:31:37.590 30686-30686/no.rogo.emptyfuel I/StationListFragment: swipeRefresh !
2020-08-16 10:31:37.595 30686-30686/no.rogo.emptyfuel I/StationListFragment: subscribeUi: viewModelJob collecting flow
2020-08-16 10:31:37.598 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: ( loadType = REFRESH )
2020-08-16 10:31:37.598 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: LoadType.REFRESH returning null (initial load)
2020-08-16 10:31:37.598 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: Begin try api
2020-08-16 10:31:37.598 30686-30686/no.rogo.emptyfuel I/APIFamappClientFactory: API: returning apiFamappInterfaceService = retrofit2.Retrofit$1@1679425
2020-08-16 10:31:37.599 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: .addLoadStateListener combinedLoadStates = CombinedLoadStates(source=LoadStates(refresh=NotLoading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)), mediator=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)))
2020-08-16 10:31:37.599 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: combinedLoadStates.refresh = Loading(endOfPaginationReached=false)
2020-08-16 10:31:37.643 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: getNullableDeviceLocation = DeviceLocation(userId=user, location_valid=true, timestamp=2020-08-16 08:31:35, latitude=61.8907267, longitude=6.6761067, locationAccuracy=5.0, hasLocationAccuracy=true, altitude=0.0, altitudeAccuracy=0.5, hasAltitude=true, hasAltitudeAccuracy=true, bearing=90.0, bearingAccuracy=30.0, hasBearing=true, hasBearingAccuracy=true, speed=0.0, speedAccuracy=0.5, hasSpeed=true, hasSpeedAccuracy=true, isFromMockProvider=false, provider=fused, countrycode=NO, countryname=Norway, hasCountry=true, countrysource=GPS)
2020-08-16 10:31:37.643 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: deviceLocation = DeviceLocation(userId=user, location_valid=true, timestamp=2020-08-16 08:31:35, latitude=61.8907267, longitude=6.6761067, locationAccuracy=5.0, hasLocationAccuracy=true, altitude=0.0, altitudeAccuracy=0.5, hasAltitude=true, hasAltitudeAccuracy=true, bearing=90.0, bearingAccuracy=30.0, hasBearing=true, hasBearingAccuracy=true, speed=0.0, speedAccuracy=0.5, hasSpeed=true, hasSpeedAccuracy=true, isFromMockProvider=false, provider=fused, countrycode=NO, countryname=Norway, hasCountry=true, countrysource=GPS)
2020-08-16 10:31:37.668 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: user = User(userKey=user, userId=108547, passfrase=eb7212853b9c14ca2975a39edc09cd5f, currentCountry=, currentVersion=v3.2.42.108.4915 debug, tmsSIMCountryISO=us, tmsNWCountryISO=us, apiServiceKey=AIzaSyD1cbX6eQDA15ZRAFUKaZUOYKco_tkez3M)
2020-08-16 10:31:37.684 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: userSelection = UserSelection(userId=user, currentKindId=0, currentStationId=null, showHidden=false, showNotAvailable=false, showUncertain=true, showNotValid=true, showUnknown=true, showOlder=true, showOld=true, showFair=true, showNew=true, showDetailNotAvailable=true, showDetailUncertain=true, showDetailPresent=true, selectionArea=Global, selectionSorting=Nearest, selectionCountry=null, selectionAutoCountry=true, addressCheckText=null, currentRange=1.0)
2020-08-16 10:31:37.685 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: resets range to 1.0
2020-08-16 10:31:37.687 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: range = 1.0
2020-08-16 10:31:37.687 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: countryCode = NO
2020-08-16 10:31:37.687 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: userSelection.selectionArea = Global
2020-08-16 10:31:37.687 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: userSelection.selectionSorting = Nearest
2020-08-16 10:31:37.687 30686-30686/no.rogo.emptyfuel I/StationListRemoteMediator2: load: global, nearest, range = 1.0
2020-08-16 10:31:37.694 30686-31115/no.rogo.emptyfuel I/okhttp.OkHttpClient: --> GET https://www.famapp.no/fuelpump/liststations_fp3_v6.php?userid=yyyyy&passfrase=xxxxxxxxxxxxxxxxxx&latitude=61.8907267&longitude=6.6761067&limit=20&offset=0&lastversion=v3.2.42.108.4915%20debug&killed=0&range=1.0
2020-08-16 10:31:37.694 30686-31115/no.rogo.emptyfuel I/okhttp.OkHttpClient: --> END GET
2020-08-16 10:31:37.706 30686-30686/no.rogo.emptyfuel I/StationListFragment: subscribeUi: viewModelJob collecting flow
2020-08-16 10:31:37.708 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: .addLoadStateListener combinedLoadStates = CombinedLoadStates(source=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)), mediator=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)))
2020-08-16 10:31:37.708 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: combinedLoadStates.refresh = Loading(endOfPaginationReached=false)
2020-08-16 10:31:37.773 30686-31115/no.rogo.emptyfuel I/okhttp.OkHttpClient: <-- HTTP FAILED: java.io.IOException: Canceled
2020-08-16 10:31:37.785 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: .addLoadStateListener combinedLoadStates = CombinedLoadStates(source=LoadStates(refresh=NotLoading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)), mediator=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)))
2020-08-16 10:31:37.786 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: combinedLoadStates.refresh = Loading(endOfPaginationReached=false)
2020-08-16 10:31:37.786 30686-30686/no.rogo.emptyfuel I/StationListFragment: adapter: .addLoadStateListener combinedLoadStates = CombinedLoadStates(source=LoadStates(refresh=NotLoading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=false), append=NotLoading(endOfPaginationReached=false)), mediator=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)))
Обратите внимание, что имя пользователя и пароль в приведенном выше журнале были скрыты yy и xx намеренно.
RG