Jelly Bean DatePickerDialog --- is there a way to cancel?
Asked Answered
I

19

148

--- Note to moderators: Today (July 15), I've noticed that someone already faced this problem here. But I'm not sure if it's appropriate to close this as a duplicate, since i think I provided a much better explanation of the issue. I'm not sure if I should edit the other question and paste this content there, but I'm not comfortable changing someone else's question too much. ---

I have something weird here.

I don't think the problem depends on which SDK you build against. The device OS version is what matters.

Problem #1: inconsistency by default

DatePickerDialog was changed (?) in Jelly Bean and now only provides a Done button. Previous versions included a Cancel button, and this may affect user experience (inconsistency, muscle memory from previous Android versions).

Replicate: Create a basic project. Put this in onCreate:

DatePickerDialog picker = new DatePickerDialog(
        this,
        new OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker v, int y, int m, int d) {
                Log.d("Picker", "Set!");
            }
        },
        2012, 6, 15);
picker.show();

Expected: A Cancel button to appear in the dialog.

Current: A Cancel button does not appear.

Screenshots: 4.0.3 (OK) and 4.1.1 (possibly wrong?).

Problem #2: wrong dismiss behavior

Dialog calls whichever listener it should call indeed, and then always calls OnDateSetListener listener. Canceling still calls the set method, and setting it calls the method twice.

Replicate: Use #1 code, but add code below (you'll see this solves #1, but only visually/UI):

picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });

Expected:

  • Pressing the BACK key or clicking outside the dialog should do nothing.
  • Pressing "Cancel" should print Picker Cancel!.
  • Pressing "Set" should print Picker Set!.

Current:

  • Pressing the BACK key or clicking outside the dialog prints Picker Set!.
  • Pressing "Cancel" prints Picker Cancel! and then Picker Set!.
  • Pressing "Set" prints Picker Set! and then Picker Set!.

Log lines showing the behavior:

07-15 12:00:13.415: D/Picker(21000): Set!

07-15 12:00:24.860: D/Picker(21000): Cancel!
07-15 12:00:24.876: D/Picker(21000): Set!

07-15 12:00:33.696: D/Picker(21000): Set!
07-15 12:00:33.719: D/Picker(21000): Set!

Other notes and comments

  • Wrapping it around a DatePickerFragment doesn't matter. I simplified the problem for you, but I've tested it.
