25

I have an Activity that calls setContentView with this XML:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    >
    <fragment android:name="org.vt.indiatab.GroupFragment"
        android:id="@+id/home_groups"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" />
            <..some other fragments ...>
</LinearLayout>

The GroupFragment extends Fragment, and all is well there. However, I show a DialogFragment from within GroupFragment. This shows correctly, HOWEVER when the screen rotates, I get a Force Close.

What's the proper way to display a DialogFragment from within another Fragment other than DialogFragment.show(FragmentManager, String)?

Weston
  • 1,882
  • 1
  • 20
  • 26

9 Answers9

55

There's a bug in the compatibility library that can cause this. Try putting this in you dialogfragment:

@Override
public void onDestroyView() {
  if (getDialog() != null && getRetainInstance())
    getDialog().setOnDismissListener(null);
  super.onDestroyView();
}

I also suggest setting your dialogfragment as retained, so it won't get dismissed after the rotation. Put "setRetainInstance(true);" e.g. in the onCreate() method.

Zsombor Erdődy-Nagy
  • 16,864
  • 16
  • 76
  • 101
  • 11
    setRetainInstance(true) fixes the crash, but the Dialog is simply dismissed on the orientation change. Having both setRetainInstance(true) and that code snippet above will cause it to be redisplayed. The only problem is that somehow the savedInstanceState Bundle is getting messed up. I can't bring my values back after the orientation change. The Bundle is ALWAYS null when onCreateDialog() is called. Any ideas? – Weston Nov 29 '11 at 13:21
  • 1
    This solves my task to create persistent dialogs. Using V4 compatibility lib. But are you sure that using native API 11 DialogFragment doesn't need this workaround? – Pointer Null Feb 09 '12 at 16:34
  • 1
    I didn't look into it, since I only use the backport library. I wouldn't be surprised if this'd be still buggy in api level 11. I recommend using the backport lib for anything below ICS, that way you can be sure that the fragment framework works consistently. – Zsombor Erdődy-Nagy Feb 10 '12 at 12:49
  • 22
    Note that there are reports that `getDialog().setOnDismissListener(null);` causes a crash on some devices. The workaround is to call `getDialog().setDismissMessage(null);` instead. See [this issue](http://code.google.com/p/android/issues/detail?id=17423) for details. – Andy Dennie Jun 28 '12 at 17:47
  • 3
    Yeah this causes an `IllegalStateException` for me: `OnDismissListener is already taken by DialogFragment and cannot be replaced.` But `setDismissMessage` works great. – Timmmm Sep 06 '12 at 14:34
  • 1
    @Weston setRetainInstance(true) will set the Bundle passed from onSaveInstanceState to null. See: http://stackoverflow.com/questions/13420448/how-i-can-break-things-with-fragments-with-setretaininstancetrue-and-adding-th/13773496#13773496 – Gunnar Karlsson Dec 18 '12 at 14:27
  • As a sidenote: it appears that this bug has already been taken care of in the newer support library versions. At least I can't reproduce it with the newest one. – Zsombor Erdődy-Nagy Dec 18 '12 at 14:49
  • I have the latest support library and I'm seeing this issue. I can't seem to get it to redisplay the dialog across orientation changes. – Matt Wolfe Jan 28 '14 at 23:16
  • This was quite a long ago, I'd be surprised if this bug would still exist in the current support lib. I think something else is wrong with your code, but without the code it's hard to give tips. – Zsombor Erdődy-Nagy Jan 29 '14 at 07:24
  • @ZsomborErdődy-Nagy it is still there, [the issue linked by Andy](https://code.google.com/p/android/issues/detail?id=17423) is not fixed. – TWiStErRob Dec 11 '14 at 13:52
  • Solved my problem. App no longer crashes after multiple rotation. – X09 Jun 04 '16 at 13:45
10

OK, while Zsombor's method works, this is due to me being inexperienced with Fragments and his solution causes issues with the saveInstanceState Bundle.

Apparently (at least for a DialogFragment), it should be a public static class. You also MUST write your own static DialogFragment newInstance() method. This is because the Fragment class calls the newInstance method in its instantiate() method.

So in conclusion, you MUST write your DialogFragments like so:

public static class MyDialogFragment extends DialogFragment {

    static MyDialogFragment newInstance() {
        MyDialogFragment d = new MyDialogFragment();
        return d;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        ...
    }
}

And show them with:

private void showMyDialog() {
    MyDialogFragment d = MyDialogFragment.newInstance();
    d.show(getFragmentManager(), "dialog");
}

This may be unique to the ActionBarSherlock Library, but the official samples in the SDK documentation use this paradigm also.

Weston
  • 1,882
  • 1
  • 20
  • 26
  • I've tried getting this same approach to work but still getting an error. Source code is https://github.com/gauntface/Android-Boiler-Plate-Kitchen-Sink [.activity.DialogActivity] – Matt Gaunt Jan 15 '12 at 16:25
  • 15
    You don't have to write "your own" newInstance() method as Fragment.instantiate() is calling [Class.newInstance()](http://developer.android.com/reference/java/lang/Class.html#newInstance%28%29). But because your fragment instance might be instantiated using Class.newInstance(), you have to provide an explicit default constructor for every fragment you write. – Szabolcs Berecz Feb 01 '12 at 14:15
  • @SzabolcsBerecz: OK wow that just added so much clarity. Thank you so much. – Weston Mar 06 '12 at 22:30
  • 2
    I agree with the @Szabolcs Berecz that the DialogFragment does not need a static constructor, but it needs an public empty (no argument) constructor so it can be properly initialised through Class.newInstance() method. – Aksel Fatih Aug 28 '12 at 14:16
  • Thanks you so much, this helped a large headache go away! Still don't understand why i can't instantiate it as usual though. – lfxgroove Dec 15 '12 at 10:19
  • Seems that you may only have to provide an _explicit_ no arg constructor in some situations. If you have __not__ defined any constructors but your `fragment` class is `public` then you _should_ be ok. See http://stackoverflow.com/a/3078399/236743 – Dori Feb 28 '13 at 15:34
  • 1
    @Weston Why do you say that DialogFragment needs to be a public static class? I use DialogFragments all the time, and have not had the need to make them static... – IgorGanapolsky Aug 20 '13 at 15:04
  • 1
    @flock.dux: All classes have a default, no argument constructor (implicit). The reason a default constructor is needed is yes, so it can be instantiated by the framework, but the proper way to initialize a DialogFragment is shown in the first code you see on this page http://developer.android.com/reference/android/app/DialogFragment.html , with a factory method that creates a new instance and sets arguments for the fragment. YOU will always be first to instantiate the fragment (using factory method). Then in onCreate, you get access to the arguments, even after screen rotation (for example.) – Jerry Destremps Feb 27 '14 at 22:36
2

To overcome the Bundle always being null, I save it to a static field in onSaveInstanceState. It's a code smell, but the only solution I found for both restoring the dialog and saving the state.

The Bundle reference should be nulled in onDestroy.

@Override
public void onCreate(Bundle savedInstanceState)
{
    if (savedInstanceState == null)
        savedInstanceState = HackishSavedState.savedInstanceState;

    setRetainInstance(true);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    if (savedInstanceState == null)
        savedInstanceState = HackishSavedState.savedInstanceState;

    ...
}

@Override
public void onDestroyView() // necessary for restoring the dialog
{
    if (getDialog() != null && getRetainInstance())
        getDialog().setOnDismissListener(null);

    super.onDestroyView();
}

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

    HackishSavedState.savedInstanceState = outState;
    super.onSaveInstanceState(outState);
}

@Override
public void onDestroy()
{
    HackishSavedState.savedInstanceState = null;
    super.onDestroy();
}

private static class HackishSavedState
{
    static Bundle savedInstanceState;
}
junkdog
  • 855
  • 7
  • 8
  • this class with static members will stay in memory as long as the classloader of that class is alive, this will take some time... – Eugene May 27 '14 at 06:12
1

I used a mix of the presented solutions and added one more thing. This is my final solution:

I used setRetainInstance(true) in the onCreateDialog; I used this:

public void onDestroyView() {
    if (getDialog() != null && getRetainInstance())
        getDialog().setDismissMessage(null);
    super.onDestroyView();
}

And as a workaround of the savedInstanceState not working, I created a private class called StateHolder (the same way a holder is create for a listView):

private class StateHolder {
    String name;
    String quantity;
}

I save the state this way:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    stateHolder = new StateHolder();
    stateHolder.name = actvProductName.getText().toString();
    stateHolder.quantity = etProductQuantity.getText().toString();
}

