How can I share a SharedPreferences file across two different android apps?

27,495

Solution 1

First, I should note that this is not officially supported, although there may be a supported way to do this (i.e. it would NOT be this method) added to Android in the future (source for both claims: see second paragraph of this link).

Again, this is unsupported and is very possibly unstable. I primarily did this as an experiment to see if it was possible; take extreme caution if you are planning to actually incorporate this method into an application.

However, it appears to be possible to share preferences between applications if a few requirements are met. First, if you want App B to be able to access App A's preferences the package name of App B must be a child of App A's package name (e.g. App A: com.example.pkg App B: com.example.pkg.stuff). Additionally, they can't be wanting to access the file at the same time (I assume the same rules apply as for accessing them between activities, if you want to ensure atomic access you'll have to use additional safeguards such as .wait() and .notify(), but I won't go into that here).

Note: all of this works on the emulator on 2.2 and 2.3.3- I haven't extensively tested across devices or android versions.




Things to do in the app which is going to own the preferences (App A from above):

1.) Declare the SharedPreferences file
This is fairly simple. Simply declare a couple variables for your sharedpreferences file and the editor in your class and instantiate them in your onCreate method. You can put a string in the preferences now that you will use to make sure the other app can read it properly.

public class stuff extends Activity {
    SharedPreferences mPrefs = null;
    SharedPreferences.Editor mEd= null; 
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mPrefs = (getApplicationContext()).getSharedPreferences("svcprefs", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);
        mEd = mPrefs.edit();
        mEd.putString("test", "original send from prefs owner");
        mEd.commit();

2.) Set up the backup file The getSharedPreferences method appears to check for a .bak file to load the preferences from. This is why it says in the documentation that it will not work across multiple processes; to minimize I/O, it loads the prefs ONCE when you grab them and only backs them up when you close your application/activity. However, if you call this from an outside application you will get a warning about not having the right file permissions for the folder (which is the first app's data folder). To fix this we are going to create the .bak file ourselves and make it publicly readable/writable. The way I chose to do this was to define three variables in my overall class.

final String[] copyToBackup = { "dd", "if=/data/data/com.example.pkg/shared_prefs/prefs.xml", "of=/data/data/com.example.pkg/shared_prefs/prefs.xml.bak", "bs=1024" };
final String[] mainFixPerm = {"chmod", "666", "/data/data/com.example.pkg/shared_prefs/prefs.xml"};
final String[] bakFixPerm = {"chmod", "666", "/data/data/com.example.pkg/shared_prefs/prefs.xml.bak"};

and make a function in my main class which would take these as arguments and execute them

public void execCommand(String[] arg0){
     try {
         final Process pr = Runtime.getRuntime().exec(arg0);
         final int retval = pr.waitFor();
         if ( retval != 0 ) {
             System.err.println("Error:" + retval);
         }
     }
     catch (Exception e) {}
}

It's not terribly pretty or good but it works. Now, in your onCreate method (right after editor.commit())you will call this function with each of the three strings.

execCommand(copyToBackup);
execCommand(mainFixPerm);
execCommand(bakFixPerm);

This will copy the file and make both the main .xml and the .xml.bak files accessible to outside programs. You should also call these three methods in your onDestroy() to make sure the database is backed up properly when your app exits, and additionally call them right before you call getSharedPreferences elsewhere in your application (as otherwise it will load the .bak file which is likely out of date if another process has been editing the main .xml file). That's all you need to do in this application though, though. You can call getSharedPreferences elsewhere in this activity and it will grab all the data from the .xml file, allowing you to then call the getdatatype("key") methods and retrieve it.


Things to do in the accessing file(s) (App B from above)

1.) Write to the file
This is even simpler. I made a button on this activity and set up code in it's onClick method which would save something to the shared preferences file. Remember that App B's package must be a child of App A's package. We will be creating a context based on App A's context and then calling getSharedPreferences on that context.

prefsbutton.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        Context myContext = null;
        try {
            // App A's context
            myContext = createPackageContext("com.example.pkg", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);
        } catch (NameNotFoundException e) {e.printStackTrace();}

        testPrefs = myContext.getSharedPreferences("svcprefs", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);
        testEd = testPrefs.edit();
        String valueFromPrefs = testPrefs.getString("test", "read failure");
        TextView test1 = (TextView)findViewById(R.id.tvprefs);
        test1.setText(valueFromPrefs);
        testEd.putString("test2", "testback");
        boolean edit_success = testEd.commit();

This grabs the string I set in the other application and displays it (or an error message) in a textview in this application. Additionally it sets a new string in the preferences file and commits the changes. After this runs, if your other application calls getSharedPreferences it will retrieve the file including the changes from this app.

Solution 2

It is better to set private mode for the file. App needs to be signed with same set of certificates to share this file.

Set sharedUserId in both apps to be same.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hello"
android:versionCode="1"
android:versionName="1.0" 
android:sharedUserId="com.example">
....

