Android - Category headers in PreferenceActivity with PreferenceFragment

I would like to display a preferences screen similar to that in the Android settings app: using headers, PreferenceActivity, PreferenceFragment categories and headers.

I do not get this result on the tablet:

enter image description here

And this is on the smartphone:

enter image description here

It works if I just use the main headers, but if I try to add categories, it works on the smartphone and crashes to the tablet, where I get the exception "java.lang.NullPointerException: name == null"

FATAL EXCEPTION: main java.lang.RuntimeException: Unable to start activity ComponentInfo{fr.ifremer.testandroid/fr.ifremer.testandroid.models.preferences.MainPreferenceActivity}: java.lang.NullPointerException: name == null at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2110) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2135) at android.app.ActivityThread.access$700(ActivityThread.java:140) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1237) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4921) 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:1038) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805) 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:574) at android.preference.PreferenceActivity.switchToHeaderInner(PreferenceActivity.java:1222) at android.preference.PreferenceActivity.switchToHeader(PreferenceActivity.java:1255) at android.preference.PreferenceActivity.onCreate(PreferenceActivity.java:630) at fr.ifremer.testandroid.models.preferences.MainPreferenceActivity.onCreate(MainPreferenceActivity.java:19) at android.app.Activity.performCreate(Activity.java:5206) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1094) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2074) ... 11 more 

The following are parts of the code. I got them mainly from the source of the Android settings application.

Any idea?

Thank you in advance


MainPreferenceActivity:

 public class MainPreferenceActivity extends PreferenceActivity { private static List<Header> _headers; @Override public void onBuildHeaders(List<Header> headers) { _headers = headers; loadHeadersFromResource(R.xml.preference_headers, headers); } @Override public void setListAdapter(ListAdapter adapter) { if (adapter == null) { super.setListAdapter(null); } else { super.setListAdapter(new HeaderAdapter(this, _headers)); } } } 

Preferences

 public class PreferencesFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String settings = getArguments().getString("settings"); if (settings.equals("DIVE")) { addPreferencesFromResource(R.xml.preference_dive_tile); } else if (settings.equals("MAP")) { addPreferencesFromResource(R.xml.preference_map_tile); } } } 

preference_headers.xml:

 <?xml version="1.0" encoding="utf-8"?> <preference-headers xmlns:android="http://schemas.android.com/apk/res/android" > <header android:id="@+id/header_section_1" android:title="Section 1" /> <header android:fragment="fr.ifremer.testandroid.models.preferences.PreferencesFragment" android:summary="DIVE summary" android:title="DIVE title" > <extra android:name="settings" android:value="DIVE" /> </header> <header android:fragment="fr.ifremer.testandroid.models.preferences.PreferencesFragment" android:summary="MAP summary" android:title="MAP title" > <extra android:name="settings" android:value="MAP" /> </header> </preference-headers> 

And last but not least: HeaderAdapter:

 public class HeaderAdapter extends ArrayAdapter<Header> { static final int HEADER_TYPE_CATEGORY = 0; static final int HEADER_TYPE_NORMAL = 1; private static final int HEADER_TYPE_COUNT = HEADER_TYPE_NORMAL + 1; private LayoutInflater mInflater; private static class HeaderViewHolder { ImageView icon; TextView title; TextView summary; } public HeaderAdapter(Context context, List<Header> objects) { super(context, 0, objects); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } static int getHeaderType(Header header) { if (header.fragment == null && header.intent == null) return HEADER_TYPE_CATEGORY; else return HEADER_TYPE_NORMAL; } @Override public int getItemViewType(int position) { Header header = getItem(position); return getHeaderType(header); } @Override public boolean areAllItemsEnabled() { return false; /* because of categories */ } @Override public boolean isEnabled(int position) { return getItemViewType(position) != HEADER_TYPE_CATEGORY; } @Override public int getViewTypeCount() { return HEADER_TYPE_COUNT; } @Override public boolean hasStableIds() { return true; } @Override public View getView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; Header header = getItem(position); int headerType = getHeaderType(header); View view = null; if (convertView == null) { holder = new HeaderViewHolder(); switch (headerType) { case HEADER_TYPE_CATEGORY: view = new TextView(getContext(), null, android.R.attr.listSeparatorTextViewStyle); holder.title = (TextView) view; break; case HEADER_TYPE_NORMAL: view = mInflater.inflate(R.layout.preference_header_item, parent, false); holder.icon = (ImageView) view.findViewById(R.id.icon); holder.title = (TextView) view.findViewById(R.id.title); holder.summary = (TextView) view.findViewById(R.id.summary); break; } view.setTag(holder); } else { view = convertView; holder = (HeaderViewHolder) view.getTag(); } // All view fields must be updated every time, because the view may be recycled switch (headerType) { case HEADER_TYPE_CATEGORY : holder.title.setText(header.getTitle(getContext().getResources())); break; case HEADER_TYPE_NORMAL : holder.icon.setImageResource(header.iconRes); holder.title.setText(header.getTitle(getContext().getResources())); CharSequence summary = header.getSummary(getContext().getResources()); if (!TextUtils.isEmpty(summary)) { holder.summary.setVisibility(View.VISIBLE); holder.summary.setText(summary); } else { holder.summary.setVisibility(View.GONE); } break; } return view; } } 
+6
source share
4 answers

Perhaps the first title is selected by default. If so, it should have a fragment attribute to show its right side.

+1
source

As bestofbest1 said, the problem was that Android tried to show the first element in the preferences_headers.xml file that did not contain the fragment.

To fix this, I added the line below (BEFORE super.onCreate) in MainPreferenceActivity onCreate to select the default fragment when using the tablet:

 if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, PreferencesFragment.class.getName()); 

I also set the default snippet in PreferencesFragment:

 String settings = "DIVE"; if(getArguments() != null) settings = getArguments().getString("settings"); 

Then the last problem: PreferenceActivity.EXTRA_SHOW_FRAGMENT does not select the header on the left. To fix this in MainPreferencesActivity, keep a link to your headers (in onBuildHeaders) and add:

 @Override protected void onResume() { // Call super : super.onResume(); // Select the displayed fragment in the headers (when using a tablet) : // This should be done by Android, it is a bug fix if(_headers != null) { final String displayedFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); if (displayedFragment != null) { for (final Header header : _headers) { if (displayedFragment.equals(header.fragment)) { switchToHeader(header); break; } } } } } 
+6
source

I am having problems with Tim's solution for me (the program will still crash). I worked on it differently, simply selecting the first heading without a default category instead of the first in the list. To do this, I redefined the onGetInitialHeader method in my PreferenceActivity

 @Override public Header onGetInitialHeader() { for (int i = 0; i < mHeaders.size(); i++) { Header h = mHeaders.get(i); if (!isCategory(h)) { return h; } } } protected static boolean isCategory(Header h) { return h.fragment == null; } 

mHeaders is just a reference to the list of headers stored in the onBuildHeaders call. It should also be noted that this is only a matter up to 4.3, since then it has been fixed. Hope this helps someone from

+2
source

As a simpler form of Tim Autin solution, turn off the multi-panel area to create a single-board phone display on tablets.

 public class PreferencesActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true); super.onCreate(savedInstanceState); } ... } 
+1
source

All Articles