42

Edit : I checked that the issue only appears when the phone is set with "French" language.

I'm currently building my own app and I have an issue when using the DatePicker widget on my phone (debug mode phone : Samsung S5).

The error gather is : java.util.IllegalFormatConversionException

I add more details about the issue gathered in logcat:

02-20 10:37:18.255  13029-13029/avappmobile.mytasks E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: avappmobile.mytasks, PID: 13029
    java.util.IllegalFormatConversionException: %d can't format java.lang.String arguments
            at java.util.Formatter.badArgumentType(Formatter.java:1489)
            at java.util.Formatter.transformFromInteger(Formatter.java:1689)
            at java.util.Formatter.transform(Formatter.java:1461)
            at java.util.Formatter.doFormat(Formatter.java:1081)
            at java.util.Formatter.format(Formatter.java:1042)
            at java.util.Formatter.format(Formatter.java:1011)
            at java.lang.String.format(String.java:1803)
            at android.content.res.Resources.getString(Resources.java:1453)
            at android.content.Context.getString(Context.java:397)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.getItemDescription(SimpleMonthView.java:684)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.onPopulateNodeForVirtualView(SimpleMonthView.java:628)
            at com.android.internal.widget.ExploreByTouchHelper.createNodeForChild(ExploreByTouchHelper.java:377)
            at com.android.internal.widget.ExploreByTouchHelper.createNode(ExploreByTouchHelper.java:316)
            at com.android.internal.widget.ExploreByTouchHelper.access$100(ExploreByTouchHelper.java:50)
            at com.android.internal.widget.ExploreByTouchHelper$ExploreByTouchNodeProvider.createAccessibilityNodeInfo(ExploreByTouchHelper.java:711)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfVirtualNode(AccessibilityInteractionController.java:1179)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1091)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchAccessibilityNodeInfos(AccessibilityInteractionController.java:888)
            at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:155)
            at android.view.AccessibilityInteractionController.access$400(AccessibilityInteractionController.java:53)
            at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1236)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:145)
            at android.app.ActivityThread.main(ActivityThread.java:5834)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

Here is my DialogFragment Code:

public class DatePFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    DatePicker dp;
    OnDatePickedListener mCallback;
    Integer mLayoutId = null;

    // Interface definition
    public interface OnDatePickedListener {
        public void onDatePicked(int textId, int year, int month, int day);
    }

    // make sure the Activity implement the OnDatePickedListener
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            mCallback = (OnDatePickedListener)activity;
        }
        catch (final ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnDatePickedListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        mCallback = (OnDatePickedListener)getActivity();

        Calendar cal = Calendar.getInstance();
        Bundle bundle = this.getArguments();
        mLayoutId = bundle.getInt("layoutId");
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        int day = cal.get(Calendar.DAY_OF_MONTH);

        View view = getActivity().getLayoutInflater().inflate(R.layout.datepfragment, null);

        dp = (DatePicker) view.findViewById(R.id.datePicker);

        // Create a new instance of DatePickerDialog and return it
        return new DatePickerDialog(getActivity(),this,year, month, day);
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (mCallback != null) {
            // Use the date chosen by the user.

            mCallback.onDatePicked(mLayoutId, year, monthOfYear+1, dayOfMonth);
        }
    }
}

And here is the associated xml layout :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/txtDPTitle"
        android:id="@+id/txtDPTitle"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="35dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnDPValidate"
        android:id="@+id/btnDPValidate"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="35dp" />

    <DatePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/datePicker"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="0dp"
        android:datePickerMode="spinner"
        android:calendarViewShown="false"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

The fact is I had two kind of issues (leading to the app crash anyway):

  1. When I enter my app and directly go to the DatePicker widget, the calendar is correctly displayed and as soon as I select a day (I say a day, because there's no problem changing the year for example), the app crashes.
  2. If I go on other function the click to display the DatePicker, it crashes directly.

I saw other threads like : DatePicker crash in samsung with android 5.0

In this thread I don't get enough information, since the given solution allows to display only the spinner view of the calendar and furthermore the spinner displayed is not centered and presented as mine (put directly at the top of the screen).

If you need further details please ask me.

Thanks. Alex.

Community
  • 1
  • 1
Alexandre
  • 1,189
  • 2
  • 13
  • 25
  • What are you expecting, exactly? Samsung broke the Material date picker. The workaround is to not use the Material date picker. – alanv Feb 19 '15 at 22:54
  • Ho ok, that would mean there's not any solution to my problem. What i would like is only to have my calendar displayed correctly. – Alexandre Feb 19 '15 at 23:22
  • For the record, besides happening in many languages other than French, this Samsung bug also happens even in English for "en-PH". – sorianiv May 17 '16 at 09:20

7 Answers7

93

It's a Samsung bug in their Lollipop UI implementation. It affects Galaxy S4, S5, Note 3 and probably more devices. For us it occurs on de_DE and de_AT languages, but it appears to be an issue that affects multiple languages.

I fixed it by forcing Samsung devices to use the Holo theme for the date picker. In our DatePickerFragment:

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    /* calendar code here */

    Context context = getActivity();
    if (isBrokenSamsungDevice()) {
        context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
    }
    return new DatePickerDialog(context, this, year, month, day);
}

