0

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.

user504342
  • 945
  • 2
  • 16
  • 36
  • Does your service get canceled when onDestroy is called by the way ? – Mr_and_Mrs_D Dec 12 '13 at 23:45
  • @Mr_and_Mrs_D yes, it gets cancelled. I see it in the logcat log. It escapes the while-loop. – user504342 Dec 13 '13 at 00:54
  • re: your edit - I guessed so - but anyway better use `getDefaultSharedPreferences` except if you really need a file – Mr_and_Mrs_D Dec 13 '13 at 01:44
  • re:your solution - I didn't know that you wanted to change something in your myTask - but anyway if you had your myTask implement the OnSharedPreferenceChangeListener wherever you registered it it would be the same. Anyway be careful with multi-threading - try to read up on it - it is very complicated and things can go wrong in weird ways. For one make mCancelled volatile – Mr_and_Mrs_D Dec 13 '13 at 13:58

1 Answers1

1

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

Community
  • 1
  • 1
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • Why use "SharedPreferences sp = ... " in the if-construction? Shouldn't that be added into the MyTask object instead? – user504342 Dec 13 '13 at 01:35
  • The if is inside `onStartCommand` - clear (no constructor) ? Now what I do is 1. retrieve the _default_ shared preferences. 2. say to android "hey, register this myTask object I just constructed to listen for changes of the default preferences I just got". Then I go on with your code. So you don't add the sp to myTask - you add myTask to the (listeners of) sp. You could do it in the myTask constructor as `SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); sp.registerOnSharedPreferenceChangeListener(this);` but better do it in the service – Mr_and_Mrs_D Dec 13 '13 at 01:40
  • The way you describe it means to me that I cannot read the value of the changed pref key/value once it is changed and triggered. I need to change a setting within the MyTask object, not in the MyService object. This way, each OnSharedPreferenceChangeListener implementation can have a different implementation for each listener you want to add (see my solution edit). – user504342 Dec 13 '13 at 01:57