How to Create Preference Activity and Preference Fragment on Android

How do you create Preference Activity and Preference Fragment on Android?

I found this post (What to use instead of “addPreferencesFromResource” in a PreferenceActivity?) that help me understand that you have to go through a PreferenceFragment in order to do it.

In the following explanation I use your.package. just to show that you have to put the package name. Everybody has its own package so please replace it with your package.

lets begin :


1. Preference Fragment

  • Create your PreferenceFragment class

    MyPreferenceFragment

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


  • Then the associated xml resource

    fragment_preference.xml (in the folder res/xml of your project)

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

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

    <PreferenceCategory
    android:title="FOO">

    <CheckBoxPreference
    android:key="checkBoxPref"
    android:title="check it out"
    android:summary="click this little box"/>

    </PreferenceCategory>

    </PreferenceScreen>

    That's all for the Fragment part.



2. Preference Activity

  • Create the PreferenceActivity class

    MyPreferenceActivity

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

    @Override
    protected boolean isValidFragment(String fragmentName)
    {
    return MyPreferenceFragment.class.getName().equals(fragmentName);
    }
    }

    Do not forget to override isValidFragment(String fragmentName) method as you will get punched in the face by your application ! ;) More seriously I have no idea why you need to do this but it is needed. If someone has an explanation about this I'd gladly read it :)

    EDIT :


    Thanks to kirtan403 I now know why it is needed : it has to be set because of an (android framework fragment injection).


    As you can see in the onBuildHeaders(List<Header> target) we load another xml file that contain the headers of the preference. In short, headers are the left part of the preference and the fragment are the right part (for tablet). For a phone you will first have the headers and when you click on an item the corresponding fragment will be put on top of the headers list.

    Read this article (Multi-pane development in Android with Fragments - Tutorial) the images explain themselves.


  • Then the associated xml resource

    headers_preference.xml (in the folder res/xml of your project)

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

    <preference-headers
    xmlns:android="http://schemas.android.com/apk/res/android">

    <header
    android:fragment="your.package.MyPreferenceFragment"
    android:title="Goto: Preference fragment"
    android:summary="An example of some preferences." />

    </preference-headers>

    As you may have noticed in the header section you have :

    android:fragment="your.package.MyPreferenceFragment"

    This will act as a Link to the fragment you want to show. On Tablet it will load on the right part and on the phone it will load on top of the current view.



3. Android Manifest

Now what you should do is to add your Activity to the AndroidManifest.xml file.

Inside the application section add these lines :

<activity
android:name="your.package.MyPreferenceActivity"
android:label="Preferences">
</activity>

You will probably tell me :

"Oh darling you forgot to put android:launchMode="singleTask" in your actvity"

But DO NOT PUT THIS as you will never load your fragment on phone. This error was solved by a great man ! This is the link to his blog (Android header preferences on small screen/phone).



4. Start the Preferences from Menu

Finally you need to add the ability to show this Preference !! To do so you will need 3 things :

  • The Menu

    menu.xml (in the folder res/menu of your project)

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

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

    <item
    android:id="@+id/preferences"
    android:title="Preferences" />

    </menu>


  • Loading this Menu in your Main activity (not the PreferenceActivity) under the method onCreateOptionsMenu(Menu menu)

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu, menu);
    return true;
    }


  • Starting the MyPreferenceActivity Activity when you click on that button.

    For that you will need to override the onOptionsItemSelected(MenuItem item) method in your Main activity.

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
    switch(item.getItemId())
    {
    case R.id.preferences:
    {
    Intent intent = new Intent();
    intent.setClassName(this, "your.package.MyPreferenceActivity");
    startActivity(intent);
    return true;
    }
    }

    return super.onOptionsItemSelected(item);
    }



Et voila les amis !

I haven't tested this code. I took it and modified it from my own code so I may have not well copy pasted things. If you encounter errors tell me, I'll try to figure out the problem and fix this.

I hope this post will help some people out there :D

Cheers !

What is a difference between PreferenceFragment class and PreferenceActivity class and what they do?

Both offer the same methods, more or less, to edit the internal SharedPreferences of the application, loaded from a file with addPreferencesFromResource

I feel like the documentation summarizes best

  • If your app supports versions of Android older than 3.0 (API level 10 and lower), you must build the activity as an extension of the PreferenceActivity class.

  • On Android 3.0 and later, you should instead use a traditional Activity that hosts a PreferenceFragment that displays your app settings. However, you can also use PreferenceActivity to create a two-pane layout for large screens when you have multiple groups of settings.

That being said, Android has evolved far beyond 3.0 API, so it's safe to consider PreferenceActivity as deprecated. Even pre-3.0, I believe there is a support library with PreferenceFragment class.

What is a meaning of android.R.id.content

Its the root element of the screen - Android: What is android.R.id.content used for?

why here fragment is not connected with Activity class(extends Activity or AppCompatActivity) instead of PreferenceActivity

Well, PreferenceActivity does extend the Activity class, so there is no real reason to use that specific one if you only are loading a Fragment

What's the proper way to set up an Android PreferenceFragment?

what class should SettingsActivity extend?

What worked for me was extending AppCompatActivity.

static final String ANIMATION = "animation" ;
static final String COUNTDOWN_ON_OFF = "countdown_on_off";

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

