web-dev-qa-db-ja.com

(個人用アプリで)クリックすると、デバイスでDatePickerがクラッシュする

編集:電話が「フランス語」言語で設定されている場合にのみ問題が表示されることを確認しました。

現在、独自のアプリを作成していますが、電話(デバッグモードの電話:Samsung S5)でDatePickerウィジェットを使用すると問題が発生します。

エラー収集は次のとおりです:Java.util.IllegalFormatConversionException

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)

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

そして、関連するxmlレイアウトは次のとおりです。

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

事実、私には2種類の問題がありました(とにかくアプリのクラッシュにつながります)。

  1. アプリを入力してDatePickerウィジェットに直接アクセスすると、カレンダーが正しく表示され、日を選択するとすぐに(たとえば、年を変更しても問題はないので1日と言うと)、アプリがクラッシュします。
  2. DatePickerを表示するために他の機能をクリックすると、直接クラッシュします。

私は次のような他のスレッドを見ました: DatePickerはAndroid 5. でサムスンでクラッシュします)==

このスレッドでは、十分な情報が得られません。特定のソリューションではカレンダーのスピナービューのみを表示でき、さらに表示されるスピナーは中央に配置されず、鉱山として表示されません(画面の上部に直接配置)。

さらに詳細が必要な場合は私に尋ねてください。

ありがとう。アレックス。

41
Alexandre

Lollipop UI実装のSamsungのバグです。 Galaxy S4、S5、Note 3、さらに多くのデバイスに影響します。私たちにとっては、de_DEおよびde_AT言語で発生しますが、複数の言語に影響する問題のようです。

Samsungデバイスで日付ピッカーにHoloテーマを強制的に使用することで修正しました。 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;
}

サムスンのおかげで、ユーザーはニースのマテリアルデザインの日付ピッカーを入手できません。

91
mreichelt

実際にユーザーがLollipopライブラリを使用し続けることができるFIX PATCHがあります。

Android 5.0.1フランス語で、S4でTalkBackを有効にすることでクラッシュを再現することができました。サムスンは、日付の代わりにアクセシビリティに使用されるアイテム選択文字列に日付全体を渡しているようです。

コンテキストをラップする@mreicheltソリューションに基づいて、getString(id、args)関数をオーバーライドし、実際に問題を修正できます。

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

したがって、このコンテキストをDatePickerDialogコンストラクターに提供すると、問題が修正されます。

PDATE:この問題は、ページに日付入力フィールドが含まれる場合にwebviewにも表示されます。両方の問題(ダイアログとwebview)を解決する最良の方法は、Activity getResources関数を次のようにオーバーライドすることです:

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

注:ソリューションにはキャッシュされたリソースが含まれます。つまり、アプリでリソースを変更するイベント(言語の変更など)がある場合、キャッシュされたリソースを更新する必要がある場合があります。

19
Raanan

回避策を見つけて、構成変数とロケール変数で遊んでいます。

フランス語( "fr")言語であることを検出し、英語( "en")言語に置き換えると、言語を変更し、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));
    }

そして、私が出たら

@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

組み込みの日付ピッカーは忘れてください。現在のロケールでピッカーを使用するため、ロケールを切り替えるIMHOは解決策ではありません。クラッシュを取り除く方法は1つしかありません。独立した実装を提供するライブラリを使用することです。

日付ピッカーフラグメントの場合: https://github.com/flavienlaurent/datetimepicker

日付選択ウィジェットの場合: https://github.com/SingleCycleKing/CustomTimePicker (これはすぐに使用できるソリューションというよりも出発点です)

1
stefan222

これは、min dateよりもmax dateをプログラムで定義した場合にも発生します

datePicker.getDatePicker().setMaxDate(15000000);

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

@mreicheltのソリューションに小さな問題が1つありました。ダイアログは、現在選択されている日付(曜日を含む)を示すタイトルなしでレンダリングされました。タイトルを明示的に設定し、すぐに日付を再度設定すると、その問題が解決しました。

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