Chapter    19

Using Preferences

In the previous chapters you learned how to store, modify, and retrieve information from the Android file system and using SQLite databases. Android has multiple ways of storing information that suit many different purposes and goals. One of the simplest pieces of information you may want to store on behalf of your users is a simple preference or option setting. For instance, you may want to store a preference for date format, or a nickname in a chat program.

Using a dedicated file or database for storing very small preference data is overkill, so Android provides the preferences system as a way of storing and using simple key/value items that persist across repeated activity lifecycles. Although the system and technique are called preferences, you can, in fact, store anything you like, so long as it can be mapped to simple key/value pairs. The main limitation, as you will explore in the examples that follow, is that the key values must be strings, and the values must be in one of the primitive Java data types such as int, Boolean, and so on.

Everything Old Is New Again

Before we delve into the details of coding and controlling preferences, here’s a little history lesson to help you understand how preferences have changed over Android’s lifetime.

With the original release of Android, preferences were controlled by the PreferenceActivity subclass, which had the job of loading preferences from resource XML files. This process was very straightforward, and simple. As a developer, you’d use PreferenceActivity to directly determine, based on individual preferences, what behavior your application should perform.

Things changed to a degree when Google released Android 3.0 (Honeycomb). Although the PreferenceActivity subclass (and its fragment parallels, PreferenceFragment and others) still has the job of controlling preferences, it does so by loading preference headers from your resource files.

As we explore both new and old approaches, you’ll see how things differ. The reason you as a developer still need to be familiar with both approaches is two-fold. First and foremost, the new PreferenceActivity approach is not included in the Android support library for older versions, meaning there is no system-provided way to mimic the new behavior in an older Android version. The second reason is related to the first—old versions of Android, particularly Android 2.2 and 2.3, still abound, and you need to make some design choices about whether you want to target those old versions. Let’s explore both new and old preference approaches, and you can then decide what you should use for your application.

Starting with Preferences

Preferences can be conceptually grouped into three “tiers” or “scopes” that target different levels of sharing and availability.

The lowest level of preferences work at the level of an activity and are accessed via the getPreferences() method. getPreferences() uses the activity class name to associate the preference with the activity (this process is transparent to you—more on this in a moment). A security visibility parameter is also passed to getPreferences(), and normally you would choose the MODE_PRIVATE constant to mark the preference as private so that no other application can access it.

The next highest level of preferences targets your entire application or application-level context. Preferences at this level are controlled with the getSharedPreferences() method, which takes a similar privacy marker to getPreferences() and an explicit preference set name. This means you can create and use multiple preference sets at the application level. If you return to getPreferences() at the activity level for the moment, you should know that it actually calls getSharedPreferences() and uses the activity class name as the preference set value.

The last scope or tier of preferences falls under the PreferenceManager object and its governance of shared preferences across the entire Android device and its preference framework. Interaction here is normally via the getDefaultSharedPreferences() method and its variants. Calls to getDefaultSharedPreferences() take a context, such as your activity.

Whichever level you work at, the associated method call returns a SharedPreferences object representing the complete map of relevant key/value pairs. A SharedPreferences object comes equipped with getter helper methods such as getInt, getString, and getBoolean, each of which takes a String as the key to look for among the preferences. A second “defValue” parameter is also provided, and it represents the default to return if the key is not found (by implication, that preference has not yet been set).

In practice it is almost always worth working with your preferences at the highest level using getDefaultSharedPreferences(). This has two benefits. First, you get a fully-formed SharedPreferences object that works seamlessly with a PreferenceActivity. Second, as a developer, you save yourself from the ongoing mental gymnastics of having to remember the various tiers of preferences and where you might have stashed away a given preference key/value pair.

Recording Preferences

Once you have your SharedPreferences object in hand, you can modify the value or values you desire by calling the edit() method to invoke the editor for your preferences. This provides you with a SharedPreferences.Editor object, which has setter methods that match the getters mentioned earlier, such as setString(), setBoolean(), and so forth.

A number of additional methods are crucial to editing your preferences:

  • commit() actually saves the changes you have made via setter calls. This returns a Boolean indicating success or failure.
  • apply() is a faster version of commit(), effectively doing a “fire and forget” attempt to persist your changes. There is no return value.
  • remove() is used to purge a single preference key and its value.
  • clear() deletes all of your preference keys and values. Use with caution!

The main behavior to note is that you must code your application to use commit() or apply() after you use any combination of setter calls in order to actually save your changes to preferences. Without such a call, your efforts are lost when the SharedPreferences.Editor object loses scope.

