java.lang.IllegalStateException: Fragment already added and state has been saved - android

I have a MainActivity and 4 fragments on it.
One of them is called ReportFragment and when the user reaches the last fragment (FinalFragment), it returns to the ReportFragment which is set as active by the fragmentManager.
Though, it is throwing an java.lang.IllegalStateException: Fragment already added and state has been saved when I put application on background and it returns to the ReportFragment.
It happens when I set arguments to the existing Fragment (ReportFragment).
Bundle arguments = newFragment.getArguments();
if (arguments == null) {
arguments = new Bundle();
}
arguments.putInt("CONTAINER", containerId);
newFragment.setArguments(arguments);
Why it does not happen when app is on foreground?

You cannot call setArguments() twice on a Fragment as written in it's java docs:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
Instead you can do the following to prevent the Exception:
if (newFragment.getArguments() == null) {
Bundle arguments = new Bundle();
arguments.putInt("CONTAINER", containerId);
} else {
newFragment.getArguments().putInt("CONTAINER", containerId);
}
In order to tell you why this happens when your app goes in background, it's is important to know when you call this peace of code. I assume you call it in a static newInstance method where you reference a static reference of your Fragment (newFragment).

Related

can we call twice setArgument() on the Fragment?

I hold two Fragment instance in the activity ,add first fragment to activity , then replace second fragment to activity with setArgument() and addBackStack(), then press back button. now we return the first fragment , then we replace first to the second fragment which activity has hold once again , as the same with setArgument(), and it throws out a Exception ---- Fragment already active .
what's wrong with this process?
As per setArguments() source documentation, arguments supplied will be retained across fragment destroy and creation. So use getArguments() and then put bundle values to change the fields.
You can call it more than once or twice IF a Fragment is not attached to any Activity.
The code below is copied from Fragment.java
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
You can call the method as long as you want IF not attached to the activity

Is the bundle in fragment getArgs() the same object that is passed through setArgs()?

If I set arguments of a fragment myfragment to a Bundle mybundle, am I guaranteed that if I change the contents of mybundle later down the road, myfragment's call to getArguments() will be consistent with the contents of mybundle?
i.e.
mybundle.putString("background", "red");
myfragment.setArguments(mybundle);
... later ...
mybundle.putString("background", "orange");
myfragment.createLayoutFromBundle(myfragment.getArguments());
Yes. Check the source code for the Fragment class. The bundle is not copied or anything, just returned as-is.
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
/**
* Return the arguments supplied when the fragment was instantiated,
* if any.
*/
final public Bundle getArguments() {
return mArguments;
}
The arguments Bundle is part of the fragment's saved instance state. If the fragment is destroyed and re-created, your newly-created fragment will have an arguments Bundle with the same contents as did the original Bundle. However, the Bundle object may be different.
This will most easily seen when:
the user is in your fragment, with the Bundle
the user presses HOME
your process is terminated (you can test this via DDMS)
the user returns to your fragment via the recent-tasks list
You will have the same data in the Bundle as before, but the Bundle object will be newly-created.

Android Fragment - Using activity's loadermanager instead of Fragment's. Is it Ok?

Given a fragment which loads (a lot of) data from the database using a loader.
Problem :
I have a pager adapter which destroys the fragment when the user moves away from the tab holding it and recreates it when user gets back to that tab. Because of this recreation, a new loader is created everytime and the data gets loaded everytime.
Question :
To avoid recreating loader everytime the fragment is created, is it ok to use getActivity.getSupportLoaderManager.initLoader(loaderId, null, false) in the onActivityCreated method of the fragment?
I have tried it, tested it and it seems to be working fine. But I'm not convinced that it is right.
Actually, checking the source code, you end up doing the same.
Fragment.getLoaderManager:
/**
* Return the LoaderManager for this fragment, creating it if needed.
*/
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
}
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mCheckedForLoaderManager = true;
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
return mLoaderManager;
}
mWho is basically the fragment ID.
final void setIndex(int index, Fragment parent) {
mIndex = index;
if (parent != null) {
mWho = parent.mWho + ":" + mIndex;
} else {
mWho = "android:fragment:" + mIndex;
}
}
The difference in Activity.getLoaderManager() is that who will be (root)
So even though you can do what you are asking, calling it directly from the Fragment might be a better approach
Activity source code
Fragment source code
Disclaimer: I only checked the source code in the latest version, but I don't expect it to be very different
May i ask why you are simply not retaining the Fragment? It seems that what you need is to create the Loader in the Fragment and create the fragment with setRetainInstance(true).
In this case remember to provide a TAG when you add the fragment.
This way the fragment will survive even to activity config changes and only the view will be recreated leaving your loader alive.

How to persist fragment data after backstack transactions?

