Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get current fragment with ViewPager2

I'm migrating my ViewPager to ViewPager2 since the latter is supposed to solve all the problems of the former. Unfortunately, when using it with a FragmentStateAdapter, I don't find any way to get the currently displayed fragment.

viewPager.getCurrentItem() gives the current displayed index and adapter.getItem(index) generally creates a new Fragment for the current index. Unless keeping a reference to all created fragments in getItem(), I have no idea how to access the currently displayed fragment.

With the old ViewPager, one solution was to call adapter.instantiateItem(index) which would return the fragment at the desired index.

Am I missing something with ViewPager2?

like image 477
guillaume-tgl Avatar asked Apr 17 '19 13:04

guillaume-tgl


People also ask

How to get current fragment from ViewPager2?

viewPager. getCurrentItem() gives the current displayed index and adapter. getItem(index) generally creates a new Fragment for the current index.

How do you get current visible fragments?

To get the current fragment that's active in your Android Activity class, you need to use the supportFragmentManager object. The supportFragmentManager has findFragmentById() and findFragmentByTag() methods that you can use to get a fragment instance.

How do I refresh a ViewPager fragment?

OnPageChangeListener is the correct way to go, but you will need to refactor your adapter a bit in order to keep a reference to each Fragment contained in the FragmentPagerAdapter. Then, instead of creating a new Fragment, use the one contained in the adapter: mViewPager. addOnPageChangeListener(new ViewPager.

How do I refresh FragmentPagerAdapter?

You can use startActivityForResult(or broadcast/receiver) to get the result from the activity. Then use adapter. notifyDataSetChanged to refresh the fragment.


3 Answers

In ViewPager2 the FragmentManager by default have assigned tags to fragments like this:

Fragment in 1st position has a tag of "f0"

Fragment in 2nd position has a tag of "f1"

Fragment in 3rd position has a tag of "f2" and so on... so you can get your fragment's tag and by concatenating the "f" with position of your fragment. To get the current Fragment you can get current position from the viewPager2 position and make your tag like this (For Kotlin):

val myFragment = supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)

For fragment at a certain position

val myFragment = supportFragmentManager.findFragmentByTag("f" + position)

You can cast the Fragment and always check if it is not null if you are using this technique.

If you host your ViewPager2 in Fragment, use childFragmentManager instead.

REMEMBER

If you have overriden the getItemId(position: Int) in your adapter. Then your case is different. It should be:

val myFragment = supportFragmentManager.findFragmentByTag("f" + your_id_at_that_position)

OR SIMPLY:

val myFragment = supportFragmentManager.findFragmentByTag("f" + adapter.getItemId(position))

If you host your ViewPager2 in Fragment, use childFragmentManager instead of supportFragmentManager.

like image 105
Xenolion Avatar answered Oct 23 '22 17:10

Xenolion


The solution to find current Fragment by its tag seems the most suitable for me. I've created these extension functions for that:

fun ViewPager2.findCurrentFragment(fragmentManager: FragmentManager): Fragment? {
    return fragmentManager.findFragmentByTag("f$currentItem")
}

fun ViewPager2.findFragmentAtPosition(
    fragmentManager: FragmentManager,
    position: Int
): Fragment? {
    return fragmentManager.findFragmentByTag("f$position")
}
  • If your ViewPager2 host is Activity, use supportFragmentManager or fragmentManager.
  • If your ViewPager2 host is Fragment, use childFragmentManager

Note that:

  • findFragmentAtPosition will work only for Fragments that were initialized in ViewPager2's RecyclerView. Therefore you can get only the positions that are visible + 1.
  • Lint will suggest you to remove ViewPager2. from fun ViewPager2.findFragmentAtPosition, because you don't use anything from ViewPager2 class. I think it should stay there, because this workaround applies solely to ViewPager2.
like image 21
Micer Avatar answered Oct 23 '22 18:10

Micer


I had similar problem when migrating to ViewPager2.

In my case I decided to use parentFragment property (I think you can make it also work for activity) and hope, that ViewPager2 will keep only the current fragment resumed. (i.e. page fragment that was resumed last is the current one.)

So in my main fragment (HostFragment) that contains ViewPager2 view I created following property:

private var _currentPage: WeakReference<MyPageFragment>? = null
val currentPage
    get() = _currentPage?.get()

fun setCurrentPage(page: MyPageFragment) {
    _currentPage = WeakReference(page)
}

I decided to use WeakReference, so I don't leak inactive Fragment instances

And each of my fragments that I display inside ViewPager2 inherits from common super class MyPageFragment. This class is responsible for registering its instance with host fragment in onResume:

override fun onResume() {
    super.onResume()
    (parentFragment as HostFragment).setCurrentPage(this)
}

I also used this class to define common interface of paged fragments:

abstract fun someOperation1()

abstract fun someOperation2()

And then I can call them from the HostFragment like this:

currentPage?.someOperation1()

I'm not saying it's a nice solution, but I think it's more elegant than relying on internals of ViewPager's adapter with instantiateItem method that we had to use before.

like image 19
Almighty Avatar answered Oct 23 '22 18:10

Almighty