On the flip side of needing to commit, once you do call commit() or apply(), preference changes are instantly visible. This means, for instance, that a preference set in one fragment of a visible activity is instantly available in another fragment within the same activity.

Working with Preference-Specific Activities and Fragments

The preferred way to incorporate preferences into your application is through the use of PreferenceActivity and PreferenceFragment contexts. There are lots of good reasons to use this approach that become apparent in the example we explore shortly. However, there is a non-code reason to use the activity and fragment approach provided by Android: consistency.

It is possible to design and build your own system of managing preferences. You might think you have a good approach that uses some kind of local storage, a database, or even a remote web service. But if you think about this from the user’s perspective, when they work with a device, they work with lots of applications, not just yours, and if each does preferences in a distinct and different way, the user experience becomes confusing, disjointed, and quite likely, a mess.

If you use the PreferenceActivity approach, along with PreferenceFragment and the related preference header and preferences XML resource files, you provide your users with a consistent and very slick preference experience. As a reminder, the preference system was revamped with Android 3.0, so you will find many old references online and in books to the previous technique. I will talk about the old approach briefly toward the end of the chapter so that those of you developing for older devices are covered. For now, let’s explore the contemporary approach in the sample application, FragmentPreferencesExample, which you can find in ch19/FragmentPreferencesExample.

Seeing the Big Picture with Preferences

I have designed the sample application, FragmentPreferencesExample, to show the many options you have with preferences in action, as well as to let you visualize the behind-the-scenes preference values as you interact with the a PreferenceActivity and its PreferenceFragments.

Figure 19-1 shows the initial view of the launcher activity.

9781430246862_Fig19-01.jpg

Figure 19-1. The FragmentPreferencesExample application, showing the menu in the action bar

We will discuss the use of getDefaultSharedPreferences, the SharedPreferences object, and using the getters to populate what you see in Figure 19-1 shortly. Of immediate interest is the simple menu that has been promoted to the action bar, offering you the option to change preferences.

Invoking the Change Preferences menu item results in the activity shown in Figure 19-2, which reveals preferences in all their glory.

9781430246862_Fig19-02.jpg

Figure 19-2. Preference headers and the preference screen in fragments

The layout and detail shown in Figure 19-2 demonstrate PreferenceFragments in all their glory and the controlling preference headers that define and manage them. Let’s start our exploration of the code from the vantage of the preference headers.

Using Preference and Preference Header Resources

In Listing 19-1, I took the layout and entries for the left-side preference headings from configuration data in the preference_headers.xml file.

Dissecting this preference header resource file is straightforward and should illuminate how control over all of the ultimate low-level preferences is performed. Within the <preference-headers> element, you can define one or more <header> elements that act to visually group individual collections of preferences. You can see that in this case, I have two <header> elements, but I could have one, five, or dozens.

This preference header resource file is loaded from a PreferenceActivity context and controls all of the preference headings, options, display, and so forth you see in Figure 19-2. Each header has a handful of attributes, including the title used for display text and a summary to further explain that header’s purpose. A child <extra> element is the secret that links a given header to the subordinate preferences it controls through a trick I introduce later in the chapter.

In the example application, the two <header> elements each include an <extra> element, named preference_cluster_1 and preference_cluster_2, respectively. These map to resource XML files preference_cluster_1.xml and preference_cluster_2.xml. You can name these files anything you like, so long as you ensure matching android:value attributes within the <extra> child element. These are used to create the PreferenceFragment subclass that will contain the respective clusters of preferences.

Note  For Android Studio users, to use the naming and referencing approach from the FragmentPreferencesExample application, you need to create a folder named xml within the res folder of your project to hold your preference-related XML files. Eclipse users with the traditional ADT should already see this folder in their project hierarchy.

You might have noticed that I have strenuously avoided using the term category when describing preference headers and preference resource files. I have used the terms cluster or group, because there is no logical restriction on what preferences can be placed together in a preference resource file, nor are there any coding or logic implications from grouping things together. The word category can be misleading when used with preferences, as developers start to think that perhaps all checkbox preferences need to be placed together, or anything that affects program logic, such as font characteristics, must be placed together. No such requirement exists—you are free to group preferences in any way you like, just as in the example application.

Returning to the two clusters of preferences defined in preference_headers.xml, Listing 19-2 shows the preference_cluster_1.xml file.

