365

I just noticed the fact that the method addPreferencesFromResource(int preferencesResId) is marked deprecated in Android's documentation (Reference Entry).

Unfortunately, no alternative method is provided in the method's description.

Which method should be used instead in order to connect a preferenceScreen.xml to the matching PreferenceActivity?

W4R10CK
  • 5,502
  • 2
  • 19
  • 30
mweisz
  • 3,899
  • 3
  • 17
  • 16
  • 2
    A very straightforward approach is provided by WannaGetHigh at http://stackoverflow.com/questions/23523806/how-do-you-create-preference-activity-and-preference-fragment-on-android – Sabin Mar 18 '15 at 07:54
  • The solution there still uses `addPreferencesFromResource(int preferencesResId)`. Am I missing something ? – Jammo Sep 08 '15 at 13:28
  • @Jammo Yes but it has been moved from the Activity to the Fragment to reflect the new way of doing it -> fragment. – WannaGetHigh Oct 02 '15 at 14:56

6 Answers6

334

No alternative method is provided in the method's description because the preferred approach (as of API level 11) is to instantiate PreferenceFragment objects to load your preferences from a resource file. See the sample code here: PreferenceActivity

Mike
  • 19,267
  • 11
  • 56
  • 72
glorifiedHacker
  • 6,410
  • 2
  • 22
  • 26
  • Thank you very much for your answer. I was just following deprecated instructions from "Video2Brain-Android Development" which led me to using PreferenceActivity's version of the method. Btw: I would love to rate your answer as useful if I only could. – mweisz Jul 25 '11 at 21:43
  • 33
    Now only if the PreferenceFragment was included in the compatibility package it would make sense to use it http://stackoverflow.com/questions/5501431/was-preferencefragment-intentionally-excluded-from-the-compatibility-package – christoff Mar 29 '12 at 00:17
  • 1
    Since I use Action Bar Sherlock, I followed the following blog to solve this problem, see... http://commonsware.com/blog/2012/10/16/conditional-preference-headers.html – Someone Somewhere Feb 11 '13 at 00:13
  • 2
    Still you need to call addPreferencesFromResource(int PreferencesID) if you want the app to be backwards compatible with api level erlier than 11 (Android 3.0). But I guess you could consider those old devices derecated as well. – Einar Sundgren Jun 23 '13 at 09:59
  • 6
    @EinarSundgren I'm Nexus One owner and my max available version is 2.2: you won't stop me! I'll NEVER be deprecated! By the Power of Grayskull...I Have the Power! – Niki Romagnoli Jun 23 '15 at 13:24
  • @above use AppCompatPreferenceActivity – Bart Burg Oct 29 '15 at 10:42
189

To add more information to the correct answer above, after reading an example from Android-er I found you can easily convert your preference activity into a preference fragment. If you have the following activity:

public class MyPreferenceActivity extends PreferenceActivity
{
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.my_preference_screen);
    }
}

The only changes you have to make is to create an internal fragment class, move the addPreferencesFromResources() into the fragment, and invoke the fragment from the activity, like this:

public class MyPreferenceActivity extends PreferenceActivity
{
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit();
    }

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

