Search code examples
androidandroid-fragmentsandroid-viewpager2

Determining when fragment no longer visible in ViewPager2


I am looking to migrate from ViewPager to ViewPager2. My existing ViewPager contains ListFragments that support bulk editing via an ActionMode. I need to cancel the current bulk editing operation when the user swipes from one page to another, so that the context header from one page isn't shown when viewing a different page.

With ViewPager, I can override setPrimaryItem in the ListFragment's FragmentPagerAdapter to know when one page replaces another. Crucially, this function takes as a parameter the actual child fragment that's becoming visible, which I can store in the adapter and later use to cancel bulk editing when it is replaced by another fragment.

How do I do the same thing with ViewPager2, namely, know when a fragment is no longer visible? I have tried overriding Fragment.onPause, but that's not what I want---it gets called whenever the phone screen turns off or the orientation changes, and I don't want to cancel bulk editing in these circumstances. I also have a callback registered via ViewPager2.registerOnPageChangeCallback, but this gives me only the index of the new fragment, not the fragment itself or anything about the old fragment.


Solution

  • I think the best solution is to register a FragmentTransactionCallback. Downside - this was only added in the latest 1.1.0 alpha release of the ViewPager2 library.

    There's a method on that class (FragmentTransactionCallback::onFragmentMaxLifecyclePreUpdated) which is called whenever the ViewPager2 changes a Fragment's maximum Lifecycle state.

    val basicAdapter: FragmentStateAdapter = // ....
    
    basicAdapter.registerFragmentTransactionCallback(object : FragmentTransactionCallback() {
        override fun onFragmentMaxLifecyclePreUpdated(fragment: Fragment, max: Lifecycle.State): OnPostEventListener {
            // Check whether a Fragment is going to move from 'resumed' to 'started'
            //
            // Note that this check won't catch some types of smooth scroll:
            // I'm not certain why, but I think it has to do with fragments
            // already being paused before the method is called.
            //
            if (max == Lifecycle.State.STARTED && fragment.isResumed) {
                // This fragment WAS the 'current item' but it's offscreen now.
                return OnPostEventListener { TODO("Cancel Bulk Editing") }
            }
            return super.onFragmentMaxLifecyclePreUpdated(fragment, max)
        }
    })