0

I am following this guide to learn about 'Creating App Settings using Preference Headers'. This question deals with "Supporting older (than HoneyComb) versions with preference headers".

I think I have followed all the steps, but still am getting this exception. So what am I missing?

LOG:

...
07-10 18:45:31.935: E/AndroidRuntime(1285): FATAL EXCEPTION: main
07-10 18:45:31.935: E/AndroidRuntime(1285): java.lang.RuntimeException: Unable to start activity ComponentInfo{practice_projects.minimalpreferencesusingpreferenceheadersoldversions/practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceActivity}: java.lang.ClassCastException: practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceFragment cannot be cast to android.app.Fragment
...
07-10 18:45:31.935: E/AndroidRuntime(1285): Caused by: java.lang.ClassCastException: practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceFragment cannot be cast to android.app.Fragment
...
07-10 18:45:31.935: E/AndroidRuntime(1285):     at practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceActivity.onCreate(MyPreferenceActivity.java:16)
...

MainActivity.java:

public class MainActivity extends FragmentActivity implements DrawerLayout.DrawerListener, ListView.OnItemClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();

    private final String [] drawerListItems = {"Settings"};

    private DrawerLayout drawerLayout;
    private FrameLayout frameLayout;
    private ListView drawerListView;

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//******LINE # 16***********

        drawerListView = (ListView) findViewById(R.id.mainActivity_listView);
        drawerListView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, drawerListItems));
        drawerListView.setOnItemClickListener(this);

        drawerLayout = (DrawerLayout) findViewById(R.id.mainActivity_drawerLayout);
        drawerLayout.setDrawerListener(this);

        frameLayout = (FrameLayout) findViewById(R.id.mainActivity_frameLayout);

        textView = (TextView) findViewById(R.id.mainActivity_textView);
    }


    /****************************************************************************************************************************************/
    /************************************************DrawerLayout.DrawerListener IMPLEMENTATION**********************************************/
    /****************************************************************************************************************************************/
    @Override
    public void onDrawerClosed(View arg0) {
        frameLayout.setBackgroundColor(Color.parseColor("#6A0888")); /* Dark Purple Color */
    }

    @Override
    public void onDrawerOpened(View arg0) {
        frameLayout.setBackgroundColor(Color.parseColor("#FE642E")); /* Orange Color */
    }

    @Override
    public void onDrawerSlide(View arg0, float arg1) {}

    @Override
    public void onDrawerStateChanged(int arg0) {}

    /****************************************************************************************************************************************/
    /************************************************OnDrawerClickListener IMPLEMENTATION**********************************************/
    /****************************************************************************************************************************************/
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        frameLayout.setBackgroundColor(Color.parseColor("#F7FE2E")); /* Yellow Color */

        startActivity(new Intent(this, MyPreferenceActivity.class));

        drawerListView.setItemChecked(position, true);
        drawerLayout.closeDrawer(drawerListView);

    }
}

MyPreferenceActivity.java:

public class MyPreferenceActivity extends PreferenceActivity {
    private static final String TAG = MyPreferenceActivity.class.getSimpleName();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate of MyPreferenceActivity called.");// check

