Jelly Bean Datepickerdialog --- How to Cancel

Jelly Bean DatePickerDialog --- is there a way to cancel?

Note: Fixed as of Lollipop, source here. Automated class for use in clients (compatible with all Android versions) updated as well.

TL;DR: 1-2-3 dead easy steps for a global solution:

  1. Download this class.
  2. Implement OnDateSetListener in your activity (or change the class to suit your needs).
  3. Trigger the dialog with this code (in this sample, I use it inside a Fragment):

    Bundle b = new Bundle();
    b.putInt(DatePickerDialogFragment.YEAR, 2012);
    b.putInt(DatePickerDialogFragment.MONTH, 6);
    b.putInt(DatePickerDialogFragment.DATE, 17);
    DialogFragment picker = new DatePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getActivity().getSupportFragmentManager(), "frag_date_picker");

And that's all it takes! The reason I still keep my answer as "accepted" is because I still prefer my solution since it has a very small footprint in client code, it addresses the fundamental issue (the listener being called in the framework class), works fine across config changes and it routes the code logic to the default implementation in previous Android versions not plagued by this bug (see class source).

Original answer (kept for historical and didactic reasons):

Bug source

OK, looks like it's indeed a bug and someone else already filled it. Issue 34833.

I've found that the problem is possibly in DatePickerDialog.java. Where it reads:

private void tryNotifyDateSet() {
if (mCallBack != null) {
mDatePicker.clearFocus();
mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
}
}

@Override
protected void onStop() {
tryNotifyDateSet();
super.onStop();
}

I'd guess it could have been:

@Override
protected void onStop() {
// instead of the full tryNotifyDateSet() call:
if (mCallBack != null) mDatePicker.clearFocus();
super.onStop();
}

Now if someone can tell me how I can propose a patch/bug report to Android, I'd be glad to. Meanwhile, I suggested a possible fix (simple) as an attached version of DatePickerDialog.java in the Issue there.

Concept to avoid the bug

Set the listener to null in the constructor and create your own BUTTON_POSITIVE button later on. That's it, details below.

The problem happens because DatePickerDialog.java, as you can see in the source, calls a global variable (mCallBack) that stores the listener that was passed in the constructor:

    /**
* @param context The context the dialog is to run in.
* @param callBack How the parent is notified that the date is set.
* @param year The initial year of the dialog.
* @param monthOfYear The initial month of the dialog.
* @param dayOfMonth The initial day of the dialog.
*/
public DatePickerDialog(Context context,
OnDateSetListener callBack,
int year,
int monthOfYear,
int dayOfMonth) {
this(context, 0, callBack, year, monthOfYear, dayOfMonth);
}

/**
* @param context The context the dialog is to run in.
* @param theme the theme to apply to this dialog
* @param callBack How the parent is notified that the date is set.
* @param year The initial year of the dialog.
* @param monthOfYear The initial month of the dialog.
* @param dayOfMonth The initial day of the dialog.
*/
public DatePickerDialog(Context context,
int theme,
OnDateSetListener callBack,
int year,
int monthOfYear,
int dayOfMonth) {
super(context, theme);

mCallBack = callBack;
// ... rest of the constructor.
}

So, the trick is to provide a null listener to be stored as the listener, and then roll your own set of buttons (below is the original code from #1, updated):

    DatePickerDialog picker = new DatePickerDialog(
this,
null, // instead of a listener
2012, 6, 15);
picker.setCancelable(true);
picker.setCanceledOnTouchOutside(true);
picker.setButton(DialogInterface.BUTTON_POSITIVE, "OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Picker", "Correct behavior!");
}
});
picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Picker", "Cancel!");
}
});
picker.show();

Now it will work because of the possible correction that I posted above.

And since DatePickerDialog.java checks for a null whenever it reads mCallback (since the days of API 3/1.5 it seems --- can't check Honeycomb of course), it won't trigger the exception. Considering Lollipop fixed the issue, I'm not going to look into it: just use the default implementation (covered in the class I provided).

At first I was afraid of not calling the clearFocus(), but I've tested here and the Log lines were clean. So that line I proposed may not even be necessary after all, but I don't know.

Compatibility with previous API levels (edited)

As I pointed in the comment below, that was a concept, and you can download the class I'm using from my Google Drive account. The way I used, the default system implementation is used on versions not affected by the bug.

I took a few assumptions (button names etc.) that are suitable for my needs because I wanted to reduce boilerplate code in client classes to a minimum. Full usage example:

class YourActivity extends SherlockFragmentActivity implements OnDateSetListener

// ...

Bundle b = new Bundle();
b.putInt(DatePickerDialogFragment.YEAR, 2012);
b.putInt(DatePickerDialogFragment.MONTH, 6);
b.putInt(DatePickerDialogFragment.DATE, 17);
DialogFragment picker = new DatePickerDialogFragment();
picker.setArguments(b);
picker.show(getActivity().getSupportFragmentManager(), "fragment_date_picker");

Is there a way to use cancel in Android JellyBean TimePickerDialog?

Based on your comment, it should be easy enough to guard the OnTimeSetListener callback with a boolean to prevent your logic from running in the case 'Cancel' is clicked.

I had a quick play with your code, and the following seemed to work alright:

private boolean mIgnoreTimeSet = false;

final TimePickerDialog timeDlg = new TimePickerDialog(PreferencesActivity.this, PreferencesActivity.this, hour, min, true);

// Make the Set button
timeDlg.setButton(DialogInterface.BUTTON_POSITIVE, "Set", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mIgnoreTimeSet = false;
// only manually invoke OnTimeSetListener (through the dialog) on pre-ICS devices
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) timeDlg.onClick(dialog, which);
Toast.makeText(getApplicationContext(), "Set", Toast.LENGTH_SHORT).show();
}
});

