Question

I am writing a custom Android Preferences screen and it crashes b/c it is expecting a fragment when the header doesn't have a fragment -- I just want to have a header that is a title for a given set of the sections. Tried searching, to no avail, so sorry if this is a dup.

pref_headers.xml

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header android:id="@+id/headerStaffGeneral" android:title="@string/staff_general" /> <!-- This causes a crash as name == null -->
  <header
      android:id="@+id/headerSettings"
      android:fragment="com.example.ex.prefs.SettingsPreferencesFragment"
      android:title="@string/staff_manager_settings"
      android:icon="@drawable/ic_action_settings"
      />
</preference-headers>

If any other code needs to be included, I can include that, but the nature of my question is if I should replace the crashing HEADER tag with something else.

UPDATE:

LogCat Output

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.ex/com.example.ex.prefs.PrefsActivity}: java.lang.NullPointerException: name == null
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2295)
at android.app.ActivityThread.access$700(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1280)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:175)
at android.app.ActivityThread.main(ActivityThread.java:5279)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException: name == null
at java.lang.VMClassLoader.findLoadedClass(Native Method)
at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:491)
at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
at android.app.Fragment.instantiate(Fragment.java:582)
at android.preference.PreferenceActivity.switchToHeaderInner(PreferenceActivity.java:1245)
at android.preference.PreferenceActivity.switchToHeader(PreferenceActivity.java:1278)
at android.preference.PreferenceActivity.onCreate(PreferenceActivity.java:647)
at android.app.Activity.performCreate(Activity.java:5283)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1097)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2209)
... 11 more

PrefsActivity.java

package com.example.ex.prefs;

import java.util.ArrayList;
import java.util.List;

import android.preference.PreferenceActivity;
import android.widget.ListAdapter;

import com.example.ex.R;

public class PrefsActivity extends PreferenceActivity {

  private List<Header> mHeaders;

  protected void onResume() {
    super.onResume();

    setTitle(R.string.staff_manager_settings); 

    if (getListAdapter() instanceof PrefsHeaderAdapter)
      ((PrefsHeaderAdapter) getListAdapter()).resume();
  }

  protected void onPause() {
    super.onPause();
    if (getListAdapter() instanceof PrefsHeaderAdapter)
      ((PrefsHeaderAdapter) getListAdapter()).pause();
  }

  public void onBuildHeaders(List<Header> target) {
    // Called when the settings screen is up for the first time
    // we load the headers from our xml description

    loadHeadersFromResource(R.xml.pref_headers, target);

    mHeaders = target;
  }

  public void setListAdapter(ListAdapter adapter) {
    int i, count;

    if (mHeaders == null) {
      mHeaders = new ArrayList<Header>();
      // When the saved state provides the list of headers,
      // onBuildHeaders is not called
      // so we build it from the adapter given, then use our own adapter

      count = adapter.getCount();
      for (i = 0; i < count; ++i)
        mHeaders.add((Header) adapter.getItem(i));
    }

    super.setListAdapter(new PrefsHeaderAdapter(this, mHeaders));
  }

}

The error does not show if the first HEADER tag is removed from the pref_headers.xml file.

Was it helpful?

Solution 2

The XML for the preference headers was correct and did not need an android:id attribute (though, never hurts). I needed to add 2 things: setListAdapter and onGetInitialHeader.

Here is the final code for all 3 files:

prefs_headers.xml

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header android:title="@string/staff_general"/>
  <header
    android:id="@+id/headerSettings"
    android:fragment="com.example.ex.prefs.SettingsPreferencesFragment"
    android:icon="@drawable/ic_action_settings"
    android:title="@string/staff_manager_settings"/>
  <header
    android:id="@+id/headerHistory"
    android:fragment="com.example.ex.prefs.HistoryPreferencesFragment"
    android:icon="@drawable/ic_action_event"
    android:title="@string/staff_manager_history"/>
  <header android:title="@string/staff_special" />
  <header
    android:id="@+id/headerNetwork"
    android:fragment="com.example.ex.prefs.NetworkPreferencesFragment"
    android:icon="@drawable/ic_action_cloud"
    android:title="@string/staff_manager_network"/>
  <header
    android:id="@+id/headerPrinter"
    android:fragment="com.example.ex.prefs.PrinterPreferencesFragment"
    android:icon="@drawable/ic_action_star"
    android:title="@string/staff_manager_star_printer"/>
