Android: ViewPager2 setting the starting page on creation not working

Viewed 440

I have a viewpager containning two fragments, and a viewModel (stored in the parent activity) holding a record to the index of current page being visible.

here is the setUp of the parent fragment:

 override fun setUp() {
    super.setUp()
    titles = arrayOf(R.string.providers, R.string.create_provider)
    fragments = arrayOf(ProvidersListFragment(), CreateProviderFragment())
    pagerAdapter = BaseFragmentAdapter(this, fragments = fragments)
}

override fun setUpViews() {
    super.setUpViews()
    binding.page.adapter = pagerAdapter
    TabLayoutMediator(binding.tabs, binding.page) { tab, position ->
        tab.text = getString(titles[position])
    }.attach()
    binding.page.currentItem = viewModel.currentPage.value!!
}

override fun setUpObservers() {
    super.setUpObservers()
    viewModel.currentPage.observe(viewLifecycleOwner, {
        Log.d(TAG, "setUpObservers: it = $it")
        if (it != binding.page.currentItem)
            binding.page.currentItem = it
    })
}

the 3 methods above are called inside onResume().

the ViewModel holds the record to the current index like this: var currentPage = MutableLiveData<Int>().apply { value = 1 }

technically the starting page should be index 1, my case the TabMediator shows as if the page 1 is selected, but the layout is showing the content of page 0 (not even fully loaded).

that's what it's showing:

the error layout

and that's what it's supposed to show:

enter image description here

this is the layout 0:

enter image description here

any ideas about this behaviour and how can i avoid it?

2 Answers

I ran into the same problem and noticed that it had something to do with setting the page too soon (before any fragments are created). So I needed to wait for the adapter to finish creating a Fragment. Unfortunately there doesn't seem to be a callback method for when the adapter is finished loading or creating Fragments so I created this subclass

inner class TabAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {

    private var finished = false

    // other stuff

    override fun onBindViewHolder(holder: FragmentViewHolder, position: Int, payloads: MutableList<Any>) {
        super.onBindViewHolder(holder, position, payloads)
        if (!finished) {
            finished = true
            // TODO set starting page
        }
    }
}

It's not a really clean solution but it did the job for me.

I worked around this by creating a navigation action to take me directly to the desired page of the fragment. And since this is a workaround not a solution, it doesn't conform a 100% to my need, when i navigate to the fragment it's being created as a single fragment and not a page.

I also did something else in a fragment that had the functionality of image picker. the issue was that when I'm on page 2 (creation fragment) and try to select an image, when I finish selecting, I'd find myself on the page 1. what I did was the following:

On the fragment holding the pager:

    override fun setUpObservers() {
        // this method is called inside onResume()
    super.setUpObservers()
    if (binding.page.currentItem != viewModel.page)
        binding.page.currentItem = viewModel.page
    }

on the other fragments:

    override fun onResume() {
    super.onResume()
    productsViewModel.page = 0 //the other fragment set this to 1
     }

the viewmodel is created with the main activity's store:

private val productsViewModel: ProductsViewModel by viewModels(this::requireActivity)

hope this can help anyone with the same issue.

Related