Working with Preferences and Saving State
来源:互联网 发布:安卓市场 知乎 编辑:程序博客网 时间:2024/06/05 09:31
From chapter 13: Working with Preferences and Saving State 340
Preferences, we mean those feature choices that a user makes and saves to customize an
application to their liking. For example, if the user wants a notifi cation via a ringtone or
vibration or not at all, that is a preference the user saves; the application remembers the
choice until the user changes it. Android provides simple APIs that hide the reading and
persisting of preferences. It also provides prebuilt user interfaces that you can use to let
the user make preference selections. Because of the power built in to the Android
preferences framework, we can also use preferences for more general-purpose storing of
application state, to allow our application to pick up where it left off for example, should
our application go away and come back later. As another example, a game’s high scores
could be stored as preferences, although you’ll want to use your own UI to display them.
Before Android 3.0, prefer ences were managed a certain way, but then things changed.
With the extra real estate of a tablet screen, preferences can be visually arranged much
more nicely than on a phone’s screen. Although the f oundational bits of preferences (the
different types of preferences) stayed the same, the way they are displayed has changed
quite a bit. This chapter covers the foundational aspects of preferences and shows how
pre-3.0 preferences are displayed. The chapter ends with coverage of
PreferenceFragment and the new capabilities of PreferenceActivity.
Exploring the Preferences Framework
Before we dig into Android’s preferences framework, let’s establish a scenario that
would require the use of preferences and then explor e how we would go about
addressing it. Suppose you are writing an app lication that provides a facility to search
for airline flights. Moreover, suppose that the app lication’s default se tting is to display
flights based on the lowest cost, but the user can set a pr eference to always sort flights
by the least number of stops or by a specific airline. How would you go about doing
that?
Understanding ListPreference
Obviously, you would have to provide a UI for the user to view the list of sort options.
The list would contain radio buttons for each option, and the default (or current)
selection would be pr eselected. To solve this proble m with the Android preferences
framework requires very littl e work. First, you would create a preferences XML file to
describe the preference, and t hen you use a prebuilt activity class that knows how to
show and persist preferences. Li sting 13–1 shows the details.
NOTE: We will give you a URL at the end of the chap ter that you can use to download projects
from this chapter. This will allow you to import these projects into Eclipse directly.
Listing 13–1. The Flight-Options Preferences XML F ile and Associated Activity Class
Listing 13–1 contains an XML fragment that represents t he flight-option preference
setting. The listing also contains an activity class that loads the preferences XML file.
Let’s start with the XML. Android provides an end-to-end preferences framework. This
means the framework lets you define your preferences, display the setting(s) to the user,
and persist the user’s selection to the data store. You define y our preferences in XML
under /res/xml/ . To show preferences to the user, you write an activity class that
extends a predefined Android class called android.preference.PreferenceActivity and
use the addPreferencesFromResource() method to add the resource to the activity’s
resource collection. The framework takes care of the rest (displaying and persisting).
In this flight scenario, you create a file called flightoptions.xml at
/res/xml/flightoptions.xml. You then create an activity class called
FlightPreferenceActivity that extends the android.preference.PreferenceActivity
class. Next, you call addPreferencesFromResource(), passing in R.xml.flightoptions .
Note that the preference res ource XML points to several string resources. To ensure
compilation, you need to add several string re sources to your project. We will show you
how to do that shortly. For now, have a look at the UI generated by Listing 13–1 (see
Figure 13–1).
Figure 13–1. The flight-options preference UI
Figure 13–1 contains tw o views. The view on the left is called a preference screen, and
the UI on the right is a list preference . When the user selects F light Options, the Choose
Flight Options view appears as a modal dialog with radio buttons for each option. The
user selects an option, which immediately saves that opti on and closes the view. When
the user returns to the options screen, the view reflects the saved selection from before.
The XML code in Listing 13–1 defines PreferenceScreen and then creates
ListPreference as a child. For PreferenceScreen, you set three properties: key , title ,
and summary . key is a string you can use to refer to the item programmati cally (similar to
how you use android:id); title is the screen’s title (Flight Options); and summary is a
description of the scr een’s purpose, shown below the title in a smaller font (Set Search
Options, in this case). For the list preference, you set key , title , and summary , as well as
attributes for entries , entryValues , dialogTitle , and defaultValue. Table 13–1
summarizes these attributes.
Table 13–1. A Few Attributes of android.preference.ListPreference
Attribute Description
android:key A name or key for the option (such as
selected_flight_sort_option ).
android:title
The title of the option.
android:summary A short summary of the option.
android:entries
The array of text items that the option can be set to.
android:entryValues The key, or value, for each item. Note that each item has some text
and a value. The text is defined by entries , and the values are
defined by entryValues .
android:dialogTitle
The title of the dialog—used if the view is shown as a modal dialog.
android:defaultValue
The default value of the option from the list of items.
Listing 13–2 contains the source of several other files for the example, which we’ll be
talking about soon.
Listing 13–2. Other Files from Our Example
When you run this application, you will first see a simple text message that says “option
value is 1 (# of Stops).” Click the M enu button and then Settings to get to the
PreferenceActivity. Click the back arrow when you’re finished, and you will see any
changes to the option text immediately.
The first file to talk about is /res/values/arrays.xml. This file contains the two string
arrays that we need to implem ent the option choices. The first array holds the text to be
displayed, and the second holds the values that we’ll get back in our method calls plus
the value that gets stored in the preferences XML file. For our purposes, we chose to
use array index values 0, 1, and 2 for flight_sort_options_values. We could use any
value that helps us run the application. If our option was numeric in nature (for example,
a countdown timer starting value), then we could have used values such as 60, 120,
300, and so on. The values don’t need to be numeric at all as long as they make sense
to the developer; the user doesn’t see t hese values unless you choose to expose them.
The user only sees the text from the first string array flight_sort_options .
As we said earlier, the Android framework also takes care of persisting preferences. For
example, when the user selects a sort option, An droid stores the sel ection in an XML file
within the application’s /data directory on the devi ce (see Figure 13–2).
Figure 13–2. Path to an application’s saved preferences
NOTE: You will only be able to inspect shared preferences files in th e emulator. On a real device,
the shared preferences files are not readable due to Android security.
The actual file path is
/data/data/[PACKAGE_NAME] /shared_prefs/ [PACKAGE_NAME] _preferences.xml . Listing 13–3
shows the com.androidbook.preferences.sample_preferences.xml file for our example.
Listing 13–3. Saved Preferences for Our Example
You can see that for a list preference, the preferences framework persists the selected
item’s value using the list’s key attribute. Note also that the selected item’s value is
stored—not the text. A word of caution here: becaus e the preferences XML file is storing
only the value and not the te xt, should you ever upgrade your application and change
the text of the options or add items to the string arrays, any value stored in the
preferences XML file should still line up with the appropriate text after the upgrade. The
preferences XML file is kept during the appli cation upgrade. If t he preferences XML file
had a “1” in it, and that meant “# of Stops” before the upgrade, it should still mean “#
of Stops” after the upgrade.
The next file we are interested in is /res/values/strings.xml . We added several strings
for our titles, summaries, and menu items. There are two strings to pay particular
attention to. The first is flight_sort_option_default_value. We set the default value to
1 to represent “# of Stops” in our example. It is usually a good idea to choose a default
value for each option. If you don’t choose a default value and no value has yet been
chosen, the methods that return the value of the option will return null. Your code
would have to deal with null values in this case. The other interesting string is
selected_flight_sort_option . Strictly speaking, the user is not going to see this string,
so we don’t need to put it inside strings.xml to provide alternate text for other
languages. However, because this string value is a key used in the method call to
retrieve the value, by creating an ID out of it, we can ensure at compile time that we
didn’t make a typographical error on the key’s name.
Next up is the source code for our MainActivity. This is a basic activity that gets a
reference to the preferences and a handle to a TextView and then calls a method to read
the current value of our option to set it into the TextView. The layout for our application
is not shown here but is simply a TextView to display a message about the current
preference setting. Next, We set up our m enu and the menu callback. Within the menu
callback, we launch an Intent for the FlightPreferenceActivity. Launching an intent
for our preferences is the best way to get to the preferences sc reen. You could use a
menu or use a button to fire the intent. We’ll not repeat this code for later examples, but
you would do the same thing with them, ex cept that you use the appropriate activity
class name. When the preferences Intent returns to us, we call the setOptionText()
method to update our TextView.
There are two ways to get a handle to the preferences:
The easiest is what we show in the example: that is, to call
PreferenceManager.getDefaultSharedPreferences(this) . The this
argument is the context for finding the default shared preferences, and
the method will use the package name of this to determine the file
name and location of the preferences file, which happens to be the
one created by our PreferenceActivity, because they share the same
package name.
The other way to get a handle to a preferences file is to use the
getSharedPreferences() method call, passing in a file name argument
as well as a mode argument. In Listing 13–2, we show this way, but it’s
been commented out. Notice that you only specify the base part of the
file name, not the path and not the file name extension. The mode
argument controls permissions to our XML preferences file. In our
preceding example, the mode argument wouldn’t affect anything
because the file is only created within the PreferenceActivity, which
sets the default permissions of MODE_PRIVATE (zero). We’ll discuss the
mode argument later, in the sections on saving state.
In most cases, you’ll use the first method of locating the preferences. However, if you
had multiple users for your application on a device, and each user managed their own
preferences, you’d need to use the second option to keep the users’ preferences
separate from each other.
Inside of setOptionText() , with a reference to the pref erences, you call the appropriate
methods to retrieve the preference values. In our example, we call getString() , because
we know we’re retrieving a string value from the preferences. The first argument is the
string value of the option key. We noted before that using an ID ensures that we haven’t
made any typographical errors while building our application. We could also have simply
used the string "selected_flight_sort_option " for the first argument, but then it’s up to
you to ensure that this string is exactly the same as other parts of your code where the
key value is used. For the second argument, you specify a default value in case the
value can’t be found in the preferences XML file. When your a pplication runs for the very
first time, you don’t have a preferences XML f ile; so without specify ing a value for the
second argument, you’ll always get null the first time. This is true even though you’ve
specified a default val ue for the option in the ListPreference specification in
flightoptions.xml . In our example, we set a defaul t value in XML, and we used a
resource ID to do it, so the code in setOptionText() can be used to read the value of
the resource ID for the default value. Note that if we had not used an ID for the default
value, it would be a lot tougher to read it directly from the ListPreference. By sharing a
resource ID between the XML and our code, we have only one place in which to change
the default value (that is, in strings.xml ).
In addition to displaying the value of the preference, we also display the text of the
preference. We’re taking a shortcut in our example, because we used array indices for
the values in flight_sort_options_values. By simply converting the value to an int , we
know which string to read from flight_sort_options . Had we used some other set of
values for flight_sort_options_values, we would need to determine the index of the
element that is our preference and then turn around and use that index to grab the text
of our preference from flight_sort_options .
Because we now have two activities in our application, we need two activity tags in
AndroidManifest.xml . The first one is a standard activity of category LAUNCHER. The
second one is for a PreferenceActivity, so we set the action name according to
convention for intents, and we set the category to PREFERENCE as shown in Listing 13–4.
We probably don’t want the PreferenceActivity showing up on the Android page with
all our other applications, which is why we chose not to use LAUNCHER for it. You would
need to make similar changes to AndroidManifest.xml if you were to add other
preferences screens.
Listing 13–4. PreferenceActivity entry in AndroidManifest.xml
We showed one way to read a default value for a preference in code. Android provides
another way that is a bit more elegant. In onCreate(), we could have done the following
instead:
PreferenceManager.setDefaultValues(this, R.xml.flightoptions, false);
Then, in setOptionText() , we could have done this to read the option value:
String option = prefs.getString(
resources.getString(R.string.selected_flight_sort_option), null);
The first call will use flightoptions.xml to find the default values and generate the
preferences XML file for us using the defaul t values. If we already have an instance of
the SharedPreferences object in memory, it will upda te that too. The second call will
then find a value for selected_flight_sort_option , because we took care of loading
defaults first.
After running this code the first time, if you look in the shared_prefs folder, you will see
the preferences XML file even if the preferences screen has not yet been invoked. You
will also see another file called _has_set_default_values.xml . This tells your application
that the preferences XML file has already been created with the default values. The third
argument to setDefaultValues()—that is, false —indicates that you only want the
defaults set in the preferences XML file if it hasn’t been done before. If you choose true
instead, you’ll always reset the preferences XML file with default values. Android
remembers this information through the exist ence of this new XML file. If the user has
selected new preference values, and you choose false for the third argument, the user
preferences won’t be overwritt en the next time this code runs. Notice that now we don’t
need to provide a default value in the getString() method call, because we should
always get a value from the preferences XML file.
If you need a reference to the preferences from inside of an activity that extends
PreferenceActivity, you could do it this way:
SharedPreferences prefs = getPreferenceManager().getDefaultSharedPreferences(this);
We showed you how to use the ListPreference view; now, let’s examine some other UI
elements within the Android preferences framework. Namely, let’s talk about the
CheckBoxPreference view and the EditTextPreference view.
Understanding CheckBoxPreference
You saw that the ListPreference preference displays a list as its UI element. Similarly,
the CheckBoxPreference preference displays a check-box widget as its UI element.
To extend the flight-search exam ple application, suppose you want to let the user set
the list of columns to see in the result set. This preference di splays the available
columns and allows the user to choose the desired columns by marking the
corresponding check boxes. The user interface for this example is shown in Figure 13–3,
and the preferences XML file is shown in Listing 13–5.
Figure 13–3. The user interface for the check-box preference
Listing 13–5. Using CheckBoxPreference
Listing 13–5 shows the preferences XML file, chkbox.xml, and a simple activity class that
loads it using addPreferencesFromResource(). As you can see, the UI has five check
boxes, each of which is represented by a CheckBoxPreference node in the preferences
XML file. Each of the check boxes also has a key , which—as you would expect—is
ultimately used to persist the state of the UI element when it comes time to save the
selected preference. With CheckBoxPreference, the state of the preference is saved
when the user sets the state. In other words, when the user checks or unchecks the
preference control, its state is saved. Listi ng 13–6 shows the preference data store for
this example.
Listing 13–6. The Preferences Data Store for the Check Box Preference
Again, you can see that each preference is saved through its key attribute. The data type
of the CheckBoxPreference is a boolean , which contains a value of either true or false :
true to indicate the pref erence is selected, and false to indicate other wise. To read the
value of one of the check-box preferences, you would get access to the shared
preferences and call the getBoolean() method, passing the key of the preference:
boolean option = prefs.getBoolean("show_price_column_pref", false);
One other useful feature of CheckBoxPreference is that you can set different summary
text depending on whether it’s checked. The XML attributes are summaryOn and
summaryOff. Now, let’s have a look at the EditTextPreference.
Understanding EditTextPreference
The preferences framework also provides a free-form text preference called
EditTextPreference. This preference allows you to capture raw text rather than ask the
user to make a selection. To demonstrate th is, let’s assume you hav e an application that
generates Java code for the user. One of the preference settings of this application
might be the default package name to use for the generated classes. Here, you want to
display a text field to the user for setting the package name for t he generated classes.
Figure 13–4 shows the UI, and Li sting 13–7 shows the XML.
Figure 13–4. Using the EditTextPreference
Listing 13–7. An Example of an EditTextPreference
You can see that Li sting 13–6 defines PreferenceScreen with a single
EditTextPreference instance as a child. The generated UI for the listing features the
PreferenceScreen on the left and the EditTextPreference on the right (see Figure 13–4).
When Set Package Name is selected, the user is presented with a dialog to input the
package name. When the OK button is clicked, the preference is saved to the
preference store.
As with the other pref erences, you can obtain the EditTextPreference from your activity
class by using the preference’s key . Once you have the EditTextPreference, you can
manipulate the actual EditText by calling getEditText() —if, for example, you want to
apply validation, prep rocessing, or post-processing on t he value the user types in the
text field. To get the text of the EditTextPreference, just use the getText() method.
Understanding RingtonePreference and
MultiSelectListPreference
There is another preference called RingtonePreference, but we won’t cover that here. It
follows the same rules as the others and isn’t used much. And finally, a preference
called MultiSelectListPreference was introduced in Android 3.0. The concept is
somewhat similar to a ListPreference, but instead of only being able to select one item
in the list, the user can select several or none. Unfortunately, the implementation is
buggy as of this writing. For example, the values array d oes not seem to be used—only
the entries array. This means the XML preferences file contains the entry strings and not
the corresponding values as the ListPreference does. It is also a mystery how to set the
default values. For more information, see http://code.google.com/p/android/
issues/detail?id=15966.
Until this type of preference is fixed, you’re better off creating a set of
CheckBoxPreferences. Figure 13–3 shows what a MultiSelectListPreference looks like
(more or less).
Organizing Preferences
The preferences framework provides some su pport for you to organize your preferences
into categories. If you have a lot of prefer ences, for example, you can build a view that
shows high-level categories of preferences. Users coul d then drill down into each
category to view and manage preferences specific to that group.
Using PreferenceCategory
You can implement something like this in one of two ways. You can introduce nested
PreferenceScreen elements within the root PreferenceScreen, or you can use
PreferenceCategory elements to get a similar result. Figure 13–5 and Listing 13–8 show
how to implement the first technique, grouping preferences by using nested
PreferenceScreen elements.
The view on the left in Figure 13–5 displays options for two preference screens, one with
the title Meats and the other with the title Vegetables. Clicking a group takes you to the
preferences within that group. Listing 13–8 shows how to create nested screens.
Figure 13–5. Creating groups of preferences by nesting PreferenceScreen elements
Listing 13–8. Nesting PreferenceScreen Elements to Organize Preferences
You create the groups in Figure 13–5 by nesting PreferenceScreen elements within the
root PreferenceScreen. Organizing preferences this way is useful if you have a lot of
preferences and you’re concerned about having the users scroll to find the preference
they are looking for. If you don’t have a lot of preferences but still want to provide high-level categories for your preferences, you can use PreferenceCategory, which is the
second technique we menti oned. Figure 13–6 and Listing 13–9 show the details.
Figure 13–6. Using PreferenceCategory to organize preferences
Figure 13–6 shows the same groups we used in our previous example, but now organized
with preference categories. The only difference between the XML in Listing 13–9 and the
XML in Listing 13–8 is that you create a PreferenceCategory for the nested screens
rather than nest PreferenceScreen elements.
Listing 13–9. Creating Categories of Preferences
Creating Child Preferences with Dependency
Another way to organize preferences is to use a preference dependency. This creates a
parent-child relationship between preferences. For example, we might have a preference
that turns on alerts; and if alerts are on, there might be severa l other alert-related
preferences to choose from. If the main alerts preference is off, the other preferences
are not relevant and should be disabled. Listing 13–10 shows the XML, and Figure 13–7
shows what it looks like.
Listing 13–10. Preference Dependency in XML
Figure 13–7. Preference dependency
Preferences with Headers
With the introduction of Android 3.0, we got another way to organize preferences. You
see this on tablets under the main Settings app. Because tablet screen real estate offers
much more room than a smartphone does, it makes sense to display more preference
information at the same time. To accomplish this, we use preference headers. Take a
look at Figure 13–8 to see what we mean.
Figure 13–8. Main Settings page with preference headers
Notice that headers appear down the left side, like a vertical tab bar. As you click each item
on the left, the screen to the right displays the preferences for that item. In Figure 13–8,
Sound is chosen, and the sound preferences are displayed at right. The right side is a
PreferenceScreen object, and this setup uses fr agments. Obviously, we need to do
something different than what has been discussed so far in this chapter.
The big change from Android 3.0 was the addition of headers to PreferenceActivity.
This also means using a new callback within PreferenceActivity to do the headers
setup. Now, when you extend PreferenceActivity, you'll want to implement this
method:
The preferences.xml file contains some new tags that look like this:
Each header tag points to a class that extends PreferenceFragment. In the example just
given, the XML specifies an i con, the title, and summary text (which acts like a subtitle).
Prefs1Fragment is an inner class of PreferenceActivity that could look something like
this:
All this inner class needs to do is pull in the appropriate preferences XML file, as shown.
That preferences XML file contains the types of preference specifications we covered
earlier, such as ListPreference, CheckBoxPreference, PreferenceCategory, and so on.
What’s very nice is that Android takes care of doing the right thing when the screen
configuration changes and when the preferences are displa yed on a small screen.
Headers behave like old pref erences when the screen is t oo small to display both
headers and the preference screen to the right. That is, you only see the headers; and
when you click a header, you then see only the appropriate preference screen.
Manipulating Preferences Programmatically
It goes without saying that you might need to access the actual preference controls
programmatically. For example, what if you need to provide the entries and
entryValues for the ListPreference at runtime? You can de fine and access preference
controls similarly to the way you define and access controls in layout files and activities.
For example, to access the list preference defined in Listing 13–1, you would call the
findPreference() method of PreferenceActivity, passing the preference’s key (note
the similarity to findViewById()). You would next cast the control to ListPreference and
then go about manipulating the control. For example, if you want to set the entries of the
ListPreference view, call the setEntries() method, and so on. Listing 13–11 shows
what this might look like with a simple example of using code to set up the preference.
Of course, you could also create the entire PreferenceScreen starting with
PreferenceManager.createPreferenceScreen().
Listing 13–11. Setting ListPreference Values Programmatically
Saving State with Preferences
Preferences are great for allowing users to customize applications to their liking, but we
can use the Android preference framework for more than that. When your application
needs to keep track of some data between invocations of the application, preferences
are one way to accomplish the task. We’ve already talked about content providers for
maintaining data. We could use custom files on the SD card. We can also use shared
preference files and code.
The Activity class has a getPreferences(int mode) method. This, in reality, simply
calls getSharedPreferences() with the class name of the activity as the tag plus the
mode as passed in. The result is an activity- specific shared prefer ences file that you can
use to store data about this activity across invocations. A simple example of how you
could use this is shown in Listing 13–12.
Listing 13–12. Using Preferences to Save State for an Activity
What this code does is acquire a reference to preferences for our activity class and
check for the existence of a boolean “preference” called initialized . We write
“preference” in double quotation marks because this value is not something the user is
going to see or set; it’s merely a value that we want to store in a shared preferences file
for use next time. If we get a value, the shar ed preferences file exists, so our application
must have been called before. We could then read other values out of the shared
preferences file. For example, someString could be an activity variable that should be
set from the last time this activity ran or set to the default value if this is the first time.
To write values to the shar ed preferences file, we mu st first get a preferences Editor.
We can then put values into preferences and commit those changes when we’re
finished. Note that, behind the scenes, Android is managing a SharedPreferences object
that is truly shared. Ideally, there is never more than one Editor active at a time. But it is
very important to call the commit() method so that the SharedPreferences object and
the shared preferences XML file get updated. In the example, we wr ite out the value of
someString to be used the next time this activity runs.
You can access, write, and commit values anytime to your preferences file. Possible
uses for this include writing out high scores for a game or recording when the
application was last r un. You can also use the getSharedPreferences() call with
different names to manage separate sets of preferences, all wi thin the same application
or even the same activity.
We’ve used MODE_PRIVATE for mode in our examples thus far. The other possible values
of mode are MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE. These modes are used
when creating the shared preferences XML file to set the file permissions accordingly.
Because the shared preferences files are stored within your application’s data directory
and therefore are not accessible to other applications, you only need to use
MODE_PRIVATE.
Using DialogPreference
So far, you’ve seen how to use the out-of-the-box capabilities of the preferences
framework, but what if you want to cr eate a custom preference? What if you want
something like the slider of the Bright ness preference under Sc reen Settings? This is
where DialogPreference comes in. DialogPreference is the parent class of
EditTextPreference and ListPreference. The behavior is a dialog that pops up, displays
choices to the user, and is closed with a button or via the Back button. But you can
extend DialogPreference to set up your own custom preference. Within your extended
class, you provide your own layout, y our own click handlers, and custom code in
onDialogClosed() to write the data for your pref erence to the shared preferences file.
Preferences, we mean those feature choices that a user makes and saves to customize an
application to their liking. For example, if the user wants a notifi cation via a ringtone or
vibration or not at all, that is a preference the user saves; the application remembers the
choice until the user changes it. Android provides simple APIs that hide the reading and
persisting of preferences. It also provides prebuilt user interfaces that you can use to let
the user make preference selections. Because of the power built in to the Android
preferences framework, we can also use preferences for more general-purpose storing of
application state, to allow our application to pick up where it left off for example, should
our application go away and come back later. As another example, a game’s high scores
could be stored as preferences, although you’ll want to use your own UI to display them.
Before Android 3.0, prefer ences were managed a certain way, but then things changed.
With the extra real estate of a tablet screen, preferences can be visually arranged much
more nicely than on a phone’s screen. Although the f oundational bits of preferences (the
different types of preferences) stayed the same, the way they are displayed has changed
quite a bit. This chapter covers the foundational aspects of preferences and shows how
pre-3.0 preferences are displayed. The chapter ends with coverage of
PreferenceFragment and the new capabilities of PreferenceActivity.
Exploring the Preferences Framework
Before we dig into Android’s preferences framework, let’s establish a scenario that
would require the use of preferences and then explor e how we would go about
addressing it. Suppose you are writing an app lication that provides a facility to search
for airline flights. Moreover, suppose that the app lication’s default se tting is to display
flights based on the lowest cost, but the user can set a pr eference to always sort flights
by the least number of stops or by a specific airline. How would you go about doing
that?
Understanding ListPreference
Obviously, you would have to provide a UI for the user to view the list of sort options.
The list would contain radio buttons for each option, and the default (or current)
selection would be pr eselected. To solve this proble m with the Android preferences
framework requires very littl e work. First, you would create a preferences XML file to
describe the preference, and t hen you use a prebuilt activity class that knows how to
show and persist preferences. Li sting 13–1 shows the details.
NOTE: We will give you a URL at the end of the chap ter that you can use to download projects
from this chapter. This will allow you to import these projects into Eclipse directly.
Listing 13–1. The Flight-Options Preferences XML F ile and Associated Activity Class
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/flightoptions.xml --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="flight_option_preference" android:title="@string/prefTitle" android:summary="@string/prefSummary"> <ListPreference android:key="@string/selected_flight_sort_option" android:title="@string/listTitle" android:summary="@string/listSummary" android:entries="@array/flight_sort_options" android:entryValues="@array/flight_sort_options_values" android:dialogTitle="@string/dialogTitle" android:defaultValue="@string/flight_sort_option_default_value" /> </PreferenceScreen>
public class FlightPreferenceActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.flightoptions); } }
Listing 13–1 contains an XML fragment that represents t he flight-option preference
setting. The listing also contains an activity class that loads the preferences XML file.
Let’s start with the XML. Android provides an end-to-end preferences framework. This
means the framework lets you define your preferences, display the setting(s) to the user,
and persist the user’s selection to the data store. You define y our preferences in XML
under /res/xml/ . To show preferences to the user, you write an activity class that
extends a predefined Android class called android.preference.PreferenceActivity and
use the addPreferencesFromResource() method to add the resource to the activity’s
resource collection. The framework takes care of the rest (displaying and persisting).
In this flight scenario, you create a file called flightoptions.xml at
/res/xml/flightoptions.xml. You then create an activity class called
FlightPreferenceActivity that extends the android.preference.PreferenceActivity
class. Next, you call addPreferencesFromResource(), passing in R.xml.flightoptions .
Note that the preference res ource XML points to several string resources. To ensure
compilation, you need to add several string re sources to your project. We will show you
how to do that shortly. For now, have a look at the UI generated by Listing 13–1 (see
Figure 13–1).
Figure 13–1. The flight-options preference UI
Figure 13–1 contains tw o views. The view on the left is called a preference screen, and
the UI on the right is a list preference . When the user selects F light Options, the Choose
Flight Options view appears as a modal dialog with radio buttons for each option. The
user selects an option, which immediately saves that opti on and closes the view. When
the user returns to the options screen, the view reflects the saved selection from before.
The XML code in Listing 13–1 defines PreferenceScreen and then creates
ListPreference as a child. For PreferenceScreen, you set three properties: key , title ,
and summary . key is a string you can use to refer to the item programmati cally (similar to
how you use android:id); title is the screen’s title (Flight Options); and summary is a
description of the scr een’s purpose, shown below the title in a smaller font (Set Search
Options, in this case). For the list preference, you set key , title , and summary , as well as
attributes for entries , entryValues , dialogTitle , and defaultValue. Table 13–1
summarizes these attributes.
Table 13–1. A Few Attributes of android.preference.ListPreference
Attribute Description
android:key A name or key for the option (such as
selected_flight_sort_option ).
android:title
The title of the option.
android:summary A short summary of the option.
android:entries
The array of text items that the option can be set to.
android:entryValues The key, or value, for each item. Note that each item has some text
and a value. The text is defined by entries , and the values are
defined by entryValues .
android:dialogTitle
The title of the dialog—used if the view is shown as a modal dialog.
android:defaultValue
The default value of the option from the list of items.
Listing 13–2 contains the source of several other files for the example, which we’ll be
talking about soon.
Listing 13–2. Other Files from Our Example
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/values/arrays.xml --> <resources> <string-array name="flight_sort_options"> <item>Total Cost</item> <item># of Stops</item> <item>Airline</item> </string-array> <string-array name="flight_sort_options_values"> <item>0</item> <item>1</item> <item>2</item> </string-array> </resources> <?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/values/strings.xml --> <resources> <string name="app_name">Preferences Demo</string> <string name="prefTitle">My Preferences</string> <string name="prefSummary">Set Flight Option Preferences</string> <string name="flight_sort_option_default_value">1</string> <string name="dialogTitle">Choose Flight Options</string> <string name="listSummary">Set Search Options</string> <string name="listTitle">Flight Options</string> <string name="selected_flight_sort_option"> selected_flight_sort_option</string> CHAPTER 13: Working with Preferences and Saving State 343 <string name="menu_prefs_title">Settings</string> </resources>
// This file is MainActivity.java public class MainActivity extends Activity { private TextView tv = null; private Resources resources; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); resources = this.getResources(); tv = (TextView)findViewById(R.id.text1); setOptionText(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.mainmenu, menu); return true; } @Override public boolean onOptionsItemSelected (MenuItem item) { if (item.getItemId() == R.id.menu_prefs) { // Launch to our preferences screen. Intent intent = new Intent() .setClass(this, com.androidbook.preferences.sample.FlightPreferenceActivity.class); this.startActivityForResult(intent, 0); } return true; } @Override public void onActivityResult(int reqCode, int resCode, Intent data) { super.onActivityResult(reqCode, resCode, data); setOptionText(); } private void setOptionText() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); // This is the other way to get to the shared preferences: // SharedPreferences prefs = getSharedPreferences( CHAPTER 13: Working with Preferences and Saving State 344 // "com.androidbook.preferences.sample_preferences", 0); String option = prefs.getString( resources.getString(R.string.selected_flight_sort_option), resources.getString(R.string.flight_sort_option_default_value)); String[] optionText = resources.getStringArray(R.array.flight_sort_options); tv.setText("option value is " + option + " (" + optionText[Integer.parseInt(option)] + ")"); } }
When you run this application, you will first see a simple text message that says “option
value is 1 (# of Stops).” Click the M enu button and then Settings to get to the
PreferenceActivity. Click the back arrow when you’re finished, and you will see any
changes to the option text immediately.
The first file to talk about is /res/values/arrays.xml. This file contains the two string
arrays that we need to implem ent the option choices. The first array holds the text to be
displayed, and the second holds the values that we’ll get back in our method calls plus
the value that gets stored in the preferences XML file. For our purposes, we chose to
use array index values 0, 1, and 2 for flight_sort_options_values. We could use any
value that helps us run the application. If our option was numeric in nature (for example,
a countdown timer starting value), then we could have used values such as 60, 120,
300, and so on. The values don’t need to be numeric at all as long as they make sense
to the developer; the user doesn’t see t hese values unless you choose to expose them.
The user only sees the text from the first string array flight_sort_options .
As we said earlier, the Android framework also takes care of persisting preferences. For
example, when the user selects a sort option, An droid stores the sel ection in an XML file
within the application’s /data directory on the devi ce (see Figure 13–2).
Figure 13–2. Path to an application’s saved preferences
NOTE: You will only be able to inspect shared preferences files in th e emulator. On a real device,
the shared preferences files are not readable due to Android security.
The actual file path is
/data/data/[PACKAGE_NAME] /shared_prefs/ [PACKAGE_NAME] _preferences.xml . Listing 13–3
shows the com.androidbook.preferences.sample_preferences.xml file for our example.
Listing 13–3. Saved Preferences for Our Example
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="selected_flight_sort_option">1</string> </map>
You can see that for a list preference, the preferences framework persists the selected
item’s value using the list’s key attribute. Note also that the selected item’s value is
stored—not the text. A word of caution here: becaus e the preferences XML file is storing
only the value and not the te xt, should you ever upgrade your application and change
the text of the options or add items to the string arrays, any value stored in the
preferences XML file should still line up with the appropriate text after the upgrade. The
preferences XML file is kept during the appli cation upgrade. If t he preferences XML file
had a “1” in it, and that meant “# of Stops” before the upgrade, it should still mean “#
of Stops” after the upgrade.
The next file we are interested in is /res/values/strings.xml . We added several strings
for our titles, summaries, and menu items. There are two strings to pay particular
attention to. The first is flight_sort_option_default_value. We set the default value to
1 to represent “# of Stops” in our example. It is usually a good idea to choose a default
value for each option. If you don’t choose a default value and no value has yet been
chosen, the methods that return the value of the option will return null. Your code
would have to deal with null values in this case. The other interesting string is
selected_flight_sort_option . Strictly speaking, the user is not going to see this string,
so we don’t need to put it inside strings.xml to provide alternate text for other
languages. However, because this string value is a key used in the method call to
retrieve the value, by creating an ID out of it, we can ensure at compile time that we
didn’t make a typographical error on the key’s name.
Next up is the source code for our MainActivity. This is a basic activity that gets a
reference to the preferences and a handle to a TextView and then calls a method to read
the current value of our option to set it into the TextView. The layout for our application
is not shown here but is simply a TextView to display a message about the current
preference setting. Next, We set up our m enu and the menu callback. Within the menu
callback, we launch an Intent for the FlightPreferenceActivity. Launching an intent
for our preferences is the best way to get to the preferences sc reen. You could use a
menu or use a button to fire the intent. We’ll not repeat this code for later examples, but
you would do the same thing with them, ex cept that you use the appropriate activity
class name. When the preferences Intent returns to us, we call the setOptionText()
method to update our TextView.
There are two ways to get a handle to the preferences:
The easiest is what we show in the example: that is, to call
PreferenceManager.getDefaultSharedPreferences(this) . The this
argument is the context for finding the default shared preferences, and
the method will use the package name of this to determine the file
name and location of the preferences file, which happens to be the
one created by our PreferenceActivity, because they share the same
package name.
The other way to get a handle to a preferences file is to use the
getSharedPreferences() method call, passing in a file name argument
as well as a mode argument. In Listing 13–2, we show this way, but it’s
been commented out. Notice that you only specify the base part of the
file name, not the path and not the file name extension. The mode
argument controls permissions to our XML preferences file. In our
preceding example, the mode argument wouldn’t affect anything
because the file is only created within the PreferenceActivity, which
sets the default permissions of MODE_PRIVATE (zero). We’ll discuss the
mode argument later, in the sections on saving state.
In most cases, you’ll use the first method of locating the preferences. However, if you
had multiple users for your application on a device, and each user managed their own
preferences, you’d need to use the second option to keep the users’ preferences
separate from each other.
Inside of setOptionText() , with a reference to the pref erences, you call the appropriate
methods to retrieve the preference values. In our example, we call getString() , because
we know we’re retrieving a string value from the preferences. The first argument is the
string value of the option key. We noted before that using an ID ensures that we haven’t
made any typographical errors while building our application. We could also have simply
used the string "selected_flight_sort_option " for the first argument, but then it’s up to
you to ensure that this string is exactly the same as other parts of your code where the
key value is used. For the second argument, you specify a default value in case the
value can’t be found in the preferences XML file. When your a pplication runs for the very
first time, you don’t have a preferences XML f ile; so without specify ing a value for the
second argument, you’ll always get null the first time. This is true even though you’ve
specified a default val ue for the option in the ListPreference specification in
flightoptions.xml . In our example, we set a defaul t value in XML, and we used a
resource ID to do it, so the code in setOptionText() can be used to read the value of
the resource ID for the default value. Note that if we had not used an ID for the default
value, it would be a lot tougher to read it directly from the ListPreference. By sharing a
resource ID between the XML and our code, we have only one place in which to change
the default value (that is, in strings.xml ).
In addition to displaying the value of the preference, we also display the text of the
preference. We’re taking a shortcut in our example, because we used array indices for
the values in flight_sort_options_values. By simply converting the value to an int , we
know which string to read from flight_sort_options . Had we used some other set of
values for flight_sort_options_values, we would need to determine the index of the
element that is our preference and then turn around and use that index to grab the text
of our preference from flight_sort_options .
Because we now have two activities in our application, we need two activity tags in
AndroidManifest.xml . The first one is a standard activity of category LAUNCHER. The
second one is for a PreferenceActivity, so we set the action name according to
convention for intents, and we set the category to PREFERENCE as shown in Listing 13–4.
We probably don’t want the PreferenceActivity showing up on the Android page with
all our other applications, which is why we chose not to use LAUNCHER for it. You would
need to make similar changes to AndroidManifest.xml if you were to add other
preferences screens.
Listing 13–4. PreferenceActivity entry in AndroidManifest.xml
<activity android:name=".FlightPreferenceActivity" android:label="@string/prefTitle"> <intent-filter> <action android:name= "com.androidbook.preferences.sample.intent.action.FlightPreferences" /> <category android:name="android.intent.category.PREFERENCE" /> </intent-filter> </activity>
We showed one way to read a default value for a preference in code. Android provides
another way that is a bit more elegant. In onCreate(), we could have done the following
instead:
PreferenceManager.setDefaultValues(this, R.xml.flightoptions, false);
Then, in setOptionText() , we could have done this to read the option value:
String option = prefs.getString(
resources.getString(R.string.selected_flight_sort_option), null);
The first call will use flightoptions.xml to find the default values and generate the
preferences XML file for us using the defaul t values. If we already have an instance of
the SharedPreferences object in memory, it will upda te that too. The second call will
then find a value for selected_flight_sort_option , because we took care of loading
defaults first.
After running this code the first time, if you look in the shared_prefs folder, you will see
the preferences XML file even if the preferences screen has not yet been invoked. You
will also see another file called _has_set_default_values.xml . This tells your application
that the preferences XML file has already been created with the default values. The third
argument to setDefaultValues()—that is, false —indicates that you only want the
defaults set in the preferences XML file if it hasn’t been done before. If you choose true
instead, you’ll always reset the preferences XML file with default values. Android
remembers this information through the exist ence of this new XML file. If the user has
selected new preference values, and you choose false for the third argument, the user
preferences won’t be overwritt en the next time this code runs. Notice that now we don’t
need to provide a default value in the getString() method call, because we should
always get a value from the preferences XML file.
If you need a reference to the preferences from inside of an activity that extends
PreferenceActivity, you could do it this way:
SharedPreferences prefs = getPreferenceManager().getDefaultSharedPreferences(this);
We showed you how to use the ListPreference view; now, let’s examine some other UI
elements within the Android preferences framework. Namely, let’s talk about the
CheckBoxPreference view and the EditTextPreference view.
Understanding CheckBoxPreference
You saw that the ListPreference preference displays a list as its UI element. Similarly,
the CheckBoxPreference preference displays a check-box widget as its UI element.
To extend the flight-search exam ple application, suppose you want to let the user set
the list of columns to see in the result set. This preference di splays the available
columns and allows the user to choose the desired columns by marking the
corresponding check boxes. The user interface for this example is shown in Figure 13–3,
and the preferences XML file is shown in Listing 13–5.
Figure 13–3. The user interface for the check-box preference
Listing 13–5. Using CheckBoxPreference
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/chkbox.xml --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="flight_columns_pref" android:title="Flight Search Preferences" android:summary="Set Columns for Search Results"> <CheckBoxPreference android:key="show_airline_column_pref" android:title="Airline" android:summary="Show Airline column" /> <CheckBoxPreference android:key="show_departure_column_pref" android:title="Departure" android:summary="Show Departure column" /> <CheckBoxPreference android:key="show_arrival_column_pref" android:title="Arrival" android:summary="Show Arrival column" /> <CheckBoxPreference android:key="show_total_travel_time_column_pref" android:title="Total Travel Time" android:summary="Show Total Travel Time column" /> <CheckBoxPreference android:key="show_price_column_pref" android:title="Price" android:summary="Show Price column" /> </PreferenceScreen>
public class CheckBoxPreferenceActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.chkbox); } }
Listing 13–5 shows the preferences XML file, chkbox.xml, and a simple activity class that
loads it using addPreferencesFromResource(). As you can see, the UI has five check
boxes, each of which is represented by a CheckBoxPreference node in the preferences
XML file. Each of the check boxes also has a key , which—as you would expect—is
ultimately used to persist the state of the UI element when it comes time to save the
selected preference. With CheckBoxPreference, the state of the preference is saved
when the user sets the state. In other words, when the user checks or unchecks the
preference control, its state is saved. Listi ng 13–6 shows the preference data store for
this example.
Listing 13–6. The Preferences Data Store for the Check Box Preference
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <boolean name="show_total_travel_time_column_pref" value="false" /> <boolean name="show_price_column_pref" value="true" /> <boolean name="show_arrival_column_pref" value="false" /> <boolean name="show_airline_column_pref" value="true" /> <boolean name="show_departure_column_pref" value="false" /> </map>
Again, you can see that each preference is saved through its key attribute. The data type
of the CheckBoxPreference is a boolean , which contains a value of either true or false :
true to indicate the pref erence is selected, and false to indicate other wise. To read the
value of one of the check-box preferences, you would get access to the shared
preferences and call the getBoolean() method, passing the key of the preference:
boolean option = prefs.getBoolean("show_price_column_pref", false);
One other useful feature of CheckBoxPreference is that you can set different summary
text depending on whether it’s checked. The XML attributes are summaryOn and
summaryOff. Now, let’s have a look at the EditTextPreference.
Understanding EditTextPreference
The preferences framework also provides a free-form text preference called
EditTextPreference. This preference allows you to capture raw text rather than ask the
user to make a selection. To demonstrate th is, let’s assume you hav e an application that
generates Java code for the user. One of the preference settings of this application
might be the default package name to use for the generated classes. Here, you want to
display a text field to the user for setting the package name for t he generated classes.
Figure 13–4 shows the UI, and Li sting 13–7 shows the XML.
Figure 13–4. Using the EditTextPreference
Listing 13–7. An Example of an EditTextPreference
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/packagepref.xml --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="package_name_screen" android:title="Package Name" android:summary="Set package name"> <EditTextPreference android:key="package_name_preference" android:title="Set Package Name" android:summary="Set the package name for generated code" android:dialogTitle="Package Name" /> </PreferenceScreen>
public class EditTextPreferenceActivity extends PreferenceActivity{ CHAPTER 13: Working with Preferences and Saving State 351 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.packagepref); } }
You can see that Li sting 13–6 defines PreferenceScreen with a single
EditTextPreference instance as a child. The generated UI for the listing features the
PreferenceScreen on the left and the EditTextPreference on the right (see Figure 13–4).
When Set Package Name is selected, the user is presented with a dialog to input the
package name. When the OK button is clicked, the preference is saved to the
preference store.
As with the other pref erences, you can obtain the EditTextPreference from your activity
class by using the preference’s key . Once you have the EditTextPreference, you can
manipulate the actual EditText by calling getEditText() —if, for example, you want to
apply validation, prep rocessing, or post-processing on t he value the user types in the
text field. To get the text of the EditTextPreference, just use the getText() method.
Understanding RingtonePreference and
MultiSelectListPreference
There is another preference called RingtonePreference, but we won’t cover that here. It
follows the same rules as the others and isn’t used much. And finally, a preference
called MultiSelectListPreference was introduced in Android 3.0. The concept is
somewhat similar to a ListPreference, but instead of only being able to select one item
in the list, the user can select several or none. Unfortunately, the implementation is
buggy as of this writing. For example, the values array d oes not seem to be used—only
the entries array. This means the XML preferences file contains the entry strings and not
the corresponding values as the ListPreference does. It is also a mystery how to set the
default values. For more information, see http://code.google.com/p/android/
issues/detail?id=15966.
Until this type of preference is fixed, you’re better off creating a set of
CheckBoxPreferences. Figure 13–3 shows what a MultiSelectListPreference looks like
(more or less).
Organizing Preferences
The preferences framework provides some su pport for you to organize your preferences
into categories. If you have a lot of prefer ences, for example, you can build a view that
shows high-level categories of preferences. Users coul d then drill down into each
category to view and manage preferences specific to that group.
Using PreferenceCategory
You can implement something like this in one of two ways. You can introduce nested
PreferenceScreen elements within the root PreferenceScreen, or you can use
PreferenceCategory elements to get a similar result. Figure 13–5 and Listing 13–8 show
how to implement the first technique, grouping preferences by using nested
PreferenceScreen elements.
The view on the left in Figure 13–5 displays options for two preference screens, one with
the title Meats and the other with the title Vegetables. Clicking a group takes you to the
preferences within that group. Listing 13–8 shows how to create nested screens.
Figure 13–5. Creating groups of preferences by nesting PreferenceScreen elements
Listing 13–8. Nesting PreferenceScreen Elements to Organize Preferences
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="using_categories_in_root_screen" android:title="Categories" android:summary="Using Preference Categories"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="meats_screen" android:title="Meats" android:summary="Preferences related to meats"> <CheckBoxPreference android:key="fish_selection_pref" android:title="Fish" android:summary="Fish is healthy" /> CHAPTER 13: Working with Preferences and Saving State 353 <CheckBoxPreference android:key="chicken_selection_pref" android:title="Chicken" android:summary="A common type of poultry" /> <CheckBoxPreference android:key="lamb_selection_pref" android:title="Lamb" android:summary="A young sheep" /> </PreferenceScreen> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="vegi_screen" android:title="Vegetables" android:summary="Preferences related to vegetables"> <CheckBoxPreference android:key="tomato_selection_pref" android:title="Tomato " android:summary="It's actually a fruit" /> <CheckBoxPreference android:key="potato_selection_pref" android:title="Potato" android:summary="My favorite vegetable" /> </PreferenceScreen> </PreferenceScreen>
You create the groups in Figure 13–5 by nesting PreferenceScreen elements within the
root PreferenceScreen. Organizing preferences this way is useful if you have a lot of
preferences and you’re concerned about having the users scroll to find the preference
they are looking for. If you don’t have a lot of preferences but still want to provide high-level categories for your preferences, you can use PreferenceCategory, which is the
second technique we menti oned. Figure 13–6 and Listing 13–9 show the details.
Figure 13–6. Using PreferenceCategory to organize preferences
Figure 13–6 shows the same groups we used in our previous example, but now organized
with preference categories. The only difference between the XML in Listing 13–9 and the
XML in Listing 13–8 is that you create a PreferenceCategory for the nested screens
rather than nest PreferenceScreen elements.
Listing 13–9. Creating Categories of Preferences
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="using_categories_in_root_screen" android:title="Categories" android:summary="Using Preference Categories"> <PreferenceCategory xmlns:android="http://schemas.android.com/apk/res/android" android:key="meats_category" android:title="Meats" android:summary="Preferences related to meats"> <CheckBoxPreference android:key="fish_selection_pref" android:title="Fish" android:summary="Fish is healthy" /> <CheckBoxPreference android:key="chicken_selection_pref" android:title="Chicken" android:summary="A common type of poultry" /> <CheckBoxPreference android:key="lamb_selection_pref" CHAPTER 13: Working with Preferences and Saving State 355 android:title="Lamb" android:summary="A young sheep" /> </PreferenceCategory> <PreferenceCategory xmlns:android="http://schemas.android.com/apk/res/android" android:key="vegi_category" android:title="Vegetables" android:summary="Preferences related to vegetables"> <CheckBoxPreference android:key="tomato_selection_pref" android:title="Tomato " android:summary="It's actually a fruit" /> <CheckBoxPreference android:key="potato_selection_pref" android:title="Potato" android:summary="My favorite vegetable" /> </PreferenceCategory> </PreferenceScreen>
Creating Child Preferences with Dependency
Another way to organize preferences is to use a preference dependency. This creates a
parent-child relationship between preferences. For example, we might have a preference
that turns on alerts; and if alerts are on, there might be severa l other alert-related
preferences to choose from. If the main alerts preference is off, the other preferences
are not relevant and should be disabled. Listing 13–10 shows the XML, and Figure 13–7
shows what it looks like.
Listing 13–10. Preference Dependency in XML
<PreferenceScreen> <PreferenceCategory android:title="Alerts"> <CheckBoxPreference android:key="alert_email" android:title="Send email?" /> <EditTextPreference android:key="alert_email_address" android:layout="?android:attr/preferenceLayoutChild" android:title="Email Address" android:dependency="alert_email" /> </PreferenceCategory> </PreferenceScreen>
Figure 13–7. Preference dependency
Preferences with Headers
With the introduction of Android 3.0, we got another way to organize preferences. You
see this on tablets under the main Settings app. Because tablet screen real estate offers
much more room than a smartphone does, it makes sense to display more preference
information at the same time. To accomplish this, we use preference headers. Take a
look at Figure 13–8 to see what we mean.
Figure 13–8. Main Settings page with preference headers
Notice that headers appear down the left side, like a vertical tab bar. As you click each item
on the left, the screen to the right displays the preferences for that item. In Figure 13–8,
Sound is chosen, and the sound preferences are displayed at right. The right side is a
PreferenceScreen object, and this setup uses fr agments. Obviously, we need to do
something different than what has been discussed so far in this chapter.
The big change from Android 3.0 was the addition of headers to PreferenceActivity.
This also means using a new callback within PreferenceActivity to do the headers
setup. Now, when you extend PreferenceActivity, you'll want to implement this
method:
public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preferences, target); }
The preferences.xml file contains some new tags that look like this:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.example.PrefActivity$Prefs1Fragment" android:icon="@drawable/ic_settings_sound" android:title="Sound" android:summary="Your sound preferences" /> ...
Each header tag points to a class that extends PreferenceFragment. In the example just
given, the XML specifies an i con, the title, and summary text (which acts like a subtitle).
Prefs1Fragment is an inner class of PreferenceActivity that could look something like
this:
public static class Prefs1Fragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.sound_preferences); } }
All this inner class needs to do is pull in the appropriate preferences XML file, as shown.
That preferences XML file contains the types of preference specifications we covered
earlier, such as ListPreference, CheckBoxPreference, PreferenceCategory, and so on.
What’s very nice is that Android takes care of doing the right thing when the screen
configuration changes and when the preferences are displa yed on a small screen.
Headers behave like old pref erences when the screen is t oo small to display both
headers and the preference screen to the right. That is, you only see the headers; and
when you click a header, you then see only the appropriate preference screen.
Manipulating Preferences Programmatically
It goes without saying that you might need to access the actual preference controls
programmatically. For example, what if you need to provide the entries and
entryValues for the ListPreference at runtime? You can de fine and access preference
controls similarly to the way you define and access controls in layout files and activities.
For example, to access the list preference defined in Listing 13–1, you would call the
findPreference() method of PreferenceActivity, passing the preference’s key (note
the similarity to findViewById()). You would next cast the control to ListPreference and
then go about manipulating the control. For example, if you want to set the entries of the
ListPreference view, call the setEntries() method, and so on. Listing 13–11 shows
what this might look like with a simple example of using code to set up the preference.
Of course, you could also create the entire PreferenceScreen starting with
PreferenceManager.createPreferenceScreen().
Listing 13–11. Setting ListPreference Values Programmatically
public class FlightPreferenceActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.flightoptions); ListPreference listpref = (ListPreference) findPreference( "selected_flight_sort_option"); listpref.setEntryValues(new String[] {"0","1","2"}); listpref.setEntries(new String[] {"Food", "Lounge", "Frequent Flier Program"}); } }
Saving State with Preferences
Preferences are great for allowing users to customize applications to their liking, but we
can use the Android preference framework for more than that. When your application
needs to keep track of some data between invocations of the application, preferences
are one way to accomplish the task. We’ve already talked about content providers for
maintaining data. We could use custom files on the SD card. We can also use shared
preference files and code.
The Activity class has a getPreferences(int mode) method. This, in reality, simply
calls getSharedPreferences() with the class name of the activity as the tag plus the
mode as passed in. The result is an activity- specific shared prefer ences file that you can
use to store data about this activity across invocations. A simple example of how you
could use this is shown in Listing 13–12.
Listing 13–12. Using Preferences to Save State for an Activity
final String INITIALIZED = "initialized"; SharedPreferences myPrefs = getPreferences(MODE_PRIVATE); boolean hasPreferences = myPrefs.getBoolean(INITIALIZED, false); if(hasPreferences) { Log.v("Preferences", "We've been called before"); // Read other values as desired from preferences file... someString = myPrefs.getString("someString", ""); } else { Log.v("Preferences", "First time ever being called"); // Set up initial values for what will end up // in the preferences file someString = "some default value"; } // Later when ready to write out values Editor editor = myPrefs.edit(); editor.putBoolean(INITIALIZED, true); editor.putString("someString", someString); // Write other values as desired editor.commit();
What this code does is acquire a reference to preferences for our activity class and
check for the existence of a boolean “preference” called initialized . We write
“preference” in double quotation marks because this value is not something the user is
going to see or set; it’s merely a value that we want to store in a shared preferences file
for use next time. If we get a value, the shar ed preferences file exists, so our application
must have been called before. We could then read other values out of the shared
preferences file. For example, someString could be an activity variable that should be
set from the last time this activity ran or set to the default value if this is the first time.
To write values to the shar ed preferences file, we mu st first get a preferences Editor.
We can then put values into preferences and commit those changes when we’re
finished. Note that, behind the scenes, Android is managing a SharedPreferences object
that is truly shared. Ideally, there is never more than one Editor active at a time. But it is
very important to call the commit() method so that the SharedPreferences object and
the shared preferences XML file get updated. In the example, we wr ite out the value of
someString to be used the next time this activity runs.
You can access, write, and commit values anytime to your preferences file. Possible
uses for this include writing out high scores for a game or recording when the
application was last r un. You can also use the getSharedPreferences() call with
different names to manage separate sets of preferences, all wi thin the same application
or even the same activity.
We’ve used MODE_PRIVATE for mode in our examples thus far. The other possible values
of mode are MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE. These modes are used
when creating the shared preferences XML file to set the file permissions accordingly.
Because the shared preferences files are stored within your application’s data directory
and therefore are not accessible to other applications, you only need to use
MODE_PRIVATE.
Using DialogPreference
So far, you’ve seen how to use the out-of-the-box capabilities of the preferences
framework, but what if you want to cr eate a custom preference? What if you want
something like the slider of the Bright ness preference under Sc reen Settings? This is
where DialogPreference comes in. DialogPreference is the parent class of
EditTextPreference and ListPreference. The behavior is a dialog that pops up, displays
choices to the user, and is closed with a button or via the Back button. But you can
extend DialogPreference to set up your own custom preference. Within your extended
class, you provide your own layout, y our own click handlers, and custom code in
onDialogClosed() to write the data for your pref erence to the shared preferences file.
- Working with Preferences and Saving State
- 5 working with state
- Saving and Restoring android State
- iPhone SDK: Saving User Preferences With Settings Bundle
- iOS extracts: Saving and Loading Game State
- Working with Fun and Interests
- Working with promise and generators
- canvas 状态的保存和恢复 Saving and restoring state
- Working with StyleSheets and Master page
- Working with TrueType and Raster Fonts
- Generating and working with GUIDs in .NET
- Working with BeforeProperties and AfterProperties on SPItemEventReceiver
- Working with iPhone files and folders
- OpenNI- working with depth and color maps
- Working with Fragments and ViewPager on Android
- Saving Android View state correctly
- Introduction to DataSets and working with XML files
- Chapter 7. Working with ASP.NET and VB .NET
- STL源码剖析---STL容器特征总结(含迭代器失效)
- POJ2689 Prime Distance
- c#学习笔记——7
- the requested operation has failed"解决方案
- 你应该知道的应急装备配置
- Working with Preferences and Saving State
- 代码设置按钮不同状态的颜色
- iOS: Jsonkit
- 基于Bootloader的可靠嵌入式软件远程更新机制
- Android中Context详解 ---- 你所不知道的Context
- 指针你可以别再困扰我了吗?
- android 所有的传感器
- 读《拖延心理学》:战胜拖延症的方法
- Oracle 10g read by other session 等待 说明