Reputation:
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.
Upvotes: 1
Views: 3718
Reputation:
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!
Upvotes: 1
Reputation: 1006744
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.
Upvotes: 1