How to prevent a dialog from closing when a button is clicked How to prevent a dialog from closing when a button is clicked android android

How to prevent a dialog from closing when a button is clicked


EDIT: This only works on API 8+ as noted by some of the comments.

This is a late answer, but you can add an onShowListener to the AlertDialog where you can then override the onClickListener of the button.

final AlertDialog dialog = new AlertDialog.Builder(context)        .setView(v)        .setTitle(R.string.my_title)        .setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick        .setNegativeButton(android.R.string.cancel, null)        .create();dialog.setOnShowListener(new DialogInterface.OnShowListener() {    @Override    public void onShow(DialogInterface dialogInterface) {        Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                // TODO Do something                //Dismiss once everything is OK.                dialog.dismiss();            }        });    }});dialog.show();


Here are some solutions for all types of dialogs including a solution for AlertDialog.Builder that will work on all API levels (works below API 8, which the other answer here does not). There are solutions for AlertDialogs using AlertDialog.Builder, DialogFragment, and DialogPreference.

Below are the code examples showing how to override the default common button handler and prevent the dialog from closing for these different forms of dialogs. All the examples show how to prevent the positive button from closing the dialog.

Note: A description of how the dialog closing works under the hood for the base android classes and why the following approaches are chosen follows after the examples, for those who want more details


AlertDialog.Builder - Change default button handler immediately after show()

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());builder.setMessage("Test for preventing dialog close");builder.setPositiveButton("Test",         new DialogInterface.OnClickListener()        {            @Override            public void onClick(DialogInterface dialog, int which)            {                //Do nothing here because we override this button later to change the close behaviour.                 //However, we still need this because on older versions of Android unless we                 //pass a handler the button doesn't get instantiated            }        });final AlertDialog dialog = builder.create();dialog.show();//Overriding the handler immediately after show is probably a better approach than OnShowListener as described belowdialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()      {                      @Override          public void onClick(View v)          {              Boolean wantToCloseDialog = false;              //Do stuff, possibly set wantToCloseDialog to true then...              if(wantToCloseDialog)                  dialog.dismiss();              //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.          }      });      

DialogFragment - override onResume()

@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState){    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());    builder.setMessage("Test for preventing dialog close");    builder.setPositiveButton("Test",         new DialogInterface.OnClickListener()        {            @Override            public void onClick(DialogInterface dialog, int which)            {                //Do nothing here because we override this button later to change the close behaviour.                 //However, we still need this because on older versions of Android unless we                 //pass a handler the button doesn't get instantiated            }        });    return builder.create();}//onStart() is where dialog.show() is actually called on //the underlying dialog, so we have to do it there or //later in the lifecycle.//Doing it in onResume() makes sure that even if there is a config change //environment that skips onStart then the dialog will still be functioning//properly after a rotation.@Overridepublic void onResume(){    super.onResume();        final AlertDialog d = (AlertDialog)getDialog();    if(d != null)    {        Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE);        positiveButton.setOnClickListener(new View.OnClickListener()                {                    @Override                    public void onClick(View v)                    {                        Boolean wantToCloseDialog = false;                        //Do stuff, possibly set wantToCloseDialog to true then...                        if(wantToCloseDialog)                            d.dismiss();                        //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.                    }                });    }}

DialogPreference - override showDialog()

@Overrideprotected void onPrepareDialogBuilder(Builder builder){    super.onPrepareDialogBuilder(builder);    builder.setPositiveButton("Test", this);   //Set the button here so it gets created}@Overrideprotected void showDialog(Bundle state){           super.showDialog(state);    //Call show on default first so we can override the handlers    final AlertDialog d = (AlertDialog) getDialog();    d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()            {                            @Override                public void onClick(View v)                {                    Boolean wantToCloseDialog = false;                    //Do stuff, possibly set wantToCloseDialog to true then...                    if(wantToCloseDialog)                        d.dismiss();                    //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.                }            });}

Explanation of approaches:

Looking through Android source code the AlertDialog default implementation works by registering a common button handler to all the actual buttons in OnCreate(). When a button is clicked the common button handler forwards the click event to whatever handler you passed in setButton() then calls dismisses the dialog.

If you wish to prevent a dialog box from closing when one of these buttons is pressed you must replace the common button handler for the actual view of the button. Because it is assigned in OnCreate(), you must replace it after the default OnCreate() implementation is called. OnCreate is called in the process of the show() method. You could create a custom Dialog class and override OnCreate() to call the super.OnCreate() then override the button handlers, but if you make a custom dialog you don't get the Builder for free, in which case what is the point?

So, in using a dialog the way it is designed but with controlling when it is dismissed, one approach is to call dialog.Show() first, then obtain a reference to the button using dialog.getButton() to override the click handler. Another approach is to use setOnShowListener() and implement finding the button view and replacing the handler in the OnShowListener. The functional difference between the two is 'almost' nill, depending on what thread originally creates the dialog instance. Looking through the source code, the onShowListener gets called by a message posted to a handler running on the thread that created that dialog. So, since your OnShowListener is called by a message posted on the message queue it is technically possible that calling your listener is delayed some time after show completes.

Therefore, I believe the safest approach is the first: to call show.Dialog(), then immediately in the same execution path replace the button handlers. Since your code that calls show() will be operating on the main GUI thread, it means whatever code you follow show() with will be executed before any other code on that thread, whereas the timing of the OnShowListener method is at the mercy of the message queue.


An alternate solution

I would like to present an alternate answer from a UX perspective.

Why would you want to prevent a dialog from closing when a button is clicked? Presumably it is because you have a custom dialog in which the user hasn't made a choice or hasn't completely filled everything out yet. And if they are not finished, then you shouldn't allow them to click the positive button at all. Just disable it until everything is ready.

The other answers here give lots of tricks for overriding the positive button click. If that were important to do, wouldn't Android have made a convenient method to do it? They didn't.

Instead, the Dialogs design guide shows an example of such a situation. The OK button is disabled until the user makes a choice. No overriding tricks are necessary at all. It is obvious to the user that something still needs to be done before going on.

enter image description here

How to disable the positive button

See the Android documentation for creating a custom dialog layout. It recommends that you place your AlertDialog inside a DialogFragment. Then all you need to do is set listeners on the layout elements to know when to enable or disable the positive button.

The positive button can be disabled like this:

AlertDialog dialog = (AlertDialog) getDialog();dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);

Here is an entire working DialogFragment with a disabled positive button such as might be used in the image above.

import android.support.v4.app.DialogFragment;import android.support.v7.app.AlertDialog;public class MyDialogFragment extends DialogFragment {    @Override    public Dialog onCreateDialog(Bundle savedInstanceState) {        // inflate the custom dialog layout        LayoutInflater inflater = getActivity().getLayoutInflater();        View view = inflater.inflate(R.layout.my_dialog_layout, null);        // add a listener to the radio buttons        RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.radio_group);        radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(RadioGroup radioGroup, int i) {                // enable the positive button after a choice has been made                AlertDialog dialog = (AlertDialog) getDialog();                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);            }        });        // build the alert dialog        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());        builder.setView(view)                .setPositiveButton("OK", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int id) {                        // TODO: use an interface to pass the user choice back to the activity                    }                })                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {                    public void onClick(DialogInterface dialog, int id) {                        MyDialogFragment.this.getDialog().cancel();                    }                });        return builder.create();    }    @Override    public void onResume() {        super.onResume();        // disable positive button by default        AlertDialog dialog = (AlertDialog) getDialog();        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);    }}

The custom dialog can be run from an activity like this:

MyDialogFragment dialog = new MyDialogFragment();dialog.show(getFragmentManager(), "MyTag");

Notes

  • For the sake of brevity, I omitted the communication interface to pass the user choice info back to the activity. The documentation shows how this is done, though.
  • The button is still null in onCreateDialog so I disabled it in onResume. This has the undesireable effect of disabling the it again if the user switches to another app and then comes back without dismissing the dialog. This could be solved by also unselecting any user choices or by calling a Runnable from onCreateDialog to disable the button on the next run loop.

    view.post(new Runnable() {    @Override    public void run() {        AlertDialog dialog = (AlertDialog) getDialog();        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);    }});

Related