123

I have some fragments that need to show a regular dialog. On these dialogs the user can choose a yes/no answer, and then the fragment should behave accordingly.

Now, the Fragment class doesn't have an onCreateDialog() method to override, so I guess I have to implement the dialogs outside, in the containing Activity. It's ok, but then the Activity needs to report back the chosen answer somehow to the fragment. I could of course use a callback pattern here, so the fragment registers itself at the Activity with a listener class, and the Activity would report back the answer thru that, or something like that.

But this seems to be quite a big mess for a simple task as displaying a "simple" yes-no dialog in a fragment. Also, this way my Fragment would be less self-contained.

Is there some cleaner way to do this?

Edit:

The answer to this question doesn't really explain in detail how one should use DialogFragments to display dialogs from Fragments. So AFAIK, the way to go is:

  1. Display a Fragment.
  2. When needed, instantiate a DialogFragment.
  3. Set the original Fragment as the target of this DialogFragment, with .setTargetFragment().
  4. Show the DialogFragment with .show() from the original Fragment.
  5. When the user chooses some option on this DialogFragment, notify the original Fragment about this selection (e.g. the user clicked 'yes'), you can get the reference of the original Fragment with .getTarget().
  6. Dismiss the DialogFragment.
Shubhamhackz
  • 7,333
  • 7
  • 50
  • 71
Zsombor Erdődy-Nagy
  • 16,864
  • 16
  • 76
  • 101
  • 1
    Your technique works, except when a screen rotation occurs. I get a force close then. Any ideas? – Weston Nov 22 '11 at 22:16
  • @Weston check out the first answer by Zsombor: http://stackoverflow.com/questions/8235080/fragments-dialogfragment-and-screen-rotationc – mightimaus Aug 31 '12 at 21:01

7 Answers7

40

I must cautiously doubt the previously accepted answer that using a DialogFragment is the best option. The intended (primary) purpose of the DialogFragment seems to be to display fragments that are dialogs themselves, not to display fragments that have dialogs to display.

I believe that using the fragment's activity to mediate between the dialog and the fragment is the preferable option.

Mark D
  • 3,317
  • 1
  • 26
  • 27
  • 10
    Since the managed dialogs (`onCreateDialog`) approach is soon to be deprecated, I'd disagree and say that `DialogFragment` is indeed the way to go. – Dave Jun 28 '11 at 12:22
  • 4
    Accepted answer means use a DialogFragment instead of a Dialog, not instead of a ListFragment, AFAICS. – nmr Sep 01 '11 at 22:04
  • Actually a dialog fragment can be used as both a dialog and a normal fragment - see embedding http://developer.android.com/reference/android/app/DialogFragment.html – Clive Jefferies Feb 14 '14 at 15:08
  • @CliveJefferies It can, but still isn't supposed to show other dialogs from within itself. I think guys here are getting the question wrong. – Marcel Bro Sep 13 '16 at 10:17
  • 1
    @anoniim it depends on how the dialog fragment is being used. If it is used as a regular fragment then it is fine. If it is being used as a dialog, then yes, you shouldn't be showing another dialog. – Clive Jefferies Sep 13 '16 at 10:32
  • @CliveJefferies So are you suggesting that opening a DialogFragment from another Fragment is fine? Should a FragmentManager or ChildFragmentManager be used in such case? – Marcel Bro Sep 13 '16 at 11:47
  • 1
    @anoniim As long as the launching fragment is not a dialog, then yes. I'm not sure about ChildFragmentManager, I used SupportFragmentManager and that worked fine. It's all in the docs. – Clive Jefferies Sep 14 '16 at 14:18
  • 1
    This answer works perfectly for me on every level. – Matt Payne Feb 18 '21 at 11:40
37

You should use a DialogFragment instead.

mgv
  • 8,384
  • 3
  • 43
  • 47
  • 10
    Unfortunately this approach is a bit more verbose than the classic managed dialogs approach of previous Android revisions, but it is now the preferred method. You can avoid referencing the `Activity` entirely by using the `putFragment` and `getFragment` methods of `FragmentManager`, allowing the `DialogFragment` to report back directly to the calling fragment (even after orientation changes). – Dave Jun 28 '11 at 12:25
  • What if you have a ListFragment that needs to display Dialogs, can't extend them both – marchinram Aug 11 '11 at 00:17
  • 16
    The ListFragment subclass would use DialogFragments by instantiating new ones, not by subclassing DialogFragment. (A DialogFragment is a dialog implemented as a Fragment, not a Fragment which may display Dialogs.) – nmr Sep 01 '11 at 22:07
  • 4
    Please add some snippet so we can understand eaisly – Arpit Patel Aug 06 '16 at 06:12
26

Here is a full example of a yes/no DialogFragment:

The class:

public class SomeDialog extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle("Title")
            .setMessage("Sure you wanna do this!")
            .setNegativeButton(android.R.string.no, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do nothing (will close dialog)
                }
            })
            .setPositiveButton(android.R.string.yes,  new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do something
                }
            })
            .create();
    }
}

