122

Using the compatibility package to target 2.2 using Fragments.

After recoding an activity to use fragments in an app I could not get the orientation changes/state management working so I've created a small test app with a single FragmentActivity and a single Fragment.

The logs from the orientation changes are weird, with multiple calls to the fragments OnCreateView.

I'm obviously missing something - like detatching the fragment and reattaching it rather than creating a new instance, but I can't see any documentation which would indicate where I'm going wrong.

Can anyone shed some light on what I'm doing wrong here please. Thanks

The log is as follows after orientation changes.

Initial creation
12-04 11:57:15.808: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:15.945: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:16.081: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 1
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:57:39.031: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.167: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 2
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.361: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null

Main Activity (FragmentActivity)

public class FragmentTestActivity extends FragmentActivity {
/** Called when the activity is first created. */

private static final String TAG = "FragmentTest.FragmentTestActivity";


FragmentManager mFragmentManager;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Log.d(TAG, "onCreate");

    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
}

And the fragment

public class FragmentOne extends Fragment {

private static final String TAG = "FragmentTest.FragmentOne";

EditText mEditText;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Log.d(TAG, "OnCreateView");

    View v = inflater.inflate(R.layout.fragmentonelayout, container, false);

    // Retrieve the text editor, and restore the last saved state if needed.
    mEditText = (EditText)v.findViewById(R.id.editText1);

    if (savedInstanceState != null) {

        Log.d(TAG, "OnCreateView->SavedInstanceState not null");

        mEditText.setText(savedInstanceState.getCharSequence("text"));
    }
    else {
        Log.d(TAG,"OnCreateView->SavedInstanceState null");
    }
    return v;
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    Log.d(TAG, "FragmentOne.onSaveInstanceState");

    // Remember the current text, to restore if we later restart.
    outState.putCharSequence("text", mEditText.getText());
}

Manifest

<uses-sdk android:minSdkVersion="8" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:label="@string/app_name"
        android:name=".activities.FragmentTestActivity" 
        android:configChanges="orientation">
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
keyboardsurfer
  • 14,541
  • 6
  • 59
  • 82
MartinS
  • 5,563
  • 10
  • 32
  • 39
  • I dont know if it's a proper answer, but try using a tag when adding the fragment , add(R.id.fragment_container, fragment , "MYTAG") , or failing that , replace(R.id.fragment_container , fragment , "MYTAG") – Jason Dec 12 '11 at 18:01
  • 2
    Doing some investigations. When the Main Activity (FragmentTestActivity) restarts on orientation change and I obtain a new instance of FragmentManager then perform a FindFragmentByTag to locate the fragment it still exists, so the fragment it's being retained over the recreation of the main activity. If I find the fragment and do nothing then it's redisplayed with the MainActivity anyway. – MartinS Dec 14 '11 at 12:18

7 Answers7

192

You're layering your Fragments one on top of the other.

When a config change occurs the old Fragment adds itself to the new Activity when it's recreated. This is a massive pain in the rear most of the time.

You can stop errors occurring by using the same Fragment rather than recreating a new one. Simply add this code:

if (savedInstanceState == null) {
    // only create fragment if activity is started for the first time
    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
} else {        
    // do nothing - fragment is recreated automatically
}

Be warned though: problems will occur if you try and access Activity Views from inside the Fragment as the lifecycles will subtly change. (Getting Views from a parent Activity from a Fragment isn't easy).

Graeme
  • 24,857
  • 23
  • 121
  • 182
  • 56
    "This is a massive pain in the rear most of the time" (thumbs up) – rushinge Apr 29 '13 at 21:33
  • 2
    How can handled same scenario in case of ViewPage use with FragmentStatePagerAdapter...any suggestion? – CoDe Dec 30 '14 at 10:34
  • 5
    Is there a similar assertion in the official documentation? Isn't this a contradiction to what is stated in the guide: `"when the activity is destroyed, so are all fragments"`? Since `"When the screen orientation changes, the system destroys and recreates the activity [...]"`. – cYrus Mar 29 '15 at 19:33
  • 4
    Cyrus - Nope, the Activity is indeed destroyed, the Fragments it contains is referenced in the FragmentManager, not solely from the Activity, so it remains and is readded. – Graeme Mar 30 '15 at 09:42
  • 1
    If the activity is restarting due to an orientation change and that orientation is not suppose to use a fragment that was loaded in the previous orientation, good luck on trying to remove that instance of the fragment. It will still go through the creation process. Your solution makes the assumption that the new orientation wants to reuse the fragment associated with the new layout and in some cases, this is wrong. – AndroidDev Jul 24 '15 at 20:25
  • @Graeme you saved my day! thanks - this was useful in when changing landscape(assume master/detail fragment with showing data) to portrait mode ! – Shivasurya Jan 02 '16 at 13:00
  • Thanks for the answer! It was really helpful. – DH28 Feb 09 '16 at 14:31
  • 4
    logging fragments onCreate and onDestroy methods as well as its hashcode after finding in FragmentManager clearly shows that the fragment IS destroyed. it just get´s recreated and reattached automatically. only if you put setRetainInstance(true) in fragments onCreate method it really won´t be destroyed – Lemao1981 Apr 22 '16 at 04:23
  • For me this has helped in my use case. Thanks =) – j1mmyg88 Jan 24 '17 at 14:41
  • This answer is incomplete for the reasons mentioned by AndroidDev and Lemao1981. Fragments *are* destroyed during a configuration change *unless* you set setRetainInstance(true). https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean) – vlazzle Mar 21 '17 at 21:20
  • Does this apply when the fragment is not part of the host activity layout? – stdout Jan 08 '19 at 22:48
  • Could there be a case that user rotates the screen before Fragments created and added? Say, a user rotates as soon as some views are visible. In that case, will android wait until synchronous methods (or view inflation) complete to start the rotation? Because if not, savedstate could still be non-null and inside `if` statement would practically end up being unreachable. – Farid Aug 16 '19 at 12:15
