何故か、preferenceを追加するとアプリがクラッシュした。booleanにstringを入れようとしたと見るが、よくわからなかった。最終的にはkey値が同じものを複数指定していたのが原因だった。
あなた:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean at android.app.SharedPreferencesImpl.getBoolean(SharedPreferencesImpl.java:345)
ChatGPT:
このエラー:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
は、SharedPreferences#getBoolean() で boolean型として読み込もうとしたキーに、実際には文字列(<string>)として保存されていた値が入っていたことが原因です。
たとえば以下のような preferences.xml の値:
<string name="RemoveEmptyLine">0</string>
これに対して Java 側で:
boolean removeEmptyLine = prefs.getBoolean("RemoveEmptyLine", false);
と書くと、SharedPreferences は "0"(String)を boolean にキャストしようとしてクラッシュします。
<boolean name="RemoveEmptyLine" value="false" />
そして Java 側はこのままでOK:
boolean removeEmptyLine = prefs.getBoolean("RemoveEmptyLine", false);
String val = prefs.getString("RemoveEmptyLine", "0");
boolean removeEmptyLine = "1".equals(val) || "true".equalsIgnoreCase(val);
先ほど提供してくれた XML の中だと、以下が <string> なのに boolean として読まれてそうなキー です:
| キー名 | 現在の型 | 期待される型 |
|---|---|---|
| RemoveEmptyLine | |
|
| Cover | |
boolean または int にするべき |
| MaxEmptyLine | |
int か boolean(使用箇所による) |
- preferences.xml を開いて、booleanとして使っているキーを確認
- 対象の
を に修正 - アプリを再インストール or SharedPreferences.clear() して古い値を消す(※以前保存された値が残るため)
preferences.xml を開いて、booleanとして使っているキーを確認
対象の <string> を <boolean> に修正
アプリを再インストール or SharedPreferences.clear() して古い値を消す(※以前保存された値が残るため)
prefs.edit().clear().apply(); // 開発中だけ使う
- getBoolean() を使うときは SharedPreferences に保存された値が 本当に boolean かどうか確認
- XML や PreferenceFragmentCompat 側で
→ に修正 - 古いデータが残っているとクラッシュするので初期化も忘れずに!
getBoolean() を使うときは SharedPreferences に保存された値が 本当に boolean かどうか確認
XML や PreferenceFragmentCompat 側で <string> → <boolean> に修正
古いデータが残っているとクラッシュするので初期化も忘れずに!
該当箇所のコードを貼ってもらえれば、型の修正提案もできますよ。必要なら教えてください!
あなた:
FATAL EXCEPTION: main Process: io.github.kyukyunyorituryo.aozoraepub3, PID: 32254 java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean at android.app.SharedPreferencesImpl.getBoolean(SharedPreferencesImpl.java:345) at androidx.preference.Preference.getPersistedBoolean(Preference.java:1975) at androidx.preference.TwoStatePreference.onSetInitialValue(TwoStatePreference.java:207) at androidx.preference.Preference.onSetInitialValue(Preference.java:1634) at androidx.preference.Preference.dispatchSetInitialValue(Preference.java:1607) at androidx.preference.Preference.onAttachedToHierarchy(Preference.java:1330) at androidx.preference.Preference.onAttachedToHierarchy(Preference.java:1345) at androidx.preference.PreferenceGroup.addPreference(PreferenceGroup.java:253) at androidx.preference.PreferenceGroup.addItemFromInflater(PreferenceGroup.java:173) at androidx.preference.PreferenceInflater.rInflate(PreferenceInflater.java:350) at androidx.preference.PreferenceInflater.rInflate(PreferenceInflater.java:351) at androidx.preference.PreferenceInflater.inflate(PreferenceInflater.java:161) at androidx.preference.PreferenceInflater.inflate(PreferenceInflater.java:112) at androidx.preference.PreferenceManager.inflateFromResource(PreferenceManager.java:217) at androidx.preference.PreferenceFragmentCompat.setPreferencesFromResource(PreferenceFragmentCompat.java:380) at io.github.kyukyunyorituryo.aozoraepub3.SettingsFragment.onCreatePreferences(SettingsFragment.java:15) at androidx.preference.PreferenceFragmentCompat.onCreate(PreferenceFragmentCompat.java:161) at androidx.fragment.app.Fragment.performCreate(Fragment.java:3090) at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:257) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1899) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1817) at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1760) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2985) at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2895) at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263) at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351) at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251) at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1564) at android.app.Activity.performStart(Activity.java:8555) at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3859) at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221) at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2394) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:211) at android.os.Looper.loop(Looper.java:300) at android.app.ActivityThread.main(ActivityThread.java:8296) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
ChatGPT:
この FATAL EXCEPTION は、Androidの SharedPreferences に保存された値の型と、Preference(特に SwitchPreferenceCompat や CheckBoxPreference など)で期待している型が一致していないために発生しています。
以下のようなコードが原因で:
prefs.getBoolean("SomeKey", false);
でも、preferences.xml には:
<string name="SomeKey">false</string>
と <string> として保存されていた場合、アプリはクラッシュします。
at androidx.preference.Preference.getPersistedBoolean(Preference.java:1975)
これは、<SwitchPreferenceCompat> や <CheckBoxPreference> などの TwoStatePreference の defaultValue に boolean を期待していたのに、実際は SharedPreferences に String があった、ということです。
提供されたXMLでは、以下のように string になってるものが対象です:
<string name="RemoveEmptyLine">0</string> ← これはNG
↓以下のように直す:
<boolean name="RemoveEmptyLine" value="false" />
アプリ内で以前保存された不適切な型(例:String)を削除します。
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.remove("RemoveEmptyLine"); // クラッシュしてるキー名
editor.apply();
または開発中なら全削除でもOK:
prefs.edit().clear().apply(); // 全消し(慎重に)
エラーを引き起こしているのは boolean型の設定項目にstring型の値が保存されていたキー です。
たとえば:
<string name="RemoveEmptyLine">0</string> ← boolean expected <string name="Cover">0</string> ← boolean expected? <string name="MaxEmptyLine">-</string> ← int expected?
| 対策 | 内容 |
|---|---|
| XMLを修正 | |
| キャッシュクリア | prefs.edit().clear().apply() または .remove("キー") を使う |
| 型の一貫性を保つ | 保存時も読み込み時も常に同じ型を使う |
必要であれば、preferences.xml を私の方で自動チェックして、どのキーが boolean にすべきか一覧で出すこともできます。希望しますか?
あなた:
preferenceのUIを開いただけで落ちる
ChatGPT:
了解です、「PreferenceのUIを開いただけで落ちる」という状況は、かなりの確率で preferences.xml 内にある boolean を期待している項目に、string 型の値が保存されていることが原因 です。
これは SharedPreferences に保存された値の型が UI コンポーネント(SwitchPreferenceCompatなど)と一致していないために、UI初期化時 (onCreatePreferences) にクラッシュします。
以下の項目が問題を引き起こしている可能性が非常に高いです:
| キー名 | 内容 | 想定される正しい型 | 修正例 |
|---|---|---|---|
| RemoveEmptyLine | |
boolean | |
| Cover | |
boolean(または string) | ※UI側の型と要相談 |
| MaxEmptyLine | |
int? | UI側がEditTextPreferenceかSwitchかで変わる |
| ChukiRuby | |
int or boolean | どちらとして使っているか確認 |
| TitleType | |
int | OK(ただし使用箇所で型キャスト注意) |
アプリ起動時(MainActivity.onCreate() など)に 強制的に不正な型を消す:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit()
.remove("RemoveEmptyLine")
.remove("Cover")
.remove("MaxEmptyLine")
.remove("ChukiRuby")
.apply();
またはすべて初期化:
prefs.edit().clear().apply(); // 注意:全ての設定が初期化されます
RemoveEmptyLine のように boolean にすべき項目は、次のように修正してください:
<!-- 修正前 --> <string name="RemoveEmptyLine">0</string> <!-- 修正後 --> <boolean name="RemoveEmptyLine" value="false" />
- Androidの SharedPreferences は、キーごとに型を固定して保存します。
- Preference のUIは getPersistedBoolean() など、型を決め打ちで呼び出すため、保存されている型が違うと ClassCastException が出ます。
Androidの SharedPreferences は、キーごとに型を固定して保存します。
Preference のUIは getPersistedBoolean() など、型を決め打ちで呼び出すため、保存されている型が違うと ClassCastException が出ます。
- preferences.xml に
で boolean 値を入れていないか? - SharedPreferences に古い値が残っていないか?
- Preference UIの該当キーの型と保存時の型が一致しているか?
preferences.xml に <string> で boolean 値を入れていないか?
SharedPreferences に古い値が残っていないか?
Preference UIの該当キーの型と保存時の型が一致しているか?
必要であれば、問題のある <string> をすべて <boolean> や <int> に修正したバージョンの XML を出力しますよ。欲しいですか?
あなた:
数字をスライダーから選択したい
ChatGPT:
数字をキーボードで入力する EditTextPreference を、スライダー(シークバー)で選択できるようにしたい場合、androidx.preference では SeekBarPreference を使うのがベストです!
<SeekBarPreference
android:key="WebBeforeChapter"
android:title="出力話数"
android:summary="最新話からの出力数"
android:defaultValue="1"
android:min="1"
android:max="100" />
- defaultValue="1":初期値を 1 にします。
- min="1":最小値(必要に応じて調整可能)。
- max="100":最大値(必要に応じて調整可能)。
defaultValue="1":初期値を 1 にします。
min="1":最小値(必要に応じて調整可能)。
max="100":最大値(必要に応じて調整可能)。
スライダーはそのままでも使えますが、細かく調整したい場合は SeekBarPreference に app:showSeekBarValue="true" をつけると値も表示できます。
xmlns:app="http://schemas.android.com/apk/res-auto"
をルートXMLに追加して:
<SeekBarPreference
android:key="WebBeforeChapter"
android:title="出力話数"
android:summary="最新話からの出力数"
android:defaultValue="1"
android:min="1"
android:max="100"
app:showSeekBarValue="true" />
必要なら、現在の EditTextPreference の値が正しく int になっているかのチェックと変換コードも出せますよ!また、範囲を変更したい(例:最大200にしたいなど)場合も教えてください。
あなた:
次のpreferenceを追加すると設定の呼び出し時に止まる
<PreferenceCategory android:title="変換対象">
<CheckBoxPreference
android:key="WebBeforeChapter"
android:title="最新"
android:summary="最新話から指定話数のみ出力します"
android:defaultValue="false"/>
<EditTextPreference
android:key="WebBeforeChapter"
android:title="出力話数"
android:summary="最新話からの出力数"
android:defaultValue="1"
android:inputType="number" />
<CheckBoxPreference
android:key="WebModifiedOnly"
android:title="更新分"
android:summary="追加更新のあった話のみ変換します"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="WebModifiedTail"
android:title="連続"
android:summary="最新話から連続した更新分のみ変換します"
android:defaultValue="false"/>
</PreferenceCategory>
ChatGPT:
設定画面でクラッシュする原因は、以下のように 同じ key を複数の Preference に使っている ためです。
<CheckBoxPreference
android:key="WebBeforeChapter" ←①
... />
<EditTextPreference
android:key="WebBeforeChapter" ←②
... />
このように WebBeforeChapter というキーが 2 回使われており、片方は boolean(CheckBox)、もう片方は String(EditText)として保存されます。
これにより、Androidがbooleanを期待しているところでStringが保存されていて型が合わず、ClassCastException が発生しています:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
各 Preference の key は 重複させず、ユニークにしてください。
例:
<PreferenceCategory android:title="変換対象">
<CheckBoxPreference
android:key="WebBeforeChapterCheck"
android:title="最新"
android:summary="最新話から指定話数のみ出力します"
android:defaultValue="false"/>
<SeekBarPreference
android:key="WebBeforeChapter"
android:title="出力話数"
android:summary="最新話からの出力数"
android:defaultValue="1"
android:min="1"
android:max="100"
app:showSeekBarValue="true" />
<CheckBoxPreference
android:key="WebModifiedOnly"
android:title="更新分"
android:summary="追加更新のあった話のみ変換します"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="WebModifiedTail"
android:title="連続"
android:summary="最新話から連続した更新分のみ変換します"
android:defaultValue="false"/>
</PreferenceCategory>
- 元のEditTextPreferenceをSeekBarPreferenceに変更しているのは、前のご要望を踏まえてです。元に戻す場合は教えてください。
- WebBeforeChapterCheck(チェックボックス)と WebBeforeChapter(数値)を分離したことで、"使う/使わない" フラグと "使う場合の数値" が別々に管理できます。
元のEditTextPreferenceをSeekBarPreferenceに変更しているのは、前のご要望を踏まえてです。元に戻す場合は教えてください。
WebBeforeChapterCheck(チェックボックス)と WebBeforeChapter(数値)を分離したことで、"使う/使わない" フラグと "使う場合の数値" が別々に管理できます。
何かの設定がグレーアウトされてほしい(例:チェックされてないとスライダーを無効化)みたいな挙動が欲しければ、カスタムで処理できますのでそれもお知らせください!