Home / Programming / Android / Android Settings Preferences Tutorial

Android Settings Preferences Tutorial

Now that we know more about data persistence in android, it is clear that shared preferences is a promising option for saving user settings/preferences. This is because shared preferences saves data between app restarts, even when something drastic happens like when the devices loses power.

Shared Preferences was built with saving app preference in mind, that’s why they’re called shared preferences.

A settings page is needed to give users the option to personalise your app.

Shared Preferences in conjunction with Preference Fragment for creating user interface for settings activities.

A fragment is a class that represents a modular and reusable piece of an activity but preference fragment is specifically built for displaying preferences.

Before we begin

I have to warn you, there’s a lot of spaghetti code in order to have a settings screen. Thus, it’s important to know when you need to have a settings page. Creating a settings page is arduous, if you don’t need it don’t go through the trouble!

Conditions for having a settings page:

  1.  is it actually a user preference?
  2. would users change it a lot?
  3. is there a right answer for majority of the users?

If you answered NO to question 1 and YES to question 2 & 3, DO NOT create a settings page, you don’t need it! You don’t have to have a settings page just because other apps on the App Store has it.

Sample App Tutorial

Grab Source Code on GitHub

Sequential Steps

  1. Implement the SettingsPreferences SDK
  2. Create an empty Activity called SettingsActivity.kt
  3. Create a Preference Fragment and attach it to SettingsActivity.kt’s layout
  4. Create an XML file that defines what preferences that should be in the PreferenceFragment
  5. Create Resource values – arrays, bool
  6. define keys for all preferences in strings.xml, to avoid syntactic errors that may arise from duplicating keys
  7. define values and label for ListPreference in strings.xml
  8. Implement what happens after users set their preference in MainActivity.kt
  9. Instantly update user preferences on Settings Page

This sample app will include a text view and a button (to launch the settings activity).

The settings Activity will give us the options to personalise the textview – change text view background, change text view text colour and set the visibility of the textview itself. At the end of this tutorial you may attempt to add an option to change textview text size

At the end of this sample app you’ll get an assignment to add an option to change the text view font size.

Enough Talk, Let’s Code!

1. Add implementation ‘com.android.support:preference-v7:27.1.1’ to build.gradle(app)

2. Create a new directory under res, then title it xml. Under the new xml directory create a new Xml Resource File, title it settings_pref.xml (res -> xml -> settings_pref.xml)

3. In settings_pref.xml add CheckBoxPreferenceSwitchPreferenceEditTextPreference and ListPreference (twice) as shown below.

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:defaultValue="@bool/show_button_default"
        android:key="@string/textview_visibility_key"
        android:summaryOff="@string/hidden"
        android:summaryOn="@string/shown"
        android:title="Show TextView"/>
    <EditTextPreference
        android:defaultValue="@string/default_textview_text"
        android:key="@string/textview_text_key"
        android:selectAllOnFocus="true"
        android:singleLine="true"
        android:title="Change TextView's Text" />
    <ListPreference
        android:defaultValue="@string/pref_bckgrnd_colour_black_value"
        android:entryValues="@array/background_colour_values"
        android:key="@string/textview_backgroundcolor_key"
        android:entries="@array/background_colour_labels"
        android:title="TextView Background Color" />
    <ListPreference
        android:defaultValue="@string/pref_txt_colour_white_value"
        android:entries="@array/text_colour_labels"
        android:entryValues="@array/text_colour_values"
        android:key="@string/textview_textcolor_key"
        android:title="TextView Text Color"/>
    <Preference
        android:summary="Terms and Codition for app use"
        android:title="Privacy and Policy">
        <intent
            android:action="android.intent.action.VIEW"
            android:data="https://your.privacy_&_policy.url" />
    </Preference>
</PreferenceScreen>

4. Values Resource Files

strings.xml.
Utilising resources does not only help localise your app, it can also prevent syntactic errors that may come with duplicating important data like sharedPreference or bundle keys. Using resources makes changing values a lot easier. Strings tagged with translatable=”false” means that string should not and cannot be translated when localising.

