34

What is the correct way to handle an orientation change when using Fragments?

I have a landscape layout that contains 2 fragments (instantiated in code into FrameLayouts). When I switch to portrait mode (the layout of which contains only one FrameLayout that holds the left pane only), the right hand fragment is no longer required.

I am receiving an error:

E/AndroidRuntime(4519): Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f060085 for fragment myFragment{418a2200 #2 id=0x7f060085}

which is assume is my activity trying to re-attach the fragment where it was before the orientation change but as the view that contains the fragment does not exist in portrait mode the error is thrown.

I have tried the following hide/remove/detach methods but still get the error. What is the correct way to tell a fragment it is not needed any more and do not try to display?

@Override
public void onCreate(Bundle b) {
    super.onCreate(b);
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragholder2);

    //rightPane is a framelayout that holds my fragment.
    if (rightPane == null && f != null) {
         FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
         ft.hide(f);     // This doesnt work
         ft.remove(f);   // neither does this
         ft.detach(f);   // or this
         ft.commit;
    }
}
valerybodak
  • 4,195
  • 2
  • 42
  • 53
Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • Same question but the solution did not help in my case for some reason. http://stackoverflow.com/questions/6164341/handling-orientation-changes-with-fragments – Kuffs Apr 02 '12 at 13:26
  • This blog provides all the options of handling orientation change http://www.techrepublic.com/blog/android-app-builder/handling-orientation-changes-in-the-android-ui-framework/ – Dheeraj Bhaskar Jul 28 '13 at 11:35
  • Ooops! didn't check the date. Anyway I only linked that 'cause it shows all available choices and really helped me. As you know, code is available in the solutions here and elsewhere, hence submitted a comment rather than a solution. I didn't quite know how else to contribute this info that I found so useful. – Dheeraj Bhaskar Jul 28 '13 at 19:16

7 Answers7

14

I ran into the same problem and I think I figured out another solution. This solution may be better because you don't have to add the fragment to the back stack.

Remove the right hand side fragment from your activity in Activity.onSaveInstanceState() before calling super.onSaveInstanceState(). This works for me:

public MyActivity extends Activity
{   
    @Override
    public onCreate(Bundle state)
    {
        super.onCreate(state);

        // Set content view
        setContentView(R.layout.my_activity);

        // Store whether this is a dual pane layout
        mDualPane = findViewById(R.id.rightFragHolder) != null;

        // Other stuff, populate the left fragment, etc.
        .
        .
        .
        if (mDualPane)
        {
            mRightFragment = new RightFragment();
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            ft.replace(R.id.rightFragHolder, mRightFragment);
            ft.commit()
        }
    }


    @Override
    public void onSaveInstanceState(Bundle state)
    {
        if (mDualPane)
        {
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(mRightFragment);
            ft.commit()
        }

        super.onSaveInstanceState(state);
    }


    private boolean mDualPane;
    private Fragment mRightFragment;
}
Piyush
  • 18,895
  • 5
  • 32
  • 63
Mukul M.
  • 540
  • 1
  • 5
  • 15
  • 3
    Be careful with this solution because the commit doesn't happen immediately. This causes a race condition between the commit and super.onSaveInstanceState(state). Sometimes the commit happens after the state has been saved, which throws an exception. – DroidT Oct 21 '13 at 13:55
  • @DroidT So what is the proper way to do this then? – theblang Jun 02 '14 at 20:26
  • 2
    @mattblang executePendingTransactions(); – Nir Hartmann Sep 09 '14 at 10:24
  • Not the best solution. Removing the fragment for the one pane when maybe you want it loaded back even when dual panes are used causes it to be reloaded when there is no need to do that. – Johann Jul 24 '15 at 20:41
1

If you are retaining the fragment, try not retaining it.

setRetainInstance(false)

instead of

setRetainInstance(true)
NPike
  • 13,136
  • 12
  • 63
  • 80
0

If you have a two pane activity with a left and right pane and one of the panes (usually the right pane) is suppose to not show when the device switches to portrait mode, let Android do its thing and recreate the right pane. But during the onCreateView of the right pane, the first thing you should do is check if one of the layout elements used by the pane is even available. If it is not, remove the fragment using the FragmentManager and return immediately:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
    View myView = getActivity().findViewById(R.id.myView);

    if (myView == null)
    {
      FragmentTransaction fragTransaction = getFragmentManager().beginTransaction();
      fragTransaction.remove(this);
      fragTransaction.commit();
      return null;
    }
 }
Johann
  • 27,536
  • 39
  • 165
  • 279
0

Usually you'll have two fragments (left/right), one main activity and one container activity for the right fragment (only when shown on phone devices). This is described in this blog entry: The Android 3.0 Fragments API

