How to Create Preferences Settings in your app using Android Built-in Settings Preference APIs

By | October 10, 2017

There is often a settings page when we make our app and most of the people end up making it using their own UI.
But interestingly Android provides API specific for building preferences using your own values.

Check the below UI for my settings

Android Settings Preferences

Lets see how that is done.

Using Preference Headers

Preference Headers displays only the settings groups (headers) on the first screen and selecting a group displays the sublist
The preference headers usually used for tablets or if there are multiple settings with nested options. The benefit of using headers is, the screen will be automatically divided into two panels when the app is running on tablet.

How to add Settings Activity to your app?

In Android Studio go to File ⇒ Activity ⇒ Settings Activity.
This creates necessary preferences xml resources under res ⇒ xml directory.

Now you can edit the preference xml in the res/xml folder and add your own preferences.

Lets start modifying the xml based on our needs.

My preference xml is named “my_pref.xml”, you can rename it to whatever you want.

Below are the contents of my_pref.xml

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

        <EditTextPreference
            android:defaultValue="@string/your_name_def_val"
            android:key="@string/key_name"
            android:summary="@string/your_name_def_val"
            android:title="@string/your_name" />

        <CheckBoxPreference
            android:defaultValue="true"
            android:key="@string/key_upload_over_wifi"
            android:summary="@string/summary_upload_over_wifi"
            android:title="@string/title_auto_upload" />

        <ListPreference
            android:defaultValue="3"
            android:dialogTitle="@string/title_upload_quality"
            android:entries="@array/pref_upload_quality_entries"
            android:entryValues="@array/pref_upload_quality_values"
            android:key="@string/key_upload_quality"
            android:summary="@string/summary_upload_video_quality"
            android:title="@string/title_upload_quality" />

    </PreferenceCategory>

    <PreferenceCategory android:title="@string/pref_title_notifications">

        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/notifications_new_message"
            android:title="@string/title_new_notification_sound" />

        <RingtonePreference
            android:defaultValue="content://settings/system/notification_sound"
            android:dependency="notifications_new_message"
            android:key="@string/key_notifications_new_message_ringtone"
            android:ringtoneType="notification"
            android:summary="@string/summary_choose_ringtone"
            android:title="@string/pref_title_ringtone" />

        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/key_vibrate"
            android:summary="@string/summary_vibrate"
            android:title="@string/title_vibrate" />

    </PreferenceCategory>

    <PreferenceCategory android:title="@string/pref_header_about">

        <Preference
            android:summary="@string/app_version"
            android:title="@string/title_version" />

    </PreferenceCategory>
</PreferenceScreen>

Basically the above XML contains three sections indicated by “PreferenceCategory”.

Typically when you copy the above code, you will have error because the string resources are missing.

Lets add the string resources

Copy below Strings to your strings.xml

<resources>
    <string name="app_name">DemoSettings</string>
    <string name="action_settings">Settings</string>
    <string name="pref_title_ringtone">Ringtone</string>
    <string name="pref_ringtone_silent">Silent</string>
    <string name="your_name">Your Name</string>
    <string name="your_name_def_val">CoderzHeaven</string>
    <string name="title_auto_upload">Auto upload</string>
    <string name="summary_upload_over_wifi">Upload the videos when wifi is available</string>
    <string name="title_upload_quality">Upload Quality</string>
    <string name="summary_upload_video_quality">Specify video quality for uploads</string>
    <string name="pref_title_notifications">Notifications</string>
    <string name="summary_choose_ringtone">Choose notification sound</string>
    <string name="pref_header_about">About</string>
    <string name="app_version">5.0</string>
    <string name="title_version">Version</string>
    <string name="title_new_notification_sound">New message notification</string>
    <string name="title_vibrate">Vibrate</string>
    <string name="summary_vibrate">Vibrate on new notification</string>
    <string name="key_upload_over_wifi">key_upload_over_wifi</string>
    <string name="key_name">key_name</string>
    <string name="key_upload_quality">key_upload_quality</string>
    <string name="notifications_new_message">notifications_new_message</string>
    <string name="key_notifications_new_message_ringtone">key_notifications_new_message_ringtone</string>
    <string name="key_vibrate">key_vibrate</string>
</resources>

Now the string errors should be gone.