Its partner file, preference_cluster_2.xml, is shown in Listing 19-3.

Each file includes the root <PreferenceScreen> element, portraying what one onscreen display of preferences will include. Each child element is a preference variant of some sort, based on the standard widgets with which you are already familiar, such as Checkbox, EditText, and so on. When the PreferenceFragment subclass is instantiated, it takes care of inflating the preference UI items into widgets on the screen. You do not need to define your own layouts in general—the only exception is where you wish to use a custom layout for list elements in a ListView rendering for ListPreference. If you are happy with the defaults, you will see a ListPreference preference rendered with the contents of the array resource named in the android:entries attribute. These act as the key to the equivalent positional entry in the android:entryValues array. That sounds a little clunky, but it works.

For instance, if you look in the arrays.xml resource from the example project, you will see an array named colors like this:

<string-array name="colors">
    <item>Red</item>
    <item>Orange</item>
    <item>Yellow</item>
    <item>Green</item>
    <item>Blue</item>
    <item>Indigo</item>
    <item>Violet</item>
</string-array>

Following the colors array is the color-code array, which provides the possible parameter values associated with each display key:

<string-array name="color_codes">
    <item>#FF0000</item>
    <item>#FFA500</item>
    <item>#FFFF00</item>
    <item>#00FF00</item>
    <item>#0000FF</item>
    <item>#4B0082</item>
    <item>#EE82EE</item>
</string-array>

With the default ListView row layout, if you choose the Selection Dialog preference item in the first cluster of preferences, you will see the ListView dialog rendered in Figure 19-3.

9781430246862_Fig19-03.jpg

Figure 19-3. A preference that selects from an array presented in ListView fashion

The main attribute to note about all the preference types is android:key, which is the reference half of the key/value pair that constitutes a preference. Note that you do not strictly need to use reference files to break up the preference headers and resource XML files in the fashion shown. You can include bare key/value preference items in multiple <extra> elements within the <header> if you prefer. But I think you’ll agree the separation makes sense, and it is much more readable when your collection of preferences grows to moderate or large in size.

Because the Android preference framework and the preference header and individual preference screen files do much (but not all!) of the background work to bring everything together, the ChangePreferences PreferenceActivity just needs to call the loadHeadersFromResource() method in your implementation of the override for onBuildHeaders, as you can see in Listing 19-4.

The second override within ChangePreferences is the isValidFragment method. This method was introduced in Android 4.1 in response to a security vulnerability that allowed arbitrary fragments to be injected at runtime when a PreferenceActivity loaded a PreferenceFragment. You need to implement logic to ensure the fully qualified name of the fragment being loaded is the one you intended when the fragment is invoked. Do not fall into the trap of simply returning true in the isValidFragment method. Although this is syntactically correct, it leaves the original vulnerability available for exploit.

Filling in the Blanks with PreferenceFragment

You will see shortly that when we examine the launcher activity for the FragmentPreferencesExample application, we simply start the ChangePreferences activity when the user selects the Change Preferences menu item, by dint of directly starting the ChangePreferences activity with a call to startActivity(). Because it is a PreferenceActivity, it will invoke the onBuildHeaders() callback and elegantly map out all of the preference headers you decided to declare in the related XML resource. No fuss, no extra coding required. For each header, the related preference screen elements will be parsed—whether from a nominated resource file or from direct key/value entries—and then something a little jarring occurs. The Android preference framework then expects to find the custom subclass you have coded to process the items from the resource file and call addPreferenceFromResource(). You read that correctly. Android does a great job helping with the preference headers, and then it leaves you in the lurch with the individual preference screens.

You have a few choices in this situation, depending on the volume of preferences you plan for your application and your appetite for needless repetitive coding. You can

  • Customize a PreferenceFragment subclass for each preference screen, calling addPreferenceFromResource() and any optional logic you might imagine.
  • Design a single PreferenceFragment subclass that can navigate all of your preferences, for instance, via their headers using an agreed format for <extra> elements to enable them to be identified, and the associated <preference-screen> elements to be handled by the same subclass for fragment handling.

I know it seems crazy that you need to go to this additional level of effort when you would expect the Android preference framework to take care of this. Sadly, even after four major releases of Android since the advent of the new approach to preferences, this is still a work in progress.

Listing 19-5 shows an implementation of the PreferenceFragment subclass for the example application.

