can we call twice setArgument() on the Fragment? - android

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

Related

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

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).

Return to back-stacked fragment

I have fragments A, B, C which I add with add() method.
When I reach fragment C, at some point I want to go back to fragment A and remove B and C.
My approach:
val backStateName = FragmentA::class.java.name
activity.fragmentManager.popBackStackImmediate(backStateName, FragmentManager.POP_BACK_STACK_INCLUSIVE)
I also have a specialTag added to my fragment A, so I did a check to make sure, that before I try my approach, fragment A is still in back stack.
val fragmentToGoTo = activity.fragmentManager.findFragmentByTag(specialTag)
and it doesn't return null - which means fragment is still available in back stack. popBackStackImmediate returns false. Why?
I had the same behaviour. Make sure that you call popBackStackImmediate on the same Thread as you used to add it to your backstack.
Also verify that you use .add() instead of .replace()
Anyway, it's never guaranteed that the backstack is not cleared/destroyed while doing this. I solved this behaviour by just using popBackStack() until you reach the fragment which you want to have.
You may try something like:
fun popStack(tag: String) {
var isPopped = fragmentManager.popBackStackImmediate(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE)
if (!isPopped) {
fragmentManager.popBackStack()
//maybe a loop until you reached your goal.
}
}
When you attach a fragment (or perform any other action s.a. add/remove/detach etc.), you have an option to add it to the backstack with a name String:
FragmentA fragmentA = (FragmentA) fragmentManager.findFragmentByTag("A");
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (fragmentA != null) {
transaction.attach(fragmentA);
transaction.addToBackStack("attachA");
transaction.commit();
}
Notice the "attachA" String we passed to the addToBackStack() method. We'll later use it to go back. Assume we've performed other transactions - added/removed/attached/detached some other fragments. Now to get back to the state we were call one of the popBackStack() methods:
fragmentManager.popBackStack("attachA", FragmentManager.POP_BACK_STACK_INCLUSIVE);
If there was a transaction added to the back stack with the name "attachA" - the method will take us back to that state.
Regarding your question about the return case - you've probably read the documentation about these methods and what values they return. I prefer to use the popBackStack() since it
/**
* Pop the last fragment transition from the manager's fragment
* back stack. If there is nothing to pop, false is returned.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* #param name If non-null, this is the name of a previous back state
* to look for; if found, all states up to that state will be popped. The
* {#link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped. If null, only the top state is popped.
* #param flags Either 0 or {#link #POP_BACK_STACK_INCLUSIVE}.
*/
public abstract void popBackStack(String name, int flags);
/**
* Like {#link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {#link #executePendingTransactions()}
* afterwards.
* #return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(String name, int flags);

onCreateView() for Fragment being called before Activity onRestoreInstanceState()

So I have a fragment that is attached to an activity and I'm trying to make sure things go smoothly when the screen is rotated (or anything that would interrupt the activity). In order to do this, I use the methods onSaveInstanceState and onRestoreInstanceState in my activity to keep the information that my activity stores.
When the view for my fragment is created, the fragment asks the Activity for information (This is in onCreateView() for the fragment):
ArrayList<String> picList = mListener.getPics();
ArrayList<String> descripList = mListener.getDescriptions();
In order for the fragment to create the view, it needs access to picList and descripList, which are member variables for the activity. These member variables are stored and restored in onSaveInstanceState and onRestoreInstanceState.
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(photoFile != null)
outState.putString("photoFile", photoFile.getAbsolutePath());
outState.putString("currentFragTag", currentFragTag);
outState.putStringArrayList("picList", picList);
outState.putStringArrayList("descripList", descripList);
}
#Override
protected void onRestoreInstanceState(Bundle saved) {
super.onRestoreInstanceState(saved);
if(saved.getString("photoFile") != null)
photoFile = new File(saved.getString("photoFile"));
currentFragTag = saved.getString("currentFragTag");
picList = saved.getStringArrayList("picList");
descripList = saved.getStringArrayList("descripList");
currentFrag = getFragmentManager().findFragmentByTag(currentFragTag);
changeFrag(currentFrag, currentFragTag);
}
The problem is, onCreateView() is being called before onRestoreInstanceState() is being called in the activity. I tried using onActivityCreated() in the fragment instead, but that was also being called before onRestoreInstanceState(). With a debugger attached, when the screen is rotated, onRestoreInstanceState() is always called last. This means that the fragment does not have access to the activity's information when creating the view.
Is this supposed to happen? How can I have my fragment's view use information from the activity when the activity is being restored?
I think the most easiest way is using EventBus.
You can send a "msg" when your activity is recreated, and your fragment's "target method" will get this msg(the msg is Object, it can be a bundle).
Updated response:
Read the alternatives Passing data between a fragment and its container activity. Also see this.
Previous response revised:
See this and try to place your code in onResume() and invalidate the view or detach/attach the fragment as a quick solution but is not the best solution as Alex Lockwood said:
Fragments are re-usable UI components. They have their own lifecycle,
display their own view, and define their own behavior. You usually
don't need to have your Activity mess around with the internal
workings of a Fragment, as the Fragment's behavior should be
self-contained and independent of any particular Activity.
If you really need the code before, override the next methods and directly save/restore the required data in the fragment:
/**
* Called when the fragment's activity has been created and this
* fragment's view hierarchy instantiated. It can be used to do final
* initialization once these pieces are in place, such as retrieving
* views or restoring state. It is also useful for fragments that use
* {#link #setRetainInstance(boolean)} to retain their instance,
* as this callback tells the fragment when it is fully associated with
* the new activity instance. This is called after {#link #onCreateView}
* and before {#link #onViewStateRestored(Bundle)}.
*
* #param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
}
}
/**
* Called to ask the fragment to save its current dynamic state, so it
* can later be reconstructed in a new instance of its process is
* restarted. If a new instance of the fragment later needs to be
* created, the data you place in the Bundle here will be available
* in the Bundle given to {#link #onCreate(Bundle)},
* {#link #onCreateView(LayoutInflater, ViewGroup, Bundle)}, and
* {#link #onActivityCreated(Bundle)}.
*
* <p>This corresponds to {#link Activity#onSaveInstanceState(Bundle)
* Activity.onSaveInstanceState(Bundle)} and most of the discussion there
* applies here as well. Note however: <em>this method may be called
* at any time before {#link #onDestroy()}</em>. 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.
*
* #param outState Bundle in which to place your saved state.
*/
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.put...;
}
And create this one, used to retrieve the needed data from bundle:
public void restoreInstanceState(Bundle savedInstanceState) {
... = savedInstanceState.get...
}
or use getActivity() method to directly access to some method or field from here if you need the code on your activity for some reason.
/**
* Return the {#link FragmentActivity} this fragment is currently associated with.
* May return {#code null} if the fragment is associated with a {#link Context}
* instead.
*/
final public FragmentActivity getActivity() {
return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}
For example: ((YourActivity) getActivity()).getPics();
And add the getPics() method to the activity.
Further information here and an alternative solution defining an interface here.

