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
I was able to get access to current fragment in FragmentStateAdapter using reflection.
Extension function in Kotlin:
fun FragmentStateAdapter.getItem(position: Int): Fragment? {
return this::class.superclasses.find { it == FragmentStateAdapter::class }
?.java?.getDeclaredField("mFragments")
?.let { field ->
field.isAccessible = true
val mFragments = field.get(this) as LongSparseArray<Fragment>
return@let mFragments[getItemId(position)]
}
}
Add Kotlin reflection dependency if needed:
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
Example call:
val tabsAdapter = viewpager.adapter as FragmentStateAdapter
val currentFragment = tabsAdapter.getItem(viewpager.currentItem)
Similar to some other answers here, this is what I'm currently using.
I'm not clear on how reliable it is but at least one of these is bound to work
I had the same problem. I converted from ViewPager to ViewPager2, using FragmentStateAdapter. In my case I have a DetailActivity class (extends AppCompatActivity) which houses the ViewPager2, which is used to page through lists of data (Contacts, Media, etc.) on smaller form-factor devices.
I need to know the currently shown fragment (which is my own class DetailFragment which extends androidx.fragment.app.Fragment), because that class contains the string I use to update the title on the DetailActivity toolbar.
I first started down the road of registering an onPageChangeCallback listener as suggested by some, but I quickly ran into problems:
adapter.createFragment()
call as suggested by some with the idea to add the newly created fragment to a Bundle object (using FragmentManager.put()
) with that tag. This way I could then save them across config changes. The problem here is that during createFragment()
, the fragment isn't actually yet part of the FragmentManager, so the put() calls fail.createFragment()
call on the adapter - so there are no fragments yet created and added to the FragmentManager, so I can't get a reference to that first fragment using the "f0" tag.createFragment()
calls - but I could not identify any type of handler within the adapter, the associated recyclerview, the viewpager etc. that allows me to surface the list of fragments that I could then reference that to the position identified within that listener. Strangely, for example, one adapter method that looked very promising was onViewAttachedToWindow()
- however it is marked final so can't be overridden (even though the JavaDoc clearly anticipates it being used this way).So what I ended up doing that worked for me was the following:
public interface DetailFragmentShownListener {
// Allows classes that extend this to update visual items after shown
void onDetailFragmentShown(DetailFragment me);
}
public void onResume() {
super.onResume();
View v = getView();
if (v!=null && v.getViewTreeObserver().isAlive()) {
v.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
v.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Let our parent know we are laid out
if ( getActivity() instanceof DetailFragmentShownListener ) {
((DetailFragmentShownListener) getActivity()).onDetailFragmentShown(DetailFragment.this);
}
}
});
}
}
@Override
public void onDetailFragmentShown(DetailFragment me) {
mCurrentFragment = me;
updateToolbarTitle();
}
mCurrentFragment
is a property of this class as its used in various other places.
I also had your problem and used this trick
Map<Integer, Fragment> map = new HashMap<>();
@Override
public Fragment createFragment(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = new CircularFragment();
map.put(0, fragment);
return fragment;
case 1:
fragment = new LocalFragment();
map.put(1, fragment);
return fragment;
case 2:
fragment = new SettingsFragment();
map.put(2, fragment);
return fragment;
}
fragment = new CircularFragment();
map.put(0, fragment);
return fragment;
}
and you can get fragment like this
LocalFragment f = (LocalFragment) map.get(1);
supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
with FragmentStateAdapter
in placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder)
add Fragment
mFragmentManager.beginTransaction()
.add(fragment, "f" + holder.getItemId())
.setMaxLifecycle(fragment, STARTED)
.commitNow()
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")
}
Activity
, use supportFragmentManager
or fragmentManager
.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.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.