Get Context from other package:

mContext = context.createPackageContext(
                        "com.example.otherapp",
                        Context.MODE_PRIVATE);
 mPrefs = mContext.getSharedPreferences("sameFileNameHere", Activity.MODE_PRIVATE);

Get items as usual from SharedPreference. You can access it now.

Share:
27,495
matt5784
Author by

matt5784

Android/iOS/possibly BB10 developer and hardware enthusiast. Currently focusing on some of the finer points of Android development. Also know C++, python, and some more obscure things (prolog, lisp). SOreadytohelp

Updated on July 15, 2020

Comments

  • matt5784
    matt5784 almost 4 years

    I've been struggling with this for a while. Basically, I want to have two applications (which will always be installed together) share preferences, with one of them being just a service which runs in the background and needs to use the preferences (should own the preferences but only really needs to read them) and the other app being a front-end UI app which needs to be able to write to the preferences file owned by the other app. The service will be doing things in the background (which may be determined by the preferences) and the UI will allow the user to edit the preferences and view some information from the service. However, they will be different packages/apps.

    I tried following this tutorial which gave me a pretty good idea of how to have preferences in one app which can be read by another. Essentially, I create a new context through myContext = createPackageContext("com.example.package",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); and then call myContext.getSharedPreferences("pref_name", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); However, I can't write to the preferences successfully from the outside app - (SharedPreferences.Editor).commit() returns false and I get a warning in logcat about being unable to edit pref_name.xml.bak.

    How can I successfully set up my applications so both of them can read and write to the same preferences file (which is stored in the data folder of one of them)?

    • Chris Stratton
      Chris Stratton almost 12 years
      Why, when you say these are always installed together, do these need to be two different apps? Seems like a lot of headache, so presumably you are envisioning a benefit, but I wonder if there might be a way to achieve that without actually having separate apps.
    • matt5784
      matt5784 almost 12 years
      I wanted to be able to install both apps, set the preferences, and then uninstall the frontend UI but let the service keep going in the background. This also allows me to upgrade one app without re-installing both. It's definitely not necessary (and I may not even use it) but it was one of my ideas for implementing this the way I designed it.
  • CommonsWare
    CommonsWare almost 12 years
    "although it may be in the future" -- the use of command-line binaries will never be supported in the Android SDK. Personally, I would not touch this technique with a 10-meter pole. If you want to share data between apps, use a real API, such as implementing a ContentProvider that is backed by your SharedPreferences.
  • matt5784
    matt5784 almost 12 years
    I couldn't predict what the android team will do (except when they tell me). However, I'm quite sure if they implemented this it would be much cleaner and wouldn't require running dd or chmod. In fact, this may even be possible through some other technique - and if it is I hope someone will post an answer. This is just the way I got it to work.
  • Miro Markaravanes
    Miro Markaravanes over 9 years
    Not sure why this is not the accepted answer. This is the correct way to do it, and works flawlessly.
  • Miro Markaravanes
    Miro Markaravanes over 9 years
    Also you might have to replace Context.MODE_PRIVATE with Context.CONTEXT_RESTRICTED.
  • lenooh
    lenooh about 9 years
    This is easy to implement, however, unfortunately, if you add this at some later stage in development, upgrading your app will not work (you have to reinstall). Additionally, if you use in-app billing, it is very likely to not work.
  • Napolean
    Napolean almost 9 years
    @Omer and Miro How can I successfully set up my applications so both of them can read and write to the same preferences file (which is stored in the data folder of one of them)? Were you able to achieve this ?
  • Omer Cansizoglu
    Omer Cansizoglu almost 9 years
    Yes, it is possible if both apps are signed with same cert and declared same sharedUserID in the manifest.
  • Bhupesh Pant
    Bhupesh Pant over 6 years
    @lenooh, I am planning to adopt this way, could you please elaborate why this would be a hindrance for upgradation of the app. Thanks in advance.
  • Bhupesh Pant
    Bhupesh Pant over 6 years
    @Napolean, this is my requirement as well and Please share you thoughts on this if you have got this thing worked for your self.
  • Napolean
    Napolean over 6 years
    @BhupeshPant I got it working partially. Write in preferences were successful across different apps but the changes were not reflected immediately. So kind of not worked in my case as well.
  • lenooh
    lenooh over 6 years
    Bhupesh Pant: I read about it somewhere. It was a long time ago, I don't recall exactly. Anyway, you can try and see how it goes (use an alpha version).
  • H S W
    H S W over 6 years
    Is it possible for two apps to write data on same sharedprefrences?
  • dazed
    dazed over 5 years
    Based on the following, it is definitely NOT OK to change or add the sharedUserId after an app has been published: stackoverflow.com/questions/3254157/…
  • metamonkey
    metamonkey over 3 years
    Any reason this wouldn't be working anymore? @OmerCansizoglu I can't get it to work. All the values return encrypted.