4

IMPORTANT EDIT: I've done a bit of searching and it seems that the OnTimeChangedListener doesn't fire in Android 5.0 (API 21). Gustavo's answer below will work for API 20 or lower, and API 22 which I've tested. (Source)

I would like to prevent a user from entering a time prior to the current time. I check for a valid time using the before method provided by Date. How can I prevent the dialog from accepting an invalid time within this validity check?

I have omitted excess code where I initialize the selected time (as a Date).

final Calendar mcurrentTime = Calendar.getInstance();
final int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
final int minute = mcurrentTime.get(Calendar.MINUTE);
final TimePickerDialog mTimePicker;
mTimePicker = new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() {
    @Override
    public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {

        //PSEUDO: Date selectedDateTime init here

        if (selectedDateTime.before(mcurrentTime.getTime())) {
            // prevent user from selecting time
        }
    }
}, hour, minute, true);//true = 24 hour time
mTimePicker.setTitle("Select Time");
mTimePicker.show(); 
McGuile
  • 818
  • 1
  • 11
  • 29

2 Answers2

9

Here the original post.

Following answer you should add TimePickerDialog class.

public class RangeTimePickerDialog extends TimePickerDialog {

private int minHour = -1;
private int minMinute = -1;

private int maxHour = 25;
private int maxMinute = 25;

private int currentHour = 0;
private int currentMinute = 0;

private Calendar calendar = Calendar.getInstance();
private DateFormat dateFormat;


public RangeTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) {
    super(context, callBack, hourOfDay, minute, is24HourView);
    currentHour = hourOfDay;
    currentMinute = minute;
    dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);

    try {
        Class<?> superclass = getClass().getSuperclass();
        Field mTimePickerField = superclass.getDeclaredField("mTimePicker");
        mTimePickerField.setAccessible(true);
        TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this);
        mTimePicker.setOnTimeChangedListener(this);
    } catch (NoSuchFieldException e) {
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    }
}

public void setMin(int hour, int minute) {
    minHour = hour;
    minMinute = minute;
}

public void setMax(int hour, int minute) {
    maxHour = hour;
    maxMinute = minute;
}

@Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {

    Log.d("DADADADA", "onTimeChanged");

    boolean validTime = true;
    if (hourOfDay < minHour || (hourOfDay == minHour && minute < minMinute)) {
        validTime = false;
    }

    if (hourOfDay > maxHour || (hourOfDay == maxHour && minute > maxMinute)) {
        validTime = false;
    }

    if (validTime) {
        currentHour = hourOfDay;
        currentMinute = minute;
    }

    updateTime(currentHour, currentMinute);
    updateDialogTitle(view, currentHour, currentMinute);
}

private void updateDialogTitle(TimePicker timePicker, int hourOfDay, int minute) {
    calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
    calendar.set(Calendar.MINUTE, minute);
    String title = dateFormat.format(calendar.getTime());
    setTitle(title);
}
}

After that, replace your TimePickerDialog for the RangeTimePickerDialog

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final Calendar mcurrentTime = Calendar.getInstance();
    final int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
    final int minute = mcurrentTime.get(Calendar.MINUTE);
    final RangeTimePickerDialog mTimePicker;
    mTimePicker = new RangeTimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() {
        @Override
        public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {

            Log.d("TAG", "inside OnTimeSetListener");

        }
    }, hour, minute, true);//true = 24 hour time
    mTimePicker.setTitle("Select Time");
    mTimePicker.setMin(hour, minute);
    mTimePicker.show();
}
}

After that, check you dialog, time doesn't pass limits, I have checked this with both setMax() or setMin() and also setting only one.

Community
  • 1
  • 1
