Search code examples
javaandroidandroid-preferencesandroid-switch

Why does my SwitchPreference glitch on orientation change?


I have an activity (extends AppCompatActivity) that contains a PreferenceFragment with my app's settings. The first setting is a SwitchPreference to enable or disable notifications, followed by other preferences that depend upon that SwitchPreference.

My problem is this: I discovered that if I rotate the device (causing an orientation change) and then tap the switch, it doesn't render properly - it seems to show two overlapping switches (here's a screencap from a Moto X), one in the old position and one in the new position. Interestingly, this even happens with the older style of switches (here's a screencap from a Nexus S running JellyBean).

I haven't found anything about this bug around the internet, and as far as I can tell, it doesn't happen in Android OS apps (such as Settings) or in other third-party apps (such as Intent Intercept by Intrications). So it seems I'm just doing something wrong here.

Here is the setup code in my SettingsFragment:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);

        // Grab Preference objects
        final Preference wklyRemPref = findPreference(getString(R.string.prefkey_weekly_reminder));

        // Set up notification settings
        findPreference(getString(R.string.prefkey_allow_notifs))
                .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
                    @Override
                    public boolean onPreferenceChange(Preference preference, Object newValue) {
                        if (Boolean.FALSE.equals(newValue)) {
                            // Turn off and disable "Weekly Reminder" pref
                            ((TwoStatePreference) wklyRemPref).setChecked(false);
                            // (Disabling is done by the system because the "Weekly Reminder"
                            // preference depends on the "Notifications" preference)
                        }
                        return true;
                    }
                });
        wklyRemPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference preference, Object newValue) {
                Log.d("Pref", preference + " value changed (" + newValue + ")");
                return true;
            }
        });
        ...
    }

Here is my settings.xml file:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceCategory android:title="Reminders">
        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/prefkey_allow_notifs"
            android:title="@string/allow_notifs"/>
        <!-- Weekly reminder: -->
        <CheckBoxPreference
            android:dependency="@string/prefkey_allow_notifs"
            android:defaultValue="true"
            android:key="@string/prefkey_weekly_reminder"
            android:summary="Remind me weekly to choose a scripture"
            android:title="Weekly reminder" />
        <net.danmercer.ponderizer.settings.ReminderPreference
            android:defaultValue="sunday/17:00"
            android:dependency="@string/prefkey_weekly_reminder"
            android:key="@string/prefkey_weekly_reminder_time"
            android:title="Set weekly reminder time" />
        <!-- Notification preferences -->
        <RingtonePreference
            android:dependency="@string/prefkey_allow_notifs"
            android:key="@string/prefkey_notif_sound"
            android:title="Notification Sound"
            android:ringtoneType="notification"
            android:showDefault="true"
            android:showSilent="true"/>
        <CheckBoxPreference
            android:defaultValue="true"
            android:dependency="@string/prefkey_allow_notifs"
            android:key="@string/prefkey_notif_vibrate"
            android:title="Vibrate" />
    </PreferenceCategory>
    ...
</PreferenceScreen>

Edit: I have also tried to clean and rebuild my project, and uninstall and reinstall the app, to no avail.

Edit 2: I tried using a CheckBoxPreference instead of a SwitchPreference and experienced the same problem. And what's worse, I discovered that the entire fragment seems to be burned into the screen (so to speak) when I rotate the activity. I can scroll the settings fragment, but a ghost of what it was before stays behind. This artifact manifested earlier as two overlapping switches, but I didn't realize it was so pervasive.


Solution

  • I found the problem. In my SettingsActivity onCreate() method, I had code like this:

    FragmentManager fm = getFragmentManager();
    fm.beginTransaction()
            .replace(R.id.frag_container, new SettingsFragment())
            .commit();
    

    I changed the replace() call to an add() call, and now it works properly. It seems that the activity was adding two identical fragments to the layout when it was recreated upon orientation change. Using replace solved the problem, because it wouldn't add multiple fragments to the activity. Moral of the story: when in doubt, use replace(), not add().