</preference-headers>

PrefsActivity.java

package com.example.ex.prefs;

import java.util.List;

import android.preference.PreferenceActivity;
import android.preference.PreferenceActivity.Header;
import android.util.Log;
import android.widget.ListAdapter;

import com.example.ex.R;

public class PrefsActivity extends PreferenceActivity {
  private static List<Header> _headers;

  @Override
  public void setListAdapter(ListAdapter adapter) {
      if (adapter == null) {
          super.setListAdapter(null);
      } else {
          super.setListAdapter(new PrefsHeaderAdapter(this, _headers));
      }
  }

  @Override
  public void onBuildHeaders(List<Header> target) {
    _headers = target;
    loadHeadersFromResource(R.xml.pref_headers, target);
  }
  @Override
  public Header onGetInitialHeader() {
    super.onResume();
    if (PrefsActivity._headers != null) {
      for (int i = 0; i < PrefsActivity._headers.size(); i++) {
        Header h = PrefsActivity._headers.get(i);
        if (PrefsHeaderAdapter.getHeaderType(h) != PrefsHeaderAdapter.HEADER_TYPE_CATEGORY) {
            return h;
        }
      }
    }
    return null;
  }
}

PrefsHeaderAdapter.java package com.example.ex.prefs;

import java.util.List;

import android.content.Context;
import android.preference.PreferenceActivity.Header;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.ex.R;

public class PrefsHeaderAdapter extends ArrayAdapter<Header> {

  static final int           HEADER_TYPE_CATEGORY = 0;
  static final int           HEADER_TYPE_NORMAL   = 1;

  private LayoutInflater     mInflater;

  public PrefsHeaderAdapter(Context context, List<Header> objects) {
    super(context, 0, objects);
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }

  public View getView(int position, View convertView, ViewGroup parent) {
    Header header = getItem(position);
    int headerType = getHeaderType(header);
    View view = null;

    switch (headerType) {
      case HEADER_TYPE_CATEGORY:
        view = mInflater.inflate(android.R.layout.preference_category, parent, false);
        ((TextView) view.findViewById(android.R.id.title)).setText(header.getTitle(getContext().getResources()));
        break;
      case HEADER_TYPE_NORMAL:
        view = mInflater.inflate(R.layout.preference_header_item, parent, false);
        ((ImageView) view.findViewById(android.R.id.icon)).setImageResource(header.iconRes);
        ((TextView) view.findViewById(android.R.id.title)).setText(header.getTitle(getContext().getResources()));
        ((TextView) view.findViewById(android.R.id.summary)).setText(header.getSummary(getContext().getResources()));
        break;
    }

    return view;
  }

  @Override
  public boolean isEnabled(int position) {
    return getItemViewType(position) != HEADER_TYPE_CATEGORY;
  }
  @Override
  public boolean areAllItemsEnabled() {
    return false;
  }
  @Override
  public int getViewTypeCount() { 
    return 2; 
  }
  @Override
  public boolean hasStableIds() { 
    return true; 
  }

  public static int getHeaderType(Header header) {
    if ((header.fragment == null) && (header.intent == null)) {
      return HEADER_TYPE_CATEGORY;
    } else {
      return HEADER_TYPE_NORMAL;
    }
  }
}

The key was overriding the onGetInitialHeader to ensure that it wasn't the first header b/c according to the info at Android - Headers categories in PreferenceActivity with PreferenceFragment , Android loads the first by default which was my category header and assumes it is a fragment, but since it isn't, it crashed. This ListAdapter is used to manage the characteristics of the list and determine header types -- necessary to reliably determine the header type w/o hard-coding the answer.

I hope this helps someone else as I've spent days on this!

OTHER TIPS

What you have should work, though perhaps you need an android:id value. If you look at the preference headers for the Settings app, you see stuff like:

<preference-headers
        xmlns:android="http://schemas.android.com/apk/res/android">


    <!-- WIRELESS and NETWORKS -->
    <header android:id="@+id/wireless_section"
        android:title="@string/header_category_wireless_networks" />

    <!-- Wifi -->
    <header
        android:id="@+id/wifi_settings"
        android:fragment="com.android.settings.wifi.WifiSettings"
        android:title="@string/wifi_settings_title"
        android:icon="@drawable/ic_settings_wireless" />

...

So, they appear to be using a <header> without android:fragment for section headers in the headings list.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top