private static boolean isBrokenSamsungDevice() {
    return (Build.MANUFACTURER.equalsIgnoreCase("samsung")
            && isBetweenAndroidVersions(
            Build.VERSION_CODES.LOLLIPOP,
            Build.VERSION_CODES.LOLLIPOP_MR1));
}

private static boolean isBetweenAndroidVersions(int min, int max) {
    return Build.VERSION.SDK_INT >= min && Build.VERSION.SDK_INT <= max;
}

Thanks to Samsung, their users will not get the nice material-designed date picker.

mreichelt
  • 11,918
  • 6
  • 51
  • 66
  • FYI it's occur when user has enable Google TalkBack – prcaen Aug 24 '15 at 13:41
  • @prcaen It also occurs with talkback disabled. – mreichelt Aug 26 '15 at 13:38
  • 4
    @mreichelt your solution works like a charm, isolates the issue to the dialog only and only for the problematic devices and without third party libraries, exactly what I was looking for. This should be the accepted answer. – androidseb Oct 07 '15 at 19:02
  • You save my day. Thank you – Olkunmustafa Nov 17 '15 at 13:04
  • des anyone know which devices are affected? because a galaxy s6 for example is fine and it would be nice to even more narrow the devices where the fix is applied. – stamanuel Nov 27 '15 at 11:42
  • Building on the approach of context wrapper, i suggested a solution that will let the user still have the Lollipop dialog and actually prevent the crash - would be great to get feedback: http://stackoverflow.com/a/34853067/348378 – Raanan Jan 18 '16 at 13:08
  • 3
    Is there a way to reproduce this error using the simulator? Does samsung open their mods? – Felipe Jun May 12 '16 at 21:16
  • 1
    @FelipeJun you can reliably reproduce this by enabling TalkBack on an S4 or A5 with Android 5.x with a french locale. – Nachi Jul 07 '16 at 13:50
  • 2
    Created a `SupportDatePickerDialog` based on this answer: https://gist.github.com/tobiasschuerg/5dd116e1e0bb874038d84fd98cc333c3 – Tobias Apr 05 '17 at 21:25
19

I have a possible FIX PATCH that will actually let the user continue using the Lollipop library.

I could only reproduce the crash by enabling TalkBack on an S4 with Android 5.0.1 French, it seems that Samsung is passing the entire date to the item selection string used for accessability instead of the day number.

Building on @mreichelt solution of wrapping the context we can override the getString(id,args) function and actually fix the issue.

context = new ContextWrapper(getActivity()) {

    private Resources wrappedResources;

    @Override
    public Resources getResources() {
        Resources r = super.getResources();
        if(wrappedResources == null) {
            wrappedResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
                @NonNull
                @Override
                public String getString(int id, Object... formatArgs) throws NotFoundException {
                    try {
                        return super.getString(id, formatArgs);
                    } catch (IllegalFormatConversionException ifce) {
                        Log.e("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                        String template = super.getString(id);
                        template = template.replaceAll("%" + ifce.getConversion(), "%s");
                        return String.format(getConfiguration().locale, template, formatArgs);
                    }
                }
            };
        }
        return wrappedResources;
    }
};

So if we provide this context to the DatePickerDialog constructor, it will fix the issue.

UPDATE: This issue also appears in webview when pages contain date input fields, the best way I found that solve both issues (dialog and webview) is to override the Activity getResources function like this:

@Override
public Resources getResources() {
    if (wrappedResources == null) {
        wrappedResources = wrapResourcesFixDateDialogCrash(super.getResources());
    }
    return wrappedResources;
}