To start dialog:

        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // Create and show the dialog.
        SomeDialog newFragment = new SomeDialog ();
        newFragment.show(ft, "dialog");

You could also let the class implement onClickListener and use that instead of embedded listeners.

Callback to Activity

If you want to implement callback this is how it is done In your activity:

YourActivity extends Activity implements OnFragmentClickListener

and

@Override
public void onFragmentClick(int action, Object object) {
    switch(action) {
        case SOME_ACTION:
        //Do your action here
        break;
    }
}

The callback class:

public interface OnFragmentClickListener {
    public void onFragmentClick(int action, Object object);
}

Then to perform a callback from a fragment you need to make sure the listener is attached like this:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnFragmentClickListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement listeners!");
    }
}

And a callback is performed like this:

mListener.onFragmentClick(SOME_ACTION, null); // null or some important object as second parameter.
Warpzit
  • 27,966
  • 19
  • 103
  • 155
  • 5
    This does not explain how to start it from a Fragment – akohout Jan 28 '14 at 11:47
  • @raveN you'd simply make a callback to your activity which then would start the fragment. – Warpzit Jan 28 '14 at 12:43
  • @Warpzit Thank you for the comprehensive answer. I dread touching the FragmentManager, which I'm already doing ungodly things with... How I can pass onClick events back to the (non-Dialog)Fragment which needed user input? BTW, shouldn't the transaction be added to backstack? – kaay Jul 08 '14 at 12:57
  • @kaay from the activity you can call any public method in the given fragment that needs the new input. From there it should be pretty easy updating with the new content. – Warpzit Jul 09 '14 at 13:26
  • @Warpzit This assumes the Activity has a convenient handle on the visible Fragments (maybe there is one, the instancestate mechanism makes me mistrust many solutions) and knows which one called the dialog. All my Activity has is public methods for replacing/adding/popping Fragments in 2 separate container layouts with their own backstacks and subtabs. The Activity (already large enough as only an Activity is allowed to do certain things) has been relieved of the need to understand what is going on, the Fragments ask it to do things. Advice? – kaay Jul 14 '14 at 08:15
  • @kaay I'd say your question has gotten to complex for comment only. I still think the activity should be able to find the right fragment and update it accordingly. It's only a matter of identification. – Warpzit Jul 14 '14 at 19:30
  • @Warpzit identification is exactly the problem. Assume the Dialog is a "Connection failed, retry?" callable from any Fragment. Thanks anyway, though this looks more promising: http://stackoverflow.com/a/14110409/1034042 – kaay Jul 16 '14 at 09:43
  • Yes, this is great. The trick is to call back to the Activity, being careful to update WHICH Activity each time the Activity/Fragment pair is recreated by the system. For all the good they provide, fragments really provide a world of pain too. – Richard Le Mesurier Sep 15 '14 at 09:32
  • 1
    @RichardLeMesurier Indeed, fragments are ups and downs. – Warpzit Sep 15 '14 at 11:29
14

For me, it was the following-

MyFragment:

public class MyFragment extends Fragment implements MyDialog.Callback
{
    ShowDialog activity_showDialog;

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        try
        {
            activity_showDialog = (ShowDialog)activity;
        }
        catch(ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "ShowDialog interface needs to be     implemented by Activity.", e);
            throw e;
        }
    }

    @Override
    public void onClick(View view) 
    {
        ...
        MyDialog dialog = new MyDialog();
        dialog.setTargetFragment(this, 1); //request code
        activity_showDialog.showDialog(dialog);
        ...
    }

    @Override
    public void accept()
    {
        //accept
    }

    @Override
    public void decline()
    {
        //decline
    }

    @Override
    public void cancel()
    {
        //cancel
    }

}

MyDialog:

public class MyDialog extends DialogFragment implements View.OnClickListener
{
    private EditText mEditText;
    private Button acceptButton;
    private Button rejectButton;
    private Button cancelButton;

    public static interface Callback
    {
        public void accept();
        public void decline();
        public void cancel();
    }

    public MyDialog()
    {
        // Empty constructor required for DialogFragment
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.dialogfragment, container);
        acceptButton = (Button) view.findViewById(R.id.dialogfragment_acceptbtn);
        rejectButton = (Button) view.findViewById(R.id.dialogfragment_rejectbtn);
        cancelButton = (Button) view.findViewById(R.id.dialogfragment_cancelbtn);
        acceptButton.setOnClickListener(this);
        rejectButton.setOnClickListener(this);
        cancelButton.setOnClickListener(this);
        getDialog().setTitle(R.string.dialog_title);
        return view;
    }

    @Override
    public void onClick(View v)
    {
        Callback callback = null;
        try
        {
            callback = (Callback) getTargetFragment();
        }
        catch (ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "Callback of this class must be implemented by target fragment!", e);
            throw e;
        }

        if (callback != null)
        {
            if (v == acceptButton)
            {   
                callback.accept();
                this.dismiss();
            }
            else if (v == rejectButton)
            {
                callback.decline();
                this.dismiss();
            }
            else if (v == cancelButton)
            {
                callback.cancel();
                this.dismiss();
            }
        }
    }
}

