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.
Related
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).
So I've got a main activity that hosts all of the fragments in my app. Let me just say beforehand that every time I open a new fragment, I do it like this:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(vg.getId(), new MyFragment()); //obviously MyFragment varies from usage to usage but nothing else
ft.addToBackStack("My Fragment's Name");
ft.commit();
MyFragment in this case extends androidx.fragment.app.Fragment, which only has a method getFragmentManager(). It does NOT have getSupportFragmentManager().
If I want to go back to a previous fragment, from the currently shown fragment I would do getFragmentManager().popBackStackImmediate(). However, if I wanted to pop the backstack from the activity, I have to use getSupportFragmentManager().popBackStackImmediate() or else nothing happens. This led me to assume that calling getFragmentManager() from a fragment returned the same reference as calling getSupportFragmentManager() from the activity. However, if I try to run getSupportFragmentManager().getBackStackEntryAt(/* an int */) from the main activity I get a NullPointerException. If I run getSupportFragmentManager().getBackStackEntryCount() from the main activity it always returns zero.
So why is it then that getSupportFragmentManager() in the main activity simultaneously works and doesn't work? Why can I use it to pop the backstack, yet I can't access the backstack itself from the main activity? I'm totally clueless. Help would be appreciated.
EDIT: This is my onBackPressed():
#Override
public void onBackPressed() {
getSupportFragmentManager().popBackStackImmediate();
getWindow().setSoftInputMode(defaultSoftInputMode);
try {
String fragmentName = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
Toast.makeText(getApplicationContext(), fragmentName, Toast.LENGTH_SHORT).show(); // Debug to see if the correct name is being shown
} catch (NullPointerException npe) {
// Print to console
// It always catches an error here and I don't know why
}
}
The specific method which throws the exception is getSupportFragmentManager().getBackStackEntryAt(int index). Further inspection shows that this method queries an ArrayList<BackStackRecord> for a value, but the exception occurs because this ArrayList is null.
My problem: When i close my app via long-pressing home button (Samsung S4) in the app manager, everything works fine on the next startup.
But, when i close the app in onBackPressed when its time to close it, i get a crash on the next start of the app, because "getActivty" returns null.
I checked multiple times which lifecycle methods get called during these two scenarios, and wasn't able to find any difference.
Also, i use the piece of code producing the crash in another fragment, but there it works fine.
The question is: Why returns getActivity null, when the Activity is definitely alive? Better said, why is my fragment not attached to the Activity anymore?
Relevant code (i hope)
init of main fragment:
//Declared globally in Activity
FirstFragment fr;
//In activities onCreate
fr = new FirstFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, fr, "first")
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
Switching to the second fragment is done with this method (Where the parameter "Object fragment" is "new SecondFragment")
public void changeFragment(Object fragment, String name, boolean stayMain) {
if (!stayMain) {
findViewById(R.id.title_bar_left_button).setBackgroundResource(R.drawable.ic_back);
findViewById(R.id.title_bar_left_button).setOnClickListener(backToMainListener);
}
((TextView) findViewById(R.id.navTitle)).setText(name);
findViewById(R.id.title_bar_right_button).setVisibility(View.GONE);
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, (Fragment)fragment, name)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
}
Code causing the crash: (since getActivity returns null in described scenario
Context c = this.getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(c);
Any help is appreciated
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
I wrote Android JUnit test for Activity that instantiates fragments (actually tabs). During the test, when I try to do anything with these tabs, they crash because getActivity() method in them returns null. The actual application (not a test) never shows this behavior and fragment getActivity() always returns the right parent activity there. My test case looks like:
public class SetupPanelTest extends ActivityUnitTestCase<MyAct> {
FSetup s;
public SetupPanelTest() {
super(MyAct.class);
}
protected void setUp() throws Exception {
super.setUp();
startActivity(new Intent(), null, null);
final MyAct act = getActivity();
AllTabs tabs = act.getTabs();
String tabname = act.getResources().getString(R.string.configuration);
// This method instantiates the activity as said below
s = (FSetup) tabs.showTab(tabname);
FragmentManager m = act.getFragmentManager();
// m.beginTransaction().attach(s).commit();
// ... and even this does not help when commented out
assertTrue(s instanceof FSetup); // Ok
assertEquals(act, s.getActivity()); // Failure
}
public void testOnPause() {
// this crashes because s.getActivity == null;
s.onPause();
}
}
The AllTabs creates a fragment, then required, in this way:
FragmentManager manager = getFragmentManager();
Fragment fragment = manager.findFragmentByTag(tabname);
if (fragment == null || fragment.getActivity() == null) {
Log.v(TAG, "Instantiating ");
fragment = new MyFragment();
manager.beginTransaction().replace(R.id.setup_tab, fragment, tabname).commit();
....
Here, all fragments are initially placeholders that are later replaced by the actual fragments:
<FrameLayout
android:id="#+id/setup_tab"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
The logcat shows that the new fragment has been instantiated. In the same layout, there is also the previously mentioned AllTabs fragment that seems not having this problem (where and how it gets FragmentManager otherwise):
<TabWidget
android:id="#android:id/alltabs"
...
Most impressively, when I call attach directly on the fragment manager obtained on the right activity, this still has no effect. I tried to put five seconds delay (I have read that transaction may be delayed), I tried to call the rest of the test through runOnUiThread - nothing helps.
The question is that is need to do so to attach my fragments to the activity also during the test. I have fragment and I have activity, I cannot attach one to another.
Even if you call .commit() on transaction, it is still not done, fragments are attached only lazily.
FragmentManager m = activity.getFragmentManager();
m.executePendingTransactions();
This finally attaches all fragments to the activity. Seems redundant when running the application itself but required in JUnit test case.