Question

When using a thread/task within an android service that implements the OnSharedPreferenceChangeListener interface, the changes made in the preference screen aren't reflected back to the thread/task object within the android service.

I want to accomplish two things:

  • SharedPreference data should be loaded when MyTask is constructed and initialized.

  • When preference change occurs, MyTask object must be updated with the new preference values set in the preference screen.

The problem is: preference initialization and preference changes are not reflected to the MyTask object.

This is my setup (only essential parts are mentioned):

MyService.class:

public class MyService extends Sevice {
    private MyTask myTask;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!serviceStarted) {
            serviceStarted = true;
            myTask = new MyTask(this);
            Thread t = new Thread(myTask);
            t.start();
        }
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        myTask.cancel();
        super.onDestroy();
    }
}

MyTask.class:

public MyTask implements Runnable, OnSharedPreferenceChangeListener {
    private Context mContext;
    private boolean mCancelled;
    public MyTask(Context context) {
        mContext = context;
    }

    @Override
    public void run() {
        while(!mCancelled) {
            // do something
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        // FIXME: DOESN'T GET CALLED after change in preference!!!!
        Log.d(TAG, "Key= " + key);
    }

    public void cancel() {
        mCancelled = true;
    }
}

preference_devices.xml:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory
        android:key="pref_category_devices"
        android:title="@string/pref_category_devices_title" >
        <CheckBoxPreference
            android:defaultValue="true"
            android:key="pref_devices_server"
            android:title="@string/pref_devices_server_title" />
    </PreferenceCategory>
</PreferenceScreen>

I have tried coding a SharedPreferences listener object as a member field of the MyTask class and register/unregister the listener from the provided context, but that didn't work either. These changes also didn't work:

MyTask.class (using SharedPreference listener as field member of class):

public MyTask implements Runnable {
    private Context mContext;
    private boolean mCancelled;
    private boolean mServerEnabled;
    private SharedPreferences mPrefs;
    private SharedPreferences.OnSharedPreferenceChangeListener
        mPreferenceListener;

    public MyTask(Context context) {
        mContext = context;
        mPrefs = mContext.getSharedPreferences("pref_category_devices",
                Context.MODE_PRIVATE);
        mPreferenceListener = new OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key) {
                // FIXME: DOESN'T GET CALLED after change in preference!!!!
                Log.d(TAG, "Key= " + key);
            }
        };
        mPrefs.registerOnSharedPreferenceChangeListener(mPreferenceListener);
        // set the initial value of the preference setting
        mServerEnabled = mPrefs.getBoolean("pref_devices_server", false);
    }

    @Override
    public void run() {
        while(!mCancelled) {
            // do something
        }
    }

    public void cancel() {
        mCancelled = true;
    }
}

I have now reached the point of throwing my computer out of the window :(

Any help in the right direction is highly appreciated :)

EDIT: In the code

mPrefs = mContext.getSharedPreferences("pref_category_devices", Context.MODE_PRIVATE);

I assumed that the first argument should be the preference category name of the preference file, like: "pref_category_devices". THIS IS INCORRECT! The first argument must be a shared preference file name. That didn't solve the problem, but at least now you know to not fall for this pitfall.

=== SOLUTION: === See answer of Mr_and_Mrs_D + code below this line:

Change in MyTask:

mPrefs = mContext.getSharedPreferences("pref_category_devices",
            Context.MODE_PRIVATE);

into:

mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPreferenceListener = new OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals("preference_name_here")) {
            mPrefValue = sharedPreferences.getBoolean(key, false);
            // do something with boolean pref value 
        }
    }
};
mPrefs.registerOnSharedPreferenceChangeListener(myPreferenceListener);

Where mPrefValue is a field member of type boolean in MyTask that needs to be set when the "preference_name_here" preference changes.

Was it helpful?

Solution

Change :

private volatile boolean mCancelled; //otherwise the myTask thread may never stop

For your problem :

if (!serviceStarted) { 
    serviceStarted = true;
    myTask = new MyTask(this);
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
    sp.registerOnSharedPreferenceChangeListener(myTask); //err, you must register
    Thread t = new Thread(myTask); t.start();
}

Docs :

These preferences will automatically save to SharedPreferences as the user interacts with them. To retrieve an instance of SharedPreferences that the preference hierarchy in this activity will use, call getDefaultSharedPreferences(android.content.Context) with a context in the same package as this activity.

[emphasis mine]

Edit : your second snippet probably fails cause you get the wrong shared prefs - you must get the default ones - I thought it was failing because of :

SharedPreferences.onSharedPreferenceChangeListener not being called consistently

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top