Irick answered 12/7, 2012 at 2:25 Comment(12)
Congratulations, you seem to have found a bug in Android. You can report it here.Dentate
Very well written bug report. I can understand it completely without having to test run the code.Sillsby
I urge everyone to vote this issue up! Issue 34833Calorimeter
Couldn't you just override the button function to act like it was dismissed due to touch outside the dialog?Hengelo
@Torcellite not sure what you mean. The behavior is not dependent on how you dismiss the dialog (i.e., if by clicking outside or using a button, or back), since the listener is called whenever the dialog is stopped. See my answer below, when I quote the Android sources. Thus, the point is to tell the framework to not call the listener somehow, and the framework will not call only when it detects a null listener, hence why the class I wrote selectively pass the listener in the constructor or later on, depending on which API you are. Transparently to the client, of course.Irick
My point is, instead of working so much why couldn't you just make Android think that the click on the Done button was a click outside the dialog, thereby getting only one alarm anyway? It's a rough hack, but very easy.Hengelo
@Torcellite Oh, I see... If you fix "Back" behavior (either a dialog back or the system back), I guess you could. But, personally, I think that, by the time I filled all my client classes with the same calls everywhere just to "hack around" the bug in a very obscure manner ("why does this 'Done' button do nothing?"), it would be minimal effort to just put everything in a reusable class that can effortlessly replace the framework one, to begin with. Not only I just fixed the fundamental issue (onStop behavior), but also have superior readability in my clientS. I saved myself future time.Irick
Just to add another problem using TimePicker I have found, is the difference in buttons position between 4.0.3 version and the old 2.3.3 where the cancel button is on the right and the set button on the left. I haven't tested in version 3.x, but I think this hadn't been fixed till 4.0.x version. As has been posted in the answer, this fix has been broken in the 4.1.x setting one "Set" button and no possibility to cancel or dismiss.Platelayer
@XavierEgea Not sure I understand what you mean... I thought they purposely changed the button position for usability reasons (well, at least for right handed people I guess). About 2.x and below, it's better to not mess with it and rely on the system defaults where muscle memory is a factor to consider for the user.Irick
@DavidCesarino After my comment I have tested a little bit more on 2.x devices and I realised (correct please if I'm wrong) that in old versions the default cancel button was on the right, at least in the System applications that I've tested. So, you are right, it's better to rely on System Defaults to avoid changing completely the application. ThanksPlatelayer
No problem! That's what I meant as well: before, it was Ok-Cancel, like in desktop systems. After ICS, it's Cancel-OK for usability reasons. Additionally, let's remember that this applies to all dialogs if you use the standard POSITIVE and NEGATIVE identifiers. It is not exclusive to DatePicker or TimePicker dialogs.Irick
Bug is still open after 2 years...unbelievable.Renaud
N
115

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");
Neologism answered 12/7, 2012 at 2:25 Comment(12)
Awesome job on the research! Sad that it was necessary, but awesome nonetheless.Fibrosis
Very nice! I've been hammering my head against the wall on this issue that was updating my textfields and calling/not calling the correct callbacks. Thank you! I wonder if the suggested workaround is "future proof" or if you think it will cause problems when the bug is fixed?Scarify
Good question. I don't think anyone except Google can guarantee anything. I still think they should (and will) revert back to the "up-to-ICS" behavior at some point in the future. About future-proofing, I can only see the null listener being the possible point of failure. But considering legacy support for the API, I don't think they would (ever?) remove that null check, which in case you'd be fine. Please note, however, that this is a minimal example for a working solution. I'm NOT using it in production code, and I've made my own DatePickerDialogFragment since then.Irick
Btw, you can get what I'm currently using here.Irick
@DavidCesarino Nice workaround, but how do you get the date on an Android 2.x device ? (there is no getDatePicker() method on the DatePickerDialog)Lenes
@RomainGuidoux see updated answer at the end. The class in the link has the smarts to only call that method in jelly bean. On anything below it will bypass that and use the default system implementation, routing the system call for ondateset to your activity. It's just that in jelly bean it takes additional measures (avoiding the bug) before routing the callback, and that involves calling that honeycomb+ method. But again, only in JB.Irick
Awesome work here. I don't have the words for how ridiculous this issue is.Squish
I have on confusion now how can I get the selected Month, Day and Year in onClick method of 'Set' ?Morra
@AdilMalik You mean in the class I'm using? It routes the selected data to your Activity through the onDateSet call. As you can see, the Activity in which you use the dialog implements onDateSetListener. Of course, you're free to change it, but I think that way it works well (without the need to keep track of changes and everything).Irick
How about TimePickerDialog ? It seems like TimePickerDialog does not have a getTimePicker()Lactobacillus
@Lactobacillus I know. I didn't think about it yet because I don't need it.Irick
For TimePickerDialog there is another workaround https://mcmap.net/q/157843/-jelly-bean-datepickerdialog-is-there-a-way-to-cancelCuddy
L
16

I'm gonna add my own riff on the solution David Cesarino posted, in case you're not using Fragments, and want an easy way to fix it in all versions (2.1 thru 4.1):

public class FixedDatePickerDialog extends DatePickerDialog {
  //I use a Calendar object to initialize it, but you can revert to Y,M,D easily
  public FixedDatePickerDialog(Calendar dateToShow, Context context, OnDateSetListener callBack) {
    super(context, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  public FixedDatePickerDialog(Calendar dateToShow, Context context, int theme,
    OnDateSetListener callBack) {
    super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  private void initializePicker(final OnDateSetListener callback) {
    try {
      //If you're only using Honeycomb+ then you can just call getDatePicker() instead of using reflection
      Field pickerField = DatePickerDialog.class.getDeclaredField("mDatePicker");
      pickerField.setAccessible(true);
      final DatePicker picker = (DatePicker) pickerField.get(this);
      this.setCancelable(true);
      this.setButton(DialogInterface.BUTTON_NEGATIVE, getContext().getText(android.R.string.cancel), (OnClickListener) null);
      this.setButton(DialogInterface.BUTTON_POSITIVE, getContext().getText(android.R.string.ok),
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                picker.clearFocus(); //Focus must be cleared so the value change listener is called
                callback.onDateSet(picker, picker.getYear(), picker.getMonth(), picker.getDayOfMonth());
              }
          });
    } catch (Exception e) { /* Reflection probably failed*/ }
  }
}
Latea answered 10/9, 2012 at 3:48 Comment(6)
Just remembering: if you're hardwiring the button names to ok and cancel, it's probably better to use the standard android.R.string.ok and android.R.string.cancel fields, instead of user's own. And thanks for the reply.Irick
Ah, nice, I haven't noticed they provided OK and Cancel text. Thanks!Latea
getting nullpointer exception at super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));Pessa
Errr... are you passing a null dateToShow? The other null there is actually the "fix" so that should be there. Which version are you on?Latea
can you please let me know which import did you have for Field? I am having 6 options and none of them worked.Morra
It's the reflection Field, so import java.lang.reflect.FieldLatea
I
8