public Resources wrapResourcesFixDateDialogCrash(Resources r) {
    return new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
        @NonNull
        @Override
        public String getString(int id, Object... formatArgs) throws NotFoundException {
            try {
                return super.getString(id, formatArgs);
            } catch (IllegalFormatConversionException ifce) {
                Log.i("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                String template = super.getString(id);
                template = template.replaceAll("%" + ifce.getConversion(), "%s");
                return String.format(getConfiguration().locale, template, formatArgs);
            }
        }
    };
}

Note: The solution includes a cached resources - which means if you have events in the app changing the resources (like language change) than you might need to update the cached resources.

Raanan
  • 4,664
  • 25
  • 45
  • 1
    Works PERFECTLY. Fixes the issue, and lets us keep the standard date picker. Big thumps up for elegant fix. – Moonbloom Apr 14 '16 at 11:03
  • 1
    You're the man! Had the same issue with Android 5.0.1 TalkBack enabled and German. Thank you! – Bona Fide Apr 19 '16 at 19:06
  • 1
    Great!You save my day. Thank you – UFreedom May 06 '16 at 07:38
  • The constructor `Resources (AssetManager assets, DisplayMetrics metrics, Configuration config)` was deprecated in API 25 see [here](https://developer.android.com/reference/android/content/res/Resources.html). Not sure if this issue still exits in API 25, but is there any other way? – user1480019 Nov 06 '16 at 11:28
  • 1
    The issue was reported for the calendar dialog on specific Samsung devices with Lollipop so theoretically you can conditionally apply it (i.e. not apply it above API level 24). See this answer [here](http://stackoverflow.com/a/31855744/348378). – Raanan Nov 06 '16 at 22:50
  • Might there be any implication of discarding theme information like you are doing by writing `new ContextWrapper()` instead of `new ContextThemeWrapper()` as done by @mreichelt ? – Manish Kumar Sharma Oct 30 '17 at 10:38
2

I find a workaround, playing with configuration and Locale variables.

I juste modify the language if I detect it's a french ("fr") language and replace it with an english ("en") language, then put back to normal once out of the DatePicker DialogFragment.

public void showDatePickerDialog(int layoutId) {
        Integer year = cal.get(Calendar.YEAR);
        Integer month = cal.get(Calendar.MONTH);
        Integer day = cal.get(Calendar.DAY_OF_MONTH);

        // Due to an issue with DatePicker and fr language (locale), if locale language is french, this is changed to us to make the DatePicker working
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(US_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        Bundle bundle = createDatePickerBundle(layoutId, year, month, day);
        DialogFragment newFragment = new DatePFragment();
        newFragment.setArguments(bundle);
        newFragment.show(fm, Integer.toString(layoutId));
    }

And once I'm out

@Override
    public void onDatePicked(int LayoutId, int year, int month, int day){
        // Once out from the DatePicker, if we were working on a fr language, we update back the configuration with fr language.
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(FR_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        String date = day + "/" + month + "/" + year;
        txtTaskDate.setText(date);
    }
Alexandre
  • 1,189
  • 2
  • 13
  • 25
1

Forget about the built-in date picker. IMHO switching the locale is not a solution, because I want to have the picker in the current locale. There's only one way to get rid of the crash: use a library that provides an independent implementation.

For a date picker fragment: https://github.com/flavienlaurent/datetimepicker

For a date picker widget: https://github.com/SingleCycleKing/CustomTimePicker (this is more a starting point than a ready-to-use-solution)

stefan222
  • 661
  • 13
  • 30
0

I observed one minor problem with @mreichelt's solution. The dialog was rendered without the title showing the currently selected date (including the day of the week). Explicitly setting the title, and immediately setting the date again solved that problem for me.

Context context = getActivity();
if (isBrokenSamsungDevice()) {
    context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
}
DatePickerDialog datePickerDialog = new DatePickerDialog(context, this, year, month, day);
if (isBrokenSamsungDevice()) {
    datePickerDialog.setTitle("");
    datePickerDialog.updateDate(year, month, day);
}
return datePickerDialog;
mtotschnig
  • 858
  • 8
  • 18
0

Based on mreichelts answer I created a SupportDatePickerDialog.java which wraps aroudn DatePickerDialog and automatically fixes the context for android 5.0 and 5.1.

See gist

Community
  • 1
  • 1
Tobias
  • 6,533
  • 5
  • 59
  • 82
0

this also happens when you programatically define a min date further than the max date

datePicker.getDatePicker().setMaxDate(15000000);

datePicker.getDatePicker().setMinDate(16000000);
Ege Kuzubasioglu
  • 5,000
  • 10
  • 38
  • 76