Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TimePickerDialog with fixed android:timePickerMode spinner in Nougat
package my.packagename;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.TimePicker;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Workaround for this bug: https://code.google.com/p/android/issues/detail?id=222208
* In Android 7.0 Nougat, spinner mode for the TimePicker in TimePickerDialog is
* incorrectly displayed as clock, even when the theme specifies otherwise, such as:
*
* <resources>
* <style name="Theme.MyApp" parent="Theme.AppCompat.Light.NoActionBar">
* <item name="android:timePickerStyle">@style/Widget.MyApp.TimePicker</item>
* </style>
*
* <style name="Widget.MyApp.TimePicker" parent="android:Widget.Material.TimePicker">
* <item name="android:timePickerMode">spinner</item>
* </style>
* </resources>
*
* May also pass TimePickerDialog.THEME_HOLO_LIGHT as an argument to the constructor,
* as this theme has the TimePickerMode set to spinner.
*/
public class TimePickerDialogFixedNougatSpinner extends TimePickerDialog {
/**
* Creates a new time picker dialog.
*
* @param context the parent context
* @param listener the listener to call when the time is set
* @param hourOfDay the initial hour
* @param minute the initial minute
* @param is24HourView whether this is a 24 hour view or AM/PM
*/
public TimePickerDialogFixedNougatSpinner(Context context, OnTimeSetListener listener, int hourOfDay, int minute, boolean is24HourView) {
super(context, listener, hourOfDay, minute, is24HourView);
fixSpinner(context, hourOfDay, minute, is24HourView);
}
/**
* Creates a new time picker dialog with the specified theme.
*
* @param context the parent context
* @param themeResId the resource ID of the theme to apply to this dialog
* @param listener the listener to call when the time is set
* @param hourOfDay the initial hour
* @param minute the initial minute
* @param is24HourView Whether this is a 24 hour view, or AM/PM.
*/
public TimePickerDialogFixedNougatSpinner(Context context, int themeResId, OnTimeSetListener listener, int hourOfDay, int minute, boolean is24HourView) {
super(context, themeResId, listener, hourOfDay, minute, is24HourView);
fixSpinner(context, hourOfDay, minute, is24HourView);
}
private void fixSpinner(Context context, int hourOfDay, int minute, boolean is24HourView) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) { // fixes the bug in API 24
try {
// Get the theme's android:timePickerMode
final int MODE_SPINNER = 1;
Class<?> styleableClass = Class.forName("com.android.internal.R$styleable");
Field timePickerStyleableField = styleableClass.getField("TimePicker");
int[] timePickerStyleable = (int[]) timePickerStyleableField.get(null);
final TypedArray a = context.obtainStyledAttributes(null, timePickerStyleable, android.R.attr.timePickerStyle, 0);
Field timePickerModeStyleableField = styleableClass.getField("TimePicker_timePickerMode");
int timePickerModeStyleable = timePickerModeStyleableField.getInt(null);
final int mode = a.getInt(timePickerModeStyleable, MODE_SPINNER);
a.recycle();
if (mode == MODE_SPINNER) {
TimePicker timePicker = (TimePicker) findField(TimePickerDialog.class, TimePicker.class, "mTimePicker").get(this);
Class<?> delegateClass = Class.forName("android.widget.TimePicker$TimePickerDelegate");
Field delegateField = findField(TimePicker.class, delegateClass, "mDelegate");
Object delegate = delegateField.get(timePicker);
Class<?> spinnerDelegateClass;
spinnerDelegateClass = Class.forName("android.widget.TimePickerSpinnerDelegate");
// In 7.0 Nougat for some reason the timePickerMode is ignored and the delegate is TimePickerClockDelegate
if (delegate.getClass() != spinnerDelegateClass) {
delegateField.set(timePicker, null); // throw out the TimePickerClockDelegate!
timePicker.removeAllViews(); // remove the TimePickerClockDelegate views
Constructor spinnerDelegateConstructor = spinnerDelegateClass.getConstructor(TimePicker.class, Context.class, AttributeSet.class, int.class, int.class);
spinnerDelegateConstructor.setAccessible(true);
// Instantiate a TimePickerSpinnerDelegate
delegate = spinnerDelegateConstructor.newInstance(timePicker, context, null, android.R.attr.timePickerStyle, 0);
delegateField.set(timePicker, delegate); // set the TimePicker.mDelegate to the spinner delegate
// Set up the TimePicker again, with the TimePickerSpinnerDelegate
timePicker.setIs24HourView(is24HourView);
timePicker.setCurrentHour(hourOfDay);
timePicker.setCurrentMinute(minute);
timePicker.setOnTimeChangedListener(this);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private static Field findField(Class objectClass, Class fieldClass, String expectedName) {
try {
Field field = objectClass.getDeclaredField(expectedName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {} // ignore
// search for it if it wasn't found under the expected ivar name
for (Field searchField : objectClass.getDeclaredFields()) {
if (searchField.getType() == fieldClass) {
searchField.setAccessible(true);
return searchField;
}
}
return null;
}
}
@avi2108

This comment has been minimized.

Copy link

avi2108 commented Oct 3, 2016

Hi,Using this custom class also we are getting clock mode only. @jeffdgr

@jeffdgr8

This comment has been minimized.

Copy link
Owner Author

jeffdgr8 commented Oct 3, 2016

@avi2108 are you using a theme with "android:timePickerMode" set to "spinner"? (See the class doc comment.) Passing TimePickerDialog.THEME_HOLO_LIGHT into the constructor works as well, as this theme uses spinner mode.

If so, please provide more details on what OS version you're finding this class isn't working properly.

@jeffdgr8

This comment has been minimized.

Copy link
Owner Author

jeffdgr8 commented Oct 3, 2016

So there is an issue with API 21 Lollipop 5.0, the initial version of the time picker clock mode. What is the TimePickerSpinnerDelegate in API 22+ is called the TimePickerClockDelegate in API 21! Modified to address this.

@avi2108

This comment has been minimized.

Copy link

avi2108 commented Oct 12, 2016

Hi @jeffdgr , Am using Version 7.0 Nougat.
No matter what we keep in them at styles-v21 and also passing TimePickerDialog.THEME_HOLO_LIGHT are no more helping me for getting Time Picker in Spinner Mode.
For below Nougat versions , am getting spinner mode perfect using my custome Picker. But this class you provided is not giving that in Nougat 7.0 .
Did it give you spineer mode in Nougat Devices?

@TonyKazanjian

This comment has been minimized.

Copy link

TonyKazanjian commented Nov 1, 2016

@avi2108, I discovered if you change the MODE_SPINNER final variable to 2 (line 68), it worked for me.

@iammert

This comment has been minimized.

Copy link

iammert commented Dec 7, 2016

It works!! Thanks!

@scm573

This comment has been minimized.

Copy link

scm573 commented Apr 26, 2017

I discovered if you change the MODE_SPINNER final variable to 2 (line 68), it worked for me.

As @TonyKazanjian said, it works for me after changing MODE_SPINNER to 2.
Thank you guys.

@lognaturel

This comment has been minimized.

Copy link

lognaturel commented Apr 30, 2017

Thank you, @jeffdgr8! That worked for me without changes.

If someone needs to make an equivalent fix for DatePicker, this might help speed things up.

@lognaturel

This comment has been minimized.

Copy link

lognaturel commented Apr 30, 2017

@jeffdgr8 I'd love to use this in an open source (Apache 2.0) project but I just noticed that it's not explicitly licensed! Would you be willing to add a permissive software license to it? Thanks so much. =)

@AlexandruTanase

This comment has been minimized.

Copy link

AlexandruTanase commented May 18, 2017

This works perfectly, Thank You ! One Question : When I implement the spinner shows TRADITIONAL theme. Is there any way to change that to modern look ? (Now it shows the plus/minus burtons to scroll through the numberpicker values and an AM/PM button. I would like this to be spinner wheel also)

@arun07klm

This comment has been minimized.

Copy link

arun07klm commented Oct 5, 2017

Please let me know how can i fix this problem using ng-cordova ($cordovaDatePicker) .

@Cutta

This comment has been minimized.

Copy link

Cutta commented Oct 30, 2017

You saved us :) Thank you! 👍

@manunewagesmb

This comment has been minimized.

Copy link

manunewagesmb commented Jun 1, 2018

after change MODE_SPINNER to 2 it's woks for me too..Thanks alot

@patelhari

This comment has been minimized.

Copy link

patelhari commented Jun 2, 2018

Thank you @jeffdgr8 working fine.
Thanks too you @TonyKazanjian for changing MODE_SPINNER to 2 for Nougat Spinner

@johnshine

This comment has been minimized.

Copy link

johnshine commented Jun 8, 2018

Can you send me the whole project?
This is my gmail account,shinethawka8800@gmail.com.

@uqmessias

This comment has been minimized.

Copy link

uqmessias commented Jun 20, 2018

Thanks @jeffdgr8, for taking the time to post it here.

I forked your original gist and add a new file into it to fix the same issue on DatePicker. But instead of using the DatePickerSpinnerDelegate constructor directly (what didn't work for some reason), I just called the DatePicker.createSpinnerUIDelegate (private method) and it worked for me!

This is the fork, if anyone is facing the same issue!

@jeffdgr8

This comment has been minimized.

Copy link
Owner Author

jeffdgr8 commented Sep 4, 2018

@lognaturel sorry for not replying earlier. I defined a permissive software license for all my gists now: https://gist.github.com/jeffdgr8/ccc2416343e2f5d1e86101524df9630c Feel free to use!

@jeffdgr8

This comment has been minimized.

Copy link
Owner Author

jeffdgr8 commented Sep 4, 2018

For those finding the need to change MODE_SPINNER to 2, most likely you have not properly defined the time picker mode to spinner mode.

As I describe in the class comment, this can be done in your custom theme as shown. A android:timePickerStyle style needs to have android:timePickerMode defined as spinner.

Alternatively, you can pass a theme that has the time picker mode set to spinner mode, such as TimePickerDialog.THEME_HOLO_LIGHT in the TimePickerDialogFixedNougatSpinner constructor as the themeResId argument.

If you look at the definition of TimePicker you can see these constants defined:

public static final int MODE_SPINNER = 1
Presentation mode for the Holo-style time picker that uses a set of NumberPickers.

public static final int MODE_CLOCK = 2
Presentation mode for the Material-style time picker that uses a clock face.

You can use getMode() on TimePicker to check what it's set to:

public int getMode()
Returns: the picker's presentation mode, one of MODE_CLOCK or MODE_SPINNER

This class is meant as a fix for a bug in 7.0 Nougat's TimePickerDialog implementation, which fails to provide MODE_SPINNER functionality. For all other platform versions, the default TimePickerDialog will display spinner mode correctly if properly defined in your app theme or a theme constructor argument. Changing MODE_SPINNER to 2 in this code just forces spinner mode even when your theme is set to clock mode.

@Seif0o0

This comment has been minimized.

Copy link

Seif0o0 commented Jun 12, 2019

hello ...
my app crashes while using ur code in android 9 ..
This line is giving a NoSuchFieldException
Field timePickerStyleableField = styleableClass.getField("TimePicker");
but when i remove this line throw new RuntimeException(e);
app doesn't crash but time picker change from spinner to clock mode
can any one give me a hand ?

@anees17861

This comment has been minimized.

Copy link

anees17861 commented Jun 12, 2019

Hi @jeffdgr8. This is working perfectly on nougat once I change MODE_SPINNER to 2. But it is causing a crash on pie device
https://gist.github.com/jeffdgr8/6bc5f990bf0c13a7334ce385d482af9f#file-timepickerdialogfixednougatspinner-java-L70

Field timePickerStyleableField = styleableClass.getField("TimePicker");

This line is giving a NoSuchFieldException.
Is it safe to assume that your fix needs to be applied to only nougat devices and for other devices providing theme as THEME_HOLO_LIGHT is enough. My app requires minSdk to be lollipop, so that condition seems to be satisfied as well

@jeffdgr8

This comment has been minimized.

Copy link
Owner Author

jeffdgr8 commented Jun 13, 2019

@Seif0o0 @anees17861 this bug was fixed in API 25 (https://issuetracker.google.com/issues/37119315), so the fix only needs to be applied to API 24. I adjusted the code such that it will only run on API 24 devices. Let me know if there are any issues with the latest version.

@naveedahmad99

This comment has been minimized.

Copy link

naveedahmad99 commented Jul 25, 2019

hello ...
my app crashes while using ur code in android 9 ..
This line is giving a NoSuchFieldException
Field timePickerStyleableField = styleableClass.getField("TimePicker");
but when i remove this line throw new RuntimeException(e);
app doesn't crash but time picker change from spinner to clock mode
can any one give me a hand ?

this fixed my issue as app is not crashing now on Android 9

@HurJungUn

This comment has been minimized.

Copy link

HurJungUn commented Oct 24, 2019

thx!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.