Until the bug will be fixed I suggest not to use DatePickerDialog or TimePickerDialog. Use custom made AlertDialog with TimePicker/DatePicker widget;

Change TimePickerDialog with;

    final TimePicker timePicker = new TimePicker(this);
    timePicker.setIs24HourView(true);
    timePicker.setCurrentHour(20);
    timePicker.setCurrentMinute(15);

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", timePicker.getCurrentHour() + ":"
                            + timePicker.getCurrentMinute());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(timePicker).show();

Change DatePickerDialog with;

    final DatePicker datePicker = new DatePicker(this);
    datePicker.init(2012, 10, 5, null);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        datePicker.setCalendarViewShown(false);
    }

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", datePicker.getYear() + " "
                            + (datePicker.getMonth() + 1) + " "
                            + datePicker.getDayOfMonth());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(datePicker).show();
Imaginative answered 3/3, 2013 at 21:24 Comment(0)
V
4

The one for TimePicker based on the solution by David Cesarino , "TL;DR: 1-2-3 dead easy steps for a global solution"

TimePickerDialog does not provide the functionality like DatePickerDialog.getDatePicker. So, OnTimeSetListener listener has to be provided. Just to keep the similarity with DatePicker workaround solution, I have maintained the old mListener concept. You can change it if you need to.

Calling and Listener is same as original solution. Just include

import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;

extend parent class,

... implements OnDateSetListener, OnTimeSetListener

Implement

 @Override
 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
 ...
 }

example calling

    Calendar cal = Calendar.getInstance();
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int minute = cal.get(Calendar.MINUTE);


    Bundle b = new Bundle();
    b.putInt(TimePickerDialogFragment.HOUR, hour);
    b.putInt(TimePickerDialogFragment.MINUTE, minute);

    DialogFragment picker = new TimePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getSupportFragmentManager(), "frag_time_picker");

(Updated to handle cancel)

public class TimePickerDialogFragment extends DialogFragment {

    public static final String HOUR = "Hour";
    public static final String MINUTE = "Minute";

    private boolean isCancelled = false; //Added to handle cancel
    private TimePickerDialog.OnTimeSetListener mListener;

    //Added to handle parent listener
    private TimePickerDialog.OnTimeSetListener mTimeSetListener = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            if (!isCancelled)
            {
                mListener.onTimeSet(view,hourOfDay,minute);
            }
        }
    };
    //
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mListener = (TimePickerDialog.OnTimeSetListener) activity;
    }

    @Override
    public void onDetach() {
        this.mListener = null;
        super.onDetach();
    }

    @TargetApi(11)
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Bundle b = getArguments();
        int h = b.getInt(HOUR);
        int m = b.getInt(MINUTE);

        final TimePickerDialog picker = new TimePickerDialog(getActivity(), getConstructorListener(), h, m,DateFormat.is24HourFormat(getActivity()));

        //final TimePicker timePicker = new TimePicker(getBaseContext());
        if (hasJellyBeanAndAbove()) {
            picker.setButton(DialogInterface.BUTTON_POSITIVE,
                    getActivity().getString(android.R.string.ok),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = false; //Cancel flag, used in mTimeSetListener
                        }
                    });
            picker.setButton(DialogInterface.BUTTON_NEGATIVE,
                    getActivity().getString(android.R.string.cancel),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = true; //Cancel flag, used in mTimeSetListener
                        }
                    });
        }
        return picker;
    }
    private boolean hasJellyBeanAndAbove() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    private TimePickerDialog.OnTimeSetListener getConstructorListener() {
        return hasJellyBeanAndAbove() ? mTimeSetListener : mListener; //instead of null, mTimeSetListener is returned.
    }
}
Vuong answered 27/11, 2013 at 13:14 Comment(1)
It is not working for me when trying it on s3 API 18Mask
C
3

My simple solution. When you want to get it firing again just run "resetFired" (say when opening the dialog again).

private class FixedDatePickerDialogListener implements DatePickerDialog.OnDateSetListener{
    private boolean fired;

    public void resetFired(){
        fired = false;
    }

    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (fired) {
            Log.i("DatePicker", "Double fire occurred.");
            return;//ignore and return.
        } 
        //put your code here to handle onDateSet
        fired = true;//first time fired 
    }
}
Crampon answered 4/10, 2012 at 22:58 Comment(7)
@Mask your code edit was incorrect but I have edited it to make it more clear. There is no need to add a "isJellyBeanOrAbove()" method, there is no advantage, just adds unnecessary complexity. Calling resetFired is cheap.Crampon
the code here is incorrect, because onDateSet will be called once when the dialog is being closed by any means, and if that mean was to set the time then it will be fired again, so we need to catch the second call, not the first one as you did,Mask
Concerning isJellyBeanOrAbove(), versions lower than Jellybean don't have the bug that this whole question is all about, & considering we want to catch the second call, the code won't run unless we make this check, believe me, I've tried my code on Emulators & real devices (with different versions) several times & it is working like a charmMask
I'm not sure if that is worth a downvote. I posted this 2 years ago, this has been in our commercial application since then working just fine. No bugs reported by our QA teams or our thousands of users. This is meant to be a simple answer that people can extend. Call "resetFired" when you want it to fire again.Crampon
In our app "isJellyBeanOrAbove" is not needed. The app will just work fine in all versions if you call "resetFired" in the correct areas.Crampon
hi but this works for first time. how to handle for second time when user not selected by clicking done button in date and clicked outside of date picker dialog?Centrality
By calling resetFired when you open the dialog again.Crampon
C
3

In case anyone wants a quick workaround, here's the code I used:

public void showCustomDatePicker () {

final DatePicker mDatePicker = (DatePicker) getLayoutInflater().
        inflate(R.layout.date_picker_view, null);
//Set an initial date for the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
//Set the date now
mDatePicker.updateDate(year, month, day);

//create the dialog
AlertDialog.Builder mBuilder = new Builder(this);
//set the title
mBuilder.setTitle(getString(R.string.date_picker_title))
    //set our date picker
    .setView(mDatePicker)
    //set the buttons 
.setPositiveButton(android.R.string.ok, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        //whatever method you choose to handle the date changes
            //the important thing to know is how to retrieve the data from the picker
        handleOnDateSet(mDatePicker.getYear(), 
                mDatePicker.getMonth(), 
                mDatePicker.getDayOfMonth());
    }
})
.setNegativeButton(android.R.string.cancel, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
})
//create the dialog and show it.
.create().show();

}

Where layout.date_picker_view is a simple layout resource with a DatePicker as it's only element:

<!xml version="1.0" encoding="utf-8">
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/date_picker"
android:layout_width="fill_parent"   
android:spinnersShown="true" 
android:calendarViewShown="false"
android:layout_height="fill_parent"/>

Here's the full tutorial in case you are interested.

Chalcanthite answered 27/5, 2013 at 19:7 Comment(1)
This worked for me wonderfully. Easy to understand, easy to implement! Tested on 4.4Renaud
M
3

According to Ankur Chaudhary's brilliant answer on the similar TimePickerDialog issue, if we checked inside onDateSet if the given view isShown() or not, it will solve the whole issue with the minimal effort, with no need for extending the picker or checking for some hideous flags going around the code or even checking for the OS version, just do the following:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if (view.isShown()) {
        // read the date here :)
    }
}

and of course the same can be done for onTimeSet as per Ankur's answer

Mask answered 16/3, 2015 at 17:34 Comment(1)
Best answer out of all the other!Demirelief
S
2

The way I managed this situation was using a flag and overriding the onCancel and onDismiss methods.

onCancel gets called only when the user touches outside the dialog or the back button. onDismiss always gets called

Setting a flag in the onCancel method can help filter in the onDismiss method the user's intent: cancel action or done action. Below some code that shows the idea.

