1

I have a OnPreferenceClickListener which should remove a specific Preference (with key preference_to_remove) from a PreferenceScreen.

The problem is that my solution works when preference_to_remove is not located inside a nested PreferenceScreen but does not work, when it is inside a nested screen AND the screen orientation changes. Before screen orientation changes, nested screens are working as expected, too.

The following code contains two version, one with a flat non-nested PreferenceScreen and the broken nested PreferenceScreen.

What is the reason that the nested version is not able to remove the Preference with key preference_to_remove after screen orientation changes? What would be a solution besides using only flat PreferenceScreens and Intents to start new PreferenceScreens as pseudo children?

PS: I am using PreferenceActivity for FroYo compatibility.

How to reproduce with Test-App

Open App → Click Flat-Button → Click preference_to_click which should remove preference_to_remove. → Orientation change → Click preference_to_click to remove preference_to_remove again. Preference removed? Success!

Open App → Click Subscreen-Button → Click Test → Now repeat the steps from the first test, but this time preference_to_remove will be not not removable after orientation changing.

Download App (Source)

pref_flat.xml

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

    <Preference
        android:key="preference_to_click"
        android:persistent="false"
        android:title="preference_to_click" />
    <Preference
        android:key="preference_to_remove"
        android:title="preference_to_remove" />

</PreferenceScreen>

pref_subscreen.xml (Nested PreferenceScreen)

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

    <PreferenceScreen
        android:key="subscreen"
        android:persistent="false"
        android:title="Test" >
        <Preference
            android:key="preference_to_click"
            android:persistent="false"
            android:title="preference_to_click" />
        <Preference
            android:key="preference_to_remove"
            android:title="preference_to_remove" />
    </PreferenceScreen>

</PreferenceScreen>

Diff of PrefFlatActivity.java and PrefSubscreenActivity.java

1c1
< public class PrefFlatActivity extends PreferenceActivity {
---
> public class PrefSubscreenActivity extends PreferenceActivity {
5,6c5,7
<     public static final String PREFERENCE_TO_CLICK = "preference_to_click";
<     public static final String PREFERENCE_TO_REMOVE = "preference_to_remove";
---
>     private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
>     private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
>     private static final String PREFERENCE_SUBSCREEN = "subscreen";
15c16
<         addPreferencesFromResource(R.xml.pref_flat);
---
>         addPreferencesFromResource(R.xml.pref_subscreen);
28c29
<             PreferenceScreen screen = getPreferenceScreen();
---
>             PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);

PrefFlatActivity.java (Working)

/**
 * Works as expected. Clicking toggles the "visibility" of the PREFERENCE_TO_REMOVE Preference.
 */
public class PrefFlatActivity extends PreferenceActivity {
    /**
     * Preference keys.
     */
    public static final String PREFERENCE_TO_CLICK = "preference_to_click";
    public static final String PREFERENCE_TO_REMOVE = "preference_to_remove";

    private final String PREF_NAME = getClass().getName() + ".pref";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getPreferenceManager().setSharedPreferencesName(PREF_NAME);
        addPreferencesFromResource(R.xml.pref_flat);

        findPreference(PREFERENCE_TO_CLICK)
            .setOnPreferenceClickListener(new OnFlatClickListener());
    }

    /**
     * Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
     */
    private class OnFlatClickListener implements OnPreferenceClickListener {
        private Preference mRescuedPreference;

        public boolean onPreferenceClick(Preference preference) {
            PreferenceScreen screen = getPreferenceScreen();
            Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);

            Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));

            if (prefToRemove != null) {
                screen.removePreference(prefToRemove);
                mRescuedPreference = prefToRemove; // Rescue reference to re-add it later.
            }

            else {
                screen.addPreference(mRescuedPreference);
            }

            return true;
        }
    }
}

PrefSubscreenActivity.java (Nested, broken after orientation change)

/**
 * Broken after orientation change. Clicking does not remove/add PREFERENCE_TO_REMOVE.
 */
public class PrefSubscreenActivity extends PreferenceActivity {
    /**
     * Preference keys.
     */
    private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
    private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
    private static final String PREFERENCE_SUBSCREEN = "subscreen";

    private final String PREF_NAME = getClass().getName() + ".pref";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getPreferenceManager().setSharedPreferencesName(PREF_NAME);
        addPreferencesFromResource(R.xml.pref_subscreen);

