Skip to content

Collecting from multiple PagingData concurrently is an illegal operation #62

@sereden

Description

@sereden

Hello!

I use library version 3.0.0-alpha01
I'm trying to implement a multi-page search using pages. However, I faced an issue that it's not possible to replace data while the previous set is processing. And it looks like there is no way to stop processing or receive callback that processing has finished

So basically my Fragment observes data from ViewModel:

lifecycleScope.launch {
            @OptIn(ExperimentalCoroutinesApi::class)
            viewModel.searchData.distinctUntilChanged().collectLatest {
                adapter?.submitData(it)
            }
        }

ViewModel triggers repository:


    @ExperimentalCoroutinesApi
    val searchData = searchRequest.asFlow().flatMapLatest {
        val selectedStatuses = selectedStatuses.value?.toList() ?: emptyList()
        val search = _search.value.orEmpty()
        if (search.isNotBlank() || selectedStatuses.isNotEmpty()) {
            repository.search(search, selectedStatuses.value?.toList() ?: emptyList()) { statuses, total, page ->
                //  some converting data stuff
            }
        } else {
            flow { emit(PagingData.empty<SearchRecyclerModel>()) }
        }
    }

Repository:

    val pagingSource = SearchPagingSource(appDatabase, databaseDataSource, remoteDataSource, dateHelper)
    fun search(search: String, statuses: List<Long>, convertToRecyclerItems: (List<Result>, Int, Int) -> List<SearchRecyclerModel>) =
        Pager(
            PagingConfig(PAGE_SIZE),
            initialKey = SearchData(search, statuses, 0, PAGE_SIZE)
        ) { pagingSource.setConverter(convertToRecyclerItems) }.flow

And SearchPagingSource

class SearchPagingSource(
    private val appDatabase: AppDatabase,
    private val databaseDataSource: DatabaseDataSource,
    private val remoteDataSource: RemoteDataSource,
    private val dateHelper: DateHelper
) :
    PagingSource<SearchData, SearchRecyclerModel>() {
    private val dateFormat = dateHelper.serverResponseDateFormat
    private var convertToRecyclerItems: ((List<Result>, Int, Int) -> List<SearchRecyclerModel>)? = null
    
    override suspend fun load(params: LoadParams<SearchData>): LoadResult<SearchData, SearchRecyclerModel> {
        return try {
            val key = params.key!!
            val previousKey = if (key.offset == 0) null else key.copy(offset = key.offset - 1)
            var nextKey: SearchData? = null
            var total: Int

            // internet request
            // TODO why is it UI thread?
            val result = remoteDataSource.search(key.criteria, key.statusIds, key.offset, key.limit)
            
            // Here is response parsing
            // ...
            //
            
            
            total = result?.data?.total ?: 0
            nextKey = if (total < key.limit * (key.offset + 1)) null else key.copy(offset = key.offset + 1)


            LoadResult.Page(
                data = convertToRecyclerItems?.invoke(result, total, key.offset) ?: emptyList(),
                prevKey = previousKey,
                nextKey = nextKey
            )
        } catch (e: IOException) {
            LoadResult.Error(e)
        } catch (e: HttpException) {
            LoadResult.Error(e)
        }
    }

    // Convert server response into recycler's items
    fun setConverter(convertToRecyclerItems: (List<Result>, Int, Int) -> List<SearchRecyclerModel>): SearchPagingSource {
        this.convertToRecyclerItems = convertToRecyclerItems
        return this

    }
}

data class SearchData(val criteria: String, val statusIds: List<Long>, val offset: Int, val limit: Int)

Is there a way to handle it somehow?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions