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:
- Download this class.
- Implement
OnDateSetListener
in your activity (or change the class to suit your needs). 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
Android Failed to Start Emulator:Cannot Run Program
Multi Flavor App Based on Multi Flavor Library in Android Gradle
Open an Image Using Uri in Android's Default Gallery Image Viewer
Cannot Load Library: Reloc_Library[1285]: Cannot Locate 'Rand'
Windowsoftinputmode="Adjustresize" Not Working with Translucent Action/Navbar
How to Handle Cookies in Httpurlconnection Using Cookiemanager
Android 2.2 Mediaplayer Is Working Fine with One Shoutcast Url But Not with the Other One
Android Ratingbar Change Star Colors
How to Apply a Style to All Buttons of an Android Application
Android Getdefaultsharedpreferences
How to Get Higher Precision of "Cpu%" Than That from Top Command
Detect from Browser If a Specific Application Is Installed in Android
Recognizing Multiple Keywords Using Pocketsphinx
Android SQLite Db When to Close
How to Capture an Image and Store It with the Native Android Camera