Smirky
Smirky

Reputation: 187

Android Paging 3 with LoadStateAdapter - Issue of Screen flickering and jumping when new pages are loaded on scrolling

I have implemented Paging 3 with RemoteMediator. And also have a LoadStateAdapter.

Initially, I was experiencing flickers, glitches and jumps when scrolling pages. This answer worked to resolve the issue - https://stackoverflow.com/a/66713643/15392387

I can see 3 items in my RecyclerView in one screen, so setting the PageSize = 8, as suggested, resolved all flickering issues.

But since I also use PagingDataAdapter.withLoadStateHeaderAndFooter, the initial load when the app is installed, automatically scrolls down to the 8th ListItem.

It does not start from the top of the page.

Can someone help me resolve this issue?

I found an answer that might be talking about the same issue, but solution is still unclear - https://stackoverflow.com/a/66763460/15392387

HomeFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        _binding = FragmentHomeBinding.bind(view) //View Binding

        val parentAdapter = PlaylistParentPagingAdapter(this, this)

        binding.apply {
            playlistParentRecyclerView.setHasFixedSize(true)
            playlistParentRecyclerView.adapter = parentAdapter.withLoadStateHeaderAndFooter(
             header = PlaylistLoadStateAdapter { parentAdapter.retry() },
             footer = PlaylistLoadStateAdapter { parentAdapter.retry() },
           )

        }

        viewModel.playlists.observe(viewLifecycleOwner) {
            parentAdapter.submitData(viewLifecycleOwner.lifecycle, it)
        }
    }

PlaylistLoadStateAdapter.kt

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

    private val TAG = "PlaylistLoadStateAdapte"

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
        val binding = PlaylistLoadStateFooterBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return LoadStateViewHolder(binding)
    }

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

    inner class LoadStateViewHolder(private val binding: PlaylistLoadStateFooterBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            binding.retryButtonFooter.setOnClickListener {
                retry.invoke()
            }
        }

        fun bind(loadState: LoadState) {
            binding.apply {
                Log.d(TAG, "bind: loadstate = $loadState")
                progressBarFooter.isVisible = loadState is LoadState.Loading
                retryButtonFooter.isVisible = loadState !is LoadState.Loading
                errorTextViewFooter.isVisible = loadState !is LoadState.Loading
            }
    }
    }

Upvotes: 0

Views: 1199

Answers (1)

dlam
dlam

Reputation: 3895

There is an open feature request for RecyclerView where we need a way to ignore the header / footers when considering restoring scroll position. Essentially you have a list which only contains the footer, then a list which contains some items + the footer, and RV is trying to restore the scroll position to the footer since that is the only overlap.

You can follow the issue here: https://issuetracker.google.com/issues/184874613

One workaround is to use placeholders, but if that's unsuitable you could also try a custom LoadStateAdapter that is only visible after PagingSource is done loading locally. This will cause your footer to get remove and re-added during local refresh, but it most likely will not be visible to the user.

Upvotes: 1

Related Questions