In the onDismiss method I set the stateHolder back to null. When the dialog is created, it verifies if the stateHolder isn't null to recover the state or just initialize everything normally.

Fernando Camargo
  • 3,085
  • 4
  • 30
  • 49
1

I solved this issue with answers of @ZsomborErdődy-Nagy and @AndyDennie . You must subclass this class and in you parent fragment call setRetainInstance(true), and dialogFragment.show(getFragmentManager(), "Dialog");

 public class AbstractDialogFragment extends DialogFragment {

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }

        @Override
        public void onDestroyView() {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }
    }
Roger Garzon Nieto
  • 6,554
  • 2
  • 28
  • 24
0

I ran into this on my project and none of the above solutions helped.

If the exception looks something like


java.lang.RuntimeException: Unable to start activity ComponentInfo{ 

...

        Caused by: java.lang.IllegalStateException: Fragment.... 
        did not create a view.

It's caused by an issue with a fallback container Id that gets used after rotation. See this ticket for more details:

https://code.google.com/p/android/issues/detail?id=18529

Basically you can prevent the crash by making sure all of your xml fragments have a tag defined in the layout. This prevents the fallback condition from occurring if you rotate when a fragment is visible.

In my case I was able to apply this fix without having to override onDestroyView() or setRetainInstance(true), which is the common recommendation for this situation.

Travis Castillo
  • 1,807
  • 1
  • 20
  • 23
0

I encountered this problem and the onDestroyView() trick wasn't working. It turned out that it was because I was doing some rather intensive dialog creation in onCreate(). This included saving a reference to the AlertDialog, which I would then return in onCreateDialog().

When I moved all of this code to onCreateDialog() and stopped retaining a reference to the dialog, it started working again. I expect I was violating one of the invariants DialogFragment has about managing its dialog.

user1978019
  • 3,008
  • 1
  • 29
  • 38
0

In onCreate() call setRetainInstance(true) and then include this:

@Override
public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
        getDialog().setOnDismissMessage(null);
    }
    super.onDestroyView();
}

When you call setRetainInstance(true) in onCreate(), onCreate() will no longer be called across orientation changes, but onCreateView() will still be called.

So you can still save the state to your bundle in onSaveInstanceState() and then retrieve it in onCreateView():

@Override
public void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);

    outState.putInt("myInt", myInt);
}

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

    View view = inflater.inflate(R.layout.my_layout, container);

    if (savedInstanceState != null) {

        myInt = savedInstanceState.getInt("myInt");
    }

    ...

    return view;
}
JDJ
  • 4,298
  • 3
  • 25
  • 44
0

I had a similar issue, however none of the above worked for me. In the end I needed to create the fragment in code instead of in the XML layout.

See: Replacing fragments and orientation change

Community
  • 1
  • 1
Matt Gaunt
  • 9,434
  • 3
  • 36
  • 57