There may be other subtleties to making more complex preferences from fragments; if so, I hope someone notes them here.

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • 6
    Nice, thanks for this. Java and this type of thinking is so far removed from my nice little .net world :) – Tom Nov 28 '12 at 17:08
  • 45
    Why would they depreciate `addPreferencesFromResources()` in trade for PreferenceFragment? It looks unnecessary from a beginner point of view. – Howdy_McGee Feb 12 '13 at 22:08
  • @Howdy_McGee One reason might be that PreferencesFragment supports [preference headers](http://developer.android.com/guide/topics/ui/settings.html#PreferenceHeaders) out of the box, whereas you have to use a [workaround](http://developer.android.com/guide/topics/ui/settings.html#BackCompatHeaders) to use those in PreferenceActivity. If you want to have backward compatibility, also have a look at this [post](http://stackoverflow.com/questions/10186697/preferenceactivity-android-4-0-and-earlier/10258323#10258323). – schnatterer Jul 31 '13 at 18:52
  • 4
    Call requires API level 11 – mehmet Apr 15 '14 at 08:55
  • 1
    so what about if the API is on Level 9? @mehmet – gumuruh Aug 29 '14 at 03:05
  • 2
    Awesome answer! is there a way to achieve this without changing android.R.id.content? seem inelegant to me for some reason... (correct me if im wrong) – tomer.z Oct 13 '14 at 15:35
  • I wonder why ListPreference radio buttons stop to work with this approach? (android 4.0.3) – Kurovsky Oct 14 '15 at 15:33
  • I have solved this here check this out https://gist.github.com/yuviii/cd0fe254b47e01286f3 – yubaraj poudel Mar 13 '16 at 18:13
38

@Garret Wilson Thank you so much! As a noob to android coding, I've been stuck with the preferences incompatibility issue for so many hours, and I find it so disappointing they deprecated the use of some methods/approaches for new ones that aren't supported by the older APIs thus having to resort to all sorts of workarounds to make your app work in a wide range of devices. It's really frustrating!

Your class is great, for it allows you to keep working in new APIs wih preferences the way it used to be, but it's not backward compatible. Since I'm trying to reach a wide range of devices I tinkered with it a bit to make it work in pre API 11 devices as well as in newer APIs:

import android.annotation.TargetApi;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;

public class MyPrefsActivity extends PreferenceActivity
{
    private static int prefs=R.xml.myprefs;

    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        try {
            getClass().getMethod("getFragmentManager");
            AddResourceApi11AndGreater();
        } catch (NoSuchMethodException e) { //Api < 11
            AddResourceApiLessThan11();
        }
    }

    @SuppressWarnings("deprecation")
    protected void AddResourceApiLessThan11()
    {
        addPreferencesFromResource(prefs);
    }

    @TargetApi(11)
    protected void AddResourceApi11AndGreater()
    {
        getFragmentManager().beginTransaction().replace(android.R.id.content,
                new PF()).commit();
    }

    @TargetApi(11)
    public static class PF extends PreferenceFragment
    {       
        @Override
        public void onCreate(final Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(MyPrefsActivity.prefs); //outer class
            // private members seem to be visible for inner class, and
            // making it static made things so much easier
        }
    }
}

Tested in two emulators (2.2 and 4.2) with success.

Why my code looks so crappy:

I'm a noob to android coding, and I'm not the greatest java fan.

In order to avoid the deprecated warning and to force Eclipse to allow me to compile I had to resort to annotations, but these seem to affect only classes or methods, so I had to move the code onto two new methods to take advantage of this.

I wouldn't like having to write my xml resource id twice anytime I copy&paste the class for a new PreferenceActivity, so I created a new variable to store this value.

I hope this will be useful to somebody else.

P.S.: Sorry for my opinionated views, but when you come new and find such handicaps, you can't help it but to get frustrated!

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
ecv
  • 708
  • 7
  • 14
  • Oh well... I just noticed I'd have to change outer class name twice anytime I copy&paste it. There's actually a way to avoid this, by passing prefs to innerclass. You may not create an inner class constructor accepting prefs as a parameter as it is apparently not recommended for PreferenceFragment derived classes. Also, you may not create a method in inner class to get prefs and call addPreferencesFromResource right away, since addPreferencesFromResource needs to be called after super.onCreate has been called, and onCreate doesn't get called right after PreferenceFragment derived class has... – ecv Dec 12 '12 at 05:38
  • ...been instantiated. So you need to create a new variable in the inner class, create a public set method for it in the inner class, leave the addPreferencesFromResource where it is after the call to super.onCreate, inside onCreate, instantiate the inner class, set the prefs, and use it in the call to getFragmentManager()... as before. – ecv Dec 12 '12 at 05:42
  • 2
    Your code has been a lifesaver!! I was trying to target both old and new phone, and wasted 3 days..And the Answer is so simple. Thanks – Devdatta Tengshe Jan 31 '13 at 07:23