Activity:

public class MyActivity extends ActionBarActivity implements ShowDialog
{
    ..

    @Override
    public void showDialog(DialogFragment dialogFragment)
    {
        FragmentManager fragmentManager = getSupportFragmentManager();
        dialogFragment.show(fragmentManager, "dialog");
    }
}

DialogFragment layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/dialogfragment_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:text="@string/example"/>

    <Button
        android:id="@+id/dialogfragment_acceptbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/dialogfragment_textview"
        android:text="@string/accept"
        />

    <Button
        android:id="@+id/dialogfragment_rejectbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_alignLeft="@+id/dialogfragment_acceptbtn"
        android:layout_below="@+id/dialogfragment_acceptbtn"
        android:text="@string/decline" />

     <Button
        android:id="@+id/dialogfragment_cancelbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_rejectbtn"
        android:layout_below="@+id/dialogfragment_rejectbtn"
        android:text="@string/cancel" />

     <Button
        android:id="@+id/dialogfragment_heightfixhiddenbtn"
        android:layout_width="200dp"
        android:layout_height="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_cancelbtn"
        android:layout_below="@+id/dialogfragment_cancelbtn"
        android:background="@android:color/transparent"
        android:enabled="false"
        android:text=" " />
</RelativeLayout>

As the name dialogfragment_heightfixhiddenbtn shows, I just couldn't figure out a way to fix that the bottom button's height was cut in half despite saying wrap_content, so I added a hidden button to be "cut" in half instead. Sorry for the hack.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 1
    +1 the reference set with `setTargetFragment()` is recreated correctly by the system when it restarts the Activity/Fragment set after rotation. So the reference will point to the new target automatically. – Richard Le Mesurier Sep 15 '14 at 09:55
  • I'd punch myself in the nose now for not using ButterKnife back in the day, though. Use ButterKnife, it makes your view handling much prettier. – EpicPandaForce Nov 28 '14 at 00:22
  • And you can use Otto to send event from Fragment to Activity, so that you don't need the interface attach magic. Otto example here: http://stackoverflow.com/a/28480952/2413303 – EpicPandaForce Mar 04 '15 at 18:48
  • @EpicPandaForce Please, can you add the "ShowDialog" Interface/Class? It's the only thing missing in your example. – ntrch Jul 28 '18 at 15:39
  • @ntrch it was `public interface ShowDialog { void showDialog(DialogFragment dialogFragment); }` – EpicPandaForce Jul 28 '18 at 17:44
  • @EpicPandaForce Of course, thanks. I thought I had issues because of the interface, but it was because of the app library instead of v4. Anyone who still encounters errors related to v4 imports - make sure that wherever you have FragmentManager and DialogFragment you're importing the same version. – ntrch Jul 29 '18 at 04:53
4

I am a beginner myself and I honestly couldn't find a satisfactory answer that I could understand or implement.

So here's an external link that I really helped me achieved what I wanted. It's very straight forward and easy to follow as well.

http://www.helloandroid.com/tutorials/how-display-custom-dialog-your-android-application

THIS WHAT I TRIED TO ACHIEVE WITH THE CODE:

I have a MainActivity that hosts a Fragment. I wanted a dialog to appear on top of the layout to ask for user input and then process the input accordingly. See a screenshot

Here's what the onCreateView of my fragment looks

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

    View rootView = inflater.inflate(R.layout.fragment_home_activity, container, false);

    Button addTransactionBtn = rootView.findViewById(R.id.addTransactionBtn);

    addTransactionBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Dialog dialog = new Dialog(getActivity());
            dialog.setContentView(R.layout.dialog_trans);
            dialog.setTitle("Add an Expense");
            dialog.setCancelable(true);

            dialog.show();

        }
    });

I hope it will help you

Let me know if there's any confusion. :)

Junaid Aziz
  • 609
  • 1
  • 7
  • 8
3
 public void showAlert(){


     AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
     LayoutInflater inflater = getActivity().getLayoutInflater();
     View alertDialogView = inflater.inflate(R.layout.test_dialog, null);
     alertDialog.setView(alertDialogView);

     TextView textDialog = (TextView) alertDialogView.findViewById(R.id.text_testDialogMsg);
     textDialog.setText(questionMissing);

     alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
         public void onClick(DialogInterface dialog, int which) {
             dialog.cancel();
         }
     });
     alertDialog.show();

}

where .test_dialog is of xml custom

Alex Zaraos
  • 6,443
  • 2
  • 26
  • 21
0
    public static void OpenDialog (Activity activity, DialogFragment fragment){

    final FragmentManager fm = ((FragmentActivity)activity).getSupportFragmentManager();

    fragment.show(fm, "tag");
}