if (getFragmentManager().findFragmentById(android.R.id.content) == null)
{
getFragmentManager().beginTransaction().add(android.R.id.content, new Prefs()).commit();
}
}

I kicked out all the generated code related to preference headers and kept some template methods/ variables (which Android Studio generated in some earlier version) for my PreferenceFragment

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

// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.

// findPreference() uses android:key like in preferences.xml !

bindPreferenceSummaryToValue(findPreference(ANIMATION));

}
}

A static method in my Activity class (adapted from the template). You may want to check for other preference types:

 /**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference)
{
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);

// Trigger the listener immediately with the preference's
// current value.

if (preference instanceof CheckBoxPreference)
{
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getBoolean(preference.getKey(), true));
}
else
{
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
}

And finally, the Preference.OnPreferenceChangeListener as static variable in the Activity (also adapted from the template):

   /**
* A preference value change listener that updates the preference's summary
* to reflect its new value.<br>
* template by Android Studio minus Ringtone Preference
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener()
{
@Override
public boolean onPreferenceChange(Preference preference, Object value)
{
String stringValue = value.toString();

if (preference instanceof ListPreference)
{
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);

// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);

}
else if (preference instanceof RingtonePreference)
{
// my app didn't need that
return true;
}
else if (preference instanceof CheckBoxPreference)
{
Context ctx = preference.getContext();
boolean isChecked = (Boolean) value;

if (preference.getKey().equals(ANIMATION))
{
preference.setSummary(isChecked ? ctx.getString(R.string.sOn) : ctx.getString(R.string.sOff));
}
else if (preference.getKey().equals(COUNTDOWN_ON_OFF))
{
preference.setSummary(isChecked ? ctx.getString(R.string.sWhenPaused) : ctx.getString(R.string.sNever) );
}
}
else
{
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
}

add specific named SharedPreferences from resource in PreferenceActivity or PreferenceFragment

If you want to have a custom preference xml file, you need to set preference name before adding it to screen from xml in your PreferenceFragment class.

public class CustomNamePreferenceFragment extends PreferenceFragment {

private static final String PREF_FILE_NAME = "custom_name_xml";

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName(PREF_FILE_NAME);
addPreferencesFromResource(R.xml.prefs);
... //rest of the code
}
}

Note : You need to set shared preference name just after the super call of onCreate() and before calling addPreferencesFromResource() method.

How to open a new PreferenceFragment from current one, using the new Android-X API?

What you tried in 1) was the correct approach - but you should not use <PreferenceScreen> tags for this.

Your XML resource should look like this instead:

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

<Preference
app:key="screen_preference"
app:summary="Shows another screen of preferences"
app:title="Screen preference"
app:fragment="com.example.user.myapplication.MainActivity$PrefsFragment2"/>

</PreferenceScreen>

Also, if you are using a version of Preference older than androidx.preference:preference:1.1.0-alpha01, you will need to implement onPreferenceStartFragment to handle the fragment transaction. (in 1.1.0 alpha01 this method has a default implementation, but you are still encouraged to use your own implementation to customize any animations / transitions)

This should look something like:

override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment = supportFragmentManager.fragmentFactory.instantiate(
classLoader,
pref.fragment,
args
).apply {
arguments = args
setTargetFragment(caller, 0)
}
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit()
return true
}

For more information you can check out the Settings guide
and the AndroidX Preference Sample


EDIT: a sample of the first solution, after updating, available here.

Here's how it can work (sample available here) :

MainActivity.kt

class MainActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference): Boolean {
//Note: this whole function won't be needed when using new version of fragment dependency (1.1.0 and above)
val fragment = Fragment.instantiate(this, pref.fragment, pref.extras)
fragment.setTargetFragment(caller, 0)
supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack(null).commit()
return true
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null)
supportFragmentManager.beginTransaction().replace(android.R.id.content, PrefsFragment()).commit()
}

class PrefsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}

class PrefsFragment2 : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences2, null)
}
}
}

preferences.xml

  <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">

<Preference
app:fragment="com.example.user.myapplication.MainActivity$PrefsFragment2" app:key="screen_preference" app:summary="Shows another screen of preferences"
app:title="Screen preference"/>

</PreferenceScreen>

preferences2.xml

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

<PreferenceCategory android:title="Category">
<CheckBoxPreference
android:key="next_screen_checkbox_preference" android:summary="AAAA" android:title="Toggle preference"/>
</PreferenceCategory>

</PreferenceScreen>

gradle dependencies:

implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.0.0'

Implementing PreferenceFragment?

Because the preferenceFragment is an actual Fragment, you can simply exchange it using a FragmentTransation as you would with your other fragments in your Nav drawer. In whatever onClick event or otherwise, use something like the following to go to your PreferenceFragment:

getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, new MadlibSettings())
.commit();

Source: http://developer.android.com/guide/topics/ui/settings.html#Fragment

And then for accessing your preferences from anywhere, the following code should be able to get you started.

SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getContext());
String val1 = settings.getString("KEY", "default_value");
settings.putString("key", "new_value");

If you decide to make your own preferences file in addition to the settings one, then you will use:

SharedPreferences settings = getContext().getSharedPreferences("pref_file_name", 0);
String val1 = settings.getString("KEY", "default_value");
settings.putString("KEY", "new_value");


Related Topics



Leave a reply



Submit