Here's the scenario that causes problems:
I start an Acitivity with a ViewGroup that'll hold the presented fragments.
I load Fragment A into this ViewGroup by calling .replace() in the transaction that I save onto the backstack.
I load Fragment B into the Viewgroup, replacing Fragment A. Again, I save the transaction.
I rotate the device twice.
On the second rotation, Fragment A (which is not visible at the moment) will throw a NullPointer exception.
This exception is thrown by Fragment A, because I'm saving some values of Views (EditTexts e.g.) in its onSaveInstanceState() method. It seems, that on the second rotation, the system doesn't instantiate these Views, so when I'm asking their values, I get a NullPointer exception. Can this be avoided somehow? Or is using .replace operations in a fragment transcation saved onto the backstack unadvised?
I've had this but can't quite recollect the specifics of what I did to fix but generally speaking (and apologies for the brain dump) I do the following:
Call setRetainInstance(true) in onCreate to avoid fragment recreation
Save the state of edit controls etc. in onSaveInstanceState to be used if activity is killed and you get restored with a non-null bundle (you shouldn't get a non-null bundle on an orientation change with 1.)
Maintain edit control values in member variables (as the fragment is not going to be recreated) ensuring they get updated in an onDestroyView from the edit controls and then use them to restore the edit control values in onCreateView
Have a flag which I set to true in onCreateView and false in onDestroyView and ensure I don't touch UI controls when the view is not around.
BTW Using replace while adding the transaction to the back stack is perfectly OK.
Hope there's something in there that helps. Peter.
Related
Problem
onSaveInstanceState is being called on device rotation for a fragment which has been replaced in a fragment transaction. This is causing an IllegalStateException with the message "Content view not yet created.
Research
I have found the following two answers which seem most relevant
Using onSaveInstanceState with fragments in backstack?
onSaveInstanceState of previous fragment is called on-orientation of current fragment
What I've tried already
Following the answer to the second question I have removed addtobackstack from the fragment transaction but am still getting the error.
I read the accepted answer to the first question but am not sure how to get a fragment reference within onSaveInstanceState for the activity. I also don't particularly want to save that fragment, when the user leaves that fragment it's state does not need to be saved.
I have also tried adding if (getView() != null) {...} to the onSaveInstanceState in the fragment but this made no difference.
Questions
Why is onSaveInstanceState being called even though the fragment hasn't been added to the back stack?
Is the right approach to somehow kill the fragment when the user is done with it?
Is there a different solution?
Thank you in advance for your help.
Andrew
Edit
If I remove addtobackstack(null) from the transaction which adds the fragment and the one where it is removed the problem goes away, but then so does an important piece of functionality... I could add that case to my onBackPressed override function but it seems a bit of a hack, and not in a good way.
Adding an isVisible() check onSaveInstanceState of the fragment in the backstack should work.
No need to retain instance state when it is not visible.
CONSTRUCTION
Activity A holds Fragment A that is able add Fragment B on backstack.
PROBLEM
Fragment B holds Views generated via API response.
State of these Views is what I need to be able to recreate after rotation or when going back to Fragment A by using onBackPressed and lunching Fragment B again.
I've read quite some of the topics on SO about Fragments in backstack and I am aware of their inability to retain instance.
What should I do to achieve such outcome?
Fragments on backstack can always retain instance if you save it. An Activity or a Fragment on a backstack in simply in a paused state. So you want to save the data/variables in the onSaveInstanceState method for that class (you will be overriding it).
Now to restore from the saved state you would have noticed that the onCreate, onCreateView for Activity, Fragment, respectively, have a Bundle savedInstanceState parameter being passed in. This is where you saved your state in the previous step, thus, you can add
if (savedInstanceState != null) {
//TODO: restore the state
}
to your onCreate/onCreateView method and you should be good to go.
I am current working on project which is in used Fragment. But Here, When i call to activity class From Fragment it's run perfectly. What i have to do is that on Back Pressed i need to call a Fragment.But i can't , it shows me error and my app stops.
So my question here is that how can i call fragment from activity so that my sequence should be fragment>activity>fragment.
07-11 16:22:12.190: E/AndroidRuntime(11963): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
& when i want to call activity from fragment it's give error
07-11 15:52:25.961: E/FragmentManager(11885): No view found for id 0x7f05003c for fragment
So, how can i call fragment from activity & activity from fragment?
Try changing
transaction.commit();
to
transaction.commitAllowingStateLoss();
Or comment out the super onSaveInstance method on your activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}
To call activity from a fragment you can use:
((YourActivity)getActivity).someMethod();
java.lang.IllegalStateException:
Can not perform this action after onSaveInstanceState
Solution:
Use transaction.commitAllowingStateLoss(); when adding or performing
the FragmentTransaction that was causing the Exception.
Why was the exception thrown?
The exception was thrown because you attempted to commit a FragmentTransaction after the activity's state had been saved, resulting in a phenomenon known as Activity state loss.
When you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won't be remembered because it was never recorded as part of the Activity's state in the first place. From the user's point of view, the transaction will appear to be lost, resulting in accidental UI state loss. In order to protect the user experience, Android avoids state loss at all costs, and simply throws an IllegalStateException whenever it occurs.
NOTE:
Use commitAllowingStateLoss() only as a last resort. The only difference between calling commit() and commitAllowingStateLoss() is that the latter will not throw an exception if state loss occurs. Usually you don't want to use this method because it implies that there is a possibility that state loss could happen. The better solution, of course, is to write your application so that commit() is guaranteed to be called before the activity's state has been saved, as this will result in a better user experience. Unless the possibility of state loss can't be avoided, commitAllowingStateLoss() should not be used.
More from: fragment-transaction-commit-state-loss.
FragmentManager(11885): No view found for id 0x7f05003c for fragment
Solution:
The Fragment manager cannot able to find a view with R.id.Container according to what you set in a layout at setContentView of activity.
So whatever layout you have set in setContentView, that layout doesn't contain that view with that id that resolves to id 0x7f05003c say of R.id.Container.
I have Fragment which I replace by another one. I put transaction to backstack so I can move back later. If I press back button saveInstanceState Bundle of restored Fragment is null in it's methods cause saveInstanceState method of Fragment is actually called when parent Activity instance destroyed. So how I must restore Fragment state after returning it from backstack?
This problem was mostly related to ListViews. I have found a solution in more correct managing of adapter data. I had local mAdapter variable in my Fragment which was created, populated with data and set to ListView in onResume() method of my Fragment. I have found solution in moving this code into onActivityCreated() method.
I am having a pretty big issue and I am not quite understanding what is happening. I am developing an application that uses Fragments (from the support library) and am using FragmentTransaction.replace() to place new Fragments on to the back stack and replace the old one. The code looks as follows:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = ft.beginTransaction();
// Animations in my res/anim folder
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
This is successful in replacing my fragment. My issue is the following. In one Fragment, I have a list of items that is built from user input. Now, when the user clicks next and then clicks the back button (to return to the list), the list is empty because the view is destroyed. Now, I have noted the following:
onSaveInstanceState is not called. I believe this is because that is only called when the parent Activity tells it to. Based on the docs: " 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.". Apparently, performing a replace on the FragmentTransaction is not one of those times. Does anyone have confirmation on this or a better explanation?
setOnRetainInstanceState(true) is not helpful in this situation. Again, I believe this has to do with info from the docs: "Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)". I am not performing any action in re-creating the activity so this is of no use.
So, I guess my main question is: is there a way to preserve the View state (simply retain the Fragment) when using replace? There is FragmentTransaction.add(), but there are a few issues with this as well. One being that the exit animation is not performed, thus the animation is not correct. Another is that the new Fragment that the old fragment (the one that is being put into a non-visible state) is still clickable. For example, if I have a ListFragment, and I place a content fragment on top of that by using add, I can still click the list items in the ListFragment.
Without being able to see the code of your fragments this is a bit of a guess, but in the past I've run into this same issue and I've found that resetting the adapter in your ListFragment in onViewStateRestored seems to do the trick.
public void onViewStateRestored (Bundle savedInstanceState)
{
super.onViewStateRestored (savedInstanceState);
setListAdapter(new ArrayAdapter(Activity, R.layout.nav_item, objects));
}
Which is weird considering the documentation states that this method is called after onActivityCreated but before onStart. But it seems that it is also called at other times because when the most recent fragment transaction is popped off the back stack this method is called before the previously replaced fragment is displayed. The activity that owns the fragments has not been paused or obscured in any way, so according to the docs onViewStateRestored should not be called since just the fragments were modified. But this seems to work anyway.
It sounds like you simply need to make sure you have properly implemented onCreateView and onDestroyView. The situation you are describing seems to indicate that when the list fragment is put on the back stack (as a result of the replace transaction) Android is calling onDestroyView to free up some resources. However, it apparently has not destroyed the list fragment because when you tap back you are getting back the same instance of the fragment.
Assuming this is all true then, when the user taps back Android will call onCreateView. Any state that you have stored in the fragment's instance variables should still be there and all you need to do is repopulate the view...perhaps set the adapter on the ListView or whatever.
Also make sure your onSaveInstanceState() callback actually does save any instance state that you need to rebuild the view. That way if the fragment actually does get completely destroyed the FragmentManager can restore the state when it needs to recrete the fragment later.