public class DatePickerDialogFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    private boolean cancelDialog = false;
    private int year;
    private int month;
    private int day;

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        DatePickerDialog dpd = new DatePickerDialog(getActivity(), this, year, month, day);
        return dpd;
    }

    public void setDatePickerDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        cancelDialog = true;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (!cancelDialog) {
          #put the code you want to execute if the user clicks the done button
        }
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        setDatePickerDate(year, monthOfYear, dayOfMonth);
    }
}
Sallet answered 1/9, 2013 at 1:17 Comment(0)
G
1

There is a very simple workaround, if your application does not use the action bar. Note by the way, that some apps rely on this functionality to work, because cancelling out of the date picker has a special meaning (e.g. it clears the date field to an empty string, which for some apps is a valid and meaningful type of input) and using boolean flags to prevent the date from being set twice on OK will not help you in this case.

Re. the actual fix, you do not have to create new buttons or your own dialog. The point is to be compatible with both, the older versions of Android, the buggy ones (4.) and any future ones, though the latter is impossible to be sure about, of course. Note that in Android 2., the onStop() for android.app.Dialog does nothing at all, and in 4.* it does mActionBar.setShowHideAnimationEnabled(false) which is important only if your app has an action bar. The onStop() in DatePickerDialog, which inherits from Dialog, only contributes mDatePicker.clearFocus() (as of the latest fix to Android sources 4.3), which does not seem essential.

Therefore replacing onStop() with a method that does nothing should in many instances fix your app and ensure that it will remain so for the foreseeable future. Thus simply extend DatePickerDialog class with your own and override onStop() whit a dummy method. You will also have to provide one or two constructors, as per your requirements. Note also that one should not be tempted to try to overdo this fix by e.g. attempting to do something with the activity bar directly, as this would limit your compatibility to the latest versions of Android only. Also note that it would be nice to be able to call the super for DatePicker's onStop() because the bug is only in the onStop() in DatePickerDialog itself, but not in DatePickerDialog's super class. However, this would require you to call super.super.onStop() from your custom class, which Java will not let you do, as it goes against the encapsulation philosophy :) Below is my little class that I used to verride DatePickerDialog. I hope this comment would be useful for somebody. Wojtek Jarosz

public class myDatePickerDialog extends DatePickerDialog {

public myDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
}

@Override
protected void onStop() {
    // Replacing tryNotifyDateSet() with nothing - this is a workaround for Android bug https://android-review.googlesource.com/#/c/61270/A

    // Would also like to clear focus, but we cannot get at the private members, so we do nothing.  It seems to do no harm...
    // mDatePicker.clearFocus();

    // Now we would like to call super on onStop(), but actually what we would mean is super.super, because
    // it is super.onStop() that we are trying NOT to run, because it is buggy.  However, doing such a thing
    // in Java is not allowed, as it goes against the philosophy of encapsulation (the Creators never thought
    // that we might have to patch parent classes from the bottom up :)
    // However, we do not lose much by doing nothing at all, because in Android 2.* onStop() in androd.app.Dialog //actually
    // does nothing and in 4.* it does:
    //      if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); 
    // which is not essential for us here because we use no action bar... QED
    // So we do nothing and we intend to keep this workaround forever because of users with older devices, who might
    // run Android 4.1 - 4.3 for some time to come, even if the bug is fixed in later versions of Android.
}   

}

Glycerol answered 13/9, 2013 at 8:51 Comment(1)
Even if an ActionBar is used then this might be an acceptable workaround if you never hide/show the ActionBar. To make things extra safe you can override onStart to do nothing as well (then the calls for the animation would be safely unhooked). And even if you do hide/show it, it's just the animation that's disabled.Impedimenta
G
0


Try the below concepts.

DatePickerDialog picker = new DatePickerDialog(
        this,
        new OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker v, int y, int m, int d) {
                Log.d("Picker", "Set!");
            }
        },
        2012, 6, 15);
picker.show();


the onDateSet() method calls twice (if u are checking in emulator.it calls twice.If are using real device then it will call correctly single time.If you are using emulator then use the counter.if you are working in real device then ignore counter variable.For real device its working for me.)
when user clicks the button in DatePickerDialog .
for this you should maintain a counter value and nothing do when the mothod calls first time and perform the operation when the method calls 2nd time.
Refer the below coding snippets

   static int counter=0;       //Counter will be declared globally.

    DatePickerDialog picker = new DatePickerDialog(
            this,
            new OnDateSetListener() {
                @Override
                public void onDateSet(DatePicker v, int y, int m, int d) {

                   counter++;
                   if(counter==1) return;
                   counter=0;
                   //Do the operations here

                }
            },
            2012, 6, 15);
    picker.show();



