Fragment overlaps on activity rotation - android

I'm getting really confused about this. I have an actionbar with list navigation. I click on the list to open 2 fragment one after another and display in the same activity. I'm basically replacing them using this method:
public void openFragment(AprilAppsFragment createdFragment){
if (createdFragment.getClass().isInstance(getDisplayedFragment()))
return;
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.replace( R.id.main_fragment, createdFragment, "displayed fragment");
transaction.addToBackStack(null);
transaction.commit();
}
I open fragment A, then I open fragment B, then I rotate the screen. Fragment A is being recreated crashing my app
Whys is that, since I'm using replace? How do I avoid recreating fragments that no longer being shown, without losing possibility of back-pressing to them?

Your problem is that you are not saving state for your fragments, and since you haven't added
android:configChanges="orientation|screenSize"
your first fragment is called again, and since you have no saved state your app crashes. So its good that you add the line above in you Activity declaration in the manifest like:
<activity android:name=".yourActivity" android:configChanges="orientation|screenSize"
and Override the onSaveInstanceState and save states for both your fragments. And in your onCreate just do this check:
if(savedInstanceState != null){
fragmentA = (FragmentA) fragmentManager.getFragment(savedInstanceState, stringValueA);
fragmentB = (FragmentB) fragmentManager.getFragment(savedInstanceState, stringValueB);
}
And then check if any of your fragment is not null than create its instance and set that to fragmentTransaction.relplace. Or do this in your for openFragment method.
Cheers,

Fragments save their state (and the location on the back stack) automatically on rotation. Make sure you aren't recreating your fragments or calling openFragment in your onActivityCreated or onCreate methods.

Your problem is that your activity is getting recreated, which is causing you to re-setup your list navigation, which is causing the default (first) position to be triggered. You need to save the selected index between activity destruction and activity recreation. Here's an example:
#Override
public void onCreate(Bundle savedInstanceState) {
// onCreate implementation...
// Must come after setListNavigationCallbacks is called
if (savedInstanceState != null) {
int index = savedInstanceState.getInt("index");
getActionBar().setSelectedNavigationItem(index);
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("index", getActionBar().getSelectedNavigationIndex());
}

i had a similar problem and what i do is to add a code line in my
Androidmanifest.xml (inside your activity that u want to stop recreated):
android:configChanges="orientation"
And one more thing, i learned that fragment is a kind of sub activity and doesn't an activity,
We must to extends an activity fragment for getting a support with fragments, and the activity that extands the activity fragment is the actually activity that holds a "sub's activities" that called fragments.
I hope i help u...

Add this to your activity definition in your AndroidManifest.xml file:
android:configChanges="keyboardHidden|screenLayout|orientation|screenSize"
That will keep the activity from restarting and the FragmentManager from automatically recreating the fragments that it has.
If you need the activity to restart on rotate, then you'll have to look for savedInstanceState being passed into onCreate and then query the FragmentManager for your fragments using the tags you supplied.

Related

Android Restoring order of fragments in the stack

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();
}
}
}

How to change fragments within activity and save state

I have an activity that would have fragments dynamically added using the method below.
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
FragmentA fragmentA = new FragmentA();
transaction.add(R.id.fragment_container, fragmentA, "fragmentA");
transaction.addToBackStack("fragmentA");
transaction.commit();
FragmentA has a TextView. I have a navigation drawer on my activity and want to switch between fragments (for example FragmentA, FragmentB, and FragmentC) depending on which item was clicked in the navigation drawer. How do I save the state of the fragments when changing to another fragment. I've implemented onSavedInstance(Bundle outState) and onActivityCreated(Bundle savedInstanceState) but savedInstanceState is always null. I want to be able to save the fields of FragmentA when changing from FragmentB and then changing back to FragmentA from FragmentB.
I cannot save the state when pressing the backstack. It seems like the fields are not being saved.
What are the correct ways to do this?
Fragment's onSaveInstanceState(Bundle outState) will never be called unless fragment's activity call it on itself and attached fragments. Thus this method won't be called until something (typically rotation) force activity to SaveInstanceState and restore it later.
So In on createView you can do something like that
.
.
.
Bundle mySavedInstanceState = getArguments();
String value = mySavedInstanceState.getString(KEY);
.
.
Save value in onPause() method
#Override
public void onPause() {
super.onPause();
String value = //get value from view;
getArguments().putString(KEY, value);
}
You can use SharePrefrences to save Data in FragmentA and read it when it is called again. Additional advantage is that the Data saved in SharePreferences can be read in other fragments if need be.
Useful Links on Fragments;
- https://developer.android.com/training/basics/fragments/index.html
-http://www.vogella.com/tutorials/AndroidFragments/article.html
I found a quick way to maintain the state of each fragment at the below link.
How do I restore a previously displayed Fragment?
I didn't realize that FragmentTransaction had a hide() method to hide the view of the current fragment and a show() method to show it again.

How to deal with Activity and Fragment when returning to app after they're removed from memory?

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.

What to do with Fragments when they are dismissed (not visible)?

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.

Fragments not destroyed when recreate() activity

I have a support library fragment that sends a network call, and recreate() the parent activity when it receives a specific network response. I can see that the activity does get recreated as I can see the onCreate() and onDestroy() are called.
But after the activity is recreated, the fragment is still there and it got stuck in a loop which keep recreating and making new fragments.
Here's part of the onCreate() of the activity:
if (someLogic()) {
fragmentA = new FragmentA();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentA);
} else {
fragmentB = new FragmentB();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentB);
}
FragmentA is the one that does the network call, and FragmentB is the fragment that should be displayed after the recreate(). When I check the list of fragments with getSupportFragmentManager().getFragments() I see 1 instances of FragmentA, and 16 instances of FragmentB.
My question is why does this happen, and how do I fix it?
Calling recreate() essentially causes the Activity to go through a configuration change. A configuration change causes both the Activity and its Fragments to be destroyed and recreated. So your Fragments should in fact be destroyed when this occurs (you can see for yourself by adding a Log message in your fragment's onDestroy method).
The fact that configuration changes cause fragments to be destroyed and created however does not mean that the FragmentManager will simply forget the fragments ever existed. The FragmentManager will still hold a reference to the newly created fragment after the configuration change occurs.
One thing you can do to prevent multiple fragments from being created is by doing something like this in your activity's onCreate():
if (savedInstanceState == null) {
// The Activity is being created for the first time, so create and
// add new fragments.
} else {
// Otherwise, the activity is coming back after being destroyed.
// The FragmentManager will restore the old Fragments so we don't
// need to create any new ones here.
}
I had same situation as #spy wrote, recreating the Activity after Day/Night mode is changed. Activity had four Fragments which they get restored after Activity.recreate() is called, because FragmentManager saves them as #Alex Lockwood stated. But restoring and not adding Fragments after recreation does not help me because of some architectural reasons of project, i wanted them to disappear.
Solution is to delete Fragments from FragmentManager just before Activity.recreate()is called, in my case it is in Activity.onStart():
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragmentA = fragmentManager.findFragmentByTag("fragmentATag");
if(fragmentA != null)
fragmentTransaction.remove(fragment);
// Find and delete other Fragments
fragmentTransaction.commit(); // or commitAllowingStateLoss()
recreate();
This way you will not get Fragments restored when Activity.onCreate() is called.

Categories

Resources