22

My approach is very close to Garret Wilson's (thanks, I voted you up ;)

In addition it provides downward compatibility with Android < 3.

I just recognized that my solution is even closer to the one by Kevin Remo. It's just a wee bit cleaner (as it does not rely on the "expection" antipattern).

public class MyPreferenceActivity extends PreferenceActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            onCreatePreferenceActivity();
        } else {
            onCreatePreferenceFragment();
        }
    }

    /**
     * Wraps legacy {@link #onCreate(Bundle)} code for Android < 3 (i.e. API lvl
     * < 11).
     */
    @SuppressWarnings("deprecation")
    private void onCreatePreferenceActivity() {
        addPreferencesFromResource(R.xml.preferences);
    }

    /**
     * Wraps {@link #onCreate(Bundle)} code for Android >= 3 (i.e. API lvl >=
     * 11).
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void onCreatePreferenceFragment() {
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new MyPreferenceFragment ())
                .commit();
    }
}

For a "real" (but more complex) example see NusicPreferencesActivity and NusicPreferencesFragment.

Community
  • 1
  • 1
schnatterer
  • 7,525
  • 7
  • 61
  • 80
  • `@SuppressLint("NewApi")` - avoid - be more precise. Have you run it for low apis? It will throw `VerifyError` – Mr_and_Mrs_D Dec 25 '13 at 19:43
  • I testes the above on the emulator running API level 10, the APK was built using SDK Version 19. What SDK version did you use for building? What device API level did you run it on? Did you run it on the emulator or on a physical device? When exactly did the error occur? If Ur building with SDK <= 16, see [this response](http://stackoverflow.com/a/16758835/1845976). – schnatterer Jan 07 '14 at 21:20
  • You are missing the point - and add @Mr_and_Mrs_D if you want me to be notified. For the VE read here: http://stackoverflow.com/questions/20271593/is-checking-sdk-int-enough-or-is-lazy-loading-needed-for-using-newer-android-api. The `@SuppressLint("NewApi")` is just bad style – Mr_and_Mrs_D Jan 07 '14 at 22:04
  • @M Well then, how about a proposal on how to avoid `@SuppressLint("NewApi")` in this particular situation? – schnatterer Jan 08 '14 at 19:55
  • `@TargetApi(Build.VERSION_CODES.HONEYCOMB)` - not all warnings for any api :) – Mr_and_Mrs_D Jan 10 '14 at 10:16
  • @M Now that's a concrete proposal for improvement! Thanks, I updated the post. – schnatterer Jan 10 '14 at 15:56
6

Instead of exceptions, just use:

if (Build.VERSION.SDK_INT >= 11)

and use

@SuppressLint("NewApi")

to suppress the warnings.

Peter
  • 69
  • 1
  • 1
0

Instead of using a PreferenceActivity to directly load preferences, use an AppCompatActivity or equivalent that loads a PreferenceFragmentCompat that loads your preferences. It's part of the support library (now Android Jetpack) and provides compatibility back to API 14.

In your build.gradle, add a dependency for the preference support library:

dependencies {
    // ...
    implementation "androidx.preference:preference:1.0.0-alpha1"
}

Note: We're going to assume you have your preferences XML already created.

For your activity, create a new activity class. If you're using material themes, you should extend an AppCompatActivity, but you can be flexible with this:

public class MyPreferencesActivity extends AppCompatActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_preferences_activity)
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.fragment_container, MyPreferencesFragment())
                    .commitNow()
        }
    }
}

Now for the important part: create a fragment that loads your preferences from XML:

public class MyPreferencesFragment extends PreferenceFragmentCompat {

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.my_preferences_fragment); // Your preferences fragment
    }
}

For more information, read the Android Developers docs for PreferenceFragmentCompat.

Willie Chalmers III
  • 1,151
  • 1
  • 16
  • 29