        String intentAction = getIntent().getAction();
        Log.i(TAG, "The action retrieved from the intent is " + intentAction + ".");// check
        if (intentAction != null && intentAction.equals("practice_projects.minimalpreferencesusingpreferenceheadersoldversions.DB")) {
            Log.i(TAG, "YES intentAction!=null && intentAction.equals(\"practice_projects.minimalpreferencesusingpreferenceheadersoldversions.DB\"");// check
            addPreferencesFromResource(R.xml.db_preferences);
            Log.i(TAG, "Added preferences from R.xml.db_preferences.");// check
        } else if (intentAction != null && intentAction.equals("practice_projects.minimalpreferencesusingpreferenceheadersoldversions.UI")) {
            Log.i(TAG, "YES intentAction!=null && intentAction.equals(\"practice_projects.minimalpreferencesusingpreferenceheadersoldversions.UI\"");// check
            addPreferencesFromResource(R.xml.ui_preferences);
            Log.i(TAG, "Added preferences from R.xml.ui_preferences.");// check
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            Log.i(TAG, "Build version is LESS than honeycomb.");// check
            // Load the preference_headers_legacy.xml
            addPreferencesFromResource(R.xml.preference_headers_legacy);
            Log.i(TAG, "Added preferences from R.xml.preference_headers_legacy.");// check
        } else {
            Log.i(TAG, "Build version is HIGHER than honeycomb. "
                    + "NO *******intentAction!=null && intentAction.equals(\"practice_projects.minimalpreferencesusingpreferenceheadersoldversions"
                    + ".DB\"*******."
                    + " NO *******intentAction!=null && intentAction.equals(\"practice_projects.minimalpreferencesusingpreferenceheadersoldversions"
                    + ".UI\"*******.");// check
        }

    }

    // Called only on HoneyComb and later
    // Although the @TargetApi annotation is not for compiler, and is for Lint only, but no problem, as the system will call this method for honeycomb and later
    // only
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}

MyPreferenceFragment.java:

public class MyPreferenceFragment extends PreferenceFragment {
private static final String TAG = MyPreferenceFragment.class.getSimpleName();

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String preferenceHeaderExtraValue = getArguments().getString("preference_extra_key");
    if (preferenceHeaderExtraValue!=null) {
        if (preferenceHeaderExtraValue.equals("db")) {
            //getView().setBackgroundColor(Color.parseColor("#81F7BE")); /* Aqua towards green */ //NPE because the layout is not created yet**********
            Log.i(TAG, "The value of the preference intent with the key "+preferenceHeaderExtraValue+" is \"db\".");
            addPreferencesFromResource(R.xml.db_preferences);
        } else if (preferenceHeaderExtraValue.equals("ui")) {
            //getView().setBackgroundColor(Color.parseColor("#A9D0F5")); /* Aqua towards blue */ //NPE because the layout is not created yet**********
            Log.i(TAG, "The value of the preference intent with the key "+preferenceHeaderExtraValue+" is \"ui\".");
            addPreferencesFromResource(R.xml.ui_preferences);
        } else {
            Log.i(TAG, "The value of the preference intent with the key "+preferenceHeaderExtraValue+" is neither \"db\" nor \"ui\".");
        }
    } else {
        Log.i(TAG, "getArguments() returns null.");
    }
}

}

PreferenceFragment.java (Taken from PreferenceFragment for Pre-HoneyComb by Christophe Beyls):

/**
* A PreferenceFragment for the support library. Based on the platform's code with some removed features and a basic ListView layout.
* 
* @author Christophe Beyls https://gist.github.com/cbeyls/7475726
* 
*/
public abstract class PreferenceFragment extends Fragment {

    private static final int FIRST_REQUEST_CODE = 100;
    private static final int MSG_BIND_PREFERENCES = 1;
    private static final int MSG_REQUEST_FOCUS = 2;
    private static final String PREFERENCES_TAG = "android:preferences";
    private static final float HC_HORIZONTAL_PADDING = 16;