89

To cite this book, "to ensure a consistent user experience, Android persists the Fragment layout and associated back stack when an Activity is restarted due to a configuration change." (p. 124)

And the way to approach that is to first check if the Fragment back stack has already been populated, and create the new fragment instance only if it hasn't:

@Override
public void onCreate(Bundle savedInstanceState) {

        ...    

    FragmentOne fragment = (FragmentOne) mFragmentManager.findFragmentById(R.id.fragment_container); 

    if (fragment == null) {
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, new FragmentOne());
        fragmentTransaction.commit();
    }
}
k29
  • 1,715
  • 1
  • 14
  • 14
  • 2
    You probably saved me alot of time with this one ... thanks a lot. You can combine this answer with the one from Graeme to get a perfect solution to handle config changes and fragments. – azpublic Jul 05 '13 at 02:18
  • 10
    This is actually the right answer, not the marked one. Thank you very much! – Uriel Frankel Nov 16 '13 at 19:38
  • how can handled same scenarion in case of ViewPager Fragment implementation. – CoDe Dec 30 '14 at 12:38
  • This little gem helped on a problem I'd been looking at for several days. Thank you! This is definitely the solution. – Whome Feb 23 '17 at 00:14
  • This will give a ClassCastException if there are multiple fragments involved. – Sharp Edge Oct 12 '17 at 10:52
  • 2
    @SharpEdge If you have multiple fragments, you should give them tags when adding to the container, and then use mFragmentManager.findFragmentByTag (instead of findFragmentById) to get references to them - this way you will know the class of each fragment and be able to cast correctly – k29 Oct 14 '17 at 23:24
10

The onCreate() method of your activity is called after the orientation change as you have seen. So, do not execute the FragmentTransaction that adds the Fragment after the orientation change in your activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null) {
        //do your stuff
    }
}

The Fragments should and must be unchanged.

agelbess
  • 3,897
  • 3
  • 17
  • 18
  • Do we know the instance will be saved after the fragment is created and added? I mean hat if a user rotates just before Fragment is being added? We will still have non-null savedInstanceState that doesn't contain fragment state – Farid Aug 16 '19 at 12:23
4

You can @Override the FragmentActivity using onSaveInstanceState(). Please be sure to not call the super.onSaveInstanceState() in the method.

Codeversed
  • 8,437
  • 3
  • 38
  • 40
Victor.Chan
  • 57
  • 1
  • 1
  • 2
    This would most likely break the activities lifecycle introducing more potential problems in this already quite messy process. Look into the source code of FragmentActivity: it is saving the states of all fragments there. – Brian Jan 15 '13 at 10:08
  • I had the problem that i have different adapter count for different orientation. So i had always a strange situation after turn the device and swipe some pages, i got the old and wrong one. with turning of the savedInstance it works best without memory leaks (i used setSavedEnabled(false) befor and ended up with big memory leaks on every orientation change) – Informatic0re Jan 29 '13 at 08:38
0

We should always try to prevent nullpointer exception , so we have to check first in saveinstance method for bundle information. for brief explaination to check this blog link

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
            == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    } 
}
Anas Azeem
  • 2,750
  • 3
  • 21
  • 36
abhi
  • 739
  • 5
  • 15
0

If you just do a project, then the project manager says you need to achieve switching function screen, but you don't want to screen switching load different layout (can create layout and layout-port system.

You will automatically determine the screen state, load the corresponding layout), because of the need to re initialize the activity or fragment, the user experience is not good, not directly on the screen switch, I refer to ? Url=YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki&wd=&eqid=f258719e0001f24000000004585a1082

The premise is that your layout using the weight of the way the layout of the layout_weight, as follows:

<LinearLayout
Android:id= "@+id/toplayout"
Android:layout_width= "match_parent"
Android:layout_height= "match_parent"
Android:layout_weight= "2"
Android:orientation= "horizontal" >

So my approach is, when screen switching, don't need to load a new layout of the view file, modify the layout in onConfigurationChanged dynamic weights, the following steps: 1 first set: AndroidManifest.xml in the activity attribute: android:configChanges= "keyboardHidden|orientation|screenSize" To prevent screen switching, avoid re loading, so as to be able to monitor in onConfigurationChanged 2 rewrite activity or fragment in the onConfigurationChanged method.

@Override
Public void onConfigurationChanged (Configuration newConfig) {
    Super.onConfigurationChanged (newConfig);
    SetContentView (R.layout.activity_main);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById(R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Tradespace_layout.setLayoutParams (LP3);
    }
    else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById (R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Tradespace_layout.setLayoutParams (LP3);
    }
}
ddb
  • 2,367
  • 7
  • 24
  • 33
nihaoqiulinhe
  • 259
  • 3
  • 5
0

On configuration change, the framework will create a new instance of the fragment for you and add it to the activity. So instead of this:

FragmentOne fragment = new FragmentOne();

fragmentTransaction.add(R.id.fragment_container, fragment);

do this:

if (mFragmentManager.findFragmentByTag(FRAG1_TAG) == null) {
    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment, FRAG1_TAG);
}

Please note that the framework adds a new instance of FragmentOne on orientation change unless you call setRetainInstance(true), in which case it will add the old instance of FragmentOne.

vlazzle
  • 721
  • 8
  • 13