Can you imagine an Android application without any configurable options? Actually, only the simplest application can get by without any configuration. Configuration is essential for most of Android applications. But in what way are these applications being configured?
Android provides a standard way to configure an application using SharedPreferences and PreferenceScreen. Check out my hands-on guide to using these things and implementation of custom Preference screen.
Using PreferenceScreen
The simplest way to provide application’s configuration abilities to the user is to use PreferenceScreen to configure options stored in the application’s SharedPreferences. To do this create a PreferenceScreen description file in “res\xml” directory with the following content:
Now create SettingsActivity, which extends PreferenceActivity and override the on Create method in the following way:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//addPreferencesFromResource(R.xml.preferences);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit();
}
initActionBar();
}
And the last necessary thing, for now, is MyPreferenceFragment class which just provides a link with our preferences XML file:
public static class MyPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
Now, modify MainActivity to open SettingsActivity in response to menu item click. Launch the application and click on the “Settings” menu item:
You can toggle “Work in background” and change “server address” options. Change some values. Don’t bother about the “Summary” hint (the text below the settings item caption - “test1.server.com” in our case). Currently, it does not reflect actual data.
Now check the “shared preferences” file by executing the following commands:
adb shell run-as com.ls.shared_prefs_article cat /data/data/com.ls.shared_prefs_article/shared_prefs/com.ls.shared_prefs_article_preferences.xml
The result is the following: myserver.com
As you can see, Android automatically saves application settings in SharedPreferences file.
Using SharedPreferences
To access shared preferences from the code just read it using the default SharedPreferences class instance:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean enableBackground = prefs.getBoolean("enable_background", false);
String serverAddress = prefs.getString("server_address", null);
We see that it is possible to retrieve boolean and string values. But what else can we retrieve? Additionally to the mentioned earlier types, it is possible to retrieve float, int, long, Set and all values at once using Map.
Ok. What about saving SharedPreferences from the code? This is as simple as 1, 2, 3:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("enable_background", false);
editor.putString("server_address", "server1.com");
editor.apply();
Standard Preferences
We saw that it is possible to store different data types in the SharedPreferences file. But what other controls can we use to represent these values to the user except the mentioned earlier CheckBoxPreference and EditTextPreference?
Here is the list of standard preferences:
We have used CheckBoxPreference, EditTextPreference and PreferenceScreen already. Let`s use the rest of them:
To describe “color_entries” and “color_values” items create “arrays.xml” file in “values” directory with the following content:
Red Green Blue Red Green Blue
Here is the generated preferences file:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<set name="colors">
<string>Red</string>
<string>Blue</string>
</set>
<boolean name="switch" value="true" />
<string name="color">Green</string>
</map>
And the code to retrieve preferences values:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String color = prefs.getString("color", null);
Set colors = prefs.getStringSet("colors", Collections.emptySet());
boolean switchValue = prefs.getBoolean("switch", false);
Custom Preferences
What if we need to visualize a more complex settings screen? For example, audio recording quality:
This is possible with custom DialogPreference.
To create a custom DialogPreference screen you need to do three things:
Create your own class which extends DialogPreference.
Create a dialog layout.
Declare DialogPreference in your preferences file.
In own DialogPreference implementation you will need to override the following methods:
All possible constructors.
Protected void onBindDialogView(View view).
Protected void onDialogClosed(boolean positiveResult).
In constructors, you need to specify whether your DialogPreference should automatically persist its value by using “setPersistent” method. Don’t set it to true if the screen is complex and contains more than one value. Also, you need to specify a layout resource with the “setDialogLayoutResource” method.
In the “onBindDialogView” method, you can load shared preferences and init views.
In the “onDialogClosed” method, you can react on dialog closing and save shared preferences.
Here is a very basic DialogPreference class:
public class QualityPreference extends DialogPreference {
public QualityPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public QualityPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
@Override
protected void onBindDialogView(View view) {
// load shared preferences
// init views
super.onBindDialogView(view);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
// save shared preferences
}
}
private void init(Context context) {
setPersistent(false);
setDialogLayoutResource(R.layout.record_quality_pref_dialog);
}
}
And here is how to use it in preferences file:
<com.ls.shared_prefs_article.QualityPreference
android:key="quality"
android:title="Recording quality"
android:summary="Define recording quality"
android:dialogTitle="Recording quality"
android:positiveButtonText="@android:string/ok"
android:negativeButtonText="@android:string/cancel" />
Note that the “key” attribute is not used as a shared preferences key because our DialogPreference uses multiple keys. However, the key is defined here to allow accessing this preferences summary in our PreferenceFragment.
Updating preferences summary
Did you notice that the “summary” sections (text under each option title, “Red” under Color option, for example) are not filled in in compliance with the real settings?
This is because the “summary” does not reflect the actual settings automatically. To update the “summary”, subscribe for shared preferences change and change the summary accordingly. You can do this in your PreferenceFragment onCreate method:
Preference serverAddressPrefs = findPreference("server_address");
serverAddressPrefs.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
preference.setSummary((String) newValue);
return true;
}
});
Sometimes it is impossible to listen for configuration changes using this approach. For example, when we are using custom complex preference screen. In this case you will need to subscribe for shared preferences change generally. Do it in your PreferenceFragment onPause and onResume methods:
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(sharedPrefsChangeListener);
}
@Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(sharedPrefsChangeListener);
}
And in your preferences, change the listener load shared preferences and update the summary:
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPrefsChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// load shared preferences
// update summary
}
};
Are there any other custom Preferences screens available?
Yes. For example Directory chooser.
Import it in to your project and declare in preferences file:
Now, clicking on “Directory” option will open the following dialog
The selected directory path will be saved in the specified shared preferences key. Don’t forget to add “WRITE_EXTERNAL_STORAGE” for proper library work.
Demo application
To put it in a nutshell, those were all the aspects of using SharedPreferences. Hope you found it useful. Source codes of the demo application can be found on Github.