Using the Eclipse PreferenceActivity Generator, it generated the necessary files to add a setting to my app. It works on small devices, but crashes on tablets.
LogCat:
05-09 10:56:45.812: E/AndroidRuntime(1823): FATAL EXCEPTION: main
05-09 10:56:45.812: E/AndroidRuntime(1823): Process: com.nextgenintl.aimassistant, PID: 1823
05-09 10:56:45.812: E/AndroidRuntime(1823): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nextgenintl.aimassistant/com.nextgenintl.aimassistant.PrefsActivity}: java.lang.RuntimeException: Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.nextgenintl.aimassistant.PrefsActivity has not checked if fragment com.nextgenintl.aimassistant.SettingsActivity$GeneralPreferenceFragment is valid.
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread.access$800(ActivityThread.java:135)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.os.Handler.dispatchMessage(Handler.java:102)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.os.Looper.loop(Looper.java:136)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread.main(ActivityThread.java:5017)
05-09 10:56:45.812: E/AndroidRuntime(1823): at java.lang.reflect.Method.invokeNative(Native Method)
05-09 10:56:45.812: E/AndroidRuntime(1823): at java.lang.reflect.Method.invoke(Method.java:515)
05-09 10:56:45.812: E/AndroidRuntime(1823): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
05-09 10:56:45.812: E/AndroidRuntime(1823): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
05-09 10:56:45.812: E/AndroidRuntime(1823): at dalvik.system.NativeStart.main(Native Method)
05-09 10:56:45.812: E/AndroidRuntime(1823): Caused by: java.lang.RuntimeException: Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.nextgenintl.aimassistant.PrefsActivity has not checked if fragment com.nextgenintl.aimassistant.SettingsActivity$GeneralPreferenceFragment is valid.
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.preference.PreferenceActivity.isValidFragment(PreferenceActivity.java:898)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.preference.PreferenceActivity.switchToHeaderInner(PreferenceActivity.java:1179)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.preference.PreferenceActivity.switchToHeader(PreferenceActivity.java:1219)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.preference.PreferenceActivity.onCreate(PreferenceActivity.java:564)
05-09 10:56:45.812: E/AndroidRuntime(1823): at com.nextgenintl.aimassistant.PrefsActivity.onCreate(PrefsActivity.java:46)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.Activity.performCreate(Activity.java:5231)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
05-09 10:56:45.812: E/AndroidRuntime(1823): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159)
05-09 10:56:45.812: E/AndroidRuntime(1823): ... 11 more
If you want to see the source:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
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.
*/
public class PrefsActivity 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;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Show the Up button in the action bar.
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
// TODO: If Settings has multiple levels, Up should navigate up
// that hierarchy.
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
}
/**
* 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;
}
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// Add 'general' preferences.
addPreferencesFromResource(R.xml.pref_general);
// Add 'notifications' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
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(this);
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"));
}
/** {#inheritDoc} */
#Override
public boolean onIsMultiPane() {
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* 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 boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
/** {#inheritDoc} */
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
/**
* 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 {
// 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(),
""));
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
// 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"));
}
}
/**
* This fragment shows notification preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends
PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_notification);
// 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("notifications_new_message_ringtone"));
}
}
/**
* This fragment shows data and sync preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class DataSyncPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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("sync_frequency"));
}
}
}
The XML does not seem to have any errors as the strings are generated in an alternative file (e.g. string_activity_prefs.xml), and works fine on phones.
Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.nextgenintl.aimassistant.PrefsActivity has not checked if fragment com.nextgenintl.aimassistant.SettingsActivity$GeneralPreferenceFragment is valid.
Override isValidFragment(String) in onCreate() of PrefsActivity.
You can just Override isValidFragment(String) in your PreferenceActivity
protected boolean isValidFragment (String fragmentName) {
if(PrefsActivity.class.getName().equals(fragmentName)){
return true;
}
return false;
}
For more information you can Read this A New Vulnerability in the Android Framework: Fragment Injection
Related
I'm new to Android, and I have this error when I'm trying to launch the SettingsActivity:
FATAL EXCEPTION: main
Process: com.udacity.frliran.sunshine, PID: 24887
android.content.res.Resources$NotFoundException: Resource ID #0x0
at android.content.res.Resources.getValue(Resources.java:2345)
at android.content.res.Resources.getDrawable(Resources.java:1850)
at android.content.Context.getDrawable(Context.java:410)
at android.support.v4.content.ContextCompatApi21.getDrawable(ContextCompatApi21.java:26)
at android.support.v4.content.ContextCompat.getDrawable(ContextCompat.java:321)
at android.support.v7.internal.widget.TintManager.getDrawable(TintManager.java:177)
at android.support.v7.internal.widget.TintManager.getDrawable(TintManager.java:170)
at android.support.v7.widget.AppCompatImageHelper.setImageResource(AppCompatImageHelper.java:53)
at android.support.v7.widget.AppCompatImageView.setImageResource(AppCompatImageView.java:74)
at android.preference.PreferenceActivity$HeaderAdapter.getView(PreferenceActivity.java:349)
at android.widget.AbsListView.obtainView(AbsListView.java:2820)
at android.widget.ListView.onMeasure(ListView.java:1174)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1435)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:721)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:612)
at android.view.View.measure(View.java:18563)
at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1062)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:614)
at android.view.View.measure(View.java:18563)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:874)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:612)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
at android.support.v7.internal.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:135)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.support.v7.internal.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:393)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1435)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:721)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:612)
at android.view.View.measure(View.java:18563)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:430)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2888)
at android.view.View.measure(View.java:18563)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2248)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1306)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1548)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1191)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6642)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:777)
at android.view.Choreographer.doCallbacks(Choreographer.java:590)
at android.view.Choreographer.doFrame(Choreographer.java:560)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:763)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5938)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Me
Here is the SettingsActivity code:
public class SettingsActivity extends AppCompatPreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
NavUtils.navigateUpFromSameTask(this);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
/**
* {#inheritDoc}
*/
#Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* {#inheritDoc}
*/
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
/**
* 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 {
// 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(), ""));
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| WeatherPreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class WeatherPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_weather);
setHasOptionsMenu(true);
// 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(getString(R.string.pref_location_key)));
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_units_key)));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
startActivity(new Intent(getActivity(), SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
}
This is a pref_header.xml file that came when I added the SettingsActivity:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="com.udacity.frliran.sunshine.SettingsActivity$WeatherPreferenceFragment"
android:title="#string/pref_header_weather" />
</preference-headers>
And this is the pref_weather.xml file:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="#string/pref_location_key"
android:title="#string/pref_weather_title_location"
android:defaultValue="#string/pref_location_default"
android:selectAllOnFocus="true"
android:inputType="textCapWords"
android:capitalize="words"
android:singleLine="true"
android:maxLines="1" />
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
dismiss it. -->
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
<ListPreference
android:key="#string/pref_units_key"
android:title="#string/pref_weather_title_units"
android:defaultValue="#string/pref_weather_default_units"
android:entries="#array/pref_weather_units_titles"
android:entryValues="#array/pref_weather_units_values"
android:negativeButtonText="#null"
android:positiveButtonText="#null" />
</PreferenceScreen>
When I added the SettingsActivity it had a default of settings that I did not need. I thought it had something to do with this so I removed them, but it didn't solve it.
The error doesn't point me where to search for this error to fix it, and I don't know how to start investigate for a solution...
Did you initialize ImageView?
Because this error is due to ttwo reasons.
1. ImageView not initialized
2. R.drawable.image_source does not exist.
Go to line no. 74 and post that code here.
I don't know if this answer is the best option, but since I have no other answer and it was my last resort to solving this error so I posted it.
This is what I did:
I copied out the settings activity and then deleted it and added a new SettingsActivity to the project, then I merged the code I copied from my first SettingsActivity to the new one and it just works.
I think that maybe there are some resources or relations that the IDE adds when adding a SettingsActivity that may have been missing earlier, so there was actually no problem with the code.
I want to give the user the option of choosing between several different colour themes in my app in the settings activity. I know how to bundle/save/load the prefs, nevertheless I'm having trouble accomplishing this.
I have defined my colors in colors.xml, defined several themes in v21styles.xml, and added the options to a listview in the SettingsActivity.java.
The only part I am having difficulty with is actually applying the new themes. I would like to set up a switch statement to apply the themes using the built in android methods, but am not exactly how to accomplish this on API 21 (lolipop).
public class SettingsActivity 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;
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
}
/**
* 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;
}
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// Add 'general' preferences.
addPreferencesFromResource(R.xml.pref_general);
}
/**
* {#inheritDoc}
*/
#Override
public boolean onIsMultiPane() {
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* 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 boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
/**
* {#inheritDoc}
*/
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
/**
* 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 {
// 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(), ""));
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
// 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"));
}
}
/**
* This fragment shows notification preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_notification);
// 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("notifications_new_message_ringtone"));
}
}
/**
* This fragment shows data and sync preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class DataSyncPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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("sync_frequency"));
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch (item.getItemId()) {
case R.string.theme:
//apply red theme
R.style.RedTheme;
break;
case R.style.IndigoTheme:
//apply blue theme
case R.style.AppTheme:
//apply default theme
return true;
}
return super.onOptionsItemSelected(item);
}}
The part at the bottom is where I am trying to do the actual theme swapping. Is this the correct way to handle preference options even though it's technically not a "menu item"?
I have defined the theme names in my styles.xml, and v21/styles.xml. In the manifest the theme is set to AppTheme. I just want to be able to swap themes when the user selects a new theme in the preferences activity.
I think you have to do this with following in your switch case:-
themeUtils.changeToTheme(this, themeUtils.ur_theme_name);
Take a look into this following tutorial http://www.developer.com/ws/android/changing-your-android-apps-theme-dynamically.html..Hope it will helps you.
It gives me the error "findPreference(java.lang.CharSequence) is deprecated" .Currently, I am targeting API 10 and above for my application. Any kind of help to resolve this will be appreciated.
public class SettingsActivity extends PreferenceActivity
implements Preference.OnPreferenceChangeListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add 'general' preferences, defined in the XML file
// TODO: Add preferences from XML
addPreferencesFromResource(R.xml.pref_general);
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_location_key));
// For all preferences, attach an OnPreferenceChangeListener so the UI summary can be
// updated when the preference changes.
// TODO: Add preferences
}
/**
* Attaches a listener so the summary is always updated with the preference value.
* Also fires the listener once, to initialize the summary (so it shows up before the value
* is changed.)
*/
private void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(this);
// Trigger the listener immediately with the preference's
// current value.
onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
#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 (since they have separate labels/values).
ListPreference listPreference = (ListPreference) preference;
int prefIndex = listPreference.findIndexOfValue(stringValue);
if (prefIndex >= 0) {
preference.setSummary(listPreference.getEntries()[prefIndex]);
}
} else {
// For other preferences, set the summary to the value's simple string representation.
preference.setSummary(stringValue);
}
return true;
}
}
It is deprecated because Android moved to fragment-based activities. Calling findPreference(CharSequence) will still work in higher API levels. You are just encouraged to use fragments instead of a PreferenceActivity.
The reason for the deprecation can be found in the source:
This function is not relevant for a modern fragment-based PreferenceActivity.
In API 11+ you should use PreferenceFragment.
If you want to have your IDE ignore the error just add the following to your method: #SuppressWarnings("deprecation")
public class SettingsActivity extends PreferenceActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add 'general' preferences, defined in the XML file
getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit();
// For all preferences, attach an OnPreferenceChangeListener so the UI summary can be
// updated when the preference changes.
}
public static class MyPreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_details);
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_location_key)));
}
private void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(this);
// Trigger the listener immediately with the preference's
// current value.
onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
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 (since they have separate labels/values).
ListPreference listPreference = (ListPreference) preference;
int prefIndex = listPreference.findIndexOfValue(stringValue);
if (prefIndex >= 0) {
preference.setSummary(listPreference.getEntries()[prefIndex]);
}
} else {
// For other preferences, set the summary to the value's simple string representation.
preference.setSummary(stringValue);
Log.v("onpreferencechange",stringValue);
}
return true;
}
}
Instead of adding the methods
bindPreferenceSummaryToValue(Preference preference) &
onPreferenceChange(Preference preference, Object value)
inside the main SettingsActivity class create a fragment class which implements the Preference.OnPreferenceChangeListener add the both the methods inside it.
check out the code provided
hope it helps
Well here is the solution:
API Level 11+ introduced PreferenceFragment as another way of
constructing the contents of a PreferenceActivity. You are welcome to
use them, but if you are still supporting older devices, you cannot
use PreferenceFragment for those devices.
Try the following link:
Non Deprecated findPreference() Method? - Android
I am new to android and I would appreciate your help. This is my first question =D Sorry for giving only two links and no images, I have not enought reputation to made my question more clear
Acording to android API guides for settings, I am using PreferenceFragment because I am developing for Android 3.0 (API level 11) and higher.
I want to achieve a effect as shown at figure 4 and 5 from android API guides for settings link.
On tablet emulator it looks ok, but on handset emulator (and real device) I can not achieve it. Instead I get "Sound", "Display", "Storage", "Battery"... as PreferenceCategory (like "Device" seems) followed by all the options that should be in a new screen (activity). All in only one screen.
Here are my files, thanks in advance
Settings.java (NOTE: Autogenerated and completed by myself)
package com.example;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import java.util.List;
import java.util.concurrent.FutureTask;
/**
* 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.
*/
public class Settings extends PreferenceActivity {
// From stackoverflow to avoid error API 19
protected boolean isValidFragment(String fragmentName) {
if (ProfilePreferenceFragment.class.getName().equals(fragmentName)) {
return true;
} else if (ChatPreferenceFragment.class.getName().equals(fragmentName)) {
return true;
} else if (NotificationPreferenceFragment.class.getName().equals(
fragmentName)) {
return true;
} else if (ContactPreferenceFragment.class.getName().equals(
fragmentName)) {
return true;
} else if (PaymentPreferenceFragment.class.getName().equals(
fragmentName)) {
return true;
} else if (AccessibilityPreferenceFragment.class.getName().equals(
fragmentName)) {
return true;
} else {
return false;
}
}// From stackoverflow to avoid error API 19
/**
* 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;
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
}
/**
* Shows the simplified settings UI if the device configuration
* dictates that a simplified, single-pane UI should be
* shown.
*/
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// Add 'profile' preferences.
addPreferencesFromResource(R.xml.pref_profile);
// Add 'chats' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_chat);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_chat);
// Add 'notification' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_notification);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_notification);
// Add 'contact' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_contact);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_contact);
// Add 'payment' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_payment);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_payment);
// Add 'accessibility' preferences, and a corresponding header.
fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_accessibility);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_accesibility);
// 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"));
}
/** {#inheritDoc} */
#Override
public boolean onIsMultiPane() {
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* 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 boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
/** {#inheritDoc} */
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
/**
* 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).
} 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 {
// 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(),
""));
}
/**
* This fragment shows profile preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ProfilePreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_profile);
// 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(""));
// bindPreferenceSummaryToValue(findPreference(""));
}
}
/**
* This fragment shows chat preferences only. It is used when the activity
* is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ChatPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_chat);
// 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("pref_chat_enter_send"));
bindPreferenceSummaryToValue(findPreference("pref_chat_download_message"));
bindPreferenceSummaryToValue(findPreference("pref_chat_download_multimedia"));
bindPreferenceSummaryToValue(findPreference("pref_chat_font_size"));
bindPreferenceSummaryToValue(findPreference("pref_chat_wallpaper"));
bindPreferenceSummaryToValue(findPreference("pref_chat_cloud"));
}
}
/**
* This fragment shows notification preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends
PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_notification);
// 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("pref_notification_new_contact"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_vibration"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_vibration_repeat"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_led"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_led_repeat"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_screen"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_individual_screen_repeat"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_vibration"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_vibration_repeat"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_led"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_led_repeat"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_screen"));
bindPreferenceSummaryToValue(findPreference("pref_notification_cat_group_screen_repeat"));
}
}
/**
* This fragment shows contact preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ContactPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_contact);
// 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(""));
// bindPreferenceSummaryToValue(findPreference(""));
}
}
/**
* This fragment shows payment preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class PaymentPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_payment);
// 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(""));
// bindPreferenceSummaryToValue(findPreference(""));
}
}
}
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="com.example.Settings$ProfilePreferenceFragment"
android:title="#string/pref_header_profile" />
<header
android:fragment="com.example.Settings$ChatPreferenceFragment"
android:title="#string/pref_header_chat" />
<header
android:fragment="com.example.Settings$NotificationPreferenceFragment"
android:title="#string/pref_header_notification" />
<header
android:fragment="com.example.Settings$ContactPreferenceFragment"
android:title="#string/pref_header_contact" />
<header
android:fragment="com.example.Settings$PaymentPreferenceFragment"
android:title="#string/pref_header_payment" />
</preference-headers>
and one section example, others are like this. pref_notification.xml
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<SwitchPreference
android:key="pref_notification_new_contact"
android:summary="#string/pref_notification_new_contact_summary"
android:title="#string/pref_notification_new_contact_title" />
<PreferenceCategory
android:key="pref_notification_cat_individual"
android:title="#string/pref_notification_cat_individual" >
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_individual_vibration"
android:title="#string/pref_notification_cat_individual_vibration_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_individual_vibration"
android:key="pref_notification_cat_individual_vibration_repeat"
android:summary="#string/pref_notification_cat_individual_vibration_repeat_summary"
android:title="#string/pref_notification_cat_individual_vibration_repeat" />
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_individual_led"
android:title="#string/pref_notification_cat_individual_led_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_individual_led"
android:key="pref_notification_cat_individual_led_repeat"
android:summary="#string/pref_notification_cat_individual_led_repeat_summary"
android:title="#string/pref_notification_cat_individual_led_repeat" />
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_individual_screen"
android:title="#string/pref_notification_cat_individual_screen_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_individual_screen"
android:key="pref_notification_cat_individual_screen_repeat"
android:summary="#string/pref_notification_cat_individual_screen_repeat_summary"
android:title="#string/pref_notification_cat_individual_screen_repeat" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_notification_cat_group"
android:title="#string/pref_notification_cat_group" >
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_group_vibration"
android:title="#string/pref_notification_cat_group_vibration_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_group_vibration"
android:key="pref_notification_cat_group_vibration_repeat"
android:summary="#string/pref_notification_cat_group_vibration_repeat_summary"
android:title="#string/pref_notification_cat_group_vibration_repeat" />
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_group_led"
android:title="#string/pref_notification_cat_group_led_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_group_led"
android:key="pref_notification_cat_group_led_repeat"
android:summary="#string/pref_notification_cat_group_led_repeat_summary"
android:title="#string/pref_notification_cat_group_led_repeat" />
<SwitchPreference
android:defaultValue="true"
android:key="pref_notification_cat_group_screen"
android:title="#string/pref_notification_cat_group_screen_title" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="pref_notification_cat_group_screen"
android:key="pref_notification_cat_group_screen_repeat"
android:summary="#string/pref_notification_cat_group_screen_repeat_summary"
android:title="#string/pref_notification_cat_group_screen_repeat" />
</PreferenceCategory>
</PreferenceScreen>
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(), ""));
} */