0

Suppose the user can update the shared preferences from a popup dialog from MainActivity. In this case, I must listen to onSharedPreferenceChanged event in order to apply the user's new settings. When I register the listener inside MyDialogFragment's onCreateDialog() method, the listener works correctly.

public class MyDialogFragment extends dialogFragment {
  ...
  OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sf, String key) {
      Log.e("change", "pref changed");
    }
  };
  SharedPreferences sp = getActivity().getsharedPreferences(myKey, Context.MODE_PRIVATE);
  sp.registerOnSharedPreferenceChangeListener(listener);
}

However, if I register the same listener the same way from MainActivity's onResume(), the listener does not work when sharedpreferences are changed.

MainActivity

protected void onResume() {
  super.onResume();
  OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sf, String key) {
      Log.e("change", "pref changed");
    }
  };
  SharedPreferences sp = this.getsharedPreferences(myKey, Context.MODE_PRIVATE);
  sp.registerOnSharedPreferenceChangeListener(listener);
}

The only difference is that I replaced getActivity() with this when declaring sharedPreferences in MainActivity's method. Is this expected that the above listener wouldn't work from MainActivity's scope?

Maximus S
  • 10,759
  • 19
  • 75
  • 154

2 Answers2

0

In MyDialogFragment the listener is defined in class scope - will live as long as the class. In your onResume() the listener is defined as a local variable in onResume() so when onResume() returns the listener is out of scope and for reasons explained in SharedPreferences.onSharedPreferenceChangeListener not being called consistently it is garbage collected. Notice that in this answer we have listener = new SharedPreferences.OnSharedPreferenceChangeListener() { //etc while you do OnSharedPreferenceChangeListener listener = new // etc, so you define a local variable.

Community
  • 1
  • 1
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • Thanks for your answer. I still don't fully understand the differences between listener = new SharedPreferences.OnSharedPreferenceChangeListener() {} and onSharedPreferenceChangeListener listener = new something.. Aren't they essentially the same? – Maximus S Feb 27 '14 at 00:39
  • OH. The difference is that `listener` should be defined BEFORE (to be in class scope) while I define it inside `onResume()`, which makes it a local variable. Is that correct? – Maximus S Feb 27 '14 at 00:41
  • @MaximusS: correct - the ` listener = new ...` is a class _field_ that lives as long as the class (the fragment) - while the `onSharedPreferenceChangeListener listener = ...` is a _local variable_ INSIDE `onResume()`. The one will live and listen - the other when onResume ends ends its life too - and soon is collected by Java and does not listen anymore – Mr_and_Mrs_D Feb 27 '14 at 00:42
  • @MaximusS: I posted my comment while you posted your second one - your second one is correct - you need to read some Java - start by the Oracle tutorials – Mr_and_Mrs_D Feb 27 '14 at 00:43
-1

From SharedPreferences.onSharedPreferenceChangeListener not being called consistently:

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.

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

Update: The below snippet is the object which is declared completely inside a function. In your case its either declared in OnCreateDialog or OnResume. Hence it gets garbage collected when the function gets out of scope. You need to declare this object at the class level field and then just register the listener in either OnResume or OnCreateDialog.

OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sf, String key) {
      Log.e("change", "pref changed");
    }
  };
Community
  • 1
  • 1
Madhur Ahuja
  • 22,211
  • 14
  • 71
  • 124
  • Thanks! Could you elaborate a little more for me please? I thought I was aware of garbage collecting issue. How is your answer different from my approach? (Which part in my code includes anonymous inner class?) – Maximus S Feb 04 '14 at 02:39