Gustavo Morales
  • 2,614
  • 9
  • 29
  • 37
  • This is the exact code I have. The title does not change to show time selected as you suggest, nor does it limit what time you can select. Something is missing. – McGuile Jul 07 '15 at 18:24
  • Clean your project and run it again. My code is working on phone. Android SDK 14 or superior. – Gustavo Morales Jul 07 '15 at 18:31
  • Sorry, I've done that and no change. My code is exact to yours. I can't see why it is working for you and not for me. It allows me to select any time and the time changes to the selected time. I'm on API 21. – McGuile Jul 07 '15 at 18:43
  • Check my last edit, that's is how looks my code, and **activity_main.xml** on activity is blank, without any component. – Gustavo Morales Jul 07 '15 at 18:45
  • I have even tested it in a blank MainActivity and still the same result: all times can be selected. – McGuile Jul 07 '15 at 18:52
  • I have tested your code replacing mine, and works fine for me. In a Samsung S4. – Gustavo Morales Jul 07 '15 at 18:57
  • Well I'm baffled then. Thanks for your time even if it wasn't successful. Perhaps it's an API thing. Upvoted. – McGuile Jul 07 '15 at 18:58
  • 2
    Good answer. Except the use of reflection. A normal interface would have been much better. +1 nonetheless – Chad Bingham Jul 07 '15 at 19:12
  • @Gustavo - You really need to attribute this to the person who [originally wrote the answer](http://stackoverflow.com/a/16942630/1642079). Take a look at [StackOverflows Attribution Policy](https://blog.stackexchange.com/2009/06/attribution-required/) – Chad Bingham Jul 07 '15 at 19:17
  • @LoyalRayne Yes, how I do that? In my first commentary on this post I send user to original post. – Gustavo Morales Jul 07 '15 at 19:23
  • Just post a link to the original answer. That is all ;) cheers – Chad Bingham Jul 07 '15 at 19:23
  • @Gustavo I've noticed onTimeChanged() is never called. Is it called for you? – McGuile Jul 07 '15 at 20:18
  • Yes it's called, put a LOG message inside **onTimeChanged()** method and test if its called for you. I also notice that solution is not in LG G3 with API 21, but works on API 19 in a Samsung S4. I think that widgets have different behaviors. – Gustavo Morales Jul 07 '15 at 20:24
  • Post edited. Very strange that it didn't work on API 21 but works on 22 and 20 or lower. – McGuile Jul 07 '15 at 20:55
  • This code is not working on lollipop, only working on pre lollipop device. – akhil batlawala Jul 08 '16 at 10:28
  • 1
    Please give a better solution – akhil batlawala Jul 08 '16 at 10:28
2

Method onTimeSet() is called once when dialog is dismissed and is called twice when Done button is clicked.

I would just validate in that method on the second call. Ignore the first. If it is invalid, show an error (probably a toast) and show the dialog again

Elaborate

Both of the onTimeSet calls will be identical times. Now, I don't have a reference, but I believe that one call is when the dialog was shown (time set or not) and then was cancelled. Two is exactly that but instead of cancel, it is when the user hits "Done".

The reason I say ignore the first is that the could set the time, and decide to cancel. In that case, don't take that onTimeSet(). If that doesn't matter to you, then no need to worry about this.

Chad Bingham
  • 32,650
  • 19
  • 86
  • 115
  • I was unaware that it onTimeSet() is called twice. Can you elaborate on how to distinguish between the 1st call and 2nd call? – McGuile Jul 07 '15 at 16:13
  • Do you know how to ignore the first call? Or even recognize whether its the first or second call? – McGuile Jul 07 '15 at 18:59
  • 1
    Many ways. That is where you have to put your mind to an algorithm that would work. I would probably use and int. Set it to 0 when you show the dialog. Then when you receive the dateSet, check the int. If it is 0, add one/ignore date. if is 1, handle the date. (then to be safe. Set int back to 0 again). Something like that. – Chad Bingham Jul 07 '15 at 19:11
  • Ah okay, I could've thought of that. I just assumed you meant there were defined methods for it. – McGuile Jul 07 '15 at 19:17