For cancelling datepicker dilalog its working for me.For emulator its not wokring

DialogInterface.OnClickListener dialogOnClickListener=new DialogInterface.OnClickListener()
        {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

                if(which==Dialog.BUTTON_NEGATIVE)
                {
                    Log.i(tagName, "dialog negative button clicked");
                    dialog.dismiss();
                }

            }

        };

        mDatePickerDialog.setButton(Dialog.BUTTON_NEGATIVE, "Cancel", dialogOnClickListener);


Its working for me for a real device.But for emulator its not working correctly.I think its an android emulator bug.

Glycoprotein answered 20/9, 2012 at 10:17 Comment(0)
S
0

A simple solution would be using a boolean to skip second run

boolean isShow = false; // define global variable


// when showing time picker
TimePickerDialog timeDlg = new TimePickerDialog( this, new OnTimeSetListener()
            {

                @Override
                public void onTimeSet( TimePicker view, int hourOfDay, int minute )
                {
                    if ( isShow )
                    {
                        isShow = false;
                        // your code
                    }

                }
            }, 8, 30, false );

timeDlg.setButton( TimePickerDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = false;
                }
            } );
timeDlg.setButton( TimePickerDialog.BUTTON_POSITIVE, "Set", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = true;
                }
            } );

timeDlg.show();
Sapphire answered 13/6, 2013 at 10:29 Comment(4)
Once again, just like I told the other guy before you, this does not solve the bug. I don't want to sound rude, but you guys should be testing your own solution before posting here... just to prove my point, use your code and let the user press back to cancel the dialog and see what I mean. The cause of the bug is onStop calling the method when it shouldn't... it firing twice is a consequence of the bug.Irick
If I wasn't clear enough, let me be: pressing back to cancel the dialog calls onDateSet. Thus, broken.Irick
Thank you for showing the error. I edit the answer so that it will work with back button. Your answer is working but DatePicker dp = picker.getDatePicker(); is not working with TimePickers since getTimePicker() method is not added. So this would be a valid answerSapphire
How we are trying to skip the second run?!!, I've tried it several times, when closing the dialog by any means onDateSet will be called once, but when choosing "done" or "set" then it will be called twice. Therefor we need to skip only the first one, so if it is called twice then & only then we have the correct dateMask
I
0

You can override onCancel() and use setOnDismissListener() to detect negative user actions. And with a DatePickerDialog.BUTTON_POSITIVE you know that the user wants to set a new date.

 DatePickerDialog mDPD = new DatePickerDialog(
                      getActivity(), mOnDateSetListener, mYear, mMonth, mDay);
 mDPD.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        // do something onCancek
        setDate = false;
    }
 });

 mDPD.setOnDismissListener(new OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface arg0) {
        // do something onDismiss
        setDate = false;
    }
});

mDPD.setButton(DatePickerDialog.BUTTON_POSITIVE, "Finish", new DatePickerDialog.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // user set new date
        setDate = true;
    }
});

then check for setDate:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if(setDate){
        //do something with new date
    }
}
Ils answered 31/7, 2013 at 11:59 Comment(0)
Y
0

Here is my workaround class for DatePickerDialog on cancel button as well as abandoning it by back button. Copy&use in style of DatePickerDialog (Because the listener is stateful, we must create new instance when use, otherwise more code is required to make it it works)

Use:

new FixedDatePickerDialog(this,
            new FixedOnDateSetListener() {

                @Override
                public void onDateSet(DatePicker view, int year,
                        int monthOfYear, int dayOfMonth) {
                    if (isOkSelected()) {
                        // when DONE button is clicked
                    }
                }

            }, year, month, day).show();

Class:

public class FixedDatePickerDialog extends DatePickerDialog {
private final FixedOnDateSetListener fixedCallback;
public FixedDatePickerDialog(Context context,
        FixedOnDateSetListener callBack, int year, int monthOfYear,
        int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
    fixedCallback = callBack;
    this.setButton(DialogInterface.BUTTON_NEGATIVE,
            context.getString(R.string.cancel), this);
    this.setButton(DialogInterface.BUTTON_POSITIVE,
            context.getString(R.string.done), this);
}

@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == BUTTON_POSITIVE) {
        fixedCallback.setOkSelected(true);
    } else {
        fixedCallback.setOkSelected(false);
    }
    super.onClick(dialog, which);
}

