Hello! I have a problem with Paging v3 library on ...
# android
Hello! I have a problem with Paging v3 library on Android, here's what happens: When the app is opened, the list is shown correctly and everything is great, but if I call
method of PagingSource, then it starts spamming
method of PagingSource (and doesn't even wait until the job is finished, cancels it and starts anew). This is the stacktrace:
Copy code
2021-07-14 02:23:46.189 15215-15254/com.xlebnick.kitties.debug D/OkHttp: --> GET <https://myapi.com/v1/images/search?page=0&limit=24&order=ASC>
2021-07-14 02:23:46.189 15215-15254/com.xlebnick.kitties.debug D/OkHttp: x-api-key: 9b7e282d-2a67-4c7b-a9fd-3f3e4056e949
2021-07-14 02:23:46.189 15215-15254/com.xlebnick.kitties.debug D/OkHttp: --> END GET
2021-07-14 02:23:46.189 15215-15254/com.xlebnick.kitties.debug D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
2021-07-14 02:23:46.189 15215-15215/com.xlebnick.kitties.debug W/System.err: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@6c3004
And this is my implementation:
Copy code
class KittiesPagingSource(
    private val repository: Repository,
    var breedFilter: Breed? = null,
    var likedKitties: List<Like> = listOf()
) : PagingSource<Int, Kitty>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Kitty> {
        return try {
            // Start refresh at page 1 if undefined.
            val nextPage = params.key ?: 1

            val filters = if (breedFilter != null) listOf(breedFilter!!) else null
            var kitties: List<KittyRemoteModel> = listOf()
            try {
                kitties = repository.fetchKitties(nextPage, KITTIES_PAGE_SIZE, filters)
            } catch (e: Throwable) {

            val newKitties = kitties.map { kitty ->
                kitty.asKitty(likedKitties.any { it.imageId == kitty.id })

                data = newKitties,
                prevKey = if (nextPage == 1) null else nextPage - 1,
                nextKey = if (newKitties.isEmpty()) null else nextPage + 1
        } catch (e: Exception) {

    override fun getRefreshKey(state: PagingState<Int, Kitty>): Int? {
        return state.anchorPosition?.let {
            if (it < state.config.initialLoadSize) {
                // if anchor position is less than initial loading count then download from the beginning
            } else {
                // otherwise load a page around anchorPosition using initialLoadSize
                (it - state.config.initialLoadSize / 2).coerceAtLeast(0)
Copy code
private val kittiesPagingFactory = InvalidatingPagingSourceFactory {
        KittiesPagingSource(repository, breedFilter, likedKitties.value ?: listOf())
    private val kittiesPager =
        Pager(PagingConfig(pageSize = 6), pagingSourceFactory = kittiesPagingFactory)
    val kitties: Flow<PagingData<Kitty>> = kittiesPager
What am I doing wrong?
So cancellation seems to be WAI as invalidation cancels the previous generation and creates a new one to reload data.
Do you mean that you are invalidating in a loop over and over again?
How / when are you calling .invalidate() ?
No, the invalidation is only called once
I invalidate when the second batch of data comes which changes the representation (I take data A and data B and display AB, but until B came, I show only A)
I put a breakpoint on
, and it's been only fired once
What does WAI mean?
working as intended
What is the loadType / subclass of LoadParams passed to .load()? is it looping between prepend and append
is it looping between prepend and append
If I understand the question correctly, then yes, because it keeps updating the flow, and therefore, the RecyclerView
The type of LoadParams that is sent to
is always
So no prepend/append
So this implies an invalidation loop, I can take a look if you have a sample you can share
To be clear, cancellation is expected, but spamming load is not ­čÖé
Sure, I will create a minimum reproducible code in a bit
Please let me know if something is missing
I think this is a bug with InvalidatingPagingSourceFactory
To confirm, can you try something like
Copy code
public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
    private val pagingSourceFactory: () -> PagingSource<Key, Value>
) : () -> PagingSource<Key, Value> {
    private val pagingSources = mutableListOf<PagingSource<Key, Value>>()

    override fun invoke(): PagingSource<Key, Value> {
        return pagingSourceFactory().also { pagingSources.add(it) }

    public fun invalidate() {
        for (pagingSource in pagingSources.toList()) {
            if (!pagingSource.invalid) {

        pagingSources.removeAll { it.invalid }
Fix won't make it for next release, but should for the one after (3.1.0-alpha04)
Works like a charm, thanks a lot!