I'm working with the legacy code and I found an inconsistent behavior in this function:
@Override
public void openFragment(final Class<? extends BaseFragment> fragmentClass,
final boolean addToBackStack,
final Bundle args)
{
long delay = 0;
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
delay = getResources().getInteger(android.R.integer.config_shortAnimTime) * 2;
}
// FIXME: quick fix, but not all cases
final Bundle args666 = args != null ? (Bundle) args.clone() : null;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doOpenFragment(fragmentClass, addToBackStack, args666);
}
}, delay);
closeDrawer();
}
protected void doOpenFragment(final Class<? extends BaseFragment> fragmentClass,
final boolean addToBackStack,
final Bundle args)
{
try {
if (getSupportFragmentManager().getBackStackEntryCount() >= 1) {
showNavigationIcon();
}
hideKeyboard();
BaseFragment fragment = createFragment(fragmentClass, args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
fragment.initTransactionAnimation(transaction);
String tag = getTag(fragment);
transaction.add(R.id.container, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commitAllowingStateLoss();
hideLastFragment(0);
} catch (Exception e) {
Sentry.captureException(e, "Error opening fragment");
}
}
openFragment
gets non-empty Bundle args, but doOpenFragment
will get empty Bundle. Fragments are committed by calling commitAllowingStateLoss()
A quick fix can be to use Bundle.clone():
final Bundle args666 = (Bundle) args.clone();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doOpenFragment(fragmentClass, addToBackStack, args666);
}
}, delay);
It will not handle all cases and deepCopy
is available only in api26.
[UPDATE]
I played with @Pavel's solution and things get weirder
final Bundle args666 = args != null ? cloneThroughSerialization(args) : args;
final Bundle args777 = args != null ? (Bundle) args.clone() : args;
[UPDATE2]
Actually, the problem isn't with postDelayed
call. Let's see the call stack:
in goRightToTheCollectionScreen
the Bundle is created and packed (nothing suspicious, no mutation afterward).
I guess, the source of the problem in two calls inside openFragmentsChain
:
public void openRootFragmentsChain(Class<? extends BaseFragment> fragmentClass,
List<Class<? extends BaseFragment>> fragmentClasses,
boolean addToBackStack,
Bundle args)
{
openFragmentsChain(fragmentClasses, addToBackStack, args);
openFragment(fragmentClass, true, args);
}
public void openFragmentsChain(List<Class<? extends BaseFragment>> fragmentClasses,
boolean addToBackStack,
Bundle args)
{
try {
for (int i = 0; i < fragmentClasses.size(); i++) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
BaseFragment fragment = createFragment(fragmentClasses.get(i), args);
String tag = getTag(fragment);
transaction.add(R.id.container, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
if (i != fragmentClasses.size() - 1) {
transaction.hide(fragment);
}
transaction.commitAllowingStateLoss();
}
if (fragmentClasses.size() >= 1) {
updateDrawer();
}
} catch (Exception e) {
Sentry.captureException(e, "Error opening fragment chain");
}
}
protected void doOpenFragment(final Class<? extends BaseFragment> fragmentClass,
final boolean addToBackStack,
final Bundle args)
{
try {
if (getSupportFragmentManager().getBackStackEntryCount() >= 1) {
showNavigationIcon();
}
hideKeyboard();
BaseFragment fragment = createFragment(fragmentClass, args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
fragment.initTransactionAnimation(transaction);
String tag = getTag(fragment);
transaction.add(R.id.container, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commitAllowingStateLoss();
hideLastFragment(0);
} catch (Exception e) {
Sentry.captureException(e, "Error opening fragment");
}
}
protected BaseFragment createFragment(Class<? extends BaseFragment> fragmentClass, Bundle args) throws Exception {
BaseFragment fragment = fragmentClass.newInstance();
fragment.setHasOptionsMenu(true);
fragment.setArguments(args);
fragment.setNavigationHandler(BaseFragmentNavigatorActivity.this);
fragment.setToolbar(mToolbar);
fragment.setMenuLoadService(mMenuLoaderService);
return fragment;
}
You can deep clone through serialization.
public static Bundle cloneThroughSerialization(@NonNull Bundle bundle) {
Parcel parcel = Parcel.obtain();
bundle.writeToParcel(parcel, 0);
Bundle clonedBundle = new Bundle();
clonedBundle.readFromParcel(parcel);
parcel.recycle();
return clonedBundle;
}
The code you posted seems ok, so probably your bundle is modified elsewhere (even though your postDelayed delay is 0, the runnable will be executed slightly later and its possible you modify the bundle meanwhile). Try to execute it directly without postDelayed, to see if the problem still persists. You can post more of your code, maybe we can figure out where else you touch that bundle.
If nothing else helps, you can always copy the method from API26 to your code and use it (edge case - this seems a simple issue so you shouldn't have to)
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