    @SuppressLint("HandlerLeak")
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_BIND_PREFERENCES:
                bindPreferences();
                break;
            case MSG_REQUEST_FOCUS:
                mList.focusableViewAvailable(mList);
                break;
            }
        }
    };

    private boolean mHavePrefs;
    private boolean mInitDone;
    private ListView mList;
    private PreferenceManager mPreferenceManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
            c.setAccessible(true);
            mPreferenceManager = c.newInstance(this.getActivity(), FIRST_REQUEST_CODE);
        } catch (Exception ignored) {
        }
    }

    @Override
    public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle savedInstanceState) {
        ListView listView = new ListView(getActivity());
        listView.setId(android.R.id.list);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            final int horizontalPadding = (int) (HC_HORIZONTAL_PADDING * getResources().getDisplayMetrics().density);
            listView.setPadding(horizontalPadding, 0, horizontalPadding, 0);
        }
        return listView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (mHavePrefs) {
            bindPreferences();
        }

        mInitDone = true;

        if (savedInstanceState != null) {
            Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
            if (container != null) {
                final PreferenceScreen preferenceScreen = getPreferenceScreen();
                if (preferenceScreen != null) {
                    preferenceScreen.restoreHierarchyState(container);
                }
            }
        }
    }

    public void onStop() {
        super.onStop();
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
            m.setAccessible(true);
            m.invoke(mPreferenceManager);
        } catch (Exception ignored) {
        }
    }

    public void onDestroyView() {
        mList = null;
        mHandler.removeCallbacksAndMessages(null);
        super.onDestroyView();
    }

    public void onDestroy() {
        super.onDestroy();
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
            m.setAccessible(true);
            m.invoke(mPreferenceManager);
        } catch (Exception ignored) {
        }
    }

    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        PreferenceScreen preferenceScreen = getPreferenceScreen();
        if (preferenceScreen != null) {
            Bundle container = new Bundle();
            preferenceScreen.saveHierarchyState(container);
            outState.putBundle(PREFERENCES_TAG, container);
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
            m.setAccessible(true);
            m.invoke(mPreferenceManager, requestCode, resultCode, data);
        } catch (Exception ignored) {
        }
    }

    public PreferenceManager getPreferenceManager() {
        return mPreferenceManager;
    }

    public void setPreferenceScreen(PreferenceScreen screen) {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
            m.setAccessible(true);
            boolean result = (Boolean) m.invoke(mPreferenceManager, screen);
            if (result && (screen != null)) {
                mHavePrefs = true;
                if (mInitDone) {
                    postBindPreferences();
                }
            }
        } catch (Exception ignored) {
        }
    }

    public PreferenceScreen getPreferenceScreen() {
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
            m.setAccessible(true);
            return (PreferenceScreen) m.invoke(mPreferenceManager);
        } catch (Exception e) {
            return null;
        }
    }

    public void addPreferencesFromIntent(Intent intent) {
        requirePreferenceManager();
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class);
            m.setAccessible(true);
            PreferenceScreen screen = (PreferenceScreen) m.invoke(mPreferenceManager, intent, getPreferenceScreen());
            setPreferenceScreen(screen);
        } catch (Exception ignored) {
        }
    }

    public void addPreferencesFromResource(int resId) {
        requirePreferenceManager();
        try {
            Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
            m.setAccessible(true);
            PreferenceScreen screen = (PreferenceScreen) m.invoke(mPreferenceManager, getActivity(), resId, getPreferenceScreen());
            setPreferenceScreen(screen);
        } catch (Exception ignored) {
        }
    }

    public Preference findPreference(CharSequence key) {
        if (mPreferenceManager == null) {
            return null;
        }
        return mPreferenceManager.findPreference(key);
    }

    private void requirePreferenceManager() {
        if (this.mPreferenceManager == null) {
            throw new RuntimeException("This should be called after super.onCreate.");
        }
    }

    private void postBindPreferences() {
        if (!mHandler.hasMessages(MSG_BIND_PREFERENCES)) {
            mHandler.sendEmptyMessage(MSG_BIND_PREFERENCES);
        }
    }

    private void bindPreferences() {
        final PreferenceScreen preferenceScreen = getPreferenceScreen();
        if (preferenceScreen != null) {
            preferenceScreen.bind(getListView());
        }
    }

    public ListView getListView() {
        ensureList();
        return mList;
    }

    private void ensureList() {
        if (mList != null) {
            return;
        }
        View root = getView();
        if (root == null) {
            throw new IllegalStateException("Content view not yet created");
        }
        View rawListView = root.findViewById(android.R.id.list);
        if (rawListView == null) {
            throw new RuntimeException("Your content must have a ListView whose id attribute is 'android.R.id.list'");
        }
        if (!(rawListView instanceof ListView)) {
            throw new RuntimeException("Content has view with id attribute 'android.R.id.list' that is not a ListView class");
        }
        mList = (ListView) rawListView;
        mHandler.sendEmptyMessage(MSG_REQUEST_FOCUS);
    }

