Android: PreferenceFragmentCompat

Unfortunately, PreferenceFragmentCompat is missing in android support library. In this tutorial, i show you a possible implementation that you can use until Google makes its own.


Test before
If you wish to test before, you can install the application with the following link. The whole source code of the project is also available on github.

logo-app
Your app idea
Michenux
Free   
pulsante-google-play-store
pulsante-appbrain
qrcode-app

PreferenceFragmentCompat

Create the PreferenceFragmentCompat class with the source code below. This is the class your custom preference fragment will inherit from.

public abstract class PreferenceCompatFragment extends Fragment {

    private static final int FIRST_REQUEST_CODE = 100;
    private static final int MSG_BIND_PREFERENCES = 1;
    private static final String PREFERENCES_TAG = "android:preferences";
    private boolean mHavePrefs;
    private boolean mInitDone;
    private ListView mList;
    private PreferenceManager mPreferenceManager;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

                case MSG_BIND_PREFERENCES:
                    bindPreferences();
                    break;
            }
        }
    };

    final private Runnable mRequestFocus = new Runnable() {
        public void run() {
            mList.focusableViewAvailable(mList);
        }
    };

    private void bindPreferences() {
        PreferenceScreen localPreferenceScreen = getPreferenceScreen();
        if (localPreferenceScreen != null) {
            ListView localListView = getListView();
            localPreferenceScreen.bind(localListView);
        }
    }

    private void ensureList() {
        if (mList == null) {
            View view = getView();
            if (view == null) {
                throw new IllegalStateException("Content view not yet created");
            }

            View listView = view.findViewById(android.R.id.list);
            if (!(listView instanceof ListView)) {
                throw new RuntimeException("Content has view with id attribute 'android.R.id.list' that is not a ListView class");
            }

            mList = (ListView)listView;
            if (mList == null) {
                throw new RuntimeException("Your content must have a ListView whose id attribute is 'android.R.id.list'");
            }

            mHandler.post(mRequestFocus);
        }
    }

    private void postBindPreferences() {
        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) {
            mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
        }
    }

    private void requirePreferenceManager() {
        if (this.mPreferenceManager == null) {
            throw new RuntimeException("This should be called after super.onCreate.");
        }
    }

    public void addPreferencesFromIntent(Intent intent) {
        requirePreferenceManager();
        PreferenceScreen screen = inflateFromIntent(intent, getPreferenceScreen());
        setPreferenceScreen(screen);
    }

    public void addPreferencesFromResource(int resId) {
        requirePreferenceManager();
        PreferenceScreen screen = inflateFromResource(getActivity(), resId, getPreferenceScreen());
        setPreferenceScreen(screen);
    }

    public Preference findPreference(CharSequence key) {
        if (mPreferenceManager == null) {
            return null;
        }
        return mPreferenceManager.findPreference(key);
    }

    public ListView getListView() {
        ensureList();
        return mList;
    }

    public PreferenceManager getPreferenceManager() {
        return mPreferenceManager;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getListView().setScrollBarStyle(0);
        if (mHavePrefs) {
            bindPreferences();
        }
        mInitDone = true;
        if (savedInstanceState != null) {
            Bundle localBundle = savedInstanceState.getBundle(PREFERENCES_TAG);
            if (localBundle != null) {
                PreferenceScreen screen = getPreferenceScreen();
                if (screen != null) {
                    screen.restoreHierarchyState(localBundle);
                }
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        dispatchActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onCreate(Bundle paramBundle) {
        super.onCreate(paramBundle);
        mPreferenceManager = createPreferenceManager();
    }

    @Override
    public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
        return paramLayoutInflater.inflate(R.layout.preference_list_content, paramViewGroup, false);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        dispatchActivityDestroy();
    }

    @Override
    public void onDestroyView() {
        mList = null;
        mHandler.removeCallbacks(mRequestFocus);
        mHandler.removeMessages(MSG_BIND_PREFERENCES);
        super.onDestroyView();
    }

    @Override
    public void onSaveInstanceState(Bundle bundle) {
        super.onSaveInstanceState(bundle);
        PreferenceScreen screen = getPreferenceScreen();
        if (screen != null) {
            Bundle localBundle = new Bundle();
            screen.saveHierarchyState(localBundle);
            bundle.putBundle(PREFERENCES_TAG, localBundle);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        dispatchActivityStop();
    }

    /** Access methods with visibility private **/

    private PreferenceManager createPreferenceManager() {
        try {
            Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
            c.setAccessible(true);
            return c.newInstance(this.getActivity(), FIRST_REQUEST_CODE);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private PreferenceScreen getPreferenceScreen() {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
            m.setAccessible(true);
            return (PreferenceScreen) m.invoke(mPreferenceManager);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void setPreferenceScreen(PreferenceScreen preferenceScreen) {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
            m.setAccessible(true);
            boolean result = (Boolean) m.invoke(mPreferenceManager, preferenceScreen);
            if (result && preferenceScreen != null) {
                mHavePrefs = true;
                if (mInitDone) {
                    postBindPreferences();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
            m.setAccessible(true);
            m.invoke(mPreferenceManager, requestCode, resultCode, data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void dispatchActivityDestroy() {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
            m.setAccessible(true);
            m.invoke(mPreferenceManager);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void dispatchActivityStop() {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
            m.setAccessible(true);
            m.invoke(mPreferenceManager);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private void setFragment(PreferenceFragment preferenceFragment) {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("setFragment", PreferenceFragment.class);
            m.setAccessible(true);
            m.invoke(mPreferenceManager, preferenceFragment);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public PreferenceScreen inflateFromResource(Context context, int resId, PreferenceScreen rootPreferences) {
        PreferenceScreen preferenceScreen ;
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
            m.setAccessible(true);
            preferenceScreen = (PreferenceScreen) m.invoke(mPreferenceManager, context, resId, rootPreferences);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return preferenceScreen;
    }

    public PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
        PreferenceScreen preferenceScreen ;
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class);
            m.setAccessible(true);
            preferenceScreen = (PreferenceScreen) m.invoke(mPreferenceManager, queryIntent, rootPreferences);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return preferenceScreen;
    }
}

Layout of PreferenceFragmentCompat

Create the layout (file res/layout/preference_list_content.xml) used by PreferenceFragmentCompat :

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@android:id/list"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:drawSelectorOnTop="false"
   android:scrollbarAlwaysDrawVerticalTrack="true"
   android:paddingTop="6dp"
   android:paddingLeft="12dp"
   android:paddingRight="12dp"/>

Preference xml

Create the xml file for the preferences of your project. Mine is res/xml/preferences.xml. Here’s a simple example :

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

    <PreferenceCategory android:title="Preferences" >
        <CheckBoxPreference
           android:title="Enable notification"
           android:defaultValue="true"
           android:summary="Be notified when..."
           android:key="notificationPref" />
    </PreferenceCategory>

</PreferenceScreen>

Listening to events

If you need to do some specifics stuffs like listening for preference changes, create a subclass of PreferenceCompatFragment. Here is an example to listen the toggle of checkbox preference :

public class SettingsFragment extends PreferenceCompatFragment implements SharedPreferences.OnSharedPreferenceChangeListener {

    @Override
    public void onCreate(Bundle paramBundle) {
        super.onCreate(paramBundle);
        addPreferencesFromResource(R.xml.preferences);
        PreferenceManager preferenceManager = getPreferenceManager();
        preferenceManager.getSharedPreferences()
                .registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
                                          String key) {
        if ( key.equals("notificationPref")) {
            //TODO
        }
    }
}

Start the fragment

Finally, here’s the piece of code to show the PreferenceFragmentCompat :

activity.getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, new SettingsFragment (), null).commit();
Share Button

Comments

5 Responses to “Android: PreferenceFragmentCompat”

  1. Majk on October 14th, 2013 1:09 pm

    Settings in your application doesn’t work. You can disable check-box and next time you enter the application it will be checked.

  2. Michenux on October 26th, 2013 2:50 pm

    Majk: it should work now. I have updated the tutorial.

  3. cxjar on November 17th, 2013 6:41 pm

    Wonderful. What license are you releasing it under?

  4. Mayank Verma on January 10th, 2014 8:07 am

    Great tutorial. How can I add date picker on sharepreferences?

  5. Michenux on January 13th, 2014 4:12 pm

    I have not tested but you can find some implementations of DatePreference like this one : DatePreference

Leave a Reply