Now we have some arrays to show in the spinner in preferences. Let’s add those too.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="pref_upload_quality_entries">
        <item>360p</item>
        <item>480p</item>
        <item>720p</item>
        <item>1080p</item>
        <item>Original</item>
    </string-array>
    <string-array name="pref_upload_quality_values">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
    </string-array>
</resources>

Now when the settings template were created by you, you may have got two activities. One if the base Preference Activity and the other one is the Settings Activity.
The Settings activity is extended from the “AppCompatPreferenceActivity”.

My Base preference activity is named “AppCompatPreferenceActivity”.

AppCompatPreferenceActivity.java

package settings_demo.coderzheaven.com.demosettings;

import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A {@link PreferenceActivity} which implements and proxies the necessary calls
 * to be used with AppCompat.
 */
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {

    private AppCompatDelegate mDelegate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getDelegate().installViewFactory();
        getDelegate().onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        getDelegate().onPostCreate(savedInstanceState);
    }

    public ActionBar getSupportActionBar() {
        return getDelegate().getSupportActionBar();
    }

    public void setSupportActionBar(@Nullable Toolbar toolbar) {
        getDelegate().setSupportActionBar(toolbar);
    }

    @Override
    public MenuInflater getMenuInflater() {
        return getDelegate().getMenuInflater();
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().addContentView(view, params);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        getDelegate().setTitle(title);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        getDelegate().onConfigurationChanged(newConfig);
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }

    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    private AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, null);
        }
        return mDelegate;
    }
}

Now the Settings Activity

Here we replace the content of the activity with a fragment that contains the settings. The Fragment is named “MainPreferenceFragment”.

Check this line inside the fragment where we set our Preference XML.

		addPreferencesFromResource(R.xml.my_pref);
package settings_demo.coderzheaven.com.demosettings;

import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import android.view.MenuItem;

public class SettingsActivity extends AppCompatPreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // load settings fragment
        getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit();
    }

    public static class MainPreferenceFragment extends PreferenceFragment {
        @Override
        public void onCreate(final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.my_pref);

            // gallery EditText change listener
            bindPreferenceSummaryToValue(findPreference(getString(R.string.key_name)));

            // notification preference change listener
            bindPreferenceSummaryToValue(findPreference(getString(R.string.key_notifications_new_message_ringtone)));

        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
        }
        return super.onOptionsItemSelected(item);
    }

    private static void bindPreferenceSummaryToValue(Preference preference) {
        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);

        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                PreferenceManager
                        .getDefaultSharedPreferences(preference.getContext())
                        .getString(preference.getKey(), ""));
    }

    /**
     * A preference value change listener that updates the preference's summary
     * to reflect its new value.
     */
    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            String stringValue = newValue.toString();

            if (preference instanceof ListPreference) {
                // For list preferences, look up the correct display value in
                // the preference's 'entries' list.
                ListPreference listPreference = (ListPreference) preference;
                int index = listPreference.findIndexOfValue(stringValue);

                // Set the summary to reflect the new value.
                preference.setSummary(
                        index >= 0
                                ? listPreference.getEntries()[index]
                                : null);

            } else if (preference instanceof RingtonePreference) {
                // For ringtone preferences, look up the correct display value
                // using RingtoneManager.
                if (TextUtils.isEmpty(stringValue)) {
                    // Empty values correspond to 'silent' (no ringtone).
                    preference.setSummary(R.string.pref_ringtone_silent);

                } else {
                    Ringtone ringtone = RingtoneManager.getRingtone(
                            preference.getContext(), Uri.parse(stringValue));

                    if (ringtone == null) {
                        // Clear the summary if there was a lookup error.
                        preference.setSummary(R.string.summary_choose_ringtone);
                    } else {
                        // Set the summary to reflect the new ringtone display
                        // name.
                        String name = ringtone.getTitle(preference.getContext());
                        preference.setSummary(name);
                    }
                }

            } else if (preference instanceof EditTextPreference) {
                if (preference.getKey().equals("key_name")) {
                    // update the changed gallery name to summary filed
                    preference.setSummary(stringValue);
                }
            } else {
                preference.setSummary(stringValue);
            }
            return true;
        }
    };

}

Check the Preference value change CallBack “sBindPreferenceSummaryToValueListener” where we get the callback when any value is changed in the settings.

Source Code

You can download the complete source code from here.

All Done. You have created the Settings for your app.

Please leave your valuable comments below the post or send to coderzheaven@gmail.com.

Leave a Reply

Your email address will not be published. Required fields are marked *