androidsharedpreferencesclasscastexceptionswitchpreference

Android: ClassCastException on PreferencesActivity onCreate when app is updated (NOT in first run)


I'm about to launch an update of my app, but facing a problem.

One of the preferences class has been changed from CheckBox to Switch and now users who previously installed the app are getting a crash when trying to access PreferenceActivity.

This is my StackTrace:

2021-12-03 19:42:51.824 18764-18764/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xxx.xxx.xxx, PID: 18764
    java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
        at android.app.SharedPreferencesImpl.getBoolean(SharedPreferencesImpl.java:331)
        at androidx.preference.Preference.getPersistedBoolean(Preference.java:1955)
        at androidx.preference.TwoStatePreference.onSetInitialValue(TwoStatePreference.java:201)
        at androidx.preference.Preference.onSetInitialValue(Preference.java:1614)
        at androidx.preference.Preference.dispatchSetInitialValue(Preference.java:1587)
        at androidx.preference.Preference.onAttachedToHierarchy(Preference.java:1311)
        at androidx.preference.Preference.onAttachedToHierarchy(Preference.java:1326)
        at androidx.preference.PreferenceGroup.addPreference(PreferenceGroup.java:249)
        at androidx.preference.PreferenceGroup.addItemFromInflater(PreferenceGroup.java:170)
        at androidx.preference.PreferenceInflater.rInflate(PreferenceInflater.java:345)
        at androidx.preference.PreferenceInflater.rInflate(PreferenceInflater.java:346)
        at androidx.preference.PreferenceInflater.inflate(PreferenceInflater.java:157)
        at androidx.preference.PreferenceInflater.inflate(PreferenceInflater.java:109)
        at androidx.preference.PreferenceManager.inflateFromResource(PreferenceManager.java:216)
        at androidx.preference.PreferenceFragmentCompat.addPreferencesFromResource(PreferenceFragmentCompat.java:361)
        at xxx.xxx.xxx.ui.activities.settings.SettingsActivity$SettingsFragment.onCreatePreferences(SettingsActivity.java:164)
        at androidx.preference.PreferenceFragmentCompat.onCreate(PreferenceFragmentCompat.java:160)
        at xxx.xxx.xxx.ui.activities.settings.SettingsActivity$SettingsFragment.onCreate(SettingsActivity.java:149)
        at androidx.fragment.app.Fragment.performCreate(Fragment.java:2949)
        at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:278)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3138)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3072)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:502)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1440)
        at android.app.Activity.performStart(Activity.java:8109)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3806)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:235)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:215)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:187)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:105)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2386)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:213)
        at android.app.ActivityThread.main(ActivityThread.java:8178)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)

The problem is that before, CheckBox values where in String format so, as now they were migrated to Boolean, when trying to get set values is crashing.

Unluckily, the StackTrace is not giving me useful information, as it is crashing in onCreate and not in a specific line.

I tried clearing all SwitchPreference values at startup to no avail:

//clear switch preferences
SharedPreferences prefs = TMPreferences.getPrefs();
prefs.edit().remove("SHUFFLE").apply();
prefs.edit().remove("NOTIFICATIONS_RANDOMNOTIFICATIONS").apply();
prefs.edit().remove("VIDEORECOMMENDATION").apply();
//set switch preferences
prefs.edit().putBoolean("SHUFFLE", false).apply();
prefs.edit().putBoolean("NOTIFICATIONS_RANDOMNOTIFICATIONS", false).apply();
prefs.edit().putBoolean("VIDEORECOMMENDATION", false).apply();

I know one solution would be to "force" users to go to Applications > Clear app data, but wouldn't like to end doing that.

What I expect is a way to reset old CheckBoxes values and replace them with new SwitchPreferences without asking uses to clear data.

For new users the app is working fine.

This is my Preferences.xml file:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

    <PreferenceCategory
        android:key="pref_category_test"
        android:title="@string/pref_category_test"
        app:iconSpaceReserved="false">
        <ListPreference
            android:key="pref_testtime"
            android:title="@string/pref_testtime"
            android:entries="@array/testtime_texts"
            android:entryValues="@array/testtime_values"
            android:defaultValue="-1"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_shuffle"
            android:title="@string/pref_shuffle"
            android:defaultValue="false"
            app:iconSpaceReserved="false" />
        
        <ListPreference
            android:key="pref_fontsize"
            android:title="@string/pref_fontsize"
            android:entries="@array/fontsize_texts"
            android:entryValues="@array/fontsize_values"
            android:defaultValue="100"
            app:iconSpaceReserved="false" />

    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_language"
        android:title="@string/pref_category_language"
        app:iconSpaceReserved="false">
        <ListPreference
            android:key="pref_languages"
            android:title="@string/pref_language"
            android:entries="@array/language_texts"
            android:entryValues="@array/language_values"
            app:iconSpaceReserved="false" />
    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_notification"
        android:title="@string/pref_category_notifications"
        app:iconSpaceReserved="false">
        <ListPreference
            android:key="pref_notifications_interval"
            android:title="@string/pref_taketest_reminder"
            android:entries="@array/notification_texts"
            android:entryValues="@array/notification_values"
            android:defaultValue="1"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_notifications_randomnotifications"
            android:title="@string/pref_randomnotifications"
            android:defaultValue="false"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_videorecommendation"
            android:title="@string/pref_videorecommendation_notification"
            android:defaultValue="true"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_checkfornewtests"
            android:title="@string/pref_checkfornewtests_notification"
            android:defaultValue="true"
            app:iconSpaceReserved="false" />
    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_aspect"
        android:title="@string/pref_category_aspect"
        app:iconSpaceReserved="false">
    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_animation"
        android:title="@string/pref_category_animations"
        app:iconSpaceReserved="false">

        <SwitchPreference
            android:key="pref_transitionanimation"
            android:title="@string/pref_transitionanimation"
            android:defaultValue="true"
            app:iconSpaceReserved="false" />
    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_sounds"
        android:title="@string/pref_category_sounds"
        app:iconSpaceReserved="false">

        <SwitchPreference
            android:key="pref_buttonsound"
            android:title="@string/pref_buttonsound"
            android:defaultValue="false"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_pageflipsound"
            android:title="@string/pref_pageflipsound"
            android:defaultValue="false"
            app:iconSpaceReserved="false" />

        <SwitchPreference
            android:key="pref_endtestsound"
            android:title="@string/pref_endtestsound"
            android:defaultValue="false"
            app:iconSpaceReserved="false" />

    </PreferenceCategory>
    
    <PreferenceCategory
        android:key="pref_category_users"
        android:title="@string/pref_category_users"
        app:iconSpaceReserved="false">
        <Preference
            android:key="pref_loggedinas"
            android:title="@string/activitypreferences_loggedinas"
            app:iconSpaceReserved="false">
        </Preference>
        <Preference
            android:key="pref_deletecurrentuser"
            android:title="@string/activitypreferences_deletecurrentuser"
            app:iconSpaceReserved="false">
        </Preference>
        <Preference
            android:key="pref_changemypassword"
            android:title="@string/activitypreferences_changemypassword"
            app:iconSpaceReserved="false">
        </Preference>
    </PreferenceCategory>

    <PreferenceCategory
        android:key="pref_category_troubleshooting"
        android:title="@string/pref_category_troubleshooting"
        app:iconSpaceReserved="false">
        <Preference
            android:key="pref_resyncalldata"
            android:title="@string/activitypreferences_resyncalldata"
            app:iconSpaceReserved="false">
        </Preference>
    </PreferenceCategory>

</PreferenceScreen>

SettingsActivity onCreate:

protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
}

Solution

  • In the end the only way I was able to solve it was to rename the Switch "problematic" preferences keys.

    This is not the best solution, as users who installed my app previously to this new version will have some "unnecessary" keys "hanging" around here, but it works, the app won't crash anymore on updates when trying to access PreferencesActivity.