Friday 16 November 2012

Android Preferences Tutorial

Introduction

Recently I have added a preference screen to one of my Android apps and now is a good time to put all what I have learnt down on paper, more for future reference. I'll present a quick tutorial and then discuss more advanced topics and pitfalls. Note that I will not be discussing the new Preference Fragments API, for now my Nexus 7 is more than happy running the deprecated preference APIs.

[FREE][ANDROID] Crossword Solver

Creating the Preference XML

The preference screen is defined in an XML file, Android can take this XML file and create an activity (user interface) and deal with the data binding and storage. Very nice indeed.

In the res folder create a new folder called xml. The new xml folder is where we will place our preferences.xml file. Eclipse can create the file for us
  1. Right click on the xml folder
  2. Click on New on the pop-up menu
  3. Click on Android XML File
  4. The New Android XML File dialog will be displayed
  5. Select Preference in the Resource Type drop down list
  6. Enter file name, preferences.xml
  7. Select PreferenceScreen in the root element list
  8. Press Finish


Creating the Preference User Interface

Below is the code from my preference.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" 
    android:title="@string/pref_title">
    <PreferenceCategory 
        android:title="@string/pref_search_cat"
        android:key="search_category">
  <CheckBoxPreference 
      android:key="show_subanagrams_pref"
      android:summary="@string/pref_findsubanag_summary"
      android:title="@string/pref_findsubanag_title"
      android:defaultValue="True"
      />    
  <ListPreference 
      android:key="result_limit_pref"
      android:title="@string/pref_resultlimit_title"
      android:summary="@string/pref_resultlimit_summary"
      android:defaultValue="200"
      android:entries="@array/resultLimits"
      android:entryValues="@array/resultLimitsValues"
      />
    </PreferenceCategory>
</PreferenceScreen>

I have a CheckBoxPreference to toggle a boolean value and a ListPreference to select a string value. I've put the display strings in the strings.xml file in my values folder. You'll notice that the list preference references an array resource. You can create the array resources by creating a resource file (another Android XML file) in the values folder and call it arrays.xml. Below is my arrays.xml file:


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="resultLimits" >
        <item name="50">50 Results</item>
        <item name="100">100 Results</item>
        <item name="200">200 Results</item>
    </string-array>
    <string-array name="resultLimitsValues">
        <item name="50">50</item>
        <item name="100">100</item>
        <item name="200">200</item>
    </string-array>
</resources>

Next we need to create an activity class for the preference screen and  inflate the XML in preferences.xml. Create a new class under your src folder called SettingsActivity.java:


package com.mpdbailey.cleverdicandroid;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SettingsActivity extends PreferenceActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        // Note that none of the preferences are actually defined here.
        // They're all in the XML file res/xml/preferences.xml.
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

This class extends from PreferenceActivity instead of Activity and calls addPreferencesFromResource(R.xml.preferences) to create the user interface from the XML in preferences.xml.

Thats it! There is no need to worry how Android binds the data to the preference UI controls, its all done under the bonnet. Android also takes care of storing the data for you.


Using Preferences From Your App

In my MainActivity class, I have the following code that fires up the Preference Activity:

 @Override
 public boolean onOptionsItemSelected(MenuItem item)
 {
  Intent intent;
  switch (item.getItemId())
  {
  case (R.id.menu_settings):
   intent = new Intent(this, SettingsActivity.class);
   startActivity(intent);
   break;
  default:
   return false;
  }
  return true;
 }
 

I've put a menu item called Settings on my menu and the code above will start the preference activity when the user clicks on the settings menu button.

The code below is also from the MainActivity class and details with the interaction with the preference data.



 @Override
 public void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
  prefs.registerOnSharedPreferenceChangeListener(this);
  LoadPreferences(prefs);
 }

 public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
 {
  LoadPreferences(prefs);
 }

 private void LoadPreferences(SharedPreferences prefs)
 {
   this.presenter.SetFindSubAngrams(prefs.getBoolean("show_subanagrams_pref", true));
   int limit = Integer.parseInt(prefs.getString("result_limit_pref","200"));
   this.presenter.SetResultLimit(limit);
 }

 @Override
 protected void onDestroy()
 {
  super.onDestroy();
  PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
 }
 
 
 

I get the SharedPreferences object using PreferenceManager.getDefaultSharedPreferences(this), which allows the various activities of my app access to the preference data.

The Preference User Interface will fire SharedPreferenceChanged events which I listen to in my MainActivity class to keep the app up to date with the latest preferences. These events fire as soon as you change a preference, toggle a check box on the preference form and the event will fire.


 Note that when the app first starts that I call LoadPreferences() this is important as it sets up my app with the current preference information.

Application Life-Cycle Pitfalls

Be careful if you use anonymous inner classes to implement the OnSharedPreferenceChangeListener. You'll notice that in the code above I register  the MainActivity class in  OnCreate()  and unregister in OnDestroy(). This is fine if the MainActivity object is the listener. If you use anonymous classes then these can be collected by the garbage collector when the app is paused, this means that your app will then stop responding to SharedPreferenceChanged events. For more details see this StackOverflow Question about the issue.

[FREE][ANDROID][DIET] Weight Tracker App, 

Wrapping Up

The Android Preference Framework pretty much does all the work for you, its just a matter of hooking your code up to it. For an example project see the SipDemo in the Android SDK samples.




No comments:

Post a Comment