preference_headers.xml:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
    <header 
        android:fragment="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceFragment"
        android:title="@string/preferenceHeaders_title1"
        android:summary="@string/preferenceHeaders_summary1" >
        <extra android:name="preference_extra_key" android:value="db" />
    </header>

    <header 
        android:fragment="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceFragment"
        android:title="@string/preferenceHeaders_title2"
        android:summary="@string/preferenceHeaders_summary2" >
        <extra android:name="preference_extra_key" android:value="ui" />
    </header>
</preference-headers>

ui_preferences.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <EditTextPreference 
        android:title="@string/UIPreferences_editTextPreference_title"
        android:summary="@string/UIPreferences_editTextPreference_summary"
        android:key="preferences_UIPditTextPreference_key"
        android:defaultValue="UIPreferences_editTextPreference_defaultValue" />


    <EditTextPreference 
        android:title="@string/UIPreferences_editTextPreference_title1"
        android:summary="@string/UIPreferences_editTextPreference_summary1"
        android:key="UIPreferences_editTextPreference_key1"
        android:defaultValue="UIPreferences_editTextPreference_defaultValue1" />

</PreferenceScreen>

db_preferences.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <CheckBoxPreference 
        android:title="@string/DBPreferences_checkBoxPreference_title"
        android:summary="@string/DBPreferences_checkBoxPreference_summary"
        android:key="DBPreferences_checkBoxPreference_key"
        android:defaultValue="DBPreferences_checkBoxPreference_defaultValue" />


    <CheckBoxPreference 
        android:title="@string/DBPreferences_checkBoxPreference_title1"
        android:summary="@string/DBPreferences_checkBoxPreference_summary1"
        android:key="DBPreferences_checkBoxPreference_key1"
        android:defaultValue="DBPreferences_checkBoxPreference_defaultValue1" />

</PreferenceScreen>

preference_headers_legacy:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <Preference 
        android:title="@string/preferenceHeaders_title1"
        android:summary="@string/preferenceHeaders_summary1" >
        <intent 
            android:targetPackage="practice_projects.minimalpreferencesusingpreferenceheadersoldversions"
            android:targetClass="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceActivity"
            android:action="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.DB" />
    </Preference>

    <Preference 
        android:title="@string/preferenceHeaders_title2"
        android:summary="@string/preferenceHeaders_summary2" >
        <intent 
            android:targetPackage="practice_projects.minimalpreferencesusingpreferenceheadersoldversions"
            android:targetClass="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.MyPreferenceActivity"
            android:action="practice_projects.minimalpreferencesusingpreferenceheadersoldversions.UI" />
    </Preference>

</PreferenceScreen>

strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Minimal Preferences Using Preference headers OLD VERSIONS</string>
    <string name="mainActivity_textView">I am a TextView.</string>
    <string name="preferenceHeaders_title">Preference Header Title</string>
    <string name="preferenceHeaders_summary">Preference Header Summary</string>
    <string name="preferenceHeaders_title1">Preference Header Title</string>
    <string name="preferenceHeaders_summary1">Preference Header Summary</string>
    <string name="preferenceHeaders_title2">Preference Header Title</string>
    <string name="preferenceHeaders_summary2">Preference Header Summary</string>
    <string name="DBPreferences_checkBoxPreference_title">Preference Title</string>
    <string name="DBPreferences_checkBoxPreference_summary">Preference Summary</string>
    <string name="DBPreferences_checkBoxPreference_defaultValue">DBPreferences_checkBoxPreference_defaultValue</string>
    <string name="DBPreferences_checkBoxPreference_title1">Preference Title</string>
    <string name="DBPreferences_checkBoxPreference_summary1">Preference Summary</string>
    <string name="DBPreferences_checkBoxPreference_defaultValue1">DBPreferences_checkBoxPreference_defaultValue1</string>
    <string name="UIPreferences_editTextPreference_title">Preference Title</string>
    <string name="UIPreferences_editTextPreference_summary">Preference Summary</string>
    <string name="UIPreferences_editTextPreference_defaultValue">UIPreferences_editTextPreference_defaultValue</string>
    <string name="UIPreferences_editTextPreference_title1">Preference Title</string>
    <string name="UIPreferences_editTextPreference_summary1">Preference Summary</string>
    <string name="UIPreferences_editTextPreference_defaultValue1">UIPreferences_editTextPreference_defaultValue1</string>
    <string name="title_activity_my_preference">MyPreferenceActivity</string>
    <string name="hello_world">Hello world!</string>

