PreferenceActivity , PreferenceFragment and headers (Part 2)

In previous post we described how to use Preference Activity and Preference Fragment.

Android 3.0 (API Level 11) introduced Preference Headers through which we can show the user the list of headers, and upon clicking on a header, show the fragment.
A great benefit to using this design is that PreferenceActivity automatically presents the two-pane layout when running on large screens.

This behavior is particularly useful if you have more than 10 preferences, otherwise I suggest the direction of displaying headers all of the time.

Scenario 3: Preference Headers 
  1. Step 1: Define the preferences_headers_scenario3.xml
    This file lists each settings group and declares which fragment contains the corresponding list of settings.The file is placed in the res\xml folder.
    <preference-headers
        xmlns:android="http://schemas.android.com/apk/res/android">
    
      <header
     android:fragment="it.gmariotti.android.examples.preference.UpdatePreferenceFragment"
           android:title="@string/settings_update"
           android:summary="@string/settings_updateSummary" >
      </header>
      <header
     android:fragment="it.gmariotti.android.examples.preference.DisplayPreferenceFragment"
           android:title="@string/settings_display"
           android:summary="@string/settings_displaySummary">
      </header>
     <header
     android:fragment="it.gmariotti.android.examples.preference.NotifyPreferenceFragment"
           android:title="@string/settings_notify">
      </header>
     
    </preference-headers>
    
    With the android:fragment attribute, each header declares an instance of PreferenceFragment that should open when the user selects the header.

  2. Step 2: Create the Preference Activity
    To display the preference headers, you must implement the onBuildHeaders() callback method and call loadHeadersFromResource(). For example:
    public class PreferencesActivityScenario3 extends PreferenceActivity {
    
        /**
         * Populate the activity with the top-level headers.
         */
        @Override
        public void onBuildHeaders(List<Header> target) {
         loadHeadersFromResource(R.xml.preference_headers_scenario3, target);
        }
    }
    
    This Activity will only show the list of headers.
    Like all Activities, the Preference Activity must be included in the application manifest: <activity android:name=".PreferencesActivityScenario3"></activity>
  3. Step 3: Create the Preference Fragment
    public class UpdatePreferenceFragment extends PreferenceFragment {
     
     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         addPreferencesFromResource(R.xml.preference_update);
     }
    }
    
    To display the application settings hosted in this Activity, open it by calling startActivity or startActivityForResult:
              Intent i = new Intent(this, PreferencesActivityScenario3.class);
              startActivityForResult(i, SHOW_PREFERENCES);
    
    When using preference headers, your subclass of PreferenceActivity doesn't need to implement the onCreate() method, because the only required task for the activity is to load the headers.

  4. And that’s all! Just execute the app in Android emulator or real device and see following output.
    Single-pane layout with headers:

    Dual-pane layout with headers:

If we investigate the source code to PreferenceActivity , we will see that the logic that drives the single-pane vs. dual-pane UI decision boils down to:
    /**
     * Called to determine if the activity should run in multi-pane mode.
     * The default implementation returns true if the screen is large
     * enough.
     */
    public boolean onIsMultiPane() {
        boolean preferMultiPane = getResources().getBoolean(
                com.android.internal.R.bool.preferences_prefer_dual_pane);
        return preferMultiPane;
    }
The Resource com.android.internal.R.bool.preferences_prefer_dual_pane has different definitions based upon screen size.
At present, it will be true for -sw720dp devices, false otherwise.
For your curiosity in Nexus7 is false.

If you want to use ActionBarSherlock library is very simple to modify our code.

public class PreferencesActivityABSScenario3 extends SherlockPreferenceActivity {
 
    /**
     * Populate the activity with the top-level headers.
     */
    @Override
    public void onBuildHeaders(List<Header> target) {
     loadHeadersFromResource(R.xml.preference_headers_scenario3, target);
    }
}

In this example we use a Fragment for each entry in preference headers. We can reuse the same subclass of PreferenceFragment for each group and use an extra argument to specify which preferences XML file the fragment should load. We can see code here:
  1. Step 1: Define the preferences_headers2_scenario3.xml
    <preference-headers
        xmlns:android="http://schemas.android.com/apk/res/android">
    
      <header
     android:fragment="it.gmariotti.android.examples.preference.GenericPreferenceFragment"
           android:title="@string/settings_update"
           android:summary="@string/settings_updateSummary" >
        <extra android:name="settings" android:value="prefs_update" />
      </header>
      <header
     android:fragment="it.gmariotti.android.examples.preference.GenericPreferenceFragment"
           android:title="@string/settings_display"
           android:summary="@string/settings_displaySummary">
     <extra android:name="settings" android:value="prefs_display" />
      </header>
     <header
     android:fragment="it.gmariotti.android.examples.preference.GenericPreferenceFragment"
           android:title="@string/settings_notify">
            <extra android:name="settings" android:value="prefs_notify" />
      </header> 
    </preference-headers>
    
    In this case we use a GenericPreferenceFragment for each header, and we use The extras element to pass key-value pairs to the fragment in a Bundle.
  2. Step 2: Create the Preference Activity
    We can use the same Activity see above.
  3. Step 2: Create the GenericPreferenceFragment
    public class GenericePreferenceFragment extends PreferenceFragment {
     
        @Override
        public void onCreate(Bundle savedInstanceState) {
      
            int preferenceFile_toLoad=-1;
          String settings = getArguments().getString("settings");
          if (Constants.SETTING_UPDATE.equals(settings)) {
              // Load the preferences from an XML resource
              preferenceFile_toLoad= R.xml.preference_update;
          }else if (Constants.SETTING_DISPLAY.equals(settings)) {
              // Load the preferences from an XML resource
              preferenceFile_toLoad=R.xml.preference_display;
          }else if (Constants.SETTING_NOTIFY.equals(settings)) {
              // Load the preferences from an XML resource
              preferenceFile_toLoad=R.xml.preference_notify;
       }
      
          addPreferencesFromResource(preferenceFile_toLoad);
     }
    }
    
Wrapping up, if you want to display all prefereces in a screen ,preferred way is to show a single PreferenceFragment as the main content of any activity.(as we described in Scenario2). If you want a built-in dual-panel mode, we can use new Preference Headers.
In the next scenario we try to use Preference Header with all devices, not only with android 3.0 or higher.

You can find the next post here

You can get code from GitHub:

Comments

  1. Thank you. Your tutorial helped me out a great deal.
    Could you do one on AccountManager in the future?

    ReplyDelete

Post a Comment

Popular posts from this blog

AntiPattern: freezing a UI with Broadcast Receiver

NotificationListenerService and kitkat

How to centralize the support libraries dependencies in gradle