Last active
April 26, 2024 09:32
-
-
Save Zhuinden/ef743346eda60a314d2a100eeaf069d5 to your computer and use it in GitHub Desktop.
Dynamic FragmentPagerAdapter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DynamicFragmentPagerAdapter extends PagerAdapter { | |
private static final String TAG = "DynamicFragmentPagerAdapter"; | |
private final FragmentManager fragmentManager; | |
public static abstract class FragmentIdentifier implements Parcelable { //should be concrete children with @Parcelize if possible, don't forget CREATOR field | |
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"); | |
notifyDataSetChanged(); | |
} | |
} |
Oh yeah, that makes sense. Surprising that I hadn't been notified of that, I somewhat figured that restoration would occur before trying to render, but alas. Added.
Works great otherwise!
Glad to hear! I made this specifically because ViewPager2 claims in its documentation that "dynamic fragment count" isn't possible with regular ViewPager, but that isn't true at all.
Reminder that you cannot use anonymous fragment identifiers because the fragment identifier requires a CREATOR
field for Parcelable to work, otherwise it wouldn't be able to recreate the createFragment()
lambdas.
So,
public static final Creator<FragmentIdentifier> CREATOR = new Creator<FragmentIdentifier>() {
@Override
public FragmentIdentifier createFromParcel(Parcel in) {
return new FragmentIdentifier(in); // <-- abstract
}
@Override
public FragmentIdentifier[] newArray(int size) {
return new FragmentIdentifier[size];
}
};
Using @Parcelize data class
for your concrete identifiers is preferred.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add
notifyDataSetChanged();
after line 162 in"fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
because it crashes with a
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 0, found: ...
exception on state restoreWorks great otherwise! (better than ViewPager2' new adapter :D)