You can find many alternative examples and approaches to the same write-once-use-everywhere approach to the PreferenceFragment subclassing issue. As addPreferencesFromResource() takes a resource identifier for the preferences to inflate in the fragment. This implementation tries to find the related <header> by expecting the <extras> element to include an android:name attribute with the value "fragprefresource" and uses the getIdentifier() trick to go from knowing a textual name of the related android:value, which is the preference file that Android will have automatically loaded, to finding its related resource ID at runtime. This is a reflection-based technique, which also relies on two other parameters: first, a resource type, which for our preference files is xml, and second, the package to hold the resulting resource, which is almost always your own package.

Tip  Using reflection is a slow process in any language and system, and particularly in the case of Android with limited processing power and system resources. Use it sparingly, and never in frequently called code, loops, and so on.

Bringing the Preference Puzzle Pieces Together

Now that you understand how the internals of preferences are managed, and the pieces to fill in the blanks that Android has left, you can review the interesting parts of the example FragmentPreferencesExample application. Most of the code in Listing 19-6 is simple onscreen widget entry setup, so you can see the preference values, even when they are not interacting with the PreferenceActivity and fragments.

The two areas of code worth noting are the onResume() override and the onOptionsItemSelected() override. In the case of onResume(), whether the application is starting for the first time, as shown in Figure 19-1, or resuming from running in the background or being paused, I used a SharedPreferences object and the getter methods described at the beginning of the chapter to update the onscreen widget values. If you do this as well, you can always see the details of your preference settings. You may never need to do this in a real application, but it is useful for learning and debugging. Figure 19-4 shows the activity once I have toyed with the preferences to some degree.

9781430246862_Fig19-04.jpg

Figure 19-4. The FragmentPreferencesExample application after various preferences are set

In onOptionsItemSelected(), I test which menu item was chosen, and if it is the change_preferences resource identified in my menu layout, I explicitly launch my ChangePreferences() PreferenceActivity and the fun begins.

Other Preference Considerations

The Android preference framework continues to evolve; it received minor tweaks and changes in all of the releases up to Android 6.0 (Marshmallow). There are still areas where it pays to know the boundaries of what Android does and does not offer.

Customizing Preferences

There are two fundamental approaches to extending the preference framework if you find you are limited by the many options already offered in PreferenceActivity, PreferenceFragment, and the headers and screens of the preferences that populate them.

  • Extend the DialogPreference class to create a custom preference dialog. For instance, you can create a dialog with multiple fields, performing calculations, and so forth, which then ultimately sets a preference value.
  • Extend a <header> to include an <intent> child element, and include the attribute android:action="your-fully-package-qualified-intent-here". When it is chosen, the intent is fired, and then whichever suitable activity has the relevant intent-filter set is selected to run.

Both of these approaches are a left as an exercise for you to experiment with.

Nesting Preferences and Display Quirks

If you develop applications with many preferences, it can be tempting to think about nesting one set of preferences inside another, and so on. Be aware that the modern themes, such as Holo and the various material-design inspired options shipped with Android versions 4.0, 5.0, and 6.0, do not provide nested preference screen thematics, so your nested PreferenceScreen items will appear unskinned and look very different from any other theme or design language you might have chosen.

Using the Old-School Approach to Preferences

Now that you have mastered the contemporary Android preference framework, let’s take a quick look at the old approach to preferences so that you are familiar with it should you ever need to target pre-Android 3.0 with your applications, but also so you can spot outdated advice when you inevitably go searching the Internet for tweaks and tricks for preferences.

Losing Headers and Fragments

The main conceptual differences in the original Android preferences framework were the single “level” of preference resources, and the lack of fragments in general and the PreferenceFragment in particular. Remember that fragments themselves were only introduced in Android 3.0 as part of the scramble to address the then-booming tablet market.

The main help you receive is via the PreferenceActivity class, which still bootstraps most of the things you need to manage preferences; this includes offering shared access to the system’s SharedPreferences object in a very smooth way.

In other respects, the main launcher activity can remain the same, and indeed, whatever activities you would otherwise code for a given application are not affected when you change to using the old approach to preferences.

Adapting PreferenceActivty for Older Behavior

You can change the ChangePreferences class to deal with multiple preference resource files by “stacking” calls to addPreferencesFromResource() in the standard onCreate() override, as shown in Listing 19-7.

As you can see in Listing 19-7, you can call addPreferencesFromResource() to stack more and more preference UI widgets into the PreferenceActivity. This might become a bit silly if you get carried away, so you can either confine your preferences to a small collection, or use some switch logic in a menu callback to selectively load a few of the preference resources.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset