Working with Android Preferences - Creating a Feature‐Rich Application - Android Application Development For Dummies, 3rd Edition (2015)

Android Application Development For Dummies, 3rd Edition (2015)

Part III. Creating a Feature‐Rich Application

Chapter 15. Working with Android Preferences

In This Chapter

arrow Seeing how preferences work in Android

arrow Building a preferences screen

arrow Working with preferences programmatically

Most programs need to be configured to suit a user’s needs with ­individual settings or preferences. Allowing users to configure your Android application gives it a usability advantage. Thankfully, creating and providing a mechanism to edit preferences in Android is a fairly easy ­process.

Android provides, out of the box, a robust preferences framework that lets you define preferences for your application. Android stores preferences as persistent key‐value pairs of primitive data types for you. The Android preferences framework commits the values you provide to internal storage on behalf of your application. You can use the preferences framework to store Boolean, float, int, long, and string elements. The data persists across user sessions — if the user closes the app and reopens it later, the preferences are saved and can be used, even if your application is killed or the phone restarts.

This chapter delves into the Android preferences framework and describes how to incorporate it into your applications. You find out how to use the built‐in PreferenceFragment to create and edit preferences and how to read and write preferences from code within your application. At the end of this chapter, you’ll have integrated preferences fully into the Tasks app.

Understanding the Android Preferences Framework

One outstanding quality of the Android preferences framework is the simplicity of developing a screen that allows users to modify their preferences. Most of the heavy lifting is done for you by Android because developing a preferences screen is as simple as defining it in the XML located in the res/xml folder of your project. Though these XML files aren’t the same as layout files, there are specific XML definitions that define screens, categories, and actual preferences. Common preferences that are built into the framework include

· EditTextPreference: Stores plain text as a string

· CheckBoxPreference: Stores a Boolean value

· RingtonePreference: Allows the user to store a preferred ringtone from those available on the device

· ListPreference: Allows the user to select a preferred item from a list of items in the dialog box

If the built‐in preferences don’t suit your needs, you can create your own preference by deriving it from the base Preference class or DialogPreference. A DialogPreference is the base class for preferences that are dialog‐box‐based. Tapping one of these preferences opens a dialog box showing the preference controls. Examples of built‐in DialogPreferences are EditTextPreference and ListPreference.

Android also provides a PreferenceFragment in which you can load a preferences screen similar to how you load a layout for a basic Fragment class. This base class allows you to tap into the PreferenceFragment events and perform advanced work, such as setting an EditTextPreference to accept only numbers.

Understanding the PreferenceFragment Class

The responsibility of the PreferenceFragment class is to show a hierarchy of Preference objects as lists, possibly spanning multiple screens, as shown in Figure 15-1.

image

Figure 151: The ­preferences screen for the call settings in Android.

When preferences are edited, they’re stored using an instance of SharedPreferences. The SharedPreferences class is an interface for accessing and modifying preference data returned by getShared Preferences() from any Context object.

A PreferenceFragment is a base class that’s similar to the Fragment base class. However, the PreferenceFragment behaves a bit differently. One of the most important features that the PreferenceFragment handles is the displaying of preferences in the visual style that resembles the system preferences. This gives your application a consistent feel across the board in regard to Android user interface components. You should use the PreferenceFragment when dealing with preferences screens in your Android applications.

Persisting preference values

Because the Android framework stores preferences in the SharedPreferences, which automatically stores the preference data in internal storage, you can easily create a preference. When a user edits a ­preference, the value is automatically saved for you; you don’t have to do any persisting yourself.

Figure 15-2 shows a preference being set in the Tasks app. After the user taps OK, Android persists the value to SharedPreferences. Android does all the heavy lifting in regard to persisting the preference values.

image

Figure 152: Setting a preference.

Laying out preferences

Working with layouts in Android can sometimes be a painstaking process of alignment, gravity, and other complicating factors. Building layouts is almost like building a website with various tables all over the place. Sometimes it’s easy; sometimes it isn’t. Thankfully, laying out Android preferences is much simpler than defining a layout for the application screen.

Android preference building blocks are broken into these types:

· Preference: A preference that’s shown onscreen. This preference can be any common preference (such as a check box or text field), or a custom one that you define.

· PreferenceCategory: This building block is used to group preference objects and provide a title that describes the category. In Figure 15-1, the Contact display options item is a PreferenceCategory.

· PreferenceScreen: Represents a top‐level preference that’s the root of a preference hierarchy. All the categories and preferences in Figure 15-1 are rooted in a PreferenceScreen called “General settings.” You can use a PreferenceScreen in these two places:

· In a PreferenceFragment: All the categories and preferences in the PreferenceScreen are shown in the PreferenceFragment.

· In another preference hierarchy: When present in another hierarchy, the PreferenceScreen serves as a gateway to another screen of preferences (similar to nesting PreferenceScreen ­declarations inside other PreferenceScreen declarations). Though this concept might seem confusing, you can think of it as XML, where you can declare an element and any element can contain the same parent element. At that point, you’re nesting the elements. The same statement applies to the PreferenceScreen. By nestingPreferenceScreens, you’re informing Android that it should show a new screen when selected.

By laying out a combination of the PreferenceScreen, PreferenceCategory, and Preference in XML, you can easily create a preferences screen that looks similar to Figure 15-1.

Creating Your Preferences Screen

Creating preferences using the PreferenceFragment and a preference XML file is a fairly straightforward process. The first thing you do is create the preference XML file, which defines the layout of the preferences and the string resource values that show up onscreen. These string resources are presented as TextViews onscreen to help the user determine what the preference does.

Your PreferenceScreen should give users the chance to set the default time for a reminder (in minutes) and a default title for a new task. As the application stands now, the default title is empty and the default reminder time is set to the current time. These preferences allow the user to save a couple of steps while building new tasks. For example, if the user normally builds tasks with a reminder time of 60 minutes from the current time, the user can now specify it in the preferences. This new value becomes the value of the reminder time when the user creates a new task.

Building the preferences file

To build your first preferences screen, create a res/xml folder in your ­project. Inside the res/xml folder, create an XML file and name it task_preferences.xml. Add the code in Listing 15-1 to the file.

Listing 151: The task_preferences.xml File

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

<PreferenceCategory →4
android:title="@string/tasks"> →5

<EditTextPreference →7
android:key="@string/pref_task_title_key" →8
android:summary="@string/default_title_description" →9
android:title="@string/default_title"/> →10

<EditTextPreference →12
android:key="@string/pref_default_time_from_now_key" →13
android:summary="@string/minutes_from_now_description" →14
android:title="@string/minutes_from_now"/> →15

</PreferenceCategory>
</PreferenceScreen>

Each numbered line of code is explained as follows:

2 The root‐level PreferenceScreen; it’s the container for the screen itself. All other preferences live below this declaration.

4 A PreferenceCategory that defines the category for task defaults, such as title or body.

5 Defines the category title. You define the @string/tasks and other strings in the next section.

7 Contains the definition of the EditTextPreference, which is responsible for storing the preference for the default title of a task.

8 Contains the key for the default title text EditTextPreference. The key is the name of the preference, which you will use when you want to check the value of the preference.

9 Defines the summary text that’s present on the preferences screen. It’s a helpful message that describes what the preference does in more detail.

10 Defines the title of the preference on the preferences screen. It is also the title used in the dialog box that pops up when the user edits the preference.

12 The start of the definition of the EditTextPreference, which stores the default time in minutes (digits) that the task reminder time defaults to from the current time.

13 Defines the key for the default task time preference.

14 Defines the summary of the preference that’s present on the main preferences screen.

15 Defines the title of the preference on the preferences screen.

Adding string resources

For your application to compile, you need the string resources for the preferences. In the res/values/strings.xml file, add these values:

<string name="default_title">Default Title</string>
<string name="tasks">Tasks</string>
<string name="default_title_description">
The default title for a task.</string>
<string name="minutes_from_now">Minutes From Now</string>
<string name="minutes_from_now_description">
The number of minutes into the future to set the reminder.</string>
<string name="pref_task_title_key">default_task_title</string>
<string name="pref_default_time_from_now_key">time_from_now_default</string>

You should now be able to compile your application.

Working with the PreferenceFragment Class

Defining a preferences screen is fairly simple: You provide the values to the necessary attributes and you’re done. Though the preferences screen may be defined in XML, simply defining it in XML doesn’t mean that it will show up onscreen. To display your preferences screen, you create a PreferenceFragment.

To inflate and display the PreferenceScreen you may have just built, add a fragment that derives from PreferenceFragment to your application and name it PreferencesFragment. Add the code in Listing 15-2 .

Listing 152: The PreferencesFragment File

public class PreferencesFragment extends PreferenceFragment { →1

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Construct the preferences screen from the XML config
addPreferencesFromResource(R.xml.task_preferences); →8

// Use the number keyboard when editing the time preference
EditTextPreference timeDefault = (EditTextPreference) →11
findPreference(getString(R.string
.pref_default_time_from_now_key));
timeDefault.getEditText().setKeyListener(DigitsKeyListener →14
.getInstance());
}
}

That’s all the code needed to display, edit, and persist preferences in Android. The numbered lines of code are explained in this list:

1 The PreferencesFragment class file is defined by inheriting from the PreferenceFragment base class.

8 The call to the addPreferencesFromResource() method is provided with the resource ID of the task_preferences.xml file that’s stored in the res/xml directory.

11 Retrieves the EditTextPreference for the default task reminder time by calling the findPreference() method and providing it with the key that was defined in the task_preferences.xml file.

14 Obtains the EditText object from the EditTextPreference using the getEditText() method. The setKeyListener() method is called to set the key listener on the EditText to an instance of DigitsKeyListener, which allows only digits to be typed into the EditTextPreference.

You don’t want users to enter string values such as foo or bar into the field because it isn’t a valid integer value. Using the DigitsKeyListener ensures that the only values passed into the preferences are digits.

Now that you have the PreferencesFragment, you need an activity to ­display it. Create a new file named PreferencesActivity.java in com/dummies/tasks/activity and add the following code:

/**
* An activity for displaying and editing preferences.
* Uses a PreferencesFragment to do all of the dirty work.
*/
public class PreferencesActivity extends Activity {

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

getFragmentManager().beginTransaction().replace( →11
android.R.id.content,
new PreferencesFragment()).commit();
}
}

This activity is very simple. All it does is create a new PreferencesFragment and replace the existing activity content with that fragment on line 11.

At this point, you can use your activity. This PreferencesActivity allows users to edit and save their preferences. As you can see, this implementation requires only a snippet of code.

Add your new PreferencesActivity to the AndroidManifest.xml file by using this line of code:

<activity android:name="com.dummies.tasks.activity.PreferencesActivity"/>

The next step is displaying the preferences screen by adding a menu item.

Starting the PreferencesActivity

To open this new activity, you add a menu item to the TaskListFragment by simply adding a new menu definition to the menu_list.xml file that’s located in the res/menu directory. Updating this file updates the menu on the TaskListFragment. The updatedmenu_list.xml file is shown here with the new entry in bold:

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

<item
android:id="@+id/menu_insert"
android:icon="@android:drawable/ic_menu_add"
android:showAsAction="always"
android:title="@string/menu_insert"/>

<item
android:id="@+id/menu_settings"
android:showAsAction="never"
android:title="@string/menu_settings"/>
</menu>

The last item adds a menu item for settings, which uses the menu_settings string resource. You add a new string resource named menu_settings with a value of Settings in your string resources. Because the Settings menu is significantly less important than the Insert menu item, you don’t want to clutter the action bar with an icon for the Settings menu. Instead, use showAsAction="never" to ensure that the Settings menu is always displayed in the overflow menu rather than directly on the action bar.

See http://d.android.com/design/patterns/settings.html and http://www.google.com/design/spec/patterns/settings.html for more tips about how to create your settings menus.

Handling menu selections

After your menu is updated, the app needs to respond whenever the user taps a menu item. To make it do this, you add code to the onOptionsItemSelected() method in the TaskListFragment. The code to handle the Settings menu selection is bold in this snippet:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_insert:
((OnEditTask) getActivity()).editTask(0);
return true;
case R.id.menu_settings:
startActivity(new Intent(getActivity(),
PreferencesActivity.class));
return true;
}

return super.onOptionsItemSelected(item);
}

This code creates a new Intent object with a destination class of PreferencesActivity. A user who selects the Settings menu item is shown the preferences screen to edit his preferences. If you start the app and select Settings, you should see a screen similar to the one shown in Figure 15-3.

image

Figure 153: The ­preferences screen.

Working with Preferences in Your Activities at Runtime

Though setting preferences in a PreferencesFragment is useful, it ­provides no value in the end unless you can read the preferences from the SharedPreferences object at runtime and use them in your application. Thankfully, Android makes the process fairly simple.

In the Tasks app, you read these values in the TaskEditFragment to set the default values when a user creates a new task. Because the preferences are stored in SharedPreferences, you can access the preferences across ­various activities in your application.

Retrieving preference values

Open the TaskEditFragment and navigate to the onCreateView() method. It determines whether the task is an existing task or a new task. If the task is new, you pull the default values from SharedPreferences and load them into the activity for the user. If for some reason the user has never specified her preferences, they’re empty strings and you ignore the defaults. You use the preferences only if the user has set them.

To retrieve preference values, you use the SharedPreferences object, as shown in Listing 15-3 . Add the bold code to the very bottom of onCreateView().

Listing 153: Retrieving Values from SharedPreferences

if (taskId == 0) { →1
SharedPreferences prefs = PreferenceManager2
.getDefaultSharedPreferences(getActivity());
String defaultTitleKey = getString(R.string4
.pref_task_title_key);
String defaultTimeKey = getString(R.string6
<.pref_default_time_from_now_key);

String defaultTitle = prefs.getString(defaultTitleKey, null);9
<String defaultTime = prefs.getString(defaultTimeKey, null);10

if (defaultTitle != null)
titleText.setText(defaultTitle);13

if (defaultTime != null && defaultTime.length() > 0)
taskDateAndTime.add(Calendar.MINUTE,16
Integer.parseInt(defaultTime));

updateDateAndTimeButtons(); →19

} else {

// Fire off a background loader to retrieve the data from the
// database
getLoaderManager().initLoader(0, null, this);
}

Each new line of code is explained in this list:

1 If the taskId is 0, then you know the task’s ID hasn’t been set yet. This means it’s a new task.

2 Retrieves the SharedPreferences object from the static getDefaultSharedPreferences() call on the PreferenceManager object.

4 Retrieves the key value for the default title preference from the string resources. This same key is used in Listing 15-1 to define the preference.

6 Retrieves the key value for the default time offset, in minutes, from the preferences.

9 Retrieves the default title value from the preferences with a call to getString() on the SharedPreferences object. The first parameter is the key for the preference, and the second parameter is the default value if the preference doesn’t exist (or hasn’t been set). In this instance, the default value is null if the preference doesn’t exist.

10 Retrieves the default time value from the preferences, using the same method as described on line 9 with a different key.

13 Sets the text value of the EditText view — which is the title of the task. This value is set if the preference wasn’t null.

16 Increments time on the taskDateAndTime Calendar field by calling the add() method with the parameter of Calendar.MINUTE if the value from the preferences wasn’t equal to an empty string. The Calendar.MINUTE constant informs theCalendar object that the next parameter should be treated as minutes and the value should be added to the calendar’s Minute field. If the minutes force the calendar into a new hour or day, the Calendar object updates the other fields for you.

For example, if the calendar was originally set to 2016‐08‐31 11:45 p.m. and you add 60 minutes to the calendar, the new value of the calendar is 2016‐09‐01 12:45 a.m. Because EditTextPreference stores all values as strings, the string parses the minute value to an integer with the Integer.parseInt() method. By adding time to the taskDateAndTime Calendar field, the time picker and button text associated with opening the time picker update as well.

19 Updates the date and time buttons to reflect the time added to the existing taskDateAndTime Calendar field.

When you build, reinstall, and start the application, you can now set the preferences and see them reflected when you choose to add a new task to the list. Try clearing the preferences and then choosing to create a new task. Notice that the defaults no longer apply — easy!

Setting preference values

Though updating preference values via Java isn’t done in the Tasks app, at times you might need to in your own apps. Suppose that you develop a help‐desk ticket system application that requires users to enter their current departments. You have a Preference object for the default department, but the user never uses the preferences screen and therefore repeatedly enters the department into your application manually. Using logic that you define and write, you determine that the user is entering the same department for each help‐desk ticket (assume that it’s the Accounting department), so you prompt him to determine whether he wants to set the default department to Accounting. If he chooses Yes, you programmatically update the preferences for him.

To edit preferences programmatically, you need an instance of Shared Preferences. You can obtain it via PreferenceManager, as shown in Listing 15-4 . After you obtain an instance of SharedPreferences, you can edit various preferences by obtaining an instance of the preference Editor object. After the preferences are edited, you need to apply the changes, also demonstrated in Listing 15-4 .

Listing 154: Programmatically Editing Preferences

SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(this); →2
Editor editor = prefs.edit(); →3
editor.putString("default_department", "Accounting"); →4
editor.apply(); →5

The numbered lines of code are explained in this list:

2 An instance of SharedPreferences is retrieved from the PreferenceManager.

3 An instance of the preferences Editor object is obtained by ­calling the edit() method on the SharedPreferences object.

4 Edits a preference with the key value of default_department by calling the putString() method on the Editor object. The value is set to "Accounting". Normally, the key value is retrieved from the string resources, and the value of the string is retrieved via your program or user input. The code snippet remains simple for brevity.

5 After changes are made to any preferences, you must call the apply() method on the Editor object to persist them to Shared Preferences. The apply call automatically replaces any value stored in SharedPreferences with the key given in theputString() call.

If you don’t call apply() on the Editor object, your changes don’t persist and your application may not function as you expect.