Skip to content

Instantly share code, notes, and snippets.

@nseidm1
Last active April 12, 2017 15:29
Show Gist options
  • Save nseidm1/b711ab6d3919fc385f7e819a9a3ba8b6 to your computer and use it in GitHub Desktop.
Save nseidm1/b711ab6d3919fc385f7e819a9a3ba8b6 to your computer and use it in GitHub Desktop.
Double save state fix for FragmentStatePagerAdapter
package com.huffingtonpost.android.base.widget;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import com.fuzz.android.common.GlobalContext;
import com.fuzz.android.util.FZLog;
import com.google.common.io.Files;
import com.huffingtonpost.android.utils.AsyncUtils;
import com.huffingtonpost.android.utils.SafeRunnable;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
public abstract class FragmentStatePagerAdapter2 extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private final FragmentManager mFragmentManager;
private LinkedList<FragmentTransaction> currentTransactions = new LinkedList<>();
private long[] mItemIds = new long[]{};
protected ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
private ArrayList<Fragment> mFragments = new ArrayList<>();
private Fragment mCurrentPrimaryItem = null;
public FragmentStatePagerAdapter2(FragmentManager fm) {
mFragmentManager = fm;
mItemIds = new long[getCount()];
for (int i = 0; i < mItemIds.length; i++) {
mItemIds[i] = getItemId(i);
}
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
/**
* Return a unique identifier for the item at the given position.
*/
public abstract long getItemId(int position);
public Fragment getFragmentAtPosition(int position) {
if (position < 0 || position >= mFragments.size()) {
return null;
}
return mFragments.get(position);
}
@Override
public void notifyDataSetChanged() {
long[] newItemIds = new long[getCount()];
for (int i = 0; i < newItemIds.length; i++) {
newItemIds[i] = getItemId(i);
}
FZLog.d(FragmentStatePagerAdapter2.class.getSimpleName(), "newItemIds: " + Arrays.toString(newItemIds));
FZLog.d(FragmentStatePagerAdapter2.class.getSimpleName(), "mItemIds: " + Arrays.toString(mItemIds));
boolean arraysEqual = Arrays.equals(mItemIds, newItemIds);
FZLog.d(FragmentStatePagerAdapter2.class.getSimpleName(), "Arrays are equal: " + arraysEqual);
if (!arraysEqual) {
ArrayList<Fragment.SavedState> newSavedState = new ArrayList<>();
ArrayList<Fragment> newFragments = new ArrayList<>();
for (int newPosition = 0; newPosition < newItemIds.length; newPosition++) {
for (int i = 0; i < mItemIds.length; i++) {
if (mItemIds.length > i && mFragments.size() > i && mSavedState.size() > i && mItemIds[i] == newItemIds[newPosition]) {
newFragments.add(mFragments.get(i));
newSavedState.add(mSavedState.get(i));
break;
}
}
}
mItemIds = newItemIds;
mSavedState = newSavedState;
mFragments = newFragments;
}
super.notifyDataSetChanged();
}
@Override
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
currentTransactions.add(fragmentTransaction);
Fragment fragment = getItem(position);
FZLog.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
fragmentTransaction.add(container.getId(), fragment, generateTag(position));
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
currentTransactions.add(fragmentTransaction);
FZLog.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView());
if (position >= 0) {
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
try {
Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment);
mSavedState.set(position, savedState);
} catch (Exception e) {
FZLog.throwable(FragmentStatePagerAdapter2.class.getSimpleName(), e);
}
mFragments.set(position, null);
}
fragmentTransaction.remove(fragment);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
if (fragment.getFragmentManager() == null) {
try {
// this is a hack to not cause a crash if fragment manager does not exist.
// however this may be fixed already if fragments getItemPosition and getItemId are set properly.
Field[] fields = Fragment.class.getDeclaredFields();
for (Field field : fields) {
if ("mFragmentManager".equalsIgnoreCase(field.getName())) {
field.setAccessible(true);
field.set(fragment, mFragmentManager);
break;
}
}
} catch (IllegalAccessException e) {
FZLog.throwable(FragmentStatePagerAdapter2.class.getSimpleName(), e);
}
}
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
for (FragmentTransaction fragmentTransaction : currentTransactions) {
fragmentTransaction.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
currentTransactions.clear();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
@Override
public Parcelable saveState() {
Bundle state = new Bundle();
if (mItemIds.length > 0) {
state.putLongArray("itemids", mItemIds);
}
AsyncUtils.poolExecutor.execute(new SafeRunnable() {
@Override
public void safeRun() throws Throwable {
Parcel parcel = Parcel.obtain();
parcel.writeList(mSavedState);
byte[] savedStateBytes = parcel.marshall();
File cache = new File(GlobalContext.getContext().getCacheDir(), "saved_states");
cache.mkdir();
Files.write(savedStateBytes, cache);
}
});
for (int i = 0; i < mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
mItemIds = bundle.getLongArray("itemids");
if (mItemIds == null) {
mItemIds = new long[]{};
}
AsyncUtils.poolExecutor.execute(new SafeRunnable() {
@Override
public void safeRun() throws Throwable {
File cache = new File(GlobalContext.getContext().getCacheDir(), "saved_states");
byte[] savedStateBytes = Files.toByteArray(cache);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(savedStateBytes, 0, savedStateBytes.length);
parcel.setDataPosition(0);
mSavedState = parcel.readArrayList(Fragment.SavedState.class.getClassLoader());
if (mSavedState == null) {
mSavedState = new ArrayList<>();
}
File[] files = cache.listFiles();
if (files != null) {
for (File file : cache.listFiles()) {
file.delete();
}
}
}
});
Iterable<String> keys = bundle.keySet();
for (String key : keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
mFragments.set(index, f);
f.setMenuVisibility(false);
} else {
FZLog.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
public static String generateTag(int position) {
return "huffington_post_fragment" + position;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment