Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 749
ysfcyln Avatar asked Mar 06 '26 14:03

ysfcyln


2 Answers

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)
like image 152
dlam Avatar answered Mar 08 '26 03:03

dlam


Using the footer LoadStateAdapter for showing initial loading will cause issues. The list will automatically be scrolled to the bottom as mentioned in the comments.

The way to go about this is to use two LoadStateAdapters, one to show the initial loading and the second to show loading when more items are being loaded.

fun <T : Any, V : RecyclerView.ViewHolder> PagingDataAdapter<T, V>.withLoadStateAdapters(
    header: LoadStateAdapter<*>,
    footer: LoadStateAdapter<*>
): ConcatAdapter {
    addLoadStateListener { loadStates ->
        header.loadState = loadStates.refresh
        footer.loadState = loadStates.append
    }

    return ConcatAdapter(header, this, footer)
}
like image 36
Xid Avatar answered Mar 08 '26 02:03

Xid