SharedPreferences.onSharedPreferenceChangeListener not being called consistently SharedPreferences.onSharedPreferenceChangeListener not being called consistently android android

SharedPreferences.onSharedPreferenceChangeListener not being called consistently


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 referencedlistener = 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.


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:

@Overridepublic 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...


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);    }}