// Set the Cancel button
timeDlg.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "Cancel", Toast.LENGTH_SHORT).show();
mIgnoreTimeSet = true;
dialog.cancel();
}
});

timeDlg.show();

@Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
if (mIgnoreTimeSet) return;
// get the values here - this will only run if 'Set' was clicked
}

For the sake of the example, I've assumed your PreferencesActivity implements OnTimeSetListener. Since onTimeSet(...) gets called after onClick(...), you can simply toggle a boolean that will determine whether you should do something with the values in the callback or just ignore them.

I have to agree it's not ideal, but at least functional. After the holidays I'll try to have another look in a 'better' solution.

One alternative would be to use reflection to get a handle on the internally used TimePicker widget, but that's more likely to break in future Android releases.


// Edit: Potential alternative? (not tested)

Second alternative, which might be a bit 'cleaner': extend TimePickerDialog and overwrite the (empty) public method:

public void onTimeChanged(TimePicker view, int hourOfDay, int minute)

In there, keep track of the user-set hour of the day and minute and implement two getters that return them. Then only get these values from the dialog if the positive button gets clicked, but just fall through for 'Cancel'. Basically you then won't need an OnTimeSetListener anymore, and hence it won't matter whether it is ever gets called or not.


// Edit2: Support for pre-ICS devices

After receiving a couple of remarks about the proposed solution not working on pre-ICS devices, I made a small change to the above code - even tough it wasn't within the scope of the original question.

The main difference between pre-ICS and post-ICS devices is that the OnTimeSetListener callback isn't automatically invoked after your dialog button's onClick() method(s) is run. A simple workaround for that is to call the onClick() method on the dialog, which will then call the listener. In the solution above, I've added a version code check, because otherwise your listener will end up being called twice on post-ICS devices, which may lead to undesired side-effects.

Tested and confirmed behaviour on Android 2.1-update1 and Android 4.2.2.

Android:DatePickerdialog with Cancel button

Jelly Bean DatePickerDialog --- is there a way to cancel? answer in the link gives you way to work around. I found this link trying to answer the one of the question regarding data picker.

Looks like it's a bug and someone else already filled it.http://code.google.com/p/android/issues/detail?id=34833. The answer in the link gives you way to work around.

Edit1

public static final int TIME_DIALOG_ID = 1;
public static final int DATE_DIALOG_ID = 2;

@Override
protected Dialog onCreateDialog(int id) {

switch (id) {
case DATE_DIALOG_ID:
// set date picker as current date
myDialog = new DatePickerDialog(this, datePickerListener, year, month,day);
break;

case TIME_DIALOG_ID:
//set time picker as current time
myDialog = new TimePickerDialog(this, timePickerListener, hour, minute,false);
break;

}

return myDialog;
}

TimePickerDialog and Jelly Bean, onTimeSet fires on cancel

OK, it looks like there's the similar bug for DatePicker here, but I had to modify my code by casting the Dialog to an AlertDialog before calling setButton()

((AlertDialog) d).setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Turn on your cancel flag here
}
});

android DatePickerDialog display only one button

I've finally found an easier solution. Here is the workaraound in my CustomDatePickerDialog which extends DatePickerDialog

public CustomDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear,int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
this.setButton(DatePickerDialog.BUTTON_POSITIVE, "OK",this);
this.setButton(DatePickerDialog.BUTTON_NEGATIVE, "",this); // hide cancel button

}

Dismiss DatePickerDialog on pressing back button

It looks like there is a bug with Jelybean where the Cancel button isn't working(& hence the back button). This is discussed in Jelly Bean DatePickerDialog --- is there a way to cancel?

David Cesarino, who reported the bug and workaround in the above post, posted his solution here and SO.

cavega slightly modified the above solution to allow initialization of the date in the DatePickerDialog to something other than today's date. Code can be found here. I used his solution and got it to work.

Update date in Android DatePickerDialog with Today button without closing dialog

By default click button calls also dismiss() method to close the DatePickerDialog. It applies also to the DialogInterface.BUTTON_NEUTRAL button which you use for 'Today'.

Probably the simplest way to avoid this is to override the DatePickerDialog.dismiss() method and skip calling super.dismiss() if the 'Today' button was clicked.
Here is my working code in Kotlin I use for the same purpose (Today button for the DatePickerDialog). I use skipDismiss flag to know when to skip dismissal.

val datePickerDialog = object : DatePickerDialog(
this@MainActivity,
settings.currentTheme,
DatePickerDialog.OnDateSetListener { _, chosenYear, chosenMonth, dayOfMonth ->
calendar.set(Calendar.YEAR, chosenYear)
calendar.set(Calendar.MONTH, chosenMonth)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
text_view_chosen_date.text = SimpleDateFormat("dd/MM/yyyy", Locale.US).format(calendar.time)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
) {
override fun dismiss() {
if (!skipDismiss) {
super.dismiss()
} else {
skipDismiss = false
}
}
}

And of course the 'Today' button listener should set the skipDismiss flag to true when called. Here is my code for that:

datePickerDialog.setButton(DialogInterface.BUTTON_NEUTRAL, "Today") { dialog, which ->
val today = Calendar.getInstance()
(dialog as DatePickerDialog).updateDate(
today.get(Calendar.YEAR),
today.get(Calendar.MONTH),
today.get(Calendar.DAY_OF_MONTH)
)
skipDismiss = true;
}


Related Topics



Leave a reply



Submit