Is there a way of handling an OnPreferenceChangeListener in API11. Simply calling Preference.setOnPreferenceChangeListener throws a lint error stating it requires API14+, however my app is designed for API11+ and I don't want to limit the number of devices it can run on further.
Update: I have checked in the development documentation and this should be available from API1
Edit: Code
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.preference.SwitchPreference;
#SuppressWarnings("deprecation")
public class SettingsActivity extends PreferenceActivity {
private static final String tag = "PREFERENCES";
SwitchPreference appPower;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences_layout);
setupPreferenceElements();
LogHelper.i(tag, "Preferences Activity Started");
}
private void setupPreferenceElements() {
appPower = (SwitchPreference) getPreferenceManager().findPreference(
"power");
appPower.setChecked(SettingsManager.getPower());
appPower.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
LogHelper.i(tag, "App Power Changed to " + newValue.toString());
SettingsManager.setPower(Boolean.parseBoolean(newValue
.toString()));
return true;
}
});
}
}
android.preference.SwitchPreference was only added in API v14 (along with TwoStatePreference in case your first though was to use that instead)
If you want to continue using SwitchPreference for 14+ and support 13- you will need to:
make a folder res/xml-v14/
have your current preferences.xml with your SwitchPreference
make another folder (if needed) res/xml/ with another preferences.xml and change your SwitchPreference to a CheckBoxPreference.
This will then load the correct version depending on the API version used. You will need to check for API version in your Java code too.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
appPower = new SwitchPreference(this);
} else {
appPower = new CheckBoxPreference(this);
}
If you don't like the CheckboxPreference you can always extend Preference or extend CheckboxPreference and roll your own solution. This may actually be the best solution as it would have the advantage of being able to wrap both versions in one class where you can then support your preferred implementation, ensuring the methods you want to use are exposed and do not fail, without manual version checks type casting everywhere you use the Preference.
While doing a bit of searching, I stumbled across this library you might use. It aims to make a support SwitchPreference. I can't speak to how well it works that or if you should use it though, I've never tried it.
Related
I have a PreferenceActivty in my Android app, which due to compatibility reasons I use via the getPreferenceScreen() method and some Preference objects which I create in code, mostly CheckBoxPreference and SwitchPreference.
Up to the previous version of my app there were 8 preferences in total and everything worked fine, but now I added 2 more preferences and I'm experiencing a REALLY weird issue.
The second preference on the screen is a SwitchPreference. When I open the activity, it is checked. If I scroll down the screen without actually changing anything, suddenly its value is automatically set to OFF. I tried adding an OnChangeListener to the Preference and implementing OnSharedPreferenceChangeListener, but the results are the same: once that particular Preference disappears from the screen, it is turned OFF. If it's set to OFF, it keeps its value and the change listener is not called.
Does anyone have any idea as to why could this be happening? I'm completely lost...
Thanks in advance!
The code for my preferences is basically this, repeated 5 times for 5 different settings, on the onCreate method:
controlWifiPreference = new CheckBoxPreference(this);
controlWifiPreference.setKey(Constants.PREF_1_KEY);
getPreferenceScreen().addPreference(controlWifiPreference);
wifiPreference = new SwitchPreference(this);
wifiPreference.setKey(Constants.PREF_2_KEY);
getPreferenceScreen().addPreference(wifiPreference);
Since the preferences are inside a TabActivity, on the onResume method I call setChecked() for every preference to set its value again, though I'm not sure that it's completely neccessary.
And, finally, I have an onSharedPreferenceChanged method that activates/deactivates preferences when others are clicked, because I couldn't get the setDependency method to work. It's something like this (again, repeated five times):
if (key.equals(controlWifiPreference.getKey())) {
wifiPreference.setEnabled(controlWifiPreference.isChecked());
}
Turns out it was an Android bug in the SwitchPreference class. Someone (who I'm VERY thankful to ;)) reported it to b.android.com and even posted a workaround. It's all here: https://code.google.com/p/android/issues/detail?id=26194
How you implemented preferences inside TabActivity?I checked your code in my own IDE inside a PreferenceActivity and its working like a charm.If you need to have some pseudo prefences inside your activity, you should not use prefernces and instead, you will need to use normal forms items and save their values to the preferences manually.here is the code i tested and its working ok:
public class PreferencesFromCode extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
private SwitchPreference switchPref;
private CheckBoxPreference checkboxPref;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setPreferenceScreen(createPreferenceHierarchy());
}
private PreferenceScreen createPreferenceHierarchy() {
// Root
#SuppressWarnings("deprecation")
PreferenceScreen root = getPreferenceManager().createPreferenceScreen(
this);
// Inline preferences
PreferenceCategory inlinePrefCat = new PreferenceCategory(this);
inlinePrefCat.setTitle(R.string.inline_preferences);
root.addPreference(inlinePrefCat);
// Checkbox preference
checkboxPref = new CheckBoxPreference(this);
checkboxPref.setKey("checkbox_preference");
checkboxPref.setTitle(R.string.title_checkbox_preference);
checkboxPref.setSummary(R.string.summary_checkbox_preference);
inlinePrefCat.addPreference(checkboxPref);
// Switch preference
switchPref = new SwitchPreference(this);
switchPref.setKey("switch_preference");
switchPref.setTitle(R.string.title_switch_preference);
switchPref.setSummary(R.string.summary_switch_preference);
inlinePrefCat.addPreference(switchPref);
/*
* The Preferences screenPref serves as a screen break (similar to page
* break in word processing). Like for other preference types, we assign
* a key here so that it is able to save and restore its instance state.
*/
// Screen preference
PreferenceScreen screenPref = getPreferenceManager()
.createPreferenceScreen(this);
screenPref.setKey("screen_preference");
screenPref.setTitle(R.string.title_screen_preference);
screenPref.setSummary(R.string.summary_screen_preference);
return root;
}
#Override
protected void onResume() {
super.onResume();
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
/*
* getPreferenceScreen().getSharedPreferences()
* .registerOnSharedPreferenceChangeListener(this);
*/
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
Log.i("ghjg", "changed key is : " + key);
if (key.equals(checkboxPref.getKey())) {
switchPref.setEnabled(checkboxPref.isChecked());
}
}
}
However you may override onContentChanged() and see what happens.
NOTICE: Please save yourself some time and refer to the accepted answer, no need to read all the quesiton.
You may read the rest of the question and the answer I provided for an alternative (although less sophisticated) method.
Also, you may want to take advantage of the fix for the background glitch in Android 2.X, by adding the related piece of code to your preference activity class.
Background
Being a newbie to Android coding, but somewhat experienced in other programming languages/frameworks, I was expecting my walk to Android application coding would be a rather pleasant one. It was so, until I stumbled upon this problem:
Eclipse wizard for Android projects suggested I could reach a 95% of devices if I set my minimum API to 8 (Android 2.2). I didn't need to do any fancy things with my app anyways, so I thought, "sure, why not?". Everything was okay, except occasionally I'd find several methods/classes that were deprecated in most recent API versions, and so I had to devise ways to keep using the old ways for old devices, and try to use as much as possible the new ways for newer Android versions. This is one such occasion.
After using the Eclipse wizard for creating a preference activity, I realized that the Eclipse precompiler/parser/checker(or whatever it's called) Lint, would complain about not being able to use the new ways of creating/managing preferences in older API versions. So I thought, "all right, screw the new ways. Let's do it old way and since new API versions are supposed to be backward-compatible, it should be okay", but it wasn't. Old way used methods/classes that are marked as deprecated; which, to me, means, even though they'd still work in current API, they'd stop working at some point in future releases.
So I started searching for the right way to do this, and finally hit this page: What to use instead of "addPreferencesFromResource" in a PreferenceActivity? where Garret Wilson, explains a way to use old preference screen resources in a way compatible with the new ways. It was great, and finally had the feeling I could move on with my app coding, except it wouldn't work when targeting older APIs, as it was using newer APIs code. So I had to devise a way to make it work for both old APIs and newer. After tinkering with it for a while I managed to find a way, by using precompiler(or whatever it's called) annotations and the great getClass().getMethod() along with exceptions.
Everything seemed to work flawlessly until I created a preference sub-screen. It was displaying correctly in newer Android versions, but when I tried in older ones, I could merely see a black screen. After much searching, I found this page which explains the issue: http://code.google.com/p/android/issues/detail?id=4611 This is apparently a known glitch that's been around several Android versions for a good while. I read the whole thread and found several proposed solutions to the problem, but I really didn't like entirely any of them. I, for one, prefer to avoid as much static stuff as I can, and do things programmatically. I prefer automation over repetitive work. Some solutions suggested to create sub-screens as parent screens, then adding them onto the manifest file, and calling them from the parent screen through an intent. I'd really hate having to keep track of those things: entries in manifest, separated screen resource file, intents... So that was a no-no for me. I kept looking and found a programmatic approach I liked much better... only to find that it didn't work. It consisted of iterating through the whole view tree of the preference screen and assigning a proper background to preference sub-screens, but it just didn't work because, as I later found out after much debugging, preference sub-screens views are not a child of preference screen views. I had to find a way to achieve this myself. I tried as many things as I could think of, researched and researched to no avail. I was at the verge of abandoning at several occasions, but after some two weeks of continued effort and much debugging I found a workaround, which I posted in comment #35.
Opinion
It really isn't the perfect solution/approach, and I'm aware of several of its drawbacks, but it's one that works, so I decided I would share it. Hopefully I'm not being too ridiculous in my enthusiasm to share what has taken me what I'd consider quite a lot of effort, as I'm aware it's not that great of an issue, that any experienced coder could solve. But hey, I think sharing knowledge makes me a bit better, no matter how much I brag, than an experienced coder who keeps everything to himself. Just sharing my opinion, because I can't believe nobody ever had this problem before, but I do believe many have had it and didn't bother to share their knowledge.
I present you in the answer with a proposed class to use over several versions of Android, and some suggestions on its usage. I'm open to discussion and contributions to make it a better class.
Known issues:
Parent screen Decor view background is cloned onto child screen Decor view background, which apparently isn't the normal behavior.
Status: dismissed until somebody comes up with a good reason to fix this
Crashes upon screen rotation
Status: Fixed.
Probably related to resource visibility by newer API implementation (inner class PF)
Apparently inherited classes from preferenceFragment need to have all their members static. I guess it makes sense if you're supposed to inherit every time you need to use a new fragment
If you are on the latest ADT plugin, there is an option to easily create a preference Activity that supports most older Android versions as well as all the new ones.
Right click on your project -> Other -> Android Activity
Then choose SettingsActivity
The Activity created will take take care of working with both high and low API versions since it uses if statements to choose the appropriate method of displaying the preferences.
EDIT
A good point was brought up: Phone-Sized devices, regardless of API version use the old PreferenceActivity methods.
The quickest way to get API 11+ devices to use Fragments is to remove !isXLargeTablet(context); from isSimplePreferences()
private static boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;
}
However, now the user has more navigation to do.
This is because onBuildHeaders() is called.
To get rid of this, we will need to make our own PreferenceFragment that adds each xml resource.
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class AllPreferencesFragment extends PreferenceFragment{
#Override
public void onCreate (Bundle savedInstanceState){
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
// Add 'notifications' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(getActivity());
fakeHeader.setTitle(R.string.pref_header_notifications);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_notification);
// Add 'data and sync' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(getActivity());
fakeHeader.setTitle(R.string.pref_header_data_sync);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_data_sync);
// 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.
bindPreferenceSummaryToValue(findPreference("example_text"));
bindPreferenceSummaryToValue(findPreference("example_list"));
bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
bindPreferenceSummaryToValue(findPreference("sync_frequency"));
}
}
If you can determine the screen size from outside the Activity that launches the settings, you can specify a fragment for it to launch via EXTRA_SHOW_FRAGMENT
i.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "com.example.test.SettingsActivity$AllPreferencesFragment");
Or you can have the SettingsActivity determine whether or not to show this Fragment (assuming you're happy with the isXLargeTablet() method.
Change onBuildHeaders() to:
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this) && isXLargeTablet(this)) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
Add this method:
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupNewApiPhoneSizePreferences() {
if (!isXLargeTablet(this) && Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){
getFragmentManager().beginTransaction().replace(android.R.id.content, new AllPreferencesFragment()).commit();
}
}
And in onPostCreate() add the method call.
setupNewApiPhoneSizePreferences();
This should now use non-deprecated calls from API 11 onwards.
You can use this class to display a preference screen in all Android versions from 2.X to 4.X, by feeding it with a preference screen resource.
You may use it directly by renaming it if you like, but I'd suggest you to add it to your project as is, and inherit from it, which is much cleaner if you need to work with several parent preference screens.
If you'd like to use it directly, just replace prefs value with your preference screen resource ID.
If you'd like to inherit from it, you should do it like this:
import android.os.Bundle;
public class MyPreferencesActivity extends CompatiblePreferenceActivity
{
#Override
protected void onCreate(final Bundle savedInstanceState)
{
setPrefs(R.xml.mypreferencesactivity);
super.onCreate(savedInstanceState);
}
}
ALWAYS call setPrefs(int) before calling super.onCreate(Bundle)
If, for some reason, you'd just like to take advantage of the glitch-fix and create preferences on your own, you may either just copy the glitch-fix code into your own preference activity, or inherit from the class and catch the PrefsNotSet exception as follows:
import android.os.Bundle;
public class MyPreferencesActivity extends CompatiblePreferenceActivity
{
#Override
protected void onCreate(final Bundle savedInstanceState)
{
try{
super.onCreate(savedInstanceState);
}catch(PrefsNotSetException e){};
}
}
And finally, the class:
import android.annotation.TargetApi;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
public class CompatiblePreferenceActivity extends PreferenceActivity
{
private int prefs=0;
//Get/Set
public void setPrefs(int prefs)
{
this.prefs=prefs;
}
//Exception
protected static class PrefsNotSetException extends RuntimeException
{
private static final long serialVersionUID = 1L;
PrefsNotSetException()
{
super("\"prefs\" should be set to a valid preference resource ID.");
}
}
//Creation
#Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (prefs==0)
throw new PrefsNotSetException();
else
try {
getClass().getMethod("getFragmentManager");
AddResourceApi11AndGreater();
}
catch (NoSuchMethodException e) { //Api < 11
AddResourceApiLessThan11();
}
}
#SuppressWarnings("deprecation")
protected void AddResourceApiLessThan11()
{
addPreferencesFromResource(prefs);
}
#TargetApi(11)
protected void AddResourceApi11AndGreater()
{
PF.prefs=prefs;
getFragmentManager().beginTransaction().replace(
android.R.id.content, new PF()).commit();
}
#TargetApi(11)
public static class PF extends PreferenceFragment
{
private static int prefs;
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(prefs);
}
}
//Sub-screen background glitch fix
#SuppressWarnings("deprecation")
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference)
{
super.onPreferenceTreeClick(preferenceScreen, preference);
if (preference!=null)
if (preference instanceof PreferenceScreen)
if (((PreferenceScreen)preference).getDialog()!=null)
((PreferenceScreen)preference).getDialog().
getWindow().getDecorView().
setBackgroundDrawable(this.getWindow().
getDecorView().getBackground().getConstantState().
newDrawable());
return false;
}
}
Well, working with the autogenerated SettingsActivity got pretty old pretty quickly. One has to scroll up and down past boilerplate code - moreover it's full of yellow warnings and I hate yellow (deprecated warnings can't be avoided altogether though - see What to use instead of "addPreferencesFromResource" in a PreferenceActivity?, where also the matter of how to make cross API PreferenceActivity is touched also - and Was PreferenceFragment intentionally excluded from the compatibility package? for a discussion). And also you may easily get an NPE - did you know that onPostCreate() is actually onPostStart() - so findPreference() returns null in onStart().
Now there are solutions involving reflection but reflection is to be avoided (like hell it is) - and since we are not interested in pre 2 versions of android reflection can be avoided (see Is checking SDK_INT enough or is lazy loading needed for using newer android APIs ? Why?). Also there are solutions involving choosing a class at runtime - but having 2 classes sucks and is not OOP anyways (for those and other solutions see the answer to related question: PreferenceActivity Android 4.0 and earlier).
So I came up with an abstract base class, which is the correct Java and OO way of doing things (except if you need Eclair and below where you do need reflection and/or lazy loading of classes to avoid VerifyErrors), where I moved the autogenerated boilerplate code:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import java.util.List;
/**
* A {#link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*
* Defines two abstract methods that need be implemented by implementators.
*/
public abstract class BaseSettings extends PreferenceActivity {
/**
* Determines whether to always show the simplified settings UI, where
* settings are presented in a single list. When false, settings are shown
* as a master/detail two-pane view on tablets. When true, a single pane is
* shown on tablets.
*/
private static final boolean ALWAYS_SIMPLE_PREFS = false;
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
#TargetApi(Build.VERSION_CODES.GINGERBREAD)
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/** {#inheritDoc} */
#Override
public final boolean onIsMultiPane() { // never used by us
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Determines whether the simplified settings UI should be shown. This is
* true if this is forced via {#link #ALWAYS_SIMPLE_PREFS}, or the device
* doesn't have newer APIs like {#link PreferenceFragment}, or the device
* doesn't have an extra-large screen. In these cases, a single-pane
* "simplified" settings UI should be shown.
*/
private static final boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
#Override
protected final void onCreate(Bundle savedInstanceState) {
// disallow onCreate(), see comment in onPostCreate()
super.onCreate(savedInstanceState);
}
#Override
protected final void onStart() {
// disallow onStart(), see comment in onPostCreate()
super.onStart();
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
// onPostCreate() probably is needed because onBuildHeaders() is called
// after onCreate() ? This piece of err code should be called
// onPostStart() btw - so yeah
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
// findPreference will return null if setupSimplePreferencesScreen
// hasn't run, so I disallow onCreate() and onStart()
}
/**
* Shows the simplified settings UI if the device configuration if the
* device configuration dictates that a simplified, single-pane UI should be
* shown.
*/
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
buildSimplePreferences();
}
/** {#inheritDoc} */
/*
* Subclasses of PreferenceActivity should implement onBuildHeaders(List) to
* populate the header list with the desired items. Doing this implicitly
* switches the class into its new "headers + fragments" mode rather than
* the old style of just showing a single preferences list (from
* http://developer
* .android.com/reference/android/preference/PreferenceActivity.html) -> IE
* this is called automatically - reads the R.xml.pref_headers and creates
* the 2 panes view - it was driving me mad - #inheritDoc my - It does not
* crash in Froyo cause isSimplePreferences is always true for
* Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB - #Override has
* nothing to do with runtime and of course on Froyo this is never called by
* the system
*/
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public final void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(getHeadersXmlID(), target);
}
}
// =========================================================================
// Abstract API
// =========================================================================
/**
* Must return an id for the headers xml file. There you define the headers
* and the corresponding PreferenceFragment for each header which you must
* of course implement. This is used in the super implementation of
* {#link #onBuildHeaders(List)}
*
* #return an id from the R file for the xml containing the headers
*/
abstract int getHeadersXmlID();
/**
* Builds a pre Honeycomb preference screen. An implementation would use the
* (deprecated)
*{#link android.preference.PreferenceActivity#addPreferencesFromResource(int)}
*/
abstract void buildSimplePreferences();
}
And a sample implementation:
public final class SettingsActivity extends BaseSettings implements
OnSharedPreferenceChangeListener {
private static final int PREF_HEADERS_XML = R.xml.pref_headers;
private static CharSequence master_enable;
private OnPreferenceChangeListener listener;
private static Preference master_pref;
private static final String TAG = SettingsActivity.class.getSimpleName();
private SharedPreferences sp;
/** Used as canvas for the simple preferences screen */
private static final int EMPTY_PREF_RESOURCE = R.xml.pref_empty;
private static int PREF_RESOURCE_SETTINGS = R.xml.pref_data_sync;
// abstract overrides
#Override
int getHeadersXmlID() {
return PREF_HEADERS_XML;
}
#Override
void buildSimplePreferences() {
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// THIS is a blank preferences layout - which I need so
// getPreferenceScreen() does not return null - so I can add a header -
// alternatively you can very well comment everything out apart from
// addPreferencesFromResource(R.xml.pref_data_sync);
addPreferencesFromResource(EMPTY_PREF_RESOURCE);
// Add 'data and sync' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_data_sync);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
}
// here is the work done
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
master_enable = getResources().getText(
R.string.enable_monitoring_master_pref_key);
listener = new ToggleMonitoringListener();
// DefaultSharedPreferences - register listener lest Monitor aborts
sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.registerOnSharedPreferenceChangeListener(this);
master_pref = findPreference(master_enable.toString());
}
#Override
protected void onResume() {
super.onResume();
master_pref.setOnPreferenceChangeListener(listener); // no way to
// unregister, see: https://stackoverflow.com/a/20493608/281545 This
// listener reacts to *manual* updates - so no need to be active
// outside onResume()/onPause()
}
#Override
protected void onDestroy() {
// may not be called (as onDestroy() is killable), but no leak,
// see: https://stackoverflow.com/a/20493608/281545
sp.unregisterOnSharedPreferenceChangeListener(this);
super.onDestroy();
}
/**
* Toggles monitoring and sets the preference summary.Triggered on *manual*
* update of the *single* preference it is registered with, but before this
* preference is updated and saved.
*/
private static class ToggleMonitoringListener implements
OnPreferenceChangeListener {
ToggleMonitoringListener() {}
#Override
public boolean
onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
final boolean enable = (Boolean) newValue;
Monitor.enableMonitoring(preference.getContext(), enable);
final CheckBoxPreference p = (CheckBoxPreference) preference;
preference.setSummary((enable) ? p.getSummaryOn() : p
.getSummaryOff());
return true;
}
return false;
}
}
/**
* This fragment is used when the activity is showing a two-pane
* settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public final static class DataSyncPreferenceFragment extends
PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "onCreate");
addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
master_pref = findPreference(master_enable.toString());
}
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (master_enable == null || master_pref == null) return;
if (master_enable.toString().equals(key)) {
refreshMasterPreference();
}
}
/**
* #param key
*/
private void refreshMasterPreference() {
final Boolean isMonitoringEnabled = AccessPreferences.get(this,
master_enable.toString(), false);
Log.w(TAG, "Stored value: " + isMonitoringEnabled);
final CheckBoxPreference p = (CheckBoxPreference) master_pref;
final boolean needsRefresh = p.isChecked() != isMonitoringEnabled;
if (needsRefresh) {
p.setChecked(isMonitoringEnabled);
p.setSummary((isMonitoringEnabled) ? p.getSummaryOn() : p
.getSummaryOff());
}
}
}
So the main idea is you provide an xml for preferences with headers:
public final void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(getHeadersXmlID(), target);
}
}
where:
#Override
int getHeadersXmlID() {
return PREF_HEADERS_XML;
}
and PREF_HEADERS_XML:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- These settings headers are only used on tablets. -->
<header
android:fragment=".activities.SettingsActivity$DataSyncPreferenceFragment"
android:title="#string/pref_header_data_sync" />
</preference-headers>
and setting up the simple preferences in buildSimplePreferences()
I am interested into making this into a more general API - probably including the sBindPreferenceSummaryToValueListener - so ideas welcome.
Ah, yes, the sBindPreferenceSummaryToValueListener fluff:
// FLUFF AHEAD:
// the fluff that follows is for binding preference summary to value -
// essentially wrappers around OnPreferenceChangeListener - just so
// you get an idea of the mess this autogenerated piece of, code, was
// formatter:off
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
/* 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) {
// For ringtone preferences, look up the correct display
// value using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
// preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone
// display name.
String name = ringtone
.getTitle(preference.getContext());
preference.setSummary(name);
}
}
} else if (preference instanceof CheckBoxPreference) {
boolean b = (Boolean) value;
Log.w(TAG, "::::value " + b);
final CheckBoxPreference p =(CheckBoxPreference)preference;
preference.setSummary((b) ? p.getSummaryOn() : p
.getSummaryOff());
Log.w(TAG, p.getKey() + " :: " + p.isChecked());
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
}; */
/**
* 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.
sBindPreferenceSummaryToValueListener.onPreferenceChange(
preference,
PreferenceManager.getDefaultSharedPreferences(
preference.getContext()).getString(preference.getKey(), ""));
} */
Ok i'm following an android book and they are adding settings to a sudoku game using a class that extends PreferenceActivity, this class is called by an intent and all it does is addPreferencesFromResource(R.xml.settings), this approach has been deprecated and its not working anymore, here is the code from the book:
package org.example.sudoku;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class Prefs extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.settings);
}
}
Now i have looked into this matter and found that you have to instance a PreferenceManager in order to do this, but in the example i found they extends the Prefs class from PreferenceFragment (not PreferenceActivity as in the book), i managed to work on the code as follows:
/*
* this is for use from API version 11 and after...
*
*/
package org.example.sudoku;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
public class Prefs extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure default values are applied. In a real app, you would
// want this in a shared function that is used to retrieve the
// SharedPreferences wherever they are needed.
PreferenceManager.setDefaultValues(getActivity(),
R.xml.settings, false);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.settings);
}
}
But this doesn´t do the job, i don´t know if its because i am calling this class from an intent and this class extends PreferenceFragment instead of PreferenceActivy or this is not the way of doing this thing, can someone help me out to understand this?
final String PREF_SETTINGS_FILE_NAME = "PrefSettingsFile";
To read the values of your preference variables.
SharedPreferences preferences = getSharedPreferences(PREF_SETTINGS_FILE_NAME, MODE_PRIVATE);
prefSettingsValue1= preferences.getInt("value1", 1); // default value of prefSettingsValue1 will be 1 in case you are trying to read a non-existent value. You can specify it according to your wish. (like I have done for the next value)
prefSettingsValue2= preferences.getInt("value2", 0);
prefSettingsValue3= preferences.getInt("value3", 1);
To write values into your preference variables.
SharedPreferences preferences = getSharedPreferences(PREF_SETTINGS_FILE_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("vaule1", prefSettingsValue1);
editor.putInt("value2",prefSettingsValue2);
editor.putInt("value3",prefSettingsValue3);
editor.commit();
Note : No need to use any XML file to store the preferences. Just save the values in the Preference variables and read them later in you application. Not just integers, you can also get and put Strings by using getString and putString functions to read and write, respectively. Also, you do not need to extend any Preference Activity/Fragment.
I am having a few issues with updating the summary line in the SharedPreferences as a preference changes. I have a registered OnSharePreferenceChangeListener in the onResume(), and an unregister of the same in the onPause().
The listener is functioning, and I am able to use the onSharedPreferenceChanges() method. The issue I am having is being able to retrieve the preference there so that I can call setSummary(). I am in Ice Cream Sandwich, and it appears as though the findPreference(key) method is deprecated. So:
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);}
is not functioning, and actually returns null for pref. From the examples I have seen, you need to get a preference to call setSummary() on it, and ideas?
You shouldn't use an onSharedPreferenceChangedListener for this.
Instead, use something similar to this.
ListPreference myPreference = (ListPreference) findPreference("preference_key");
myPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (((String)newValue).equals("some_value")) {
preference.setSummary("my summary");
}
}
});
findPreference is not deprecated, but rather you shouldn't be using a PreferenceActivity (that is deprecated). If you only need to support Android 3.0+ then you should switch to PreferenceFragment's, the new method. If you need to support Android 2.1+ then it is fine and you can ignore the warnings.
I have been trying to use PreferenceFragment in my code, and I was also seeing findPreference(key) return null. The sample code on the Settings documentation page for using OnSharedPreferenceChangeListener hasn't been fully updated for PreferenceFragment and you'll crash with NullPointerException if you use it verbatim.
I finally figured it out: You have to find the Preference via the PreferenceFragment because of course that's where the preferences are now. Obvious in hindsight. Something like this:
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener
{
protected SettingsFragment settingsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settingsFragment = new SettingsFragment();
getFragmentManager().beginTransaction().replace(android.R.id.content, settingsFragment).commit();
}
// ...
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("your_key")) {
String newValue = sharedPreferences.getString(key, "");
settingsFragment.findPreference(key).setSummary(newValue);
}
}
}
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?
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.
#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.
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.
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
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"
/>
I am using this library, which has an AAR in mavenCentral so you can easily include it if you are using Gradle.
compile 'com.github.machinarius:preferencefragment:0.1.1'