I liked David Cesarino's answer above, but wanted something that was a drop in replacement for the broken dialog and would work on any dialog that might be missing cancel / have incorrect cancel behavior. Here are derived classes for DatePickerDialog / TimePickerDialog that should work as drop in replacements. These are not custom views. It uses the system dialog, but just changes the cancel / back button behavior to work as expected.
This should work on API level 3 and higher. So, basically any version of Android (I tested it on jellybean and lollipop specifically).
DatePickerDialog:
package snappy_company_name_here;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.DatePicker;
/**
* This is a modified version of DatePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
* kitkat date pickers.
*
* Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
* Here is an SO post with a bunch of details: https://mcmap.net/q/157843/-jelly-bean-datepickerdialog-is-there-a-way-to-cancel
*
* @author stuckj, created on 5/5/15.
*/
public class DatePickerDialog extends android.app.DatePickerDialog implements DialogInterface.OnClickListener
{
final CallbackHelper callbackHelper;
// NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
private static class CallbackHelper implements OnDateSetListener
{
private final OnDateSetListener callBack;
private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...
// NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
public CallbackHelper(final OnDateSetListener callBack)
{
this.callBack = callBack;
}
@Override
public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth)
{
if (!dialogButtonPressHandled && (callBack != null))
{
callBack.onDateSet(view, year, monthOfYear, dayOfMonth);
}
}
}
/**
* Sets the positive and negative buttons to use the dialog callbacks we define.
*/
private void setButtons(final Context context)
{
setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
}
@Override
public void onClick(final DialogInterface dialog, final int which)
{
// ONLY call the super method in the positive case...
if (which == DialogInterface.BUTTON_POSITIVE)
{
super.onClick(dialog, which);
}
callbackHelper.dialogButtonPressHandled = true;
}
@Override
public void onBackPressed()
{
getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
}
// Need this so we can both pass callbackHelper to the super class and save it off as a variable.
private DatePickerDialog(final Context context,
final OnDateSetListener callBack,
final int year,
final int monthOfYear,
final int dayOfMonth,
final CallbackHelper callbackHelper)
{
super(context, callbackHelper, year, monthOfYear, dayOfMonth);
this.callbackHelper = callbackHelper;
setButtons(context);
}
/**
* @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(final Context context,
final OnDateSetListener callBack,
final int year,
final int monthOfYear,
final int dayOfMonth)
{
this(context, callBack, year, monthOfYear, dayOfMonth, new CallbackHelper(callBack));
}
// Need this so we can both pass callbackHelper to the super class and save it off as a variable.
private DatePickerDialog(final Context context, final int theme, final OnDateSetListener listener, final int year,
final int monthOfYear, final int dayOfMonth, final CallbackHelper callbackHelper)
{
super(context, theme, callbackHelper, year, monthOfYear, dayOfMonth);
this.callbackHelper = callbackHelper;
setButtons(context);
}
/**
* @param context The context the dialog is to run in.
* @param theme the theme to apply to this dialog
* @param listener 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(final Context context, final int theme, final OnDateSetListener listener, final int year,
final int monthOfYear, final int dayOfMonth)
{
this(context, theme, listener, year, monthOfYear, dayOfMonth, new CallbackHelper(listener));
}
}
TimePickerDialog:
package snappy_company_name_here;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.TimePicker;
/**
* This is a modified version of TimePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
* kitkat date pickers.
*
* Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
* Here is an SO post with a bunch of details: https://mcmap.net/q/157843/-jelly-bean-datepickerdialog-is-there-a-way-to-cancel
*
* @author stuckj, created on 5/5/15.
*/
public class TimePickerDialog extends android.app.TimePickerDialog implements DialogInterface.OnClickListener
{
final CallbackHelper callbackHelper;
// NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
private static class CallbackHelper implements OnTimeSetListener
{
private final OnTimeSetListener callBack;
private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...
// NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
public CallbackHelper(final OnTimeSetListener callBack)
{
this.callBack = callBack;
}
@Override
public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute)
{
if (!dialogButtonPressHandled && (callBack != null))
{
callBack.onTimeSet(view, hourOfDay, minute);
}
}
}
/**
* Sets the positive and negative buttons to use the dialog callbacks we define.
*/
private void setButtons(final Context context)
{
setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
}
@Override
public void onClick(final DialogInterface dialog, final int which)
{
// ONLY call the super method in the positive case...
if (which == DialogInterface.BUTTON_POSITIVE)
{
super.onClick(dialog, which);
}
callbackHelper.dialogButtonPressHandled = true;
}
@Override
public void onBackPressed()
{
getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
}
// Need this so we can both pass callbackHelper to the super class and save it off as a variable.
private TimePickerDialog(final Context context,
final OnTimeSetListener callBack,
final int hourOfDay, final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
{
super(context, callbackHelper, hourOfDay, minute, is24HourView);
this.callbackHelper = callbackHelper;
setButtons(context);
}
/**
* @param context Parent.
* @param callBack How parent is notified.
* @param hourOfDay The initial hour.
* @param minute The initial minute.
* @param is24HourView Whether this is a 24 hour view, or AM/PM.
*/
public TimePickerDialog(final Context context,
final OnTimeSetListener callBack,
final int hourOfDay, final int minute, final boolean is24HourView)
{
this(context, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
}
// Need this so we can both pass callbackHelper to the super class and save it off as a variable.
private TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
{
super(context, theme, callbackHelper, hourOfDay, minute, is24HourView);
this.callbackHelper = callbackHelper;
setButtons(context);
}
/**
* @param context Parent.
* @param theme the theme to apply to this dialog
* @param callBack How parent is notified.
* @param hourOfDay The initial hour.
* @param minute The initial minute.
* @param is24HourView Whether this is a 24 hour view, or AM/PM.
*/
public TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
final int minute, final boolean is24HourView)
{
this(context, theme, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
}
}
Done
button was a click outside the dialog, thereby getting only one alarm anyway? It's a rough hack, but very easy. – HengeloonStop
behavior), but also have superior readability in my clientS. I saved myself future time. – Irick