I've got an activity, containing fragment 'list', which upon clicking on one of its items will replace itself to a 'content' fragment. When the user uses the back button, he's brought to the 'list' fragment again.
The problem is that the fragment is in its default state, no matter what I try to persist data.
Facts:
both fragments are created through public static TheFragment newInstance(Bundle args), setArguments(args) and Bundle args = getArguments()
both fragments are on the same level, which is directly inside a FrameLayout from the parent activity (that is, not nested fragments)
I do not want to call setRetainInstance, because my activity is a master/detail flow, which has a 2 pane layout on larger screens. 7" tablets have 1 pane in portrait and 2 panes in landscape. If I retain the 'list' fragment instance, it will (I think) fuck things up with screen rotations
when the users clicks an item in the 'list' fragment, the 'content' fragment is displayed through FragmentTransaction#replace(int, Fragment, String), with the same ID but a different tag
I did override onSaveInstanceState(Bundle), but this is not always called by the framework, as per the doc: "There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state."
I'm using the support library
From the bullet 5 above, I guess that low-end devices that need to recover memory after a fragment transaction may call Fragment#onSaveInstanceState(Bundle). However, on my testing devices (Galaxy Nexus and Nexus 7), the framework doesn't call that method. So that's not a valid option.
So, how can I retain some fragment data? the bundle passed to Fragment#onCreate, Fragment#onActivityCreated, etc. is always null.
Hence, I can't make a difference from a brand new fragment launch to a back stack restore.
Note: possible related/duplicate question
This doesn't seem right, but here's how I ended up doing:
public class MyActivity extends FragmentActivity {
private Bundle mMainFragmentArgs;
public void saveMainFragmentState(Bundle args) {
mMainFragmentArgs = args;
}
public Bundle getSavedMainFragmentState() {
return mMainFragmentArgs;
}
// ...
}
And in the main fragment:
public class MainFragment extends Fragment {
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = ((MyActivity) getActivity()).getSavedMainFragmentState();
if (args != null) {
// Restore from backstack
} else if (savedInstanceState != null) {
// Restore from saved instance state
} else {
// Create from fragment arguments
args = getArguments();
}
// ...
}
// ...
#Override
public void onDestroyView() {
super.onDestroyView();
Bundle args = new Bundle();
saveInstance(args);
((MyActivity) getActivity()).saveMainFragmentState(args);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveInstance(outState);
}
private void saveInstance(Bundle data) {
// put data into bundle
}
}
It works!
if back from backstack, the fragment uses the parameters saved in onDestroyView
if back from another app/process/out of memory, the fragment is restored from the onSaveInstanceState
if created for the first time, the fragment uses the parameters set in setArguments
All events are covered, and the freshest information is always kept.
It's actually more complicated, it's interface-based, the listener is un/registered from onAttach/onDetach. But the principles are the same.

Got exception: fragment already active

I have a fragment;
MyFragment myFrag = new MyFragment();
I put bundle data to this fragment:
Bundle bundle = new Bundle();
bundle.putString("TEST", "test");
myFrag.setArguments(bundle);
Then, I replace old fragment with this one and put on backstack:
//replace old fragment
fragmentTransaction.replace(R.id.fragment_placeholder, myFrag, "MyTag");
//put on backstack
fragmentTransaction.addToBackStack(null);
//commit & get transaction ID
int transId = fragmentTransaction.commit();
Later, I pop backstack with the above transaction ID(transId):
//pop the transaction from backstack
fragmentManager.popBackStack(transId,FragmentManager.POP_BACK_STACK_INCLUSIVE);
Later, I set bundle data as argument again to my fragment(myFrag):
//Got Java.lang.IllegalStateException: fragment already active
myFrag.setArguments(bundle);
As you see, my above code got exception Java.lang.IllegalStateException: fragment already active . I don't understand why myFrag is still active though I have popped the transaction of it from backstack., anyhow, since I got the exception I thought I have no choice but de-active the fragment, So, I did:
Fragment activeFragment = fragMgr.findFragmentByTag("MyTag");
fragmentTransaction.remove(activeFragment);
I am not sure if my above code really can de-active the fragment, since I didn't find how to de-active an fragment. :(
After that, when I try to set bundle data to my fragment myFrag again, I still got the same error:
Java.lang.IllegalStateException: fragment already active
Seems even I removed the fragment, it is still active...Why? How to de-active a fragment?
Reading the setArguments(Bundle args) source will help you understand:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
You cannot use setArguments(Bundle args) again in your code on the same Fragment. What you want to do I guess is either create a new Fragment and set the arguments again. Or you can use getArguments() and then use the put method of the bundle to change its values.
Try removing the previous fragment before adding the new one: https://stackoverflow.com/a/6266144/969325
remove() change fragment status to de-actiive. In your case, you just didn't call commit() after remove(..).
fragmentTransaction.remove(activeFragment);
You would do commit() after remove(), too.
fragmentTransaction.remove(activeFragment).commit();
Had the same issue. I was adding the fragment to backstack. And the error was because I didn't call popbackstack(). Using popbackstack helped me
I'm running into the same issue on Xamarin.android. Here's what the documentation says.
This can only be called before the fragment has been attached to its activity
Just call public method from fragment
if(userFragment==null){
userFragment = new UserFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.EXTRA_CUSTOMER, result);
userFragment.setArguments(bundle);
}else{
try {
Customer customer = new Customer();
customer.parseCustomer(new JSONObject(result));
userFragment.updateVeiw(customer);
} catch (JSONException e) {
e.printStackTrace();
}
}
First I start with describing why this happens and then I'll come up with the solution I found working... .
This issue happens when Android is removing the fragment from the stack but is not yet finished with removing. In order to check this, you can use the isRemoving() method of the fragment. If false, i.e. the fragment is not active, you can go on with setting the arguments using setArguments(bundle). Otherwise, you can't set arguments to an already active fragment and can only override it by addressing the same arguments using getArguments().putAll(bundle).
To summarize,
if (myFrag.isRemoving()) {
myFrag.getArguments().putAll(bundle);
} else {
myFrag.setArguments(bundle);
}
If you want to avoid this, i.e. removing the fragment at once so there is no active fragment, you might want to use onBackPressed() in onBackStackChangedListener(), which will set the isRemoving() to false.
Check whether your layout current one or old one for example
setContentView(R.layout.activity_main);
Delete old .gradle file in your project file and rebuild gradle file for project.

Categories

Resources