public abstract static class FixedOnDateSetListener implements
        OnDateSetListener {
    private boolean okSelected = false;

    @Override
    abstract public void onDateSet(DatePicker view, int year,
            int monthOfYear, int dayOfMonth);

    public void setOkSelected(boolean okSelected) {
        this.okSelected = okSelected;
    }

    public boolean isOkSelected() {
        return okSelected;
    }
}

}

Yesima answered 31/7, 2014 at 16:58 Comment(0)
W
0

I'm using date pickers, time pickers and number pickers. The number pickers call onValueChanged whenever the user selects a number, before the picker is dismissed, so I already had structure like this to do something with the value only when the picker is dismissed:

public int interimValue;
public int finalValue;

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    this.finalValue = this.interimValue;
}

I extended this to set custom onClickListeners for my buttons, with an argument to see which button was clicked. Now I can check which button was tapped before I set my final value:

public int interimValue;
public int finalValue;
public boolean saveButtonClicked;

public void setup() {
    picker.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.BUTTON_SAVE), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(true);
        }
    });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.BUTTON_CANCEL), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(false);
        }
    });
}

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onButtonClicked(boolean save) {
    this.saveButtonClicked = save;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    if (this.saveButtonClicked) {
        // save
        this.finalValue = this.interimValue;
    } else {
        // cancel
    }
}

And then I extended that to work with the date and time types for date and time pickers as well as the int type for number pickers.

I posted this because I thought it was simpler than some of the solutions above, but now that I've included all the code, I guess it's not much simpler! But it fit nicely into the structure I already had.

Update for Lollipop: Apparently this bug doesn't happen on all Android 4.1-4.4 devices, because I received a few reports from users whose date and time pickers weren't calling the onDateSet and onTimeSet callbacks. And the bug was officially fixed in Android 5.0. My approach only worked on devices where the bug is present, because my custom buttons didn't call the dialog's onClick handler, which is the only place that onDateSet and onTimeSet are called when the bug is not present. I updated my code above to call the dialog's onClick, so now it works whether or not the bug is present.

Wheelchair answered 20/11, 2014 at 22:53 Comment(0)
E
0

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));
    }
}
Ellipsis answered 6/5, 2015 at 2:46 Comment(1)
This is pretty similar to The Sea's answer above.Ellipsis
S
0

My working version with ClearButton using Lambda Expressions:

public class DatePickerFragment extends DialogFragment {
    private OnDateSelectListener dateSelectListener;
    private OnDateClearListener dateClearListener;

    public void setDateSelectListener(OnDateSelectListener dateSelectListener) {
        this.dateSelectListener = dateSelectListener;
    }

    public void setDateClearListener(OnDateClearListener dateClearListener) {
        this.dateClearListener = dateClearListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the current date as the default date in the picker
        final Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);

        // Create a new instance of DatePickerDialog and return it
        DatePickerDialog dialog = new DatePickerDialog(getActivity(), null, year, month, day);
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.setTitle("Select Date");
        dialog.setButton(BUTTON_POSITIVE, ("Done"), (dialog1, which) -> {
            DatePicker dp = dialog.getDatePicker();
            dialog.dismiss();
            dateSelectListener.onDateSelect(dp.getYear(), dp.getMonth(), dp.getDayOfMonth());
        });
        dialog.setButton(BUTTON_NEUTRAL, ("Clear"), (dialog1, which) -> {
            dialog.dismiss();
            dateClearListener.onDateClear();
        });
        dialog.setButton(BUTTON_NEGATIVE, ("Cancel"), (dialog1, which) -> {
            if (which == DialogInterface.BUTTON_NEGATIVE) {
                dialog.cancel();
            }
        });
        dialog.getDatePicker().setCalendarViewShown(false);
        return dialog;
    }


    public interface OnDateClearListener {
        void onDateClear();
    }

    public interface OnDateSelectListener {
        void onDateSelect(int year, int monthOfYear, int dayOfMonth);
    }
}
Scissel answered 27/1, 2016 at 11:44 Comment(0)
C
0

For TimePickerDialog the workaround can be as follows:

