Here's the issue I'm having. I've got an activity A that has a fragment F, which is contained in FragmentPagerAdapter FPA, which is in view V. (A->V->FPA->F)
When A gets destroyed (or in this case, swapped out), F is attached, and is in FPA, which is in V. However, when A gets recreated (someone hits the back button back into the activity, for instance), V and FPA don't exist, so F is recreated (in the attached state!), but to something that doesn't exist, so it's not in the view hierarchy at all. Then, when FPA tries to instantiateState on this fragment, it'll try to attach it, which does nothing because it's already attached to thin air.
There are obviously a few ways to fix this (have V and FPA exist in onCreate of the activity, so that the fragment has somewhere to go when it gets created, for instance), but I'd like to continue to lazily create FPA and V only when needed.
Thusly, it seems like updating the state of F to detached in onDestroy() would be desirable. However, state is saved in onPause(), which means I'm kinda out of luck here.
Is there a way to update the saved state of F in A's onDestroy()? Is there a way to say "don't rehydrate this fragment if the activity gets destroyed"? Is there some other obvious way of thinking about this that I'm not considering? It feels like I'm going about things the wrong way here.
I've had my trouble with FragmentPagers. What I do is passing a null bundle in the Activity onCreate() and then create everything from scratch every time it is created. Like so:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
// do my stuff
}
This way the fragment wont be passed on when the activity is recreated.
You wrote:
Is there some other obvious way of thinking about this that I'm not considering?
This doesn't directly answer your title question but provides a convenient solution to how to preserve Fragment state in a ViewPager.
You can save the Fragment states when the Activity is destroyed by tagging the Fragment in the Activity that initializes the Fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photos_pager_activity);
MyImageFragment fragment;
if (savedInstanceState != null) {
fragment = (MyImageFragment) getFragmentManager().findFragmentByTag("my_image_fragment_tag");
} else {
fragment = new MyImageFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "my_image_fragment_tag").commit();
}
See also:
Uses of fragment tags
ViewPager and fragments — what's the right way to store fragment's state?
Related
In my application I create a fragment with the keyword new and set it by FragmentTransaction.
Upon rotation a stumbled upon a NullPointerException in the method onActivityCreated() indicating a missing injection, that I do after the call to new. I suspected the fragment was not created by my code und proved this by logging the hashCode(). It looks like a fragment is created automatically by the system upon rotation.
Where does it come from?
Is it created by the fragment manager?
How am I supposed to use it correctly?
How can I access it, to set the missing value?
For now I ignore it by testing for the null value, in which case onActivityCreated() does nothing. Instead use the fragment I create with new. However, this does not feel very satisfying, to throw away an object, that was already created.
Where does it come from? Is it created by the fragment manager?
On Activity recreation, Android will restore the fragments which already exist in activity's fragments manager
How am I supposed to use it correctly?
public void onCreate(Bundle savedInstanceState){
if(savedInstanceState == null){
//activity is created for first time
//commit the fragment
}else{
//Activity is recreated(by means of rotation or something else)
//Dont commit the fragment, fragmet will be restored by the system
}
}
How can I access it, to set the missing value?
Normally, you have to handle this inside the fragment using onSaveInstanceState method. You can get the fragment instance by using, getSupportFragmentManager.findFragmentById(R.id.container) or getSupportFragmentManager.findFragmentByTag(tagName)
In Android, Activity and Fragment can be destroyed at any time (if, for example, user has navigated away from the Activity or another Activity has been called). I have some data in the Fragment and I want to save it, but I can not find a proper way to do it.
For example, in the Fragment, I have this code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != savedInstanceState)
initState(savedInstanceState);
else
initStage(getArguments());
}
private void initState(Bundle bundle) {
mData = bundle.getParcelable(ARG_DATA);
}
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(ARG_DATA, mData);
}
And in Activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_container);
if (findViewById(R.id.fragment_container) != null) {
Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_container);
if (null == fragment) {
MyData data = getIntent().getParcelableExtra(ARG_DATA);
fragment = MyFragment.newInstance(data);
fragment.setRetainInstance(true);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
}
}
}
But what will happen if hosting Activity and it's Fragment will be destroyed and created again? Will it recreate the Fragment and try to initialize it with old arguments? How to avoid that, do I have to transfer the data back to the Activity every time, then it will be destroyed? What is the point of having the Fragment, then?
Thanks
Update.
To explain the problem, I have a Service working to keep core data in it. I only need Activity to present the data, for example to create a new record in db. But what I have found out initially, is what if Activity gets destroyed and recreated, it will also recreate the underlying Fragment and will initialize it with data, sitting in the Activity arguments bundle. And all user changes will be lost. What I want is that after Fragment gets created, it will store the data, submitted by user until user will hit "submit" button and exit from the Activity. So, even if user navigates away from the activity and then come back, he would not lost the data he already entered. The problem is, I can not update the record straight after user entered something. It is complicated system, the record not only go to the local db, the service will also send it to the server and I want to minimise traffic going between phone and the server.
I answered a similar question on SO # Fragment calling fragments loosing state on screen rotation. Search for my user name "The Original Android".
There is a difference in the sample code, compared to your code, that is the main Activity has the saved data or states. The reason is the Fragment data may be destroyed, gone from memory. But main activity gets destroyed when user exits the app, and the lifecycle ends for the app in general. So it is safer to store data in the Main Activity.
EDIT:
If you want to read up on Fragment lifecycle, it is # Fragments.
Good luck, keep us posted...
I'm having an issue of having two instances of the same fragment being attached to the activity. I have ActivityA attaching FragmentA on onCreate. When I leave the app while being on this Activity, browse other apps for a while, and return to the app, I see that the system is trying to re-create the activity. My log shows the code from the Fragment being ran TWICE. My guess is the Fragment is already attached but then the Activity attempts to create a new instance of FragmentA.
What happens to the Activity/Fragment when the system removes them from memory, and what's the best way to handle this? Any links would be helpful.
Will provide code if needed.
The best way to handle this is to check in your onCreate() method if your activity if being recreated from a previous state or not. I'm assuming you add your fragment on the onCreate() method of your activity. You can do something like this:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState == null)
{
// Add the fragment here to your activity
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content, new YourFragment());
ft.commit();
}
}
By doing this, you are basically saying that if a previous state is not found, you add your fragment. Otherwise you automatically get back the fragment that already exists.
I'm currently dealing with an issue with Android & It's Re-Creation Cycle on screen rotation:
I have one single Activity and lots of Fragments (Support-V4) within.
For example, the Login it's on a Single Activity with a Fragment, when the logs-in then the App changes it's navigation behavior and uses multiple fragments, I did this, because passing data between Fragment A to Fragment B it's way much easier than passing data Between an Activity A to an Activity B.
So My issue it's presented when I rotate the device, on my first approach, the initial fragment was loaded, but what would happen, if the user it's on Page 15 and it rotates it's device, it would return to Fragment 1 and give a very bad user-experience. I set all my fragments to retain their instance and added this on the MainActivity on Create:
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
initBackStackManager();
initControllers();
mayDownloadData();
setTitle();
if(savedInstanceState == null){
addAreaFragment();
}
}
Now, the first fragment is not loaded after screen orientation change, but If I try to make a fragment transaction, it says Can not perform FragmentTransaction.commit() after onSaveInstanceState(), is there a way to handle this? Or Do I really really need to use multiple Activities with a Fragment embedded within?
Thank you very much!
EDITED
I forgot to add that this happens only on a specific Fragment... For example I have the following fragment flow:
AreaFragment -> WaiterSelectionFragment -> WaiterOptionsFragment.
If I'm in the AreaFragment and I rotate the device I can still add/replace fragments and nothing happens, no error it's being thrown. If I'm on the WaiterSelectionFragment no error happens too. BUT, If I'm on the WaiterOptionsFragment the error it's being thrown. The WaiterSelectionFragment has the following structure:
LinearLayout
FragmentTabHost
Inside the FragmentTabHost there are some fragments, and that's where the error it's happening. You might wonder Why FragmentTabHost? easy, the Customer wants that App to show the TabBar, If I use Native Android Tabs the Tabs get rearranged to the ActionBar when on
Landscape position.
EDIT 2
I've used the method provided by #AJ Macdonald, but no luck so far.
I have my Current Fragment being saved at onSaveInstanceState(Bundle) method and restore my fragment on onRestoreInstanceState(Bundle) method on the Android Activity, I recover my back button and the current Fragment but when I get to the third Fragment the error still occurs. I'm using a ViewPager that holds 4 Fragments, Will this be causing the Issue? Only on this section of the App Happens. I've 4 (main workflow) fragments, on the First, Second and Third Fragment no error it's being presented, only on the ViewPager part.
Give each of your fragments a unique tag.
In your activity's onSaveInstanceState, store the current fragment. (This will probably be easiest to do if you keep a variable that automatically updates every time the fragment changes.)
In your activity's onCreate or onRestoreInstanceState, pull the tag out of the saved bundle and start a new fragment of that type.
public static final int FRAGMENT_A = 0;
public static final int FRAGMENT_B = 1;
private int currentFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//other stuff
if(savedInstanceState == null){
addAreaFragment();
currentFragment = FRAGMENT_A;
}else{
currentFragment = savedInstanceState.getInt("currentFragment");
switch(currentFragment){
case FRAGMENT_A:
addAreaFragment();
break;
case FRAGMENT_B:
addFragmentB();
}
}
}
// when you switch fragment A for fragment B:
currentFragment = FRAGMENT_B;
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("currentFragment", currentFragment);
super.onSaveInstanceState(savedInstanceState);
}
A suggestion to try is to use FragmentTransaction.commitAllowingStateLoss() in place of FragmentTransaction.commit(). That should stop the Exception from being thrown, but the downside is if you rotate the device again the most recent state of the UI may not return. That is a suggestion given that I am not sure of the effect of using FragmentTabHost, if it has any effect at all.
I am really confused with the internal state of a Fragment.
I have an Activity holding only one Fragment at once and replaces it, if another Fragment should get shown. From the docs onSaveInstanceState is called ONLY if the Activitys onSaveInstanceState is getting called (which isn't called in my case).
If I stop my Fragment, I'll store its state myself inside a Singleton (yeah, I know I hate Singletons, too, but wasn't my idea to do so).
So I have to recreate the whole ViewHirarchy, create new Views (by using the keyword new), restore its state and return them in onCreateView.
I also have a Checkbox inside this View from which I explicitly do NOT want to store its state.
However the FragmentManager wants to be "intelligent" and calls onViewStateRestored with a Bundle I never created myself, and "restores" the state of the old CheckBox and applies it to my NEW CheckBox. This throws up so many questions:
Can I control the bundle from onViewStateRestored?
How does the FragmentManager take the state of a (probably garbage-collected) CheckBox and applies it to the new one?
Why does it only save the state of the Checkbox (Not of TextViews??)
So to sum it up: How does onViewStateRestored work?
Note I'm using Fragmentv4, so no API > 17 required for onViewStateRestored
Well, sometimes fragments can get a little confusing, but after a while you will get used to them, and learn that they are your friends after all.
If on the onCreate() method of your fragment, you do: setRetainInstance(true); The visible state of your views will be kept, otherwise it won't.
Suppose a fragment called "f" of class F, its lifecycle would go like this:
- When instantiating/attaching/showing it, those are the f's methods that are called, in this order:
F.newInstance();
F();
F.onCreate();
F.onCreateView();
F.onViewStateRestored;
F.onResume();
At this point, your fragment will be visible on the screen.
Assume, that the device is rotated, therefore, the fragment information must be preserved, this is the flow of events triggered by the rotation:
F.onSaveInstanceState(); //save your info, before the fragment is destroyed, HERE YOU CAN CONTROL THE SAVED BUNDLE, CHECK EXAMPLE BELLOW.
F.onDestroyView(); //destroy any extra allocations your have made
//here starts f's restore process
F.onCreateView(); //f's view will be recreated
F.onViewStateRestored(); //load your info and restore the state of f's view
F.onResume(); //this method is called when your fragment is restoring its focus, sometimes you will need to insert some code here.
//store the information using the correct types, according to your variables.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("foo", this.foo);
outState.putBoolean("bar", true);
}
#Override
public void onViewStateRestored(Bundle inState) {
super.onViewStateRestored(inState);
if(inState!=null) {
if (inState.getBoolean("bar", false)) {
this.foo = (ArrayList<HashMap<String, Double>>) inState.getSerializable("foo");
}
}
}