I am trying to add a setting to my app. I have added the new setting but I am not sure where to put OnSharedPreferenceChangeListener. I put it in the activity and added a Log.d(), but the Log.d() is never triggered. Any ideas?
The best place according to Android Settings doc would be:
#Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
And you should store the listener in a field variable (or use the Activity object itself - as in the above source code), so that it does not get garbage collected.
I.e. an anonymous class object can't be used as OnSharedPreferenceChangeListener.
You need to register your listener by invoking setOnPreferenceChangeListener. I'm going to assume that you have an Activity class that extends PreferenceActivity. If so, that is the best place for your listener. You'd write something like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
Preference myPreference = findPreference("my_pref");
myPreference.setOnPreferenceChangeListener(this);
}
Related
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
notify("onCreate");
}
#Override
protected void onPause() {
super.onPause();
notify("onPause");
}
#Override
protected void onResume() {
super.onResume();
notify("onResume");
}
#Override
protected void onStop() {
super.onStop();
notify("onStop");
}
#Override
protected void onDestroy() {
super.onDestroy();
notify("onDestroy");
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
notify("onRestoreInstanceState");
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
notify("onSaveInstanceState");
}
I'm if right, these are the methods to handle the life cycle of an activity. My question is, do you need to put these methods in each class if you want to handle your app life cycle properly, or just in the main class?
They have to be implemented once per activity. The code of one activity won't be used in anotherone unless the second is a subclass of the first.
As an additional note, only onCreate must be implemented, the others are optional.
You only need to override the methods that you want to use.
If you don't need to perform any special functions in onDestroy(), for example, you don't need to override it in any of your Activities.
A good way to identify whether or not you need it is to look at what you have in those methods. If all you are doing is calling super, then you don't need to include that method in your Activity because the default behavior will work just fine for you.
If you want your app to develop some certain behavior that can be applied to some specific part of the life cycle you have to put those methods in each class Activity. Because of the inheritance that a class have if you want some specific behavior through all the app I recommend you to use the Application class that modifies all the subsequent Activities the only problem is that not all the normal methods of an activity life cycle are available.
Here is some reference about lifecycle in Application class:
https://www.inkling.com/read/programming-android-mednieks-1st/chapter-11/life-cycle-methods-of-the
The best example of the method you always have to override is onCreate() because here is where you add all the normal start up behavior of the views and logic in your activity.
If you are not very well acquainted with android life cycle I do not recommend you to override these methods because they can lead you to strange behavior in your app, that is for my personal experience.
In my preferences screen I set listeners to some preferences on onCreate().
But I've noticed the listener is called every time the preference is loaded (probably on the onCreate()).
Is there a way to prevent this ?
I want of course the listener to be called only when the preference value in the given key is changed.
Thanks
You can achieve that this way. You need to register your listener in onResume and unregister in onPause. This way it won't get called when your activity is created as the initial changes to the preferences values have already taken place.
public class SettingsActivity extends PreferenceActivity
implements OnSharedPreferenceChangeListener {
#Override
protected void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
// Let's do something a preference value changes
}
}
Change listeners fire even when the change happens programmatically, not exclusively as a result of user input (because user input ultimately leads to a programmatic change in the vaue, hence not differentiating).
The solution is to add the listeners after the view has been created and populated with the currently set preferences instead of adding them in onCreate.
So I have two activities: main with all data and secondary with Preferences. There are two arrays in main activity that I want to have access to from Preferences activity and work with this two arrays. In fact, I need to reset all data in arrays to default. Is there any way to do it?
I've tried to use SharedPreferences because I store all this data in it. I changed values in it from Preferences activity, but data in main activity haven't changed, I guess, because it's not being restarted, just paused.
You can use SharedPreferences and register a OnSharedPreferenceChangeListener and/or update the data in the onResume() method of your Activity.
Here's some basic example:
public class MainActivity extends Activity implements OnSharedPreferenceChangeListener {
private SharedPreferences prefs;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get your SharedPreferences objects
prefs = ...;
}
// This method will be called every time your activty comes to the front
#Override
protected void onResume() {
super.onResume();
// register a listener to get notified when the preferences change
prefs.registerOnSharedPreferenceChangeListener(this);
// do whatever you need to do to update the data
updateData();
}
#Override
protected void onPause() {
super.onPause();
// unregister listener if another activity comes to the front to save battery
prefs.unregisterOnSharedPreferenceChangeListener(this);
}
// this method will be called when some preference changes
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// do whatever you need to do to update the data
updateData();
}
}
I've implemented onSharedPreferenceChanged in my main activity.
If I change the preferences in the main activity, my event fires.
If I change the preferences through my preferences screen (PreferenceActivity) my event does NOT fire when preferences are changed (because it's a separate activity and separate reference to sharedPreferences?)
Does anybody have a recommendation of how I should go about overcoming this situation?
Thanks!
EDIT1: I tried adding the event handler right in my preference activity but it never fires. The following method gets called during onCreate of my preference activity. When I change values, it never prints the message (msg() is a wrapper for Log.d).
private void registerChangeListener () {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener () {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
msg (" ***** Shared Preference Update ***** ");
Intent i = new Intent();
i.putExtra("KEY", key);
i.setAction("com.gtosoft.dash.settingschanged");
sendBroadcast(i);
// TODO: fire off the event
}
});
}
The OnSharedPreferenceChangeListener gets garbage collected in your case if you use an anonymous class.
To solve that problem use the following code in PreferenceActivity to register and unregister a change listener:
public class MyActivity extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
#Override
protected void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,String key)
{
// do stuff
}
Furthermore be aware that the listener only gets called if the actual value changes. Setting the same value again will not fire the listener.
see also SharedPreferences.onSharedPreferenceChangeListener not being called consistently
This happen because garbage collector. its works only one time. then the reference is collected as garbage. so create instance field for listener.
private OnSharedPreferenceChangeListener listner;
listner = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
//implementation goes here
}
};
prefs.registerOnSharedPreferenceChangeListener(listner);
I arrived here, like many others, because my listener won't be fired when I changed my boolean from true to false, or viceversa.
After much reading, and refactoring, switching contexts/inner classes/privates/static/ and the like, I realized my (stupid) error:
The onSharedPreferenceChanged is only called if something changes. Only. Ever.
During my tests, I was so dumb to click on the same button all the time, thus assigning the same boolean value to the preference all the time, so it did not ever change.
Hope this helps somebody!!
One other way of avoiding the problem is to make your activity the listener class. Since there is only one override method with a distinctive name you can do this:
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
...
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
...
}
}
Note the original question spoke of a MainActivity listening to setting changes in a PreferenceActivity. The asker then added an "EDIT1" and changed the question to listening in the PreferenceActivity itself. That is easier than the former and seems to be what all the answers assume. But what if you still want the former scenario?
Well, it will work too, but do not use OnResume() and OnPause() to register and unregister the listener. Doing so will cause the listener to be ineffectual because the user leaves the MainActivity when they use the PreferenceActivity (which makes sense when you think about it). So it will work, but then your MainActivity will still be listening in the background even when the user is not using it. Kind of a waste of resources isn't it? So there is another solution that seems to work, simply add a method to OnResume() to re-read all preferences. That way when a user finishes editing preferences in a PreferenceActivity, the MainActivity will pick them up when the user returns to it and you don't need a listener at all.
Someone please let me know if they see a problem with this approach.
Why don't you just add a onSharedPreferenceChanged in the rest of the activities where the preferences could change?
The garbage collector erases that... you should consider using an Application context instead...or just add the code when app launchs... and then add the the listener with application context...
Consider keeping PreferencesChangeListener inside Android App class instance. Although it's NOT a clean solution storing reference inside App should stop GC from garbage collecting your listener and you should still be able to receive DB change updates. Remember that preference manager does not store a strong reference to the listener! (WeakHashMap)
/**
* Main application class
*/
class MyApp : Application(), KoinComponent {
var preferenceManager: SharedPreferences? = null
var prefChangeListener: MySharedPrefChangeListener? = null
override fun onCreate() {
super.onCreate()
preferenceManager = PreferenceManager.getDefaultSharedPreferences(this)
prefChangeListener = MySharedPrefChangeListener()
preferenceManager?.registerOnSharedPreferenceChangeListener(prefChangeListener)
}
}
and
class MySharedPrefChangeListener : SharedPreferences.OnSharedPreferenceChangeListener {
/**
* Called when a shared preference is changed, added, or removed.
*/
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (sharedPreferences == null)
return
if (sharedPreferences.contains(key)) {
// action to perform
}
}
}
While reading Word readable data shared by first app,we should
Replace
getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);
with
getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);
in second app to get updated value in second app.
I'm registering a preference change listener like this (in the onCreate() of my main activity):
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(
SharedPreferences prefs, String key) {
System.out.println(key);
}
});
The trouble is, the listener is not always called. It works for the first few times a preference is changed, and then it is no longer called until I uninstall and reinstall the app. No amount of restarting the application seems to fix it.
I found a mailing list thread reporting the same problem, but no one really answered him. What am I doing wrong?
This is a sneaky one. SharedPreferences keeps listeners in a WeakHashMap. This means that you cannot use an anonymous inner class as a listener, as it will become the target of garbage collection as soon as you leave the current scope. It will work at first, but eventually, will get garbage collected, removed from the WeakHashMap and stop working.
Keep a reference to the listener in a field of your class and you will be OK, provided your class instance is not destroyed.
i.e. instead of:
prefs.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Implementation
}
});
do this:
// Use instance field for listener
// It will not be gc'd as long as this instance is kept referenced
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
The reason unregistering in the onDestroy method fixes the problem is because to do that you had to save the listener in a field, therefore preventing the issue. It's the saving the listener in a field that fixes the problem, not the unregistering in onDestroy.
UPDATE: The Android docs have been updated with warnings about this behavior. So, oddball behavior remains. But now it's documented.
this accepted answer is ok, as for me it is creating new instance each time the activity resumes
so how about keeping the reference to the listener within the activity
OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener(){
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// your stuff
}
};
and in your onResume and onPause
#Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}
#Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
}
this will very similar to what you are doing except we are maintaining a hard reference.
The accepted answer creates a SharedPreferenceChangeListener every time onResume is called. #Samuel solves it by making SharedPreferenceListener a member of the Activity class. But there's a third and a more straightforward solution that Google also uses in this codelab. Make your activity class implement the OnSharedPreferenceChangeListener interface and override onSharedPreferenceChanged in the Activity, effectively making the Activity itself a SharedPreferenceListener.
public class MainActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
}
#Override
protected void onStart() {
super.onStart();
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onStop() {
super.onStop();
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
}
}
As this is the most detailed page for the topic I want to add my 50ct.
I had the problem that OnSharedPreferenceChangeListener wasn't called. My SharedPreferences are retrieved at the start of the main Activity by:
prefs = PreferenceManager.getDefaultSharedPreferences(this);
My PreferenceActivity code is short and does nothing except showing the preferences:
public class Preferences extends PreferenceActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// load the XML preferences file
addPreferencesFromResource(R.xml.preferences);
}
}
Every time the menu button is pressed I create the PreferenceActivity from the main Activity:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
//start Preference activity to show preferences on screen
startActivity(new Intent(this, Preferences.class));
//hook into sharedPreferences. THIS NEEDS TO BE DONE AFTER CREATING THE ACTIVITY!!!
prefs.registerOnSharedPreferenceChangeListener(this);
return false;
}
Note that registering the OnSharedPreferenceChangeListener needs to be done AFTER creating the PreferenceActivity in this case, else the Handler in the main Activity won't be called!!! It took me some sweet time to realize that...
Kotlin Code for register SharedPreferenceChangeListener it detect when change will happening on the saved key :
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
if(key=="language") {
//Do Something
}
}
you can put this code in onStart() , or somewhere else..
*Consider that you must use
if(key=="YourKey")
or your codes inside "//Do Something " block will be run wrongly for every change that will happening in any other key in sharedPreferences
So, I don't know if this would really help anyone though, it solved my issue.
Even though I had implemented the OnSharedPreferenceChangeListener as stated by the accepted answer. Still, I had an inconsistency with the listener being called.
I came here to understand that the Android just sends it for garbage collection after some time. So, I looked over at my code.
To my shame, I had not declared the listener GLOBALLY but instead inside the onCreateView. And that was because I listened to the Android Studio telling me to convert the listener to a local variable.
It make sense that the listeners are kept in WeakHashMap.Because most of the time, developers prefer to writing the code like this.
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
new OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key);
}
});
This may seem not bad. But if the OnSharedPreferenceChangeListeners' container was not WeakHashMap, it would be very bad.If the above code was written in an Activity . Since you are using non-static (anonymous) inner class which will implicitly holds the reference of the enclosing instance. This will cause memory leak.
What's more, If you keep the listener as a field, you could use registerOnSharedPreferenceChangeListener at the start and call unregisterOnSharedPreferenceChangeListener in the end. But you can not access a local variable in a method out of it's scope. So you just have the opportunity to register but no chance to unregister the listener. Thus using WeakHashMap will resolve the problem. This is the way I recommend.
If you make the listener instance as a static field, It will avoid the memory leak caused by non-static inner class. But as the listeners could be multiple, It should be instance-related. This will reduce the cost of handling the onSharedPreferenceChanged callback.
While reading Word readable data shared by first app,we should
Replace
getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);
with
getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);
in second app to get updated value in second app.
But still it is not working...