Prevent DialogFragment from dismissing when button is clicked

I have a DialogFragment with a custom view which contains two text fields where the user is to input their username and password. When the positive button is clicked, I want to validate that the user actually did input something before dismissing the dialog.

public class AuthenticationDialog extends DialogFragment {

    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = getActivity().getLayoutInflater();
        builder.setView(inflater.inflate(R.layout.authentication_dialog, null))
            .setPositiveButton(getResources().getString(R.string.login), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // TODO
                }
            })
            .setNegativeButton(getResources().getString(R.string.reset), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // TODO
                }
            });

        return builder.create();
    }
}

So how can I prevent the dialog from dismissing? Is there some method I should override?

Answers


Override the default button handlers in OnStart() to do this.

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

@Override
public void onStart()
{
    super.onStart();    //super.onStart() is where dialog.show() is actually called on the underlying dialog, so we have to do it after this point
    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)
                            dismiss();
                        //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
                    }
                });
    }
}

See my answer here https://stackoverflow.com/a/15619098/579234 for more explanation and examples on other dialog types too.


This is the "sweetspot" solution of both Karakuri's and Sogger's answers. Karakuri was on the right track, however you can only get the button that way if is already shown (it's null otherwise, as stated in the comments). This is why Sogger's answer works, however I prefer the setup in the same method, which is onCreateDialog, and not additionally in onStart. The solution is to wrap the fetching of the buttons into the OnShowListener of the dialog.

public Dialog onCreateDialog(Bundle savedInstanceState) {
  AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
  // your dialog setup, just leave the OnClick-listeners empty here and use the ones below

  final AlertDialog dialog = builder.create();
  dialog.setOnShowListener(new DialogInterface.OnShowListener() {
    @Override
    public void onShow(final DialogInterface dialog) {
      Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
      positiveButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
          // TODO - call 'dismiss()' only if you need it
        }
      });
      Button negativeButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_NEGATIVE);
      // same for negative (and/or neutral) button if required
    }
  });

  return dialog;
}

Thanks to Luksprog, I was able to find a solution.

AuthenticationDialog.java:

public class AuthenticationDialog extends DialogFragment implements OnClickListener {

    public interface AuthenticationDialogListener {
        void onAuthenticationLoginClicked(String username, String password);
        void onAuthenticationResetClicked(String username);
    }

    private AuthenticationDialogListener mListener;

    private EditText mUsername;
    private EditText mPassword;
    private Button mReset;
    private Button mLogin;

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.authentication_dialog, container);
        this.getDialog().setTitle(R.string.login_title);

        mUsername = (EditText) view.findViewById(R.id.username_field);
        mPassword = (EditText) view.findViewById(R.id.password_field);
        mReset = (Button) view.findViewById(R.id.reset_button);
        mLogin = (Button) view.findViewById(R.id.login_button);

        mReset.setOnClickListener(this);
        mLogin.setOnClickListener(this);

        return view;
    }

    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the NoticeDialogListener so we can send events to the host
            mListener = (AuthenticationDialogListener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement AuthenticationDialogListener");
        }
    }

    public void onClick(View v) {
        if (v.equals(mLogin)) {
            if (mUsername.getText().toString().length() < 1 || !mUsername.getText().toString().contains("@")) {
                Toast.makeText(getActivity(), R.string.invalid_email, Toast.LENGTH_SHORT).show();
                return;
            } else if (mPassword.getText().toString().length() < 1) {
                Toast.makeText(getActivity(), R.string.invalid_password, Toast.LENGTH_SHORT).show();
                return;
            } else {
                mListener.onAuthenticationLoginClicked(mUsername.getText().toString(), mPassword.getText().toString());
                this.dismiss();
            }
        } else if (v.equals(mReset)) {
            mListener.onAuthenticationResetClicked(mUsername.getText().toString());
        }
    }
}

authentication_dialog.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <EditText
        android:id="@+id/username_field"
        android:inputType="textEmailAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="4dp"
        android:hint="@string/username"
        />
    <EditText
        android:id="@+id/password_field"
        android:inputType="textPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="12dp"
        android:fontFamily="sans-serif"
        android:hint="@string/password"
        />
    <View
        android:layout_width="fill_parent"
        android:layout_height="1dip"
        android:background="?android:attr/dividerVertical" 
        />
    <LinearLayout 
        style="?android:attr/buttonBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="0dp"
        android:measureWithLargestChild="true" >
        <Button 
            android:id="@+id/reset_button"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_height="wrap_content"
            android:layout_width="0dp"
            android:layout_weight="1.0"
            android:text="@string/reset"
            />
        <Button 
            android:id="@+id/login_button"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_height="wrap_content"
            android:layout_width="0dp"
            android:layout_weight="1.0"
            android:text="@string/login"
            />
    </LinearLayout>
</LinearLayout>

You could just pop up the dialog again. Or, you could keep the positive button disabled until there is input in both fields. This is easy enough if you are creating the layout in onCreateVew(). If you are using the AlertDialog.Builder class instead, you can get a handle to the button like so:

AlertDialog.Builder builder = new AlertDialog.Builder(context);
/* ... */
Dialog dialog = builder.create();
Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
/* now you can affect the button */

Need Your Help

How to close the new html <dialog> tag by clicking on its ::backdrop

html5 google-chrome dialog opera pseudo-element

I didn't find any built in solution or workaround for closing the html5 element by clicking on its background(::backdrop), although it's clearly a basic functionality.