ViewPager + Fragment + saveInstanceState

I have a simple Activity containing a ViewPager, which displays Fragments.
My Activity should display information about a football league, and each fragment displays information like livescroes/matchdays, tables, etc.
The Intent with which I start the Activity, contains the league id.
And each Fragment needs this league id to load the correct data.
So my FragmentPagerAdapter looks like this
public class LeaguePagerAdapter extends FragmentPagerAdapter {
private String leagueId;
public LeaguePagerAdapter(FragmentManager fm, String leagueId) {
super(fm);
this.leagueId = leagueId;
}
#Override
public Fragment getItem(int pos) {
if (pos == 0){
return TableFragment.newInstance(leagueId);
} else {
return MatchdayFragment.newInstance(leagueId);
}
}
}
The TableFragment looks like this ( the matchday fragment looks similar):
public class TableFragment extends PullToRefreshListViewAdFragment {
private String leagueId;
public static TableFragment newInstance(String leagueId) {
TableFragment t = new TableFragment();
t.leagueId = leagueId;
return t;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Setup UI and load data
}
}
Sometimes the leagueId is null. I see the exceptions in the crash logs (crittercism). But Im asking my self why. It seems to me, that the problem is when the activity has been destroyed in the background and reconstructed if (for instance) the user uses the multitasking button to switch to my app.
So as far as I know, the original Intent will be stored internally by Android itself if the Activity has been destoryed. Therefore I have not implemented any onSaveInstanceState() in my activity nor in the fragment. In my activity I read the Intent Extra to retrieve the leagueId. This works fine, also on restoring the activity. I have assumed that by recreating the activity, a new LeaguePagerAdapter will be created and all fragments will also be new created.
Is that correct? Or does the "old" fragment instance will be restored and hence the leagueId is null (because the fragment has not stored the leagueId in Fragments onSaveInstanceState method?).
Is there a way to test such lifecycle things
The reason it is null is because the system restores the Fragment with the default constructor. Here's what the documents say:
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
edit: also, take a look at this: Fragment's onSaveInstanceState() is never called
edit: To further add on, you are creating your Fragment with your newInstance(String) method. If your Fragment is killed by Android, it uses the default constructor and so your leagueId variable won't be set. Try using setArguments/getArguments to pass the value into your Fragment instead.

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