<resources>
    <string name="app_name">Settings Preferences Tutorial</string>

    <!--Settings Prefrences key-->
    <string name="textview_visibility_key">textview_visibility</string>
    <string name="textview_text_key">textview_text</string>
    <string name="textview_textcolor_key">textview_textcolor</string>
    <string name="textview_backgroundcolor_key">textview_backgroundcolor</string>

    <!--summary of text view visibility-->
    <string name="hidden">Hidden</string>
    <string name="shown">Shown</string>

    <string name="default_textview_text">Hello World</string>

    <!--Text Color Values-->
    <string name="pref_txt_colour_white_value" translatable="false">white</string>
    <string name="pref_txt_colour_yellow_value" translatable="false">yellow</string>
    <string name="pref_txt_colour_sea_green_value" translatable="false">sea_green</string>

    <!--Text Color Labels-->
    <string name="pref_txt_colour_white_label">White</string>
    <string name="pref_txt_colour_yellow_label">Yellow</string>
    <string name="pref_txt_colour_sea_green_label">Sea Green</string>

    <!--Background Color Values-->
    <string name="pref_bckgrnd_colour_black_value" translatable="false">black</string>
    <string name="pref_bckgrnd_colour_red_value" translatable="false">red</string>
    <string name="pref_bckgrnd_colour_blue_value" translatable="false">blue</string>

    <!--Background Color Labels-->
    <string name="pref_bckgrnd_colour_black_label">Black</string>
    <string name="pref_bckgrnd_colour_red_label">Red</string>
    <string name="pref_bckgrnd_colour_blue_label">Blue</string>

</resources>

arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="background_colour_labels">
        <item>@string/pref_bckgrnd_colour_black_label</item>
        <item>@string/pref_bckgrnd_colour_red_label</item>
        <item>@string/pref_bckgrnd_colour_blue_label</item>
    </array>
    <array name="background_colour_values">
        <item>@string/pref_bckgrnd_colour_black_value</item>
        <item>@string/pref_bckgrnd_colour_red_value</item>
        <item>@string/pref_bckgrnd_colour_blue_value</item>
    </array>
    <array name="text_colour_labels">
        <item>@string/pref_txt_colour_white_label</item>
        <item>@string/pref_txt_colour_yellow_label</item>
        <item>@string/pref_txt_colour_sea_green_label</item>
    </array>
    <array name="text_colour_values">
        <item>@string/pref_txt_colour_white_value</item>
        <item>@string/pref_txt_colour_yellow_value</item>
        <item>@string/pref_txt_colour_sea_green_value</item>
    </array>
</resources>

bool.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="show_button_default">true</bool>
</resources>

styles.xml -pay attention to preferenceTheme, without it your app will CRASH

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

        <!--Without this theme preference your app will CRASH -->
        <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
    </style>
</resources>

5. Activity Layout Resources

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        tools:background="#000000"
        android:padding="16dp"
        tools:textColor="#ffffff"
        android:textSize="22sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        tools:text="Hello World!" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:onClick="launchSettings"
        android:text="Lauch Settings Page"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

activity_settings.xml – this is where Preference Fragment is attached to SettingsActivity.kt’s layout

<?xml version="1.0" encoding="utf-8"?>
<fragment
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.edgedevstudio.androidsettingspreferencestutorial.SettingsFragement"
    tools:context=".SettingsActivity"/>

5. Activity Codes.

Recall i told you, there’s a lot of spaghetti code? Well, i wasn’t joking.