</resources>
Solace
  • 8,612
  • 22
  • 95
  • 183

2 Answers2

1

You can't use my PreferenceFragment to display headers or anything Honeycomb-specific. It's just a hack to allow showing a single Preferences set in an ActionBarActivity, because previously it was not possible to have a compatibility ActionBar in a PreferenceActivity (it's now possible since AppCompat 22.1 so you don't need this class).

I think the support library will soon provide support for preferences on older versions so keep an eye on it.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
  • Thank you for answering. The problem is that I don't want to use an ActionBar at all. In [this](http://developer.android.com/guide/topics/ui/settings.html#BackCompatHeaders) guide, the heading is "Supporting older versions with preference headers", and I have tried to follow their method. The thing is that they are completely missing the fact that `PreferenceFragment` is not supported in pre-honeycomb. =s – Solace Jul 10 '15 at 20:51
  • No, the guide explains how to use PreferenceActivity on older versions, without any PreferenceFragment. You don't need to use a PreferenceFragment at all on older versions. – BladeCoder Jul 10 '15 at 20:56
  • In the same section of the guide: _"All you need to do is create an **additional** preferences XML file that uses basic elements that behave like the header items (to be used by the older Android versions)."_ which means this is continued from [this](http://developer.android.com/guide/topics/ui/settings.html#PreferenceHeaders) previous section, in which we use PreferenceFragment (See the `android:fragment` attribute [in code snippet here](http://developer.android.com/guide/topics/ui/settings.html#CreateHeaders)). – Solace Jul 10 '15 at 21:43
  • Secondly, in the [same section of the guide](http://developer.android.com/guide/topics/ui/settings.html#BackCompatHeaders), see the `android:fragment="com.example.prefs.SettingsFragmentOne"` attribute in the code snippet of `preference_headers.xml`. It's true that this file will be used by Android 3.0 and higher, but we have it in the same application. I have followed the guide except that I added your class (I understand that it was a mistake) because otherwise I was getting an error when trying to use PreferenceFragment (meant for Android 3.0 and higher). – Solace Jul 10 '15 at 21:50
  • 1
    I agree it's not very clear, but in the section about supporting older versions, there is an example where they explain you need to call the deprecated method PreferenceActivity.addPreferencesFromResource() before Android 3, so this means the Preferences will be loaded from the Activity and not from a PreferenceFragment. – BladeCoder Jul 10 '15 at 22:18
1

From the link that you have provided, it looks like the PreferenceFragment that you are using uses android.support.v4.app.Fragment. See you have this line in the imports section in the GitHub page:

import android.support.v4.app.Fragment;

What this does is use the Fragment class from the support library. But somewhere within you code you are expecting an instance of the regular Fragment class found in package android.app.Fragment (see this from your stack trace). You are using the fragment from the support library in a class from the regular library - they are incompatible with each other and will throw the exception that you are seeing. What you really have is a workaround that is not supported.

As a suggestion, see the following answer for more info on backwards compatibility of PreferenceActivity.

EDIT: Including link in answer PreferenceActivity Android 4.0 and earlier

Community
  • 1
  • 1
ucsunil
  • 7,378
  • 1
  • 27
  • 32
  • Firstly, thank you for answering. Secondly, "see the following answer" - you forgot to post a link to the answer? – Solace Jul 10 '15 at 20:53
  • 1
    Oops.. updated the answer to include the link. Just keep in mind that the answer I provided is somewhat of a more preferred workaround. You will still have to call some deprecated APIs but there doesn't seem to be an alternative for now. – ucsunil Jul 10 '15 at 21:55