Nikola Srdoč
Nikola Srdoč

Reputation: 321

How to switch List fragment and Detail fragment in view pager tab?

Im trying to implement switching of fragments inside first tab of view pager.I tried with approach found here link which uses root fragment in first tab and then switches child fragments. But when in second child fragment screen rotation occurs app switches back to first child fragment and besides that it seems like a hack.

I tried searching for other approaches but found dozens of answers that all seems messy. Explanation from here seems right but i dont fully understand it.Here is the explanation and code from that link:

This approach is to switch the fragment that displays inside the FragmentPagerAdapter by overriding the getItem(...) method as well as getItemPosition(Object object) which is invoked every time you call viewPager.getAdapter().notifyDataSetChanged(). For example:

public static class MyPagerAdapter extends FragmentStatePagerAdapter {
// Return a different fragment for position based on additional state tracked in a member variable
@Override
public Fragment getItem(int position) {
    // For a given position, return two different potential fragments based on a condition
}

// Force a refresh of the page when a different fragment is displayed
@Override
public int getItemPosition(Object object) {
    // this method will be called for every fragment in the ViewPager
    if (object instanceof SomePermanantCachedFragment) {
        return POSITION_UNCHANGED; // don't force a reload
    } else {
        // POSITION_NONE means something like: this fragment is no longer valid
        // triggering the ViewPager to re-build the instance of this fragment.
        return POSITION_NONE;
    }
}
}

Lets say i have ListFragment and DetailFragment that should switch in first tab.

  1. Where will i call viewPager.getAdapter().notifyDataSetChanged() ?
  2. (...based on additional state tracked in a member variable) how do i implement this?
  3. SomePermanantCachedFragment refers to ListFragment in my case?

I also tried searching github for example but with no success...

Upvotes: 0

Views: 66

Answers (1)

EpicPandaForce
EpicPandaForce

Reputation: 81539

If your fragments in a PagerAdapter are dynamic, then you should use a Dynamic FragmentPagerAdapter.

public class DynamicFragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "DynamicFragmentPagerAdapter";
  
    private final FragmentManager fragmentManager;
  
    public static abstract class FragmentIdentifier implements Parcelable {
        private final String fragmentTag;
        private final Bundle args;
      
        public FragmentIdentifier(@NonNull String fragmentTag, @Nullable Bundle args) {
            this.fragmentTag = fragmentTag;
            this.args = args;
        }
      
        protected FragmentIdentifier(Parcel in) {
            fragmentTag = in.readString();
            args = in.readBundle(getClass().getClassLoader());
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(fragmentTag);
            dest.writeBundle(args);
        }
      
        protected final Fragment newFragment() {
            Fragment fragment = createFragment();
            Bundle oldArgs = fragment.getArguments();
            Bundle newArgs = new Bundle();
            if(oldArgs != null) {
                newArgs.putAll(oldArgs);
            }
            if(args != null) {
                newArgs.putAll(args);
            }
            fragment.setArguments(newArgs);
            return fragment;
        }

        protected abstract Fragment createFragment();
    }
  
    private ArrayList<FragmentIdentifier> fragmentIdentifiers = new ArrayList<>();

    private FragmentTransaction currentTransaction = null;

    private Fragment currentPrimaryItem = null;

    public DynamicFragmentPagerAdapter(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
    }

    private int findIndexIfAdded(FragmentIdentifier fragmentIdentifier) {
        for (int i = 0, size = fragmentIdentifiers.size(); i < size; i++) {
            FragmentIdentifier identifier = fragmentIdentifiers.get(i);
            if (identifier.fragmentTag.equals(fragmentIdentifier.fragmentTag)) {
                return i;
            }
        }
        return -1;
    }

    public void addFragment(FragmentIdentifier fragmentIdentifier) {
        if (findIndexIfAdded(fragmentIdentifier) < 0) {
            fragmentIdentifiers.add(fragmentIdentifier);
            notifyDataSetChanged();
        }
    }

    public void removeFragment(FragmentIdentifier fragmentIdentifier) {
        int index = findIndexIfAdded(fragmentIdentifier);
        if (index >= 0) {
            fragmentIdentifiers.remove(index);
            notifyDataSetChanged();
        }
    }

    @Override
    public int getCount() {
        return fragmentIdentifiers.size();
    }

    @Override
    public void startUpdate(@NonNull ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this
                    + " requires a view id");
        }
    }

    @SuppressWarnings("ReferenceEquality")
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (currentTransaction == null) {
            currentTransaction = fragmentManager.beginTransaction();
        }
        final FragmentIdentifier fragmentIdentifier = fragmentIdentifiers.get(position);
        // Do we already have this fragment?
        final String name = fragmentIdentifier.fragmentTag;
        Fragment fragment = fragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            currentTransaction.attach(fragment);
        } else {
            fragment = fragmentIdentifier.newFragment();
            currentTransaction.add(container.getId(), fragment, fragmentIdentifier.fragmentTag);
        }
        if (fragment != currentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }
        return fragment;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        if (currentTransaction == null) {
            currentTransaction = fragmentManager.beginTransaction();
        }
        currentTransaction.detach((Fragment) object);
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != currentPrimaryItem) {
            if (currentPrimaryItem != null) {
                currentPrimaryItem.setMenuVisibility(false);
                currentPrimaryItem.setUserVisibleHint(false);
            }
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
            currentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(@NonNull ViewGroup container) {
        if (currentTransaction != null) {
            currentTransaction.commitNowAllowingStateLoss();
            currentTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList("fragmentIdentifiers", fragmentIdentifiers);
        return bundle;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        Bundle bundle = ((Bundle)state);
        bundle.setClassLoader(loader);
        fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
    }
}

Once you have that, you can use removeFragment and addFragment to change the fragments inside your ViewPager.

You could technically add a setFragment(int index) method to replace it at index 0 specifically.

If the fragments don't seem to update, the trick to refreshing a ViewPager is always setAdapter(null); setAdapter(adapter);.

Upvotes: 1

Related Questions