        findPreference(PREFERENCE_TO_CLICK)
            .setOnPreferenceClickListener(new OnFlatClickListener());
    }

    /**
     * Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
     */
    private class OnFlatClickListener implements OnPreferenceClickListener {
        private Preference mRescuedPreference;

        public boolean onPreferenceClick(Preference preference) {
            PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);
            Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);

            Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));

            if (prefToRemove != null) {
                screen.removePreference(prefToRemove);
                mRescuedPreference = prefToRemove; // Rescue reference to re-add it later.
            }

            else {
                screen.addPreference(mRescuedPreference);
            }

            return true;
        }
    }
}
Rayne
  • 2,620
  • 20
  • 28

1 Answers1

1

edit: I've been having trouble getting this to work as you requested. I read through the source for Preferences and how they handle state without any luck, so I'm probably missing something obvious (isn't that how it often goes?)

I did some testing, and I believe that the state of the PreferenceScreen and/or the Dialog it uses to display the nested PreferenceScreen is behaving in a way that neither of us expects. This behavior is not observed when using a PreferenceCategory instead of a PreferenceScreen, for example.

Since this way of working with Preferences is deprecated, you can always try using fragments:

http://developer.android.com/reference/android/preference/PreferenceActivity.html

However, I suspect you are avoiding this for legacy reasons.

Another option would be to create your Preference programmatically:

Building Preference screen in code depending on another setting

Or, you could handle configuration changes yourself:

http://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
https://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes/8127813#8127813
<activity ..
 android:configChanges="orientation|keyboardHidden" .. >

Hopefully you get this figured out soon. I'm sure the solution will pop out at us eventually; it can't be that hard! Here's hoping ;)

Original source (mostly) retained below:

    package com.example.removepref;

    import android.preference.Preference;
    import android.preference.Preference.OnPreferenceClickListener;
    import android.preference.PreferenceActivity;
    import android.preference.PreferenceScreen;
    import android.util.Log;


    public class PrefSubscreenActivity extends PreferenceActivity {
        /**
         * Preference keys.
         */
        private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
        private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
        private static final String PREFERENCE_SUBSCREEN = "subscreen";

        private final String PREF_NAME = getClass().getName() + ".pref";

        @Override
        protected void onResume() {
            super.onResume();
            getPreferenceManager().setSharedPreferencesName(PREF_NAME);
            addPreferencesFromResource(R.xml.pref_subscreen);
            findPreference(PREFERENCE_TO_CLICK).setOnPreferenceClickListener(new OnFlatClickListener());
        }

        /**
         * Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
         */
        private class OnFlatClickListener implements OnPreferenceClickListener {
            private Preference mRescuedPreference;
            private boolean mPrefToRemoveVisible = true;
            
            public boolean onPreferenceClick(Preference preference) {
                // toggle visibility
                mPrefToRemoveVisible = !mPrefToRemoveVisible; 
                
                PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);
                Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);
                
                Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));
                Log.d("test", "And noted mPrefToRemoveVisible: " + mPrefToRemoveVisible);
                
                //Replaced the conditional blocks:
                if (mPrefToRemoveVisible && null == prefToRemove) {
                    boolean added = screen.addPreference(mRescuedPreference);
                    Log.d("test", "screen.addPreference(mRescuedPreference) success?" + added);
                } else if (!mPrefToRemoveVisible && null != prefToRemove) {
                    mRescuedPreference = prefToRemove;
                    boolean removed = screen.removePreference(prefToRemove);
                    Log.d("test", "screen.removePreference(mRescuedPreference) success?" + removed);
                }
                
                return true;
            }
        }
    }

Recommended Reading: http://developer.android.com/guide/topics/resources/runtime-changes.html

Community
  • 1
  • 1
CodeShane
  • 6,480
  • 1
  • 18
  • 24
  • I am sorry that I can not accept this as answer for several reasons: When changing the orientation, the parent screen will be shown (not good) but `PREFERENCE_TO_REMOVE` is toggle-able (good). When I move `getPreferenceManager().setSharedPreferencesName(PREF_NAME); addPreferencesFromResource(R.xml.pref_subscreen);` to `onCreate(Bundle)` the correct screen is shown on orientation changes, but toggling still fails. – Rayne Oct 27 '12 at 09:07
  • Sorry my "answer" didn't really help (I swear I had a working sample..) I'm confident this is a `state` issue, but haven't resolved it yet. I updated the answer with some workarounds, and can't believe mine is the only question-upvote. Have you tried anything else or had any luck? – CodeShane Nov 14 '12 at 19:05
  • I am currently short on time so I have not continued to work on it. I will keep you informed (and probably accept your answer then). – Rayne Nov 15 '12 at 17:53