The issue here is that, you have to operate via proxy. For example we can’t have colour code (#000000) as a value but rather we have to specify a name (black) and then manually cross check if it matches our preference, before setting the actual value we want. This is evident in setTextViewTextColor(), setTextViewBackgroundColor(). Remember to unregister it in onDestroy()

The job of OnSharedPreferenceChangeListener is to listen to changes on any sharedPreference registered on it. This eliminates the need to reload the app (call onCreate) before the updated values are delivered to MainActivity.kt

You see, by default, when you navigate to a new activity (settings page) and back to the previous activity (main activity), the previous activity (main activity) is recreated, but my goal is to demonstrate how to dynamically update shared preferences without recreating the activity. Thus, i changed MainActivity’s default launchMode in AndroidManifest to SingleTop.

 AndroidManifest.xml

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

    <application
       ...
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"

            android:launchMode="singleTop"> <!--changed launchMode-->

            <intent-filter>
                ...
            </intent-filter>
        </activity>
        <activity
            ...
        </activity>
    </application>

</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
    private var textView: TextView? = null
    private val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textView = findViewById(R.id.textView)

        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
        sharedPreferences.registerOnSharedPreferenceChangeListener(this)
        val showTextView = sharedPreferences.getBoolean(getString(R.string.textview_visibility_key), resources.getBoolean(R.bool.show_button_default))
        val textViewString = sharedPreferences.getString(getString(R.string.textview_text_key), getString(R.string.default_textview_text))
        val backgroundColorString = sharedPreferences.getString(getString(R.string.textview_backgroundcolor_key), getString(R.string.pref_bckgrnd_colour_black_value))
        val textColorString = sharedPreferences.getString(getString(R.string.textview_textcolor_key), getString(R.string.pref_txt_colour_white_value))

        setTextViewText(textViewString)
        setTextViewVisibility(showTextView)
        setTextViewBackgroundColor(backgroundColorString)
        setTextViewTextColor(textColorString)

        Log.d(TAG, "onCreate, " +
                "visibility = $showTextView, " +
                "text = $textViewString, " +
                "backgroundColor = $backgroundColorString, " +
                "textColor = $textColorString")

    }

    fun launchSettings(view: View) {
        startActivity(Intent(this, SettingsActivity::class.java))
    }

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
        if (sharedPreferences == null) return
        Log.d(TAG, "onSharedPreferenceChanged and is not null")
        val showTextView = sharedPreferences.getBoolean(getString(R.string.textview_visibility_key), resources.getBoolean(R.bool.show_button_default))
        val textViewString = sharedPreferences.getString(getString(R.string.textview_text_key), getString(R.string.default_textview_text))
        val backgroundColorString = sharedPreferences.getString(getString(R.string.textview_backgroundcolor_key), getString(R.string.pref_bckgrnd_colour_black_value))
        val textColorString = sharedPreferences.getString(getString(R.string.textview_textcolor_key), getString(R.string.pref_txt_colour_white_value))

        setTextViewText(textViewString)
        setTextViewVisibility(showTextView)
        setTextViewBackgroundColor(backgroundColorString)
        setTextViewTextColor(textColorString)
    }

    fun setTextViewText(text: String) {
        textView?.setText(text)
    }

    fun setTextViewTextColor(text_color_value: String) {
        var color_string = ""
        if (text_color_value.equals(getString(R.string.pref_txt_colour_white_value)))
            color_string = "#ffffff"
        else if (text_color_value.equals(getString(R.string.pref_txt_colour_yellow_value)))
            color_string = "#ffff00"
        else if (text_color_value.equals(getString(R.string.pref_txt_colour_sea_green_value)))
            color_string = "#2E8B57"
        textView?.setTextColor(Color.parseColor(color_string))
    }

    fun setTextViewBackgroundColor(background_color_value: String) {
        var color_string = ""
        if (background_color_value.equals(getString(R.string.pref_bckgrnd_colour_black_value)))
            color_string = "#000000"
        else if (background_color_value.equals(getString(R.string.pref_bckgrnd_colour_red_value)))
            color_string = "#ff0000"
        else if (background_color_value.equals(getString(R.string.pref_bckgrnd_colour_blue_value)))
            color_string = "#0000ff"
        textView?.setBackgroundColor(Color.parseColor(color_string))
    }

    fun setTextViewVisibility(showTextView: Boolean) {
        if (showTextView) textView?.visibility = View.VISIBLE
        else textView?.visibility = View.INVISIBLE
    }
    override fun onDestroy() {
        super.onDestroy()
        PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this)
    }
}

SettingsActivity.kt

because we’ve connected settings activity layout to SettingsPreference fragment, there’s no reason to put any code here. SettingsActivity is hosting SettingsFragment. All necessary code should be in SettingsFragment.kt

class SettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
    }
}

SettingsFragment.kt
Please make sure you import the correct preference

import android.support.v7.preference.*
class SettingsFragement : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
    val TAG = "SettingsFragement"
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        addPreferencesFromResource(R.xml.settings_pref) //just like setContentView in an Activity 

        val sharedPref = preferenceScreen.sharedPreferences
        sharedPref.registerOnSharedPreferenceChangeListener(this) // attaching a listener to update settings page
        val prefScreen = preferenceScreen
        val count = preferenceScreen.preferenceCount

        for (index in 0..(count - 1)) {
            val preference = prefScreen.getPreference(index)
            val prefKey = preference.key
            if (preference !is CheckBoxPreference) {
                val value = sharedPref.getString(prefKey, "")
                setPreferenceSummary(preference, value)
            }
        }
    }

    fun setPreferenceSummary(preference: Preference, value: String) {
        if (preference is ListPreference) {
            val prefIndex = preference.findIndexOfValue(value)
            if (prefIndex >= 0)
                preference.setSummary(preference.entries[prefIndex])
        } else if (preference is EditTextPreference) {
            val msg = preference.text
            preference.setSummary(msg)
        }
    }

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
        val preference = findPreference(key)
        if (preference != null) {
            if (preference !is CheckBoxPreference) {
                val sharedPrefKey = sharedPreferences?.getString(preference.key, "")
                setPreferenceSummary(preference, sharedPrefKey!!)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
    }
}

 

Summary
Article Name
Android Settings Preferences Tutorial
Description
Shared Preferences was built with saving app preference in mind, that’s why they’re called shared preferences. Shared Preferences in conjunction with PreferenceFragment is used for creating user interface for settings activities.
Author
Publisher Name
Edge Dev Studio
Publisher Logo

About Edge Developer

Hey there! am Opeyemi Olorunleke (aka Edge Developer), an Android developer. I Love Sharing Android Tutorials and code snippets.

Check Also

Android Intents : All you need to know + Example

What are Android Intents? Android Intent is a simple message object which is used to communicate …

Leave a Reply

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