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.
Related
I got 2 activities, A and B. Each activity is a container for fragments which are replaced with a FragmentTransaction.
I got an issue on some devices that when a user opens Activity B while he was in Activity A, the first activity is probably destroyed, which means that when a user clicks the back button, it makes the first activity recreated while in a normal device, it would just resume.
My main issue is that the user loses its fragment stack he had in the first activity. When the user opened the 2nd activity, he was already 3 fragments "deep" the first activity. How can I restore the stack and return the user to the point he's been before the first activity was destroyed?
This should be handled by the Android OS automatically. You can turn developer option "don't keep activities" on to always mimic this behavior (destroying your activity) when your activity goes to the background. After that you can start debugging. Some things to check:
In onCreate of the activity, are you calling the super onCreate with
the savedInstanceState?
If you put a breakpoint at the start of onCreate, when you "come
back" to the activity, is there a saved instance state?
Where are you creating the fragments? Are you re-creating them
manually (you shouldn't)?
Are your fragments hardcoded in the layout or replaced in the layout
(replacing a container view)?
* EDIT *
From your reply I derive that this is the problem, you say: "In the end of the onCreate I am replacing the fragment with a fragment transaction and thus load the first fragment of the app" => you should not do that when the savedInstanceState is not null. Otherwise you're destroying what is already there from the saved state.
Check here: https://developer.android.com/training/basics/fragments/fragment-ui.html
Notice the return when savedInstanceState is not null.
public class MainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
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 am rewriting a Bluetooth app with 3 Activities to use just 1 Activity and 3 Fragments:
So I have now 4 files:
MainActivity.java (contains bluetooth and shared preferences code)
MainFragment.java (contains ellipsis menu to show SettingsFragment)
SettingsFragment.java (contains "scan" button to show ScanningFragment)
ScanningFragment.java (displays nearby bluetooth devices in a list)
It almost works, but as an Android programming newbie I don't understand - what to do with Fragments when I show some other Fragment?
Should I just drop the Fragments (and remove from FragmentManager?) to be garbage collected?
Or should I add these 3 private variables to MainActivity.java and reuse them (when the user navigates forwards and backwards)?
private MainFragment mMainFragment;
private SettingsFragment mSettingsFragment;
private ScanningFragment mScanningFragment;
Or does FragmentManager somehow manage all 3 Fragment for me - regardless if they are visible or not?
Here is my current code (it is simple, I just call replace() all the time)-
public class MainActivity extends Activity implements
MainListener,
SettingsListener,
ScanningListener,
BleWrapperUiCallbacks {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
// implementing SettingsFragment.SettingsListener interface
public void scanClicked() {
// TODO how to stop indicator when returned?
setProgressBarIndeterminateVisibility(true);
String address = // get from shared preferences
Fragment fragment = ScanningFragment.newInstance(address);
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "scan")
.commit();
}
Should I just drop the Fragments (and remove from FragmentManager?) to
be garbage collected?
No need to do anything else. FragmentManager is the guy in charge of Fragments' lifecycle. Once you call replace(), FragmentManager takes care for the rest. If needed it will keep fragment in memory, or release it.
Or should I add these 3 private variables to MainActivity.java and
reuse them (when the user navigates forwards and backwards)?
No, don't do it because of the said above.
Or does FragmentManager somehow manage all 3 Fragment for me -
regardless if they are visible or not?
Yes, it does. For instance, if you have invisible retained fragment, it's enough to create it once, and FragmentManager will take care of it and will keep it even when activity gets re-created during configuration change.
If you create fragments dynamically (as far as I can see, this is your case) then I suggest to add very first fragment dynamically too. You can do it like this.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
if (savedInstanceState == null) { // <- important
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
}
This will ensure you don't duplicate MainFragment on configuration change, because when savedInstanceState is not null, then FragmentManager keeps instance of your fragment already.
Since you are calling .replace() on the fragment manager it's essentially the same thing as calling .remove(). According to the docs:
This is essentially the same as calling remove(Fragment) for all currently
added fragments that were added with the same containerViewId and
then add(int, Fragment, String) with the same arguments given here.
So you don't need to worry about any further management since it will be taken care of for you (and be removed to free up resources). This basically means that when one is shown the other is removed. If you were to call .add() then the fragments would still be alive in the background using up resources but you don't have to worry about that since using .replace() only allows one to live at a time.
If I understand your question correctly, you need not call any method to destroy the fragments after using them. Android OS will take of them. According to the documentation, When you replace the fragment with another, the onStop() method of the fragment will be executed, and documentaed as,
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
So the fragment will be killed by the OS when the activity is killed. Till the activity is live, fragment objects will reside in the memory.
EDT:
So, if you want to use the fragment again in future, as document suggests,
Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document.
Fragment are hard coded in xml can not replaced....
but when you call replace(..) then what happen ??
Ok just consider you have 3 fragment A,B,C . In primary stage A initialize in MainActivity... now you are going to call B from A using replace(....). that means A will go onPause(),onStop() state of lifecycle and B will be initialized and paced in MainActivity...same as for C then B is onPause() ,onStop() state. If you want to reuse A or B then you need to call again A or B from C using replace (..). and then A or B will reinitialize and C goes to onPause(),onStop(). But there is another way you can do this
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
hide(A);
show(B);
ft.commit();
if you use above code block then A still be in the running state of its lifecycle but not visible (just detached from UI ).. so it would be better if you need those fragment again use hide()or show() method because it is less expensive operation then reinitialize the fragment.
i am using four fragments in one activity
Four buttons at bottom are used to switch between fragments
I have search button of action bar
But when i am clicking on search button keyboard appears and then after that fragment started loading again..
So how to save state of fragment on configuration change..
I have also tried this
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
Use public void setRetainInstance (true) in fragment.
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
...
...
}
This will retain only when there is change in orientation, not on back stack.
You can use setRetainInstance(true). No matter where you write - in onActivityCreated or OnCreateView. But in this case you can have problems with memory or link on old Activity in future.
As for me, I think that it's better to save your data in onSaveInstanceState and get them, for example, in onRestoreInstanceState.
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?