I am making a file selection page with viewpager2 with FragmentStateAdapter. In one of the pages I show all mounted storage devices on which I would like to tap to view the contents but I want the contents to be on another fragment.
I want to replace fragment with another one in the last page of viewpager. but with the following code, the page just refreshes and new Fragment is not created as createFragment
does not get called.
My PagerAdapter
public static class PagerAdapter extends FragmentStateAdapter {
private Fragment mFragmentAtPos0;
private final FragmentManager mFragmentManager;
private final FirstPageListener listener = new FirstPageListener();
public final class FirstPageListener implements
FirstPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
.commit();
if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage){
mFragmentAtPos0 = new FileExplorer(listener);
}else{ // Instance of NextFragment
mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
}
notifyDataSetChanged();
}
public void onSwitchToNextFragment(Bundle bundle) {
// mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
// .commit();
if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage){
mFragmentAtPos0 = new FileExplorer(listener);
mFragmentAtPos0.setArguments(bundle);
}else{ // Instance of NextFragment
mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
mFragmentAtPos0.setArguments(bundle);
}
notifyDataSetChanged();
// notifyItemChanged(getItemPosition(mFragmentAtPos0));
}
}
// @Override
// public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) {
// super.onBindViewHolder(holder, position, payloads);
// if (position == getItemPosition(mFragmentAtPos0)) {
//
// Fragment f = mFragmentManager.findFragmentByTag("f" + holder.getItemId());
// if (f != null) {
// mFragmentManager.beginTransaction().replace(holder.getItemId(), )
// }
// }
// }
private int getItemPosition(Fragment mFragmentAtPos0) {
for (int i = 0; i < getItemCount(); i++){
if (createFragment(i).equals(mFragmentAtPos0)){
return i;
}
}
return -1;
}
public PagerAdapter(FragmentActivity fm) {
super(fm);
mFragmentManager = fm.getSupportFragmentManager();
mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position){
case 0:
return new AppSelectionFragment();
case 1:
return new Photos();
case 2:
return new VideoGalleryFragment();
case 3:
return new Gallery();
default:
return mFragmentAtPos0;
}
}
@Override
public int getItemCount() {
return 5;
}
}
Here is the constructor of Fragment from which I would like to replace to another Fragment:
private static FileSelection.PagerAdapter.FirstPageListener pageFragmentListener;
public FilesandFolder_Others_MainPage() {
}
public FilesandFolder_Others_MainPage(FileSelection.PagerAdapter.FirstPageListener firstPageFragmentListener) {
pageFragmentListener = firstPageFragmentListener;
}
And on tap of a view, the following code is supposed to be called:-
Bundle bundle = new Bundle();
bundle.putString("PATH", volumes.get(position).path);
pageFragmentListener.onSwitchToNextFragment(bundle);
I have searched all over stackoverflow, and never saw a proper answer with viewpager 2 and FragmentStateAdapter as everyone seems to be using Viewpager
I tried to implement this:- FragmentStateAdapter for ViewPager2 notifyItemChanged not working as expected
but couldn't understand how to edit my code. Pls help Thanks in advance
ViewPager2 is an improved version of the ViewPager library that offers enhanced functionality and addresses common difficulties with using ViewPager . If your app already uses ViewPager , read this page to learn more about migrating to ViewPager2 .
Just refresh your HomeFragment or ProfileFragment by overriding onResume method. In onResume Method you can refresh your ListFragment or Fragment easily.
The ViewPager and pager adapter just deal with data in memory. So when data in memory is updated, we just need to call the adapter's notifyDataSetChanged() . Since the fragment is already created, the adapter's onItemPosition() will be called before notifyDataSetChanged() returns.
So a Viewpager is an android widget that is used to navigate from one page to another page by swiping left or right using the same activity. So when we use Viewpager, we can add different layouts in one activity and this can be done by using the fragments. Famous applications like WhatsApp, Snapchat uses Viewpager.
Had same problem, and that's what i found:
FragmentStateAdapter
has method onBindViewHolder(holder, position, payloads)
, which you can override (unlike onBindViewHolder(holder, position)
which is final). This method calls when you call notify
methods, and for notifyItemChanged()
, notifyItemRangeChanged()
methods you can access the fragment, and manually update it.
Here is code to access the fragment:
public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) {
String tag = "f" + holder.getItemId();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
//manual update fragment
} else {
// fragment might be null, if it`s call of notifyDatasetChanged()
// which is updates whole list, not specific fragment
super.onBindViewHolder(holder, position, payloads);
}
}
FragmentStateAdapter
is tagged his fragments by "f" + holder.getItemId()
, that's why it works.
Also you can use DiffUtil
, and this is better way. Here is good example of solving this problem, and some links in the end for better understanding of ViewPager2
.
you can override this two method
override fun getItemId(position: Int): Long {
// generate new id
return getItem(position).hashCode().toLong()
}
override fun containsItem(itemId: Long): Boolean {
// false if item is changed
return dataList.find { it.hashCode().toLong() == itemId } != null
}
I faced the same issue today and thought I would share my solution here. Instead of intercepting onBindViewHolder()
, my solution simply replaces the old fragment:
OldFragment.instance.containerId?.let {
replace(it, NewFragment.instance)
}
To be more concrete:
My App has a ViewPager2 with two fragments. Let's call them MainFragment
and DetailFragment
. On a button click in the MainFragment
, I wanted to replace it with a SecondMainFragment
. And the same behaviour vice versa: Button click in SecondMainFragment
would replace itself with the MainFragment
.
class CustomFragmentStateAdapter(
private val fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity) {
var displaySecondMainFragment: Boolean = false
set(value) {
if (field != value) {
switchFragments(replaceWithSecondMainFragment = value)
}
field = value
}
private fun switchFragments(replaceWithSecondMainFragment: Boolean) {
fragmentActivity.supportFragmentManager.commit {
if (replaceWithSecondMainFragment) {
MainFragment.instance.containerId?.let {
replace(it, SecondMainFragment.instance)
}
} else {
SecondMainFragment.instance.containerId?.let {
replace(it, MainFragment.instance)
}
}
}
}
// Standard adapter overrides
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = when (position) {
0 -> if (displaySecondMainFragment) SecondMainFragment.instance else MainFragment.instance
else -> DetailFragment.instance
}
}
With this extension property to get the id of the container the fragment is in:
inline val Fragment.containerId: Int?
get() = (view?.parent as? ViewGroup?)?.id
In the end I would only need to do something like that:
button.setOnClickListener {
customFragmentStateAdapter.displaySecondMainFragment = true // Or false for the other way
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With