TimePickerDialog createTimePickerDialog(Context context, int themeResId, TimePickerDialog.OnTimeSetListener orignalListener,
                                                         int hourOfDay, int minute, boolean is24HourView) {
        class KitKatTimeSetListener implements TimePickerDialog.OnTimeSetListener {
            private int hour;
            private int minute;

            private KitKatTimeSetListener() {
            }

            @Override
            public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                this.hour = hourOfDay;
                this.minute = minute;
            }

            private int getHour() { return hour; }
            private int getMinute() {return minute; }
        };

        KitKatTimeSetListener kitkatTimeSetListener = new KitKatTimeSetListener();
        TimePickerDialog timePickerDialog = new TimePickerDialog(context, themeResId, kitkatTimeSetListener, hourOfDay, minute, is24HourView);

        timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), (dialog, which) -> {
            timePickerDialog.onClick(timePickerDialog, DialogInterface.BUTTON_POSITIVE);
            orignalListener.onTimeSet(new TimePicker(context), kitkatTimeSetListener.getHour(), kitkatTimeSetListener.getMinute());
            dialog.cancel();
        });
        timePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), (dialog, which) -> {
            dialog.cancel();
        });

        return timePickerDialog;
    }

I delegate all events to wrapper KitKatSetTimeListener, and only fire back to original OnTimeSetListener in case BUTTON_POSITIVE is clicked.

Cuddy answered 22/9, 2016 at 10:20 Comment(0)
P
0

After testing some of the sugestions posted here, I personally think this solution is the most simple. I pass "null" as my listener in the DatePickerDialog constructor, and then when I click the "OK" button I call my onDateSearchSetListener:

datePickerDialog = new DatePickerDialog(getContext(), null, dateSearch.get(Calendar.YEAR), dateSearch.get(Calendar.MONTH), dateSearch.get(Calendar.DAY_OF_MONTH));
    datePickerDialog.setCancelable(false);
    datePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Correct");
            onDateSearchSetListener.onDateSet(datePickerDialog.getDatePicker(), datePickerDialog.getDatePicker().getYear(), datePickerDialog.getDatePicker().getMonth(), datePickerDialog.getDatePicker().getDayOfMonth());
        }
    });
    datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Cancel");
            dialog.dismiss();
        }
    });
Pembroke answered 2/3, 2017 at 13:44 Comment(0)
D
-1

I know this post has been here for almost a year but I thought I should post my findings. You could still keep the listener(instead of setting it to mull) and still have this work as expected. The key is to implicitly set the "OK" or(and) the "cancel" buttons. I tested it and it works gratefully for me. The listener does not get fired twice.

Look at this example,

private void setTime(){
final Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);

final TimePickerDialog timepicker = new TimePickerDialog(this.getActivity(),
        timePickerListener,
        hour, 
        minute, 
        DateFormat.is24HourFormat(getActivity()));

timepicker.setButton(DialogInterface.BUTTON_POSITIVE, "Print", new    
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which) {
            print = true;
            timepicker.dismiss();           
        }
});

timepicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new 
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which){
            print = false;
            timepicker.dismiss();       
        }
});

timepicker.setCancelable(false);
timepicker.show();
}
Determination answered 12/4, 2013 at 15:55 Comment(4)
Plain and simple, it does not work the way you tell. timePickerListener is still called regardless of what you do in your dialog. I don't even need to test to know that, you just need to look at the sources: if you do not set it to null, tryNotifyTimeSet() will call the listener's onTimeSet() both in its onClick() and onStop().Irick
In other words, do not let the native class hold a reference to your listener (i.e., passing it in the constructor). How you deal with buttons is irrelevant.Irick
Hi David, You didn't test this and you assumed it wont work. This is the exact piece of code I am currently using in my app and it works like a champ.Determination
1) I never said it didn't work. I said it didn't work the way you said it did. Please read again. 2) you violated LSP and SRP, wasting man-hours to needlessly change all your entire client logic that didn't need any changes to begin with. 3) your answer addresses the question, yes, otherwise I'd flag it for removal as "not an answer", but 4) your answer is still (sorry about it) very inefficient and does not address the fundamental design issue (listener called), hence only the downvote. 6) you disabled back and made it a modal window to force the boolean. So, 6 serious issues there!Irick

© 2022 - 2024 — McMap. All rights reserved.