I've implemented my own PreferenceFragment subclass (detailed here), and want to listen for preference changes within it. PreferenceFragment provides you with two ways of doing this:
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
and
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
Which one should be used? What's the difference? I don't really understand the distinction made in the Android docs.
The core difference is in their names, PreferenceManger grants access to different functionalities to the developer for managing SharedPreferences, such as retrieving the map of current preference values or setting user preferences. to their default values. PreferenceScreen handles displaying a screen of user preferences, so that the user can assign values to them. Sometimes this means displaying a list item on a screen with other preferences, that opens another screen with more preferences when clicked, as is the case when PreferenceScreens are nested.
Your question implies that you think there is a difference between what PreferenceManager.getSharedPreferences() and PreferenceScreen.getSharedPreferences() does, but according to the source code, they are identical.
PreferenceScreen:
public SharedPreferences getSharedPreferences() {
if (mPreferenceManager == null) {
return null;
}
return mPreferenceManager.getSharedPreferences();
}
So the PreferenceManger and PreferenceScreen are different entities, but the SharedPreference those method return should be the same object, since PreferenceScreen calls the method from PreferenceManager. I hope that is the answer you've been seeking.
If you have a choice, go with PreferenceManager.getSharedPreferences(), it's more obvious and one fewer method call internally.
Fun fact:
PreferenceFragment:
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
public PreferenceScreen getPreferenceScreen() {
return mPreferenceManager.getPreferenceScreen();
}
The first one gets the shared preferences from the PreferenceManager. The second one, from the PreferenceScreen, that inherits this method from Preference class.
I think this is not a functional difference, because both return probably the same instance of the SharedPreferences objects, but I think it's clearer to use the first one (using PreferenceManager instead of PreferenceScreen).
PreferenceScreen see domentation here
PreferenceScreen class can appear in two places:
When a PreferenceActivity points to this, it is used as the root and
is not shown (only the contained preferences are shown).
When it appears inside another preference hierarchy, it is shown and
serves as the gateway to another screen of preferences (either by
showing another screen of preferences as a Dialog or via a
startActivity(android.content.Intent) from the getIntent()). The
children of this PreferenceScreen are NOT shown in the screen that
this PreferenceScreen is shown in. Instead, a separate screen will be
shown when this preference is clicked.
PreferenceManager see documentation here:
Difference :
getPreferenceManager () returns the current preference manager associated with the fragment.
getPreferenceScreen () returns the root PreferenceScreen i.e. root preference screen used in the fragment from preference xml file(preferences.xml).
Related
How do i update a Prefence UI instance that i created in a Setting Activity from another activity (Main Activity)?
I tried using these lines in Main Activity to update the Preference within Settings Activity, but I get ClassCastException.
Preference IsFeature =(Preference)((PreferenceActivity)context).findPreference((getString(R.string.key_enable_feature)));
IsFeature.setEnabled(True);
Just wondering whether theres another way to do this?
Any help, feedback or answers would be great!
You can try this:
In xml of settings get the "key" attribute from element you want to change (in bottom example it's "example_switch"). Than put this code in onClick method of button or wherever else you want to. This below takes the preference of switch in general settings and sets its value to false.
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean("example_switch", false); // "example_switch" - "key" attribute of your element | false - value
editor.commit();
I've created a settings menu for my app based on PreferenceFragment, and would like to access the settings dialog(s) from elsewhere in the app without having to open the settings menu.
My settings menu has this:
and I want to show the same dialog when I click this menu item from the main activity:
The main Activity has one ListFragment which is where all of the UI handling code is. Neither is a PreferenceActivity or PreferenceFragment.
I just want to invoke the same PreferenceFragment object to get to the dialog, otherwise I'd have to write custom code to handle the preference changes manually, which I'd like to avoid.
I thought adding the PreferenceFragment to the FragmentManager in the main Activity would properly instantiate it, but it doesn't seem to work.
From my menu handler code for the "Sort" option:
SettingsFragment fragment = (SettingsFragment) getFragmentManager().findFragmentByTag(SettingsActivity.FRAGMENT_TAG);
// first run case
if (fragment == null) {
fragment = SettingsFragment.newInstance(getActivity());
getFragmentManager().beginTransaction().add(fragment, SettingsActivity.FRAGMENT_TAG).commit();
}
CustomListPreference listPref = (CustomListPreference) fragment.findPreference(SettingsFragment.KEY_PREF_SORTORDER);
listPref.show(); // invokes showDialog(null)
This crashes with a NullPointerException on listPref, which shows the PreferenceFragment was not properly initialized.
Is there any way to achieve this effect, or do I have to write the functionality as an AlertDialog and manually handle the Preference changes?
I think you'll have to write this functionality yourself outside of the Preference classes.
Preference, PreferenceActivity, and PreferenceFragment were all
designed to work together to provide a consistent UIX for android
apps. As such it's recommended to use them together as they were
intended.
You can't directly replicate the UI of PreferenceActivity or
PreferenceFragment outside of those two classes in a regular activity
because the UI is built from Preference objects, not View objects like
regular Activities. So if you want that particular UI you'd have to
try and duplicate it using custom Views.
https://discussions.udacity.com/t/way-to-do-listpreference-outside-of-settings-menu/45473
I have a CheckBoxPreference defined as follows:
<CheckBoxPreference
android:defaultValue="true"
android:key="prefVisible"
android:summary="#string/pref_visible_summary"
android:title="#string/pref_visible" >
</CheckBoxPreference>
My application uses this preference to control the visibility of a view. When I first start my application (on a new wiped emulator) the view is not shown. However, when the I go to the preferences screen (activity) the checkbox is shown as checked.
Does this mean that the defaultValue attribute is not actually setting the preference but rather it's just setting the value of the checkbox if there is no underlying data (as would be the case on a brand new install). And does this also mean that the preference is set only after the user enters/exits the preferences screen (activity) for the first time, otherwise it's undefined?
Note that in order to get my app to work the way I intended it to work I relied on the default value argument to the preferences getter method as follows:
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isVisible = sharedPrefs.getBoolean("prefVisible", true); // default = true
This leaves me a bit confused as to why there are 2 ways to control the default value of a preference: defining it in the Xml or providing the default value in the getBoolean method.
No the preferences can be set if you call PreferenceManager.setDefaultValues. So if you call this when the first time the app is launched then your view will be shown.
You can read more at http://developer.android.com/guide/topics/ui/settings.html
I want to give the user the choice between a few different themes, and was wondering if this is an alright way of doing things. I did a little test with this method and it worked, but I think there may be better ways and think it may cause some problems later on, so wanted to ask.
I was thinking of creating a different layout for each theme, and in onCreate just having a switch for the setContentView() method. I'd load a saved SharedPreference value (integer) first and depending on what that value was display the corresponding layout. Obviously the user could change the SharedPreference value with a button or something.
As these layouts would be basically the same but with different colours, I'd want to use the same IDs for my TextViews and other Views in each layout file. My main question is would this cause problems?
Sorry for the wall of text with no code. I'd just like to get a general idea of good practice for this situation. Thanks in advance.
I actually have this feature in my application and additionally, I allow users to change theme at runtime. As reading a value from preferences takes some time, I'm getting a theme id via globally accessible function which holds cached value.
As already pointed out - create some Android themes, using this guide. You will have at least two <style> items in your styles.xml file. For example:
<style name="Theme.App.Light" parent="#style/Theme.Light">...</style>
<style name="Theme.App.Dark" parent="#style/Theme">...</style>
Now, you have to apply one of these styles to your activities. I'm doing this in activitie's onCreate method, before any other call:
setTheme(MyApplication.getThemeId());
getThemeId is a method which returns cached theme ID:
public static int getThemeId()
{
return themeId;
}
This field is being updated by another method:
public static void reloadTheme()
{
themeSetting = PreferenceManager.getDefaultSharedPreferences(context).getString("defaultTheme", "0");
if(themeSetting.equals("0"))
themeId = R.style.Theme_Light;
else
themeId = R.style.Theme_Dark;
}
Which is being called whenever preferences are changed (and, on startup of course). These two methods reside in MyApplication class, which extends Application. The preference change listener is described at the end of this post and resides in main activity class.
The last and pretty important thing - theme is applied, when an activity starts. Assuming, you can change a theme only in preference screen and that there's only one way of getting there, i.e. from only one (main) activity, this activity won't be restarted when you will exit preference screen - the old theme still will be used. Here's the fix for that (restarts your main activity):
#Override
protected void onResume() {
super.onResume();
if(schduledRestart)
{
schduledRestart = false;
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
scheduledRestart is a boolean variable, initially set to false. It's set to true when theme is changed by this listener, which also updates cached theme ID mentioned before:
private class ThemeListener implements OnSharedPreferenceChangeListener{
#Override
public void onSharedPreferenceChanged(SharedPreferences spref, String key) {
if(key.equals("defaultTheme") && !spref.getString(key, "0").equals(MyApplication.getThemeSetting()))
{
MyApplication.reloadTheme();
schduledRestart = true;
}
}
sp = PreferenceManager.getDefaultSharedPreferences(this);
listener = new ThemeListener();
sp.registerOnSharedPreferenceChangeListener(listener);
Remember to hold a reference to the listener object, otherwise it will be garbage colleted (and will cease to work).
If you are using Material Components themes and followed Light and Dark theme guidelines then you can do it from AppCompatDelegate. These themes can be changed/applied at run time without restarting your application.
private fun handleThemeChange(theme: String) {
when (newTheme) {
getString(R.string.light) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
getString(R.string.dark) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
getString(R.string.system) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
You can also change dynamically theme using:
ContextThemeWrapper w = new ContextThemeWrapper(this, <newTHEMEId>);
getTheme().setTo(w.getTheme());
Before onCreate for each activity.
It does work if you do it this way, and I don't think it would cause any problem, but it seems like a lot of hassle (you have to multiply all your layouts by all the themes you want to add. If later you want to modify a resource in a layout, you'll have to modify it in all the themes. You're definitely bound to forget one)
Why not using the Styles and Themes feature of Android?
They can be applied to the whole activity easily:
<activity android:theme="#style/my_theme">
So that when you detect a change in the SharedPreferences value you use (button on a preference activity, or something) you can just switch the style. Or better, you can set the style to read your preference value at runtime (when creating the activity) and apply the correct style/theme accordingly.
I'm making a fairly basic game. In my preferences, there's an option to change the difficulty setting. I'd like to be able to somehow, in the main activity that called it, sense if they've changed the difficulty. (And then restart the game)
I'm having difficulty because of how the preference activity is handled asynchronously. If I add logic to check the value before and after sending the intent to my PreferenceActivity (from a menu selection), it really doesn't work...
Can someone point me in a proper direction as to either how to serialize a chunk of code normally handled asynchronously, or have an idea of how to sense preferences changed? Is there a listener class hidden somewhere?
For posterity's sake, here's the code handling the intent and how I'm failing. (snippet, from inside menu onOptionsItemSelected, inside a switch)
case R.id.menuOptions:
String currentDifficulty = preferences.getString("difficulty","problem!");
Intent i = new Intent(this, prefs.class);
startActivity(i);
if (currentDifficulty.equals(preferences.getString("difficulty","problem!")))
return true;
else doNewGame();
return true;
Android allows you to register a preference changed listener. Here's a quick example:
public class myClass implements OnSharedPreferenceChangeListener
{
private SharedPreferences settings;
settings = PreferenceManager.getDefaultSharedPreferences(this);
settings.registerOnSharedPreferenceChangeListener(this);
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
// Process it here
}
}
OnPreferenceChangeListener You can use it to very easily listen for preference changes.
The OnPreferenceChangeListener is a good idea, but you could also start your preference activity using startActivityForResult(). Your activity is then notified in onActivityResult() after the preference activity returns (you should override it to handle preference changes).