PreferenceActivity Android 4.0 and earlier

30,190

Solution 1

PreferenceFragment will not work on 2.2 and 2.3 (only API level 11 and above). If you want to offer the best user experience and still support older Android versions, the best practice here seems to be to implement two PreferenceActivity classes and to decide at runtime which one to invoke. However, this method still includes calling deprecated APIs, but you can't avoid that.

So for instance, you have a preference_headers.xml:

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" > 
    <header android:fragment="your.package.PrefsFragment" 
        android:title="...">
        <extra android:name="resource" android:value="preferences" />
    </header>
</preference-headers>

and a standard preferences.xml (which hasn't changed much since lower API levels):

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="...">
    ...
</PreferenceScreen>

Then you need an implementation of PreferenceFragment:

public static class PrefsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

And finally, you need two implementations of PreferenceActivity, for API levels supporting or not supporting PreferenceFragments:

public class PreferencesActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
        addPreferencesFromResource(R.xml.other);
    }
}

and:

public class OtherPreferencesActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}

At the point where you want to display the preference screen to the user, you decide which one to start:

if (Build.VERSION.SDK_INT < 11) {
    startActivity(new Intent(this, PreferencesActivity.class));
} else {
    startActivity(new Intent(this, OtherPreferencesActivity.class));
}

So basically, you have an xml file per fragment, you load each of these xml files manually for API levels < 11, and both Activities use the same preferences.

Solution 2

@Mef Your answer can be simplified even more so that you do not need both of the PreferencesActivity and OtherPreferencesActivity (having 2 PrefsActivities is a PITA).

I have found that you can put the onBuildHeaders() method into your PreferencesActivity and no errors will be thrown by Android versions prior to v11. Having the loadHeadersFromResource() inside the onBuildHeaders did not throw and exception on 2.3.6, but did on Android 1.6. After some tinkering though, I found the following code will work in all versions so that only one activity is required (greatly simplifying matters).

public class PreferencesActivity extends PreferenceActivity {
    protected Method mLoadHeaders = null;
    protected Method mHasHeaders = null;

    /**
     * Checks to see if using new v11+ way of handling PrefFragments.
     * @return Returns false pre-v11, else checks to see if using headers.
     */
    public boolean isNewV11Prefs() {
        if (mHasHeaders!=null && mLoadHeaders!=null) {
            try {
                return (Boolean)mHasHeaders.invoke(this);
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }
        return false;
    }

    @Override
    public void onCreate(Bundle aSavedState) {
        //onBuildHeaders() will be called during super.onCreate()
        try {
            mLoadHeaders = getClass().getMethod("loadHeadersFromResource", int.class, List.class );
            mHasHeaders = getClass().getMethod("hasHeaders");
        } catch (NoSuchMethodException e) {
        }
        super.onCreate(aSavedState);
        if (!isNewV11Prefs()) {
            addPreferencesFromResource(R.xml.preferences);
            addPreferencesFromResource(R.xml.other);
        }
    }

    @Override
    public void onBuildHeaders(List<Header> aTarget) {
        try {
            mLoadHeaders.invoke(this,new Object[]{R.xml.pref_headers,aTarget});
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        }   
    }
}

This way you only need one activity, one entry in your AndroidManifest.xml and one line when you invoke your preferences:

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

UPDATE Oct 2013: Eclipse/Lint will warn you about using the deprecated method, but just ignore the warning. We are using the method only when we have to, which is whenever we do not have v11+ style preferences and must use it, which is OK. Do not be frightened about Deprecated code when you have accounted for it, Android won’t remove deprecated methods anytime soon. If it ever did occur, you won’t even need this class anymore as you would be forced to only target newer devices. The Deprecated mechanism is there to warn you that there is a better way to handle something on the latest API version, but once you have accounted for it, you can safely ignore the warning from then on. Removing all calls to deprecated methods would only result in forcing your code to only run on newer devices — thus negating the need to be backward compatible at all.

Solution 3

There's a newish lib that might help.

UnifiedPreference is a library for working with all versions of the Android Preference package from API v4 and up.

Solution 4

Problem with previous answers is that it will stack all preferences to a single screen on pre-Honecomb devices (due to multiple calls of addPreferenceFromResource()).

If you need first screen as list and then the screen with preferences (such as using preference headers), you should use Official guide to compatible preferences

Solution 5

I wanted to point out that if you start at http://developer.android.com/guide/topics/ui/settings.html#PreferenceHeaders and work your way down to the section for "Supporting older versions with preference headers" it will make more sense. The guide there is very helpful and does work well. Here's an explicit example following their guide:

So start with file preference_header_legacy.xml for android systems before HoneyComb

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference 
    android:title="OLD Test Title"
    android:summary="OLD Test Summary"  >
    <intent 
        android:targetPackage="example.package"
        android:targetClass="example.package.SettingsActivity"
        android:action="example.package.PREFS_ONE" />
</Preference>

Next create file preference_header.xml for android systems with HoneyComb+

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header 
    android:fragment="example.package.SettingsFragmentOne"
    android:title="NEW Test Title"
    android:summary="NEW Test Summary" />
</preference-headers>

Next create a preferences.xml file to hold your preferences...

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
        <CheckBoxPreference
        android:key="pref_key_auto_delete"
        android:summary="@string/pref_summary_auto_delete"
        android:title="@string/pref_title_auto_delete"
        android:defaultValue="false" />
</PreferenceScreen>

Next create the file SettingsActivity.java

package example.project;
import java.util.List;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SettingsActivity extends PreferenceActivity{
final static String ACTION_PREFS_ONE = "example.package.PREFS_ONE";

@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    String action = getIntent().getAction();
    if (action != null && action.equals(ACTION_PREFS_ONE)) {
        addPreferencesFromResource(R.xml.preferences);
    }
    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_header_legacy);
    }
}

@SuppressLint("NewApi")
@Override
public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preference_header, target);
}
}

Next create the class SettingsFragmentOne.java

package example.project;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.preference.PreferenceFragment;

@SuppressLint("NewApi")
public class SettingsFragmentOne extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.preferences);
}
}

AndroidManifest.xml, added this block between my <application> tags

<activity 
   android:label="@string/app_name"
   android:name="example.package.SettingsActivity"
   android:exported="true">
</activity>

and finally, for the <wallpaper> tag...

<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/description"
android:thumbnail="@drawable/ic_thumbnail"
android:settingsActivity="example.package.SettingsActivity"
/>
Share:
30,190
just_user
Author by

just_user

I design and make apps, for mobile, the web or desktops. I have positive attitude towards new challenges and striving to perfect them. I work with Javascript, Kotlin, Swift, Java, HTML, CSS and more. I use framworks like Electron, Vue, Knockout and others. I play with Rust &amp; Solidity. I have a passion for making great apps where the user comes first. I am open for new challenges, technologies and languages. I believe in the blockchain.

Updated on April 23, 2020

Comments

  • just_user
    just_user about 4 years

    Trying the different preference activities in the ApiDemos for Android 4.0, I see in the code that some methods are deprecated in PreferencesFromCode.java, for example.

    So my question is: if I use PreferenceFragment, will it work for all version or only 3.0 or 4.0 and up?

    If so, what should I use that works for 2.2 and 2.3 as well?