Kuffs
Kuffs

Reputation: 35661

Fragments and Orientation change

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

Upvotes: 34

Views: 21896

Answers (7)

Yuriy
Yuriy

Reputation: 1496

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.

Upvotes: -1

Mukul M.
Mukul M.

Reputation: 541

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

Upvotes: 14

Johann
Johann

Reputation: 29867

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

Upvotes: 0

NPike
NPike

Reputation: 13254

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

setRetainInstance(false)

instead of

setRetainInstance(true)

Upvotes: 1

Harald Wilhelm
Harald Wilhelm

Reputation: 6716

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

Upvotes: 0

Kuffs
Kuffs

Reputation: 35661

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.

Upvotes: 0

207
207

Reputation: 3804

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

Upvotes: -4

Related Questions