public class MyActivity extends FragmentActivity
    implements MyListFragment.MyContextItemSelectedListener {

    @Override
    public void onCreate(final Bundle bundle) {
        super.onCreate(bundle);

        setContentView(R.layout.activity);
    }

    // Callback from ListFragment
    @Override
    public void myContextItemSelected(final int action, final long id) {
        if (action == R.id.men_show) {
            processShow(id);
        }
    }

    private void processShow(final long id) {
        if (Tools.isXlargeLand(getApplicationContext())) {
            Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.right);
            if (fragment == null ||
                    fragment instanceof MyEditFragment ||
                    (fragment instanceof MyShowFragment && ((MyShowFragment) fragment).getCurrentId() != id)) {
                fragment = new MyShowFragment(id);

                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
                transaction.replace(R.id.right, fragment);
                transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                transaction.commit();
            }
        } else {
            Intent intent = new Intent();
            intent.setClass(this, MyShowActivity.class);
            intent.putExtra("ID", id);
            startActivityForResult(intent, MyConstants.DLG_TABLE1SHOW);
        }
    }

    private static boolean isXlargeLand(final Context context) {
        Configuration configuration = context.getResources().getConfiguration();

        return (((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) &&
                        configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
    }
}
Harald Wilhelm
  • 6,656
  • 11
  • 67
  • 85
  • That is correct and is how my app works. The question is how to switch from dual pane to single pane without the error occurring. The blog post does not cover this eventuality (or at least does not mention how to do without experiencing the error I have detailed in my question) – Kuffs Apr 02 '12 at 12:38
  • You don't need to remove anything during orientation change if you have two different layout files (res/layout-xlarge-land with one fragment and one LinearLayout) and (res/layout with the left fragment only). When changing from landscape to portrait (your question) the new smaller layout will be fetched and the right fragment is gone automatically. However, you need to have a container activity for the right fragment in portrait mode. When using the blog mentioned above be carful with the ids titles, details and android.R.id.content. It's a litlle bit tricky what they do ... – Harald Wilhelm Apr 02 '12 at 13:08
  • I already have everything you mention and get an error because the FrameLayout that contains my 2nd fragment in landscape mode does not exist in my portrait mode layout therefore I assume I need to somehow prevent the 2nd fragment from automatically re-loading. I can hack it by including a hidden framelayout and allowing the fragment to re-instantiate itself inside that but I prefer not to use workarounds and would rather have a solution for the issue. – Kuffs Apr 02 '12 at 13:15
  • In MainActivity.onCreate simply load the layout (two different). You need to handle additional fragments on user action only (click on list entry or whatever). I did edit my answer with part of one of my apps. – Harald Wilhelm Apr 02 '12 at 14:05
  • Your example does not have any relevance to my issue. You are showing how you show the detail pane. This is not the problem I was experiencing or described in my question. I resolved my issue using the back stack to remove the redundant fragment before my single pane layout loaded. – Kuffs Apr 02 '12 at 14:27
  • I show you an activity and, as an example, how to show the details pane. Simply said - your onCreate is wrong. You don't need to load fragments if you have individual layouts containing fragments and, in case of an xlarge-land layout, a placeholder for the right pane. Sorry about wasting your time. – Harald Wilhelm Apr 02 '12 at 14:40
  • 1
    My onCreate is not wrong. All it does it load my layout. I am not manually loading my fragments in my onCreate. They are re-instantiating themselves on an orientation change which is what was causing the error. I did explain this in my original question. – Kuffs Apr 02 '12 at 14:47
  • The additional fragment code in my onCreate method in my question was my attempt at resolving the error that I displayed purely to illustrate the steps I had taken so far and was showing what did not resolve the issue. This was ex[plained in my original question. – Kuffs Apr 02 '12 at 14:54
0

I think I resolved it.

I added the fragment to the back stack and then before the activity closes popped it off again which effectively gets rid of it. Seems to work so far.

Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • Seems like an iffy solution. You would have to check the orientation before the activity closes and decide whether to pop it off because maybe you want it back when the activity restarts. – Johann Jul 24 '15 at 20:32
  • 1
    3 years ago. Long forgotten. – Kuffs Jul 25 '15 at 21:18
-1

Android does recreates both fragments during screen rotation. But if you add check below into onCreateView() it will prevent you from issues:

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        if (container == null) {
            // Currently in a layout without a container, so no
            // reason to create our view.
            return null;
        }

        // inflate view and do other stuff
    }

I took this from Android Developers blog.

Yuriy
  • 1,466
  • 2
  • 14
  • 30
-4

You do not need this activity anymore because the fragment will be shown in-line. So you can finish the activity

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

    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // we are in landscape so you do not need this activity anymore
        finish();
        return;
    }

    if (savedInstanceState == null) {
         // plugin right pane fragment
         YourFragment frgm = new YourFragment();
         getSupportFragmentManager().beginTransaction()
                .add(..., frgm).commit();
    }
}
207
  • 3,784
  • 2
  • 25
  • 22
  • I do need the activity. I only would not need the activity if it were the fragment on the left that I did not need. It is the fragment on the right that I do not want. – Kuffs Apr 02 '12 at 11:57