14

I am changing and committing a SharedPreference in my SyncAdapter after successful sync, but I am not seeing the updated value when I access the preference in my Activity (rather I am seeing the old value). What am I doing wrong? Different Contexts?

My SyncAdapter where I update the preference:

class SyncAdapter extends AbstractThreadedSyncAdapter {
    private int PARTICIPANT_ID;
    private final Context mContext;
    private final ContentResolver mContentResolver;

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mContext = context;
        mContentResolver = context.getContentResolver();
    }

    public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        mContext = context;
        mContentResolver = context.getContentResolver();
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority,
                              ContentProviderClient provider, SyncResult syncResult) {

        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        PARTICIPANT_ID = Integer.parseInt(prefs.getString("participant_id", "0"));

        if (success) {
            // save and set the new participant id
            PARTICIPANT_ID = newParticipantId;
            prefs.edit().putString("participant_id", String.valueOf(newParticipantId)).commit();
        }
    }
}

The Service initializing the SyncAdapter with the ApplicationContext:

public class SyncService extends Service {
    private static final Object sSyncAdapterLock = new Object();
    private static SyncAdapter sSyncAdapter = null;

    @Override
    public void onCreate() {
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), false);
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return sSyncAdapter.getSyncAdapterBinder();
    }
}

A static function within the Application called by the Activity that checks the SharedPreference. This does not return the value committed in the SyncAdapter, but the old value. (My SettingsActivity and other Activities also use the old value.):

public static boolean isUserLoggedIn(Context ctx) {
    final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
    int participantId = Integer.parseInt(prefs.getString("participant_id", "0"));
    LOGD("dg_Utils", "isUserLoggedIn.participantId: " + participantId);// TODO
    if (participantId <= 0) {
        ctx.startActivity(new Intent(ctx, LoginActivity.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
        return false;
    }
    return true;
}

UPDATE: I am getting the new value when I completely close the app (swipe it from the apps running). I also have a SharedPreferenceChangeListener, which is not fired when the preference is updated.

private final SharedPreferences.OnSharedPreferenceChangeListener mParticipantIDPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
        if (key.equals("participant_id")) {
            LOGI(TAG, "participant_id has changed, requesting to restart the loader.");
            mRestartLoader = true;
        }
    }
};

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // subscribe to the participant_id change lister
    final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
    PARTICIPANT_ID = Integer.parseInt(prefs.getString("participant_id", "0"));
    prefs.registerOnSharedPreferenceChangeListener(mParticipantIDPrefChangeListener);
}
matsch
  • 549
  • 4
  • 10
  • From the code that you've posted it seems that you never call the `startSync()` on the `SyncAdapter` which means that the `onPerformSync(...)` method doesn't execute and the preference is never changed. – Titus Oct 16 '14 at 16:10
  • @Titus Somewhere I do and `onPerformSync()` is definitely executed. It also uses the new value in subsequent syncs. I have found out that the `SyncService` is running in a separate process (``). Now I am trying to figure out how to use `MODE_MULTI_PROCESS` with `PreferenceManager.getDefaultSharedPreferences()`. – matsch Oct 16 '14 at 16:18
  • You can use `prefs = mContext.getSharedPreferences("myAppPrefs", Context.MODE_MULTI_PROCESS);` [Here](http://developer.android.com/reference/android/content/Context.html#getSharedPreferences(java.lang.String, int)) is the documentation. – Titus Oct 16 '14 at 16:26
  • @Titus Thanks, I've seen that. I am already using `PreferenceManager.getDefaultSharedPreferences()` extensively throughout the app. What is the `name` of the default preferences? Also, is it enough if I set `Context.MODE_MULTI_PROCESS` in the `SyncAdapter`? – matsch Oct 16 '14 at 16:59
  • Take a look at copolii's answer to this [question](http://stackoverflow.com/questions/5946135/difference-between-getdefaultsharedpreferences-and-getsharedpreferences) he says that the default name is [package name] + "_preferences". This means that if you use the same context to create the `SharedPreferences` object, both processes should use the same file. – Titus Oct 16 '14 at 17:16
  • @Titus The problem is that the `SyncService` is running in its own process and hence blocks access to the same `SharedPreferences` file. See http://stackoverflow.com/a/12144418/3930350 – matsch Oct 16 '14 at 17:27

3 Answers3

17

Ok, I figured it out myself with @Titus help and after some research and pieced together a solution for my problem.

The reason why the DefaultSharedPreferences of the same Context are not updated is that I have specified the SyncService to run in its own process in the AndroidManifest.xml (see below). Hence, starting from Android 2.3, the other process is blocked from accessing the updated SharedPreferences file (see this answer and the Android docs on Context.MODE_MULTI_PROCESS).

    <service
        android:name=".sync.SyncService"
        android:exported="true"
        android:process=":sync"
        tools:ignore="ExportedService" >
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>
        <meta-data
            android:name="android.content.SyncAdapter"
            android:resource="@xml/syncadapter" />
    </service>

So I had to set MODE_MULTI_PROCESS when accessing the SharedPreferences both in the SyncAdapter and in the UI process of my app. Because I've used PreferenceManager.getDefaultSharedPreferences(Context) extensively throughout the app I wrote a utility method and replaced all calls of PreferenceManager.getDefaultSharedPreferences(Context) with this method (see below). The default name of the preferences file is hardcoded and derived from the Android source code and this answer.

public static SharedPreferences getDefaultSharedPreferencesMultiProcess(
        Context context) {
    return context.getSharedPreferences(
            context.getPackageName() + "_preferences",
            Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
}
Community
  • 1
  • 1
matsch
  • 549
  • 4
  • 10
  • According to http://stackoverflow.com/a/6310080/8524 the above code will get the same `SharedPreferences` object as `SharedPreferences.getDefaultSharedPreferences()` does. – Diederik Oct 02 '15 at 08:08
  • 1
    Yes, it will get the same object, but with the `Context.MODE_MULTI_PROCESS` modifier set. I'm linking to the same answer you are in my last paragraph. – matsch Oct 02 '15 at 13:35
  • Good addition to your answer I think. I added the comment in case any one (like me) used `SharedPreferences.getDefaultSharedPreferences()` to get shared preferences, and then realise they need multi process access. – Diederik Oct 02 '15 at 14:24
  • Good addition to your answer I think. I added the comment in case any one (like me) used `SharedPreferences.getDefaultSharedPreferences()` to get shared preferences, and then realise they need multi process access. – Diederik Oct 02 '15 at 14:25
  • 10
    MODE_MULTI_PROCESS is deprecated since API 23. – Jyotman Singh Feb 19 '16 at 11:12
0

Since the SharedPreferences are not process-safe, i wouldn't recommend to use the AbstractThreadedSyncAdapter in another process unless you really need it.

Why do i need multiple processes in my application?

Solution

Remove android:process=":sync" from the Service that you declared in your manifest!

<service
    android:name=".sync.SyncService"
    android:exported="true"
    android:process=":sync"
    tools:ignore="ExportedService" >
    <intent-filter>
        <action android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data
        android:name="android.content.SyncAdapter"
        android:resource="@xml/syncadapter" />
</service>
oli
  • 569
  • 6
  • 16
0

In my case I was trying to access SharedPreferences from a service launched by a BroadcastReceiver.

I removed android:process=":remote" from the declaration in the AndroidManifest.xml to get it to work.

<receiver 
    android:process=":remote" 
    android:name=".Alarm">
</receiver>
running-codebase
  • 998
  • 2
  • 12
  • 17