Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Orientation Change: IllegalStateException when DialogFragment is visible

I'd like to let Android handle orientation change. Unfortunately it crashes each time a dialog is open and orientation change occurs. I already suspect the cause but don't know how to handle it:

When Application first starts backstack looks like this:

02-07 15:25:36.517: I/System.out(14541): Added Fragments:
02-07 15:25:36.517: I/System.out(14541):   #0: PortraitModeFragment{41963330 #0 id=0x7f090001 ListViewFragment}

great, now we open a dialog:

02-07 15:26:03.316: I/System.out(14541): Added Fragments:
02-07 15:26:03.316: I/System.out(14541):   #0: PortraitModeFragment{41963330 #0 id=0x7f090001 ListViewFragment}
02-07 15:26:03.326: I/System.out(14541):   #1: MyDialogFragment{4199f6a0 #1 MyDialogFragment}

And now when rotate the device:

02-07 15:26:37.502: E/AndroidRuntime(14541): FATAL EXCEPTION: main
02-07 15:26:37.502: E/AndroidRuntime(14541): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.testapp/com.example.testapp.MainActivity}: android.view.InflateException: Binary XML file line #11: Error inflating class fragment
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3692)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.access$700(ActivityThread.java:141)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1240)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.os.Handler.dispatchMessage(Handler.java:99)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.os.Looper.loop(Looper.java:137)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.main(ActivityThread.java:5039)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at java.lang.reflect.Method.invokeNative(Native Method)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at java.lang.reflect.Method.invoke(Method.java:511)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at dalvik.system.NativeStart.main(Native Method)
02-07 15:26:37.502: E/AndroidRuntime(14541): Caused by: android.view.InflateException: Binary XML file line #11: Error inflating class fragment
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:270)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.Activity.setContentView(Activity.java:1881)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at com.example.testapp.MainActivity.onCreate(MainActivity.java:17)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.Activity.performCreate(Activity.java:5104)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
02-07 15:26:37.502: E/AndroidRuntime(14541):    ... 12 more
02-07 15:26:37.502: E/AndroidRuntime(14541): Caused by: java.lang.IllegalStateException: Fragment com.example.testapp.fragments.LandscapeModeFragment did not create a view.
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:303)
02-07 15:26:37.502: E/AndroidRuntime(14541):    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:676)

Interesting fun-fact: This does not happen if I first turn around the device and subsequently open the dialog and change orientation, because both fragments are preloaded and exist peacefully in the backstack:

02-07 15:34:00.015: I/System.out(15412):   #0: PortraitModeragment{419b99b0 #0 id=0x7f090001 ListViewFragment}
02-07 15:34:00.015: I/System.out(15412):   #1: LandscapeModeFragment{419b9b88 #1 id=0x7f090003 LandscapeModeFragment}
02-07 15:34:00.025: I/System.out(15412):   #2: MyDialogFragment{419d1da0 #2 MyDialogFragment}

I cant make the user rotate the smartphone around before using my app, so what to do? ;)

To reproduce:

How I add the Dialog to backstack:

MyDialogFragment myTvShowDialog = new MyDialogFragment();
myTvShowDialog.show(getActivity().getSupportFragmentManager(),myTvShowDialog.TAG);

What I did was create two xml files for MainActivity:

1 . res/layout/main.xml (PortraitMode):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">
     <TextView android:id="@+id/textView1" android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:text="layout-land/PORTRAIT" />
     <fragment class="com.example.testapp.fragments.PortraitModeFragment"
                        android:id="@+id/fragment" android:layout_weight="1"
                        android:layout_width="0px" android:layout_height="match_parent" android:tag="PortraitModeFragment">
     </fragment>
</LinearLayout>

2 . res/layout-land/main.xml (LandscapeMode)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" android:orientation="vertical">
    <TextView   android:id="@+id/textView1"   android:layout_width="wrap_content"     android:layout_height="wrap_content"     android:text="layout-land/LANDSCAPE" />
    <fragment class="com.example.testapp.fragments.LandscapeModeFragment"
            android:id="@+id/LandscapeModeFragment" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="0dp" android:tag="LandscapeModeFragment">
     </fragment>
</LinearLayout>

