androidandroid-pagingandroid-paging-library

Paging 3 initial loading not shown


I am working with paging 3, everything work fine except initial loading state. I am adding withLoadStateFooter but it never show loading state at first call

Here is my implementation

Load State Adapter

class LoadStateAdapter (
    private val retry: () -> Unit
): LoadStateAdapter<LoadStateViewHolder>() {

    override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
        holder.bindTo(loadState)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ): LoadStateViewHolder {
        return LoadStateViewHolder.create(parent, retry)
    }
}

Load State View Holder

class LoadStateViewHolder(
    view : View,
    private val retryCallback: () -> Unit
) : RecyclerView.ViewHolder(view) {

    private val progressBar = view.findViewById<ProgressBar>(R.id.progress_bar)
    private val errorMsg = view.findViewById<TextView>(R.id.error_msg)
    private val btnRetry = view.findViewById<Button>(R.id.retry_button)
        .also {
            it.setOnClickListener { retryCallback() }
        }
    private var loadState : LoadState? = null

    companion object {
        fun create(parent: ViewGroup, retryCallback: () -> Unit): LoadStateViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.network_state_item, parent, false)
            return LoadStateViewHolder(
                view,
                retryCallback
            )
        }
    }


    fun bindTo(loadState: LoadState) {
        this.loadState = loadState

        btnRetry.isVisible = loadState !is LoadState.Loading
        errorMsg.isVisible = loadState !is LoadState.Loading
        progressBar.isVisible = loadState is LoadState.Loading

        if (loadState is LoadState.Error){
            errorMsg.text = loadState.error.localizedMessage
        }
    }
}

Paging Source

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Model> {

    try {
        // Load page 1 if undefined.
        val currentPage = params.key ?: 0
        val offset = currentPage * 50

        val requestParams = hashMapOf<String, Any>()
        requestParams.put("limit", 50)
        requestParams.put("offset", offset)

        val response =  repository.getList(requestParams)
        val isFinish = response.paging != null && response.paging!!.next == null

        return LoadResult.Page(
            data = response.data ?: mutableListOf(),
            prevKey = null, // Only paging forward.
            nextKey = if (isFinish) null else currentPage + 1
        )
    } catch (e: Exception) {
        // Handle errors in this block
        return LoadResult.Error(e)
    }
}

View Model

val listPagingFlow = Pager(PagingConfig(pageSize = 50)) {
    MyPagingSource(repository)
}.flow.cachedIn(viewModelScope)

Activity

    val pagingAdapter = MyPagingAdapter()
    list.apply {
        setHasFixedSize(true)
        adapter = pagingAdapter.withLoadStateFooter(
            footer = LoadStateAdapter { pagingAdapter.retry() }
        )
    }

    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.listPagingFlow.collectLatest { pagingData ->
            pagingAdapter.submitData(pagingData)
        }
    }

MyPagingAdapter is simple PagingDataAdapter

In short; loading state works fine but it did not showing at first request. Can any one help?

Current version 3.0.0-alpha04


Solution

  • withLoadStateFooter returns a ConcatAdapter which concatenates results from original PagingDataAdapter with a LoadStateAdapter that listens to CombinedLoadState.append events. So it's not expected for it to return an item during initial load (loadType == REFRESH), and it was designed this way because it doesn't really make sense to show a "footer" before any items has loaded.

    However, to achieve what you want you can simply create your own ConcatAdapter which mirrors the implementation of .withLoadStateFooter very closely:

    val originalAdapter = MyPagingDataAdapter(...)
    val footerLoadStateAdapter = MyFooterLoadStateAdapter(...)
    
    addLoadStateListener { loadStates ->
        // You need to implement some logic here to update LoadStateAdapter.loadState
        // based on however you want to prioritize between REFRESH / APPEND load states.
        // Something really basic might be:
        // footerLoadStateAdapter.loadState = when {
        //     loadStates.refresh is NotLoading -> loadStates.append
        //     else -> loadStates.refresh
        // }
        footerLoadStateAdapter.loadState = ...
    }
    return ConcatAdapter(originalAdapter, footerLoadStateAdapter)