46

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?

Groppe
  • 3,819
  • 12
  • 44
  • 68
  • *how can I prevent the dialog from dismissing?* - don't use the dialog's default `Buttons`(the one you set with `setPositiveButton` etc). Set your own `Buttons` for dismissing the dialog and implement the desired logic in their `OnClickListeners`. You should post the code for your `DialogFragment`. – user Dec 06 '12 at 15:29
  • @Luksprog Code posted. When I tried what you suggested, getting references to the elements of my custom view from within onCreateDialog always returns null. – Groppe Dec 06 '12 at 18:25
  • At that point the Dialog is not rendered on the screen. Instead try to inflate the layout `R.layout.authentication_dialog` into a `View` reference and then search for the `Buttons`: `View v = inflater.inflate(R.layout.authentication_dialog); Button b = (Button) v.findviewById(R.id.the_btn_id);`. – user Dec 06 '12 at 18:36
  • @Luksprog Based on what you've suggested, I've found a solution. I'm going to post my code in an answer, but if you want to post an answer and reference mine, I'll give you the correct answer. – Groppe Dec 06 '12 at 19:30
  • The code from the comment above didn't worked? – user Dec 06 '12 at 19:44
  • Yeah, it did. I just posted my entire dialog class for anyone to use. – Groppe Dec 06 '12 at 19:48
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/20708/discussion-between-groppe-and-luksprog) – Groppe Dec 06 '12 at 19:49

4 Answers4

74

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.

Community
  • 1
  • 1
Sogger
  • 15,962
  • 6
  • 43
  • 40
  • 1
    This doesn't work for me. `d.getButton(Dialog.BUTTON_POSITIVE)` returns null. – Travis Christian Apr 18 '13 at 18:04
  • 1
    @Travis Christian You must be targeting an older version of Android. I ran into this earlier too, I have updated my answer so it now works on older versions. You need to pass a blank click listener in to the first call. – Sogger Apr 19 '13 at 15:46
  • 1
    Interesting, thanks. I'm targeting 15 so this must have been a recent change. – Travis Christian Apr 19 '13 at 19:38
  • 1
    I guess I am just assuming it was an issue with old vs new, since it was working on a newer phone (ICS I think), but I had an old Gigngerbread phone it wasn't working on without defining the blank listener. Regardless, since adding the blank listener it has been working on both. – Sogger Apr 22 '13 at 16:29
  • It helped, 10x. But very weird. Isn't it a bug? – trickbz Jan 07 '15 at 02:36
12

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;
}
Blacklight
  • 3,809
  • 2
  • 33
  • 39
  • I apologize for the downvote, but this method did not trigger the new onClick defined in setOnShowListener for me (tried on Lollipop). Have you tested this? – Abraham Philip Aug 05 '15 at 22:30
  • Of course I tested and used this multiple times, and obviously it worked not only for me. Before you downvote a months old answer you should maybe think about the fact that this was posted long before Lollipop was released... – Blacklight Aug 06 '15 at 05:22
  • Hmm. Interesting point. Having taken a look at both http://meta.stackoverflow.com/q/265749/3000919 and http://meta.stackoverflow.com/q/265749/3000919, the consensus seems to be that such answers should ideally be edited noting it's obselete nature, but left intact for legacy system support. However, in this case, this answer's usefulness as a "legacy" solution is not clear since the top voted answer works on all versions (as of now) . If you added a disclaimer on top saying this doesn't work on later versions of Android, I'd be happy to remove my downvote, so as to not deny you (continued) – Abraham Philip Aug 08 '15 at 06:56
  • (continued from previous comment) the appreciation you deserved for providing an answer that worked at the time. Also, I seem to have posted the same link twice in my previous comment. This is the question I was referring to: http://meta.stackexchange.com/q/11705 – Abraham Philip Aug 08 '15 at 07:02
  • @AbrahamPhilip In order to be able to verify or edit my answer, I set up a test project solely for this purpose. My solution still works and this answer is still valid, even on the latest SDK (API level 22). Please check your solution, because apparently you are doing something wrong. – Blacklight Aug 10 '15 at 11:22
6

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>
Groppe
  • 3,819
  • 12
  • 44
  • 68
-2

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 */
Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • 1
    For some reason, if I try to call getButton on my dialog after I've called builder.create() but before I show it, it returns null. – Groppe Dec 06 '12 at 18:19