MainActivity.java:

public class MainActivity extends FragmentActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v(TAG, "onCreate()");
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().dump("", null,
                new PrintWriter(System.out, true), null);
    }
}

FragmentPortraitMode:

public class PortraitModeFragment extends Fragment {
    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View fragment = inflater.inflate(R.layout.portraitmodefragment_layout,
            null);
    Button findViewById = (Button) fragment.findViewById(R.id.button1);
    findViewById.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            MyDialogFragment myDialog = new MyDialogFragment();
            myDialog.show(getActivity().getSupportFragmentManager(),
                    MyDialogFragment.TAG);
        }
    });
    TextView textView = (TextView) fragment.findViewById(R.id.textView1);
    textView.setText(TAG);
    return fragment;
}

Fragment LandscapeMode:

public class LandscapeModeFragment extends Fragment {

    public static final String TAG = "LandscapeModeFragment";

    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View fragment = inflater.inflate(R.layout.landscapemode_fragment_layout,
            container, false);
    return fragment;
}
}

landscapemode_fragment_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="LANDSCAPEFRAGMENT" />
</LinearLayout>

portraitmode_fragment_layout_xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button   android:id="@+id/button1"    android:layout_width="wrap_content"    android:layout_height="wrap_content"     android:text="Button" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

</LinearLayout>
like image 797
AndacAydin Avatar asked Feb 07 '13 14:02

AndacAydin


People also ask

How to show a DialogFragment?

Showing the DialogFragment It is not necessary to manually create a FragmentTransaction to display your DialogFragment . Instead, use the show() method to display your dialog. You can pass a reference to a FragmentManager and a String to use as a FragmentTransaction tag.

What is DialogFragment in android?

DialogFragment is a utility class which extends the Fragment class. It is a part of the v4 support library and is used to display an overlay modal window within an activity that floats on top of the rest of the content. Essentially a DialogFragment displays a Dialog but inside a Fragment.


1 Answers

Well I've found a workaround which works just as fine.

First: What was the problem?

Fragments defined in the layout-xml and fragments added per FragmentManager have different lifecycles abd though it is possible to mess with the layout-fragments with the FragmentManager unexpected behaviour (Exceptions) causes the application to crash.

As Dianne Hackborn mentioned in the Google Groups Forum

Defining fragments in XML is mostly intended for things that are going to stay around. If you are going to add and remove, you should probably consistently do it dynamically.

I can look into this for a future release, but please note that whatever I do you can easily get yourself into bad situations using this -- for example if you don't have a unique containing view group for the fragment, what happens as you remove/add fragments at that point in the hierarchy is going to behave inconsistently.

So we are not allowed to mix worlds (FragmentManager-Fragments and LayoutXML-Fragments). This unfortunately involves DialogFragments added at Runtime too. What happens: Android-Framework adds on orientation-change the newly layout-created Fragment into the backstack. The FragmentManager trys to add the DialogFragment afterwards on the same index as the newly created Layout-Fragment. Thats when things get messy and IllegalStateException occurs.

So, whats the workaround?

Most examples using Fragments for different orientations involve defining a FrameLayout as container, to put the correct Fragment into it. So we'll go all the way with the FragmentManager and bann the LayoutFragments from our design:

ActivityMain uses just this simple xml as its contentView:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/fragment_container_id"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
</FrameLayout>

and when started it injects correct Fragment into the container

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.mainactivity_framelayout);
    createFragmentIfNotExits();
}
private void createFragmentIfNotExits() {
    int orientation = getResources().getConfiguration().orientation;
    Fragment newFragment;
    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
        newFragment = new TvShowListPortraitModeFragment();
    } else {
        newFragment = new TvShowListLandscapeModeFragment();
    }
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.replace(R.id.fragment_container_id, newFragment);
    ft.commit();
}

The Drawback: Unfortunately retaining the saved state of fragments is not possible this way. Or at least I still need to figure our how to do this.

So much from me. I will not mark this answer as "answered" since I did not really make it work, maybe someone will figure it out and post a simpler solution. I will mark this then ofcourse as correct.

like image 161
AndacAydin Avatar answered Oct 03 '22 00:10

AndacAydin