Skip to content

Instantly share code, notes, and snippets.

@lognaturel
Last active February 14, 2023 13:15
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lognaturel/232395ee1079ff9e4b1b8e7096c3afaf to your computer and use it in GitHub Desktop.
Save lognaturel/232395ee1079ff9e4b1b8e7096c3afaf to your computer and use it in GitHub Desktop.
Use DatePicker spinners in API 24
/**
* Workaround for this bug: https://code.google.com/p/android/issues/detail?id=222208
* In Android 7.0 Nougat, spinner mode for the DatePicker in DatePickerDialog is
* incorrectly displayed as calendar, even when the theme specifies otherwise.
*
* Modified slightly from the equivalent fix for TimePicker from @jeffdgr8:
* https://gist.github.com/jeffdgr8/6bc5f990bf0c13a7334ce385d482af9f
*/
private void fixSpinner(Context context, int year, int month, int dayOfMonth) {
// The spinner vs not distinction probably started in lollipop but applying this
// for versions < nougat leads to a crash trying to get DatePickerSpinnerDelegate
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) {
try {
// Get the theme's android:datePickerMode
final int MODE_SPINNER = 1;
Class<?> styleableClass = Class.forName("com.android.internal.R$styleable");
Field datePickerStyleableField = styleableClass.getField("DatePicker");
int[] datePickerStyleable = (int[]) datePickerStyleableField.get(null);
final TypedArray a = context.obtainStyledAttributes(null, datePickerStyleable,
android.R.attr.datePickerStyle, 0);
Field datePickerModeStyleableField = styleableClass.getField("DatePicker_datePickerMode");
int datePickerModeStyleable = datePickerModeStyleableField.getInt(null);
final int mode = a.getInt(datePickerModeStyleable, MODE_SPINNER);
a.recycle();
if (mode == MODE_SPINNER) {
DatePicker datePicker = (DatePicker) findField(DatePickerDialog.class,
DatePicker.class, "mDatePicker").get(this);
Class<?> delegateClass = Class.forName("android.widget.DatePicker$DatePickerDelegate");
Field delegateField = findField(DatePicker.class, delegateClass, "mDelegate");
Object delegate = delegateField.get(datePicker);
Class<?> spinnerDelegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate");
// In 7.0 Nougat for some reason the datePickerMode is ignored and the
// delegate is DatePickerCalendarDelegate
if (delegate.getClass() != spinnerDelegateClass) {
delegateField.set(datePicker, null); // throw out the DatePickerCalendarDelegate!
datePicker.removeAllViews(); // remove the DatePickerCalendarDelegate views
Constructor spinnerDelegateConstructor = spinnerDelegateClass
.getDeclaredConstructor(DatePicker.class, Context.class,
AttributeSet.class, int.class, int.class);
spinnerDelegateConstructor.setAccessible(true);
// Instantiate a DatePickerSpinnerDelegate
delegate = spinnerDelegateConstructor.newInstance(datePicker, context,
null, android.R.attr.datePickerStyle, 0);
// set the DatePicker.mDelegate to the spinner delegate
delegateField.set(datePicker, delegate);
// Set up the DatePicker again, with the DatePickerSpinnerDelegate
datePicker.updateDate(year, month, dayOfMonth);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private Field findField(Class objectClass, Class fieldClass, String expectedName) {
try {
Field field = objectClass.getDeclaredField(expectedName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
Timber.i(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;
}
@andersfredlund
Copy link

Nice, however I get a combined date/month picker (old style) and a calendar view when using this. Any ideas why?

@mradulb
Copy link

mradulb commented May 25, 2017

Same here andersfredlund, Can you please help us lognaturel

@taniakrisanty
Copy link

Thank you so much lognaturel! First I got combined date/month picker and calendar view, then I successfully remove the calendar view by adding "android:calendarViewShown" set to false.

@DeevD
Copy link

DeevD commented Jun 23, 2017

Hi How to use this file in my project ,, and i need to remove calendar view in API 25

@Rehckover
Copy link

It almost solve my problem. It look like spinner but number picker diplayed only on the half from the left side. How to center it?

@ShagunParikh
Copy link

ShagunParikh commented Jul 18, 2017

It shows calendar view in API 25 in DatePickerDialog. How to make it spinner view. Also i want to hide "date" field from the spinner view in API 25.

@karanatwal
Copy link

Hello !! i am getting problem at if (mode == MODE_SPINNER) i debugged ,code not going inside this if .Please help.

@Noisyfox
Copy link

Noisyfox commented Oct 10, 2018

To support Android [5.0, 6.0] just change line 33 to:

Class<?> spinnerDelegateClass;
try {
	// For android [5.0, 6.0], the DatePickerSpinnerDelegate is an inner class.
	spinnerDelegateClass = Class.forName("android.widget.DatePicker$DatePickerSpinnerDelegate");
} catch (ClassNotFoundException ignored) {
	// For android [7.0,), the DatePickerSpinnerDelegate is moved out to a normal class.
	spinnerDelegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate");
}

@ahi
Copy link

ahi commented Apr 12, 2019

Thanks to lognaturel and Noisyfox.
Here is the c# Xamarin version of this fix:

class NougatFixDatePickerDialog : DatePickerDialog
    {
        public NougatFixDatePickerDialog(Context context, IOnDateSetListener listener, int year, int month, int dayOfMonth) 
            : base(context, listener, year, month, dayOfMonth)
        {
            FixSpinner(context, year, month, dayOfMonth);
        }

        private void FixSpinner(Context context, int year, int month, int dayOfMonth)
        {
            try
            {
                const int modeSpinner = 1;
                Class styleableClass = Class.ForName("com.android.internal.R$styleable");
                Field datePickerStyleableField = styleableClass.GetField("DatePicker");
                int[] datePickerStyleable = (int[]) datePickerStyleableField.Get(null);
                TypedArray a = context.ObtainStyledAttributes(
                    null, datePickerStyleable, Android.Resource.Attribute.DatePickerStyle, 0);
                Field datePickerModeStyleableField = styleableClass.GetField("DatePicker_datePickerMode");
                int datePickerModeStyleable = datePickerModeStyleableField.GetInt(null);
                int mode = a.GetInt(datePickerModeStyleable, modeSpinner);
                a.Recycle();

                if (mode != modeSpinner)
                {
                    return;
                }

                DatePicker datePicker = (DatePicker) FindField(
                    Java.Lang.Class.FromType(typeof(DatePickerDialog)),
                    Java.Lang.Class.FromType(typeof(Android.Widget.DatePicker)), 
                    "mDatePicker").Get(this);

                Class delegateClass = Class.ForName("android.widget.DatePicker$DatePickerDelegate");
                Field delegateField = FindField(
                    Java.Lang.Class.FromType(typeof(Android.Widget.DatePicker)),
                    delegateClass, "mDelegate");
                Object jDelegate = delegateField.Get(datePicker);

                Class spinnerDelegateClass;
                try
                {
                    // For android [5.0, 6.0], the DatePickerSpinnerDelegate is an inner class.
                    spinnerDelegateClass = Class.ForName("android.widget.DatePicker$DatePickerSpinnerDelegate");
                }
                catch (ClassNotFoundException)
                {
                    // For android [7.0,), the DatePickerSpinnerDelegate is moved out to a normal class.
                    spinnerDelegateClass = Class.ForName("android.widget.DatePickerSpinnerDelegate");
                }

                if (delegateClass.Class == spinnerDelegateClass)
                {
                    return;
                }

                delegateField.Set(datePicker, null);
                datePicker.RemoveAllViews();

                Constructor spinnerDelegateConstructor = spinnerDelegateClass
                    .GetDeclaredConstructor(new Class[]
                    {
                        Class.FromType(typeof(Android.Widget.DatePicker)),
                        Java.Lang.Class.FromType(typeof(Android.Content.Context)),
                        Java.Lang.Class.FromType(typeof(IAttributeSet)),
                        Integer.Type,
                        Integer.Type
                    });
                spinnerDelegateConstructor.Accessible = true;
                    
                jDelegate.Dispose();
                jDelegate = spinnerDelegateConstructor
                    .NewInstance(datePicker, context, null, Android.Resource.Attribute.DatePickerStyle, 0);

                delegateField.Set(datePicker, jDelegate);
                datePicker.CalendarViewShown = false;
                datePicker.UpdateDate(year, month, dayOfMonth);
            }
            catch (Exception e)
            {
                Console.Out.Write(e.Message);
                throw e;
            }
        }

        private Field FindField(Class objectClass, Class fieldClass, string expectedName)
        {
            try
            {
                Field field = objectClass.GetDeclaredField(expectedName);
                field.Accessible = true;
                return field;
            }
            catch (NoSuchFieldException) { }

            // search for it if it wasn't found under the expected ivar name
            foreach (Field searchField in objectClass.GetDeclaredFields())
            {
                if (searchField.Type == fieldClass)
                {
                    searchField.Accessible = true;
                    return searchField;
                }
            }

            return null;
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment