Android development: Connecting a PreferenceFragment to a Database

Firstly, the PreferenceFragment is great. It’s a ready-to-go nice looking ‘View’ (in the MVC context) for displaying settings-like data to users.

preference fragment

Example Settings activity using PreferenceFragment

To use it you simply create an XML file containing the PreferenceScreen description of what the Preferences are. You then create a PreferenceFragment which calls:

addPreferencesFromResource(R.xml.your_preferences)

However the ‘Model’ and ‘Controller’ aspect has very little flexibility. It uses the inbuilt android SharedPreferences system, which saves each Preference one-to-one, key-to-value, and the controller doesn’t even automatically put the value back into the Preference view (e.g. the summary element).

So to use a PreferenceFragment with a database, follow these steps:

1. Create an activity using a PreferenceFragment in the same way you would if making a standard Settings activity. See: https://developer.android.com/guide/topics/ui/settings.html#Fragment.

2. For each Preference you add to the preference XML, set:

android:persistent="false"

This prevents the Preference from saving its changes to Android’s SharedPreferences.

3. In the Fragment’s onCreate(), obtain each Preference and modify it as follows (where dbItem is a database entity access class — greendao was used in this project):

// label
final EditTextPreference prefLabel = (EditTextPreference)getPreferenceManager().findPreference("label");
prefLabel.setText(dbItem.getLabel());
prefLabel.setSummary(dbItem.getLabel());
prefLabel.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        dbItem.setLabel((String)newValue);
        prefLabel.setText((String)newValue);
        prefLabel.setSummary((String)newValue);
        return true;
    }
});

This code sets the value and displayed value of the Preference, and updates the database entity and the displayed value whenever the Preference is changed.

4. For some Preferences, you will need to extend the existing Preference class, as it does not support setting its value outside the SharedPreferences system. An example is the RingtonePreference:

/**
 * Class that extends a RingtonePreference for the purpose of interfacing it with a database
 */
public class ExtRingtonePreference extends RingtonePreference {
    private String mInitialRingtone;

    public ExtRingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ExtRingtonePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ExtRingtonePreference(Context context) {
        super(context);
    }

    @Override
    protected Uri onRestoreRingtone() {
        if(mInitialRingtone == null) {
            return null;
        } else {
            return Uri.parse(mInitialRingtone);
        }
    }

    public void setInitialRingtone(String initialRingtone) {
        this.mInitialRingtone = initialRingtone;
    }
}

5. You may have an error that occurs on screen rotation regarding the MultiSelectListPreference. This is due to a bug in the android code. The fix is to override the MultiSelectListPreference and the onRestoreInstanceState():

public class ExtMultiSelectListPreference extends MultiSelectListPreference {
    public ExtMultiSelectListPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ExtMultiSelectListPreference(Context context) {
        super(context);
    }


    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        try {
            super.onRestoreInstanceState(state);
        } catch (IllegalArgumentException e) {
            // Fix of crash https://code.google.com/p/android/issues/detail?id=70088
            if (!isPersistent())
                super.onRestoreInstanceState(((BaseSavedState) state).getSuperState());
            else
                throw e;
        }
    }

}

And that’s it!