76

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
        }
    });
}
Brad Hein
  • 10,997
  • 12
  • 51
  • 74
  • possible duplicate of [SharedPreferences.onSharedPreferenceChangeListener not being called consistently](http://stackoverflow.com/questions/2542938/sharedpreferences-onsharedpreferencechangelistener-not-being-called-consistently) – Mr_and_Mrs_D Dec 21 '13 at 16:15

9 Answers9

148

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

Community
  • 1
  • 1
thumbmunkeys
  • 20,606
  • 8
  • 62
  • 110
  • 3
    Oh man you rock! How tricky!!! My onsharedpreferencelistener was being garbage collected! So per your link, I created a long term reference (global member variable) to it and voila, it works great! – Brad Hein Sep 27 '10 at 00:59
  • @pivotnig When I try your code I get a compilation error. It won't accept this, this being a PreferenceActivity subclass. Is this something else in your code? –  Feb 25 '11 at 05:11
  • @dpk without telling me the error it's hard to say what's the problem. Anyway, you should make a new question for your problem. – thumbmunkeys Feb 25 '11 at 07:38
  • @pivotnig The method getPreferenceScreen() from the type PreferenceActivity is deprecated – Dr.jacky Nov 10 '12 at 13:26
  • @Dr.jacky it wasn't at the time of the answer :) feel free to change my answer – thumbmunkeys Nov 10 '12 at 13:37
  • @Mr.Hyde If it was deprecated, then what should we do for pre-Honeycomb deployments? – IgorGanapolsky Aug 27 '13 at 00:27
  • **NOTE:** Trying to get shared preferences from a singleton didn't worked to me. I've fixed using the way you posted, getting the reference from `getPreferenceScreen().getSharedPreferences()`. – Diogo Paschoal May 15 '16 at 18:02
  • "Furthermore be aware that the listener only gets called if the actual value changes. Setting the same value again will not fire the listener." how I can call the listener in this case ???? – KH_AJU Mar 22 '17 at 13:09
  • gr8 very helpfull in PreferenceFragment also. – Hiren Dec 01 '17 at 14:12
19

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);
hderanga
  • 1,269
  • 12
  • 12
  • 1
    I think this answer is closest to the problem. It seems that listener is garbage-collected if defined right in the call-site. So one should keep a reference to the listener somewhere else to keep it working. – Petr Gladkikh Jun 10 '13 at 03:38
  • @hderanga Thank you, This works for me because my class is not extends PreferenceActivity – user2814778 Mar 17 '15 at 11:23
  • Any memory leak issue? – jfly Feb 07 '18 at 09:28
9

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!!

Math
  • 3,334
  • 4
  • 36
  • 51
mrArias
  • 1,008
  • 11
  • 7
  • 1
    It looks like that. The strange think is that in Android documentation is written this: "This may be called even if a preference is set to its existing value." . See [docs](https://developer.android.com/reference/android/content/SharedPreferences.OnSharedPreferenceChangeListener). – Vít Kapitola Dec 21 '22 at 10:03
3

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)
    {
        ...
    }
} 
Steve Waring
  • 2,882
  • 2
  • 32
  • 37
  • 1
    This is what I was doing, but it did not work. Among other things I discovered that using OnResume() and OnPause() to register and unregister the listener, respectively, 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). – trans Jul 15 '16 at 04:18
1

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.

trans
  • 1,411
  • 1
  • 13
  • 13
0

Why don't you just add a onSharedPreferenceChanged in the rest of the activities where the preferences could change?

Cristian
  • 198,401
  • 62
  • 356
  • 264
  • You mean like define onSharedPreferenceChanged right in my settings activity? I did and it's not firing. – Brad Hein Sep 26 '10 at 18:10
  • For the record, the problem was caused by the garbage collector reclaiming my event handler. I had to create a global member reference to the event handler. – Brad Hein Sep 28 '10 at 14:37
0

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

superUser
  • 1,032
  • 12
  • 9
0

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
        }
    }
}
-2

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.

Shridutt Kothari
  • 7,326
  • 3
  • 41
  • 61
  • 2
    Android doesn't support accessing `SharedPreferences` from multiple processes. Doing so causes concurrency issues, which can result in all preferences being lost. Also, `MODE_MULTI_PROCESS` is no longer supported. – Sam Oct 15 '17 at 03:45