web-dev-qa-db-fra.com

DatePicker se bloque sur mon appareil lorsque l'utilisateur clique dessus (avec une application personnelle)

Modifier: j'ai vérifié que le problème n'apparaissait que lorsque le téléphone était configuré avec la langue "française".

Je suis en train de créer ma propre application et je rencontre un problème d'utilisation du widget DatePicker sur mon téléphone (téléphone en mode débogage: Samsung S5).

La collecte des erreurs est la suivante: Java.util.IllegalFormatConversionException

J'ajoute plus de détails sur le problème recueilli dans 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)

Voici mon code DialogFragment:

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);
        }
    }
}

Et voici la mise en page XML associée:

<?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>

Le fait est que j'ai eu deux types de problèmes (menant au crash de l'application de toute façon):

  1. Lorsque j'entre dans mon application et que je vais directement au widget DatePicker, le calendrier est correctement affiché et dès que je sélectionne un jour (je dis un jour, car il n'y a aucun problème à changer l'année par exemple), l'application se bloque.
  2. Si je vais sur une autre fonction le clic pour afficher le DatePicker, il se bloque directement.

J'ai vu d'autres discussions telles que: Accident de DatePicker dans Samsung avec Android 5.0

Dans ce fil, je ne reçois pas assez d'informations, car la solution donnée permet d'afficher uniquement la vue panoramique du calendrier et, en outre, celle-ci n'est pas centrée et présentée comme la mienne (placée directement en haut de l'écran).

Si vous avez besoin de plus amples détails s'il vous plaît demandez-moi.

Merci . Alex.

39
Alexandre

C'est un bug de Samsung dans leur implémentation de l'interface utilisateur Lollipop. Il affecte Galaxy S4, S5, Note 3 et probablement plus de périphériques. Pour nous, cela se produit sur les langues de_DE et de_AT, mais il semble que ce soit un problème qui affecte plusieurs langues.

Je l'ai corrigé en obligeant les appareils Samsung à utiliser le thème Holo pour le sélecteur de date. Dans notre 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;
}

Grâce à Samsung, leurs utilisateurs ne recevront pas le sélecteur de date conçu par Nice.

90
mreichelt

J'ai un éventuel correctif qui permettra à l'utilisateur de continuer à utiliser la bibliothèque Lollipop. 

Je ne pouvais que reproduire l'incident en activant TalkBack sur un S4 avec Android 5.0.1 français, il semble que Samsung passe la date entière à la chaîne de sélection des éléments utilisée pour l'accessibilité au lieu du numéro du jour. 

En nous appuyant sur la solution @mreichelt d’enveloppement du contexte, nous pouvons redéfinir la fonction getString (id, args) et résoudre le problème.

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;
    }
};

Donc, si nous fournissons ce contexte au constructeur DatePickerDialog, le problème sera résolu.

UPDATE: Ce problème apparaît également dans la vue Web lorsque les pages contiennent des champs de saisie de date, la meilleure façon de résoudre ces deux problèmes (boîte de dialogue et vue Web) consiste à remplacer la fonction Activity getResources comme suit:

@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);
            }
        }
    };
}

Remarque: la solution inclut des ressources mises en cache - ce qui signifie que si l'application modifie les ressources (changement de langue, par exemple), il peut être nécessaire de mettre à jour les ressources mises en cache.

18
Raanan

Je trouve une solution de contournement en jouant avec les variables de configuration et de paramètres régionaux.

Je modifie simplement la langue si je détecte une langue française ("fr") et la remplace par une langue anglaise ("en"), puis je reviens à la normale une fois sorti du 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));
    }

Et une fois que je suis sorti

@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);
    }
2
Alexandre

Oubliez le sélecteur de date intégré. IMHO changer les paramètres régionaux n'est pas une solution, car je veux avoir le sélecteur dans les paramètres régionaux actuels. Il n'y a qu'un moyen de se débarrasser du crash: utilisez une bibliothèque qui fournit une implémentation indépendante.

Pour un fragment de sélecteur de date: https://github.com/flavienlaurent/datetimepicker

Pour un widget de sélecteur de date: https://github.com/SingleCycleKing/CustomTimePicker (il s’agit plus d’un point de départ que d’une solution prête à l’emploi)

1
stefan222

cela se produit également lorsque vous définissez par programme un min date plus avancé que le max date

datePicker.getDatePicker().setMaxDate(15000000);

datePicker.getDatePicker().setMinDate(16000000);
0
Ege Kuzubasioglu

Sur la base de mreichelts answer j'ai créé un SupportDatePickerDialog.Java qui encapsule aroudn DatePickerDialog et corrige automatiquement le contexte pour Android 5.0 et 5.1.

Voir Gist

0
Tobias

J'ai observé un problème mineur avec la solution de @ mreichelt. La boîte de dialogue a été rendue sans titre indiquant la date actuellement sélectionnée (y compris le jour de la semaine). Définir explicitement le titre et régler immédiatement la date a résolu le problème pour moi.

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;
0
mtotschnig