Android - SharedPreferences between 2 apps not working effectively

I am using SharedPreferences in Android to exchange simple data between 2 apps inside the same project. My purpose is having a 'configuration app' (app A) to save some values and make them accesible to app B through a shared file that is used by SharedPreferences class. App A must allow user to update values launching the app again and app B should see that changes.

All my apps are working as same one by declaring the following in both apps:

android:sharedUserLabel="@string/user_id"
android:sharedUserId="sos.app"

This allow my apps to use same data. So, Firstly I launch app A, then I introduce values and commit data to the file. Then I launch app B, I access to SharedPreferences via packageContent and I get my values properly.

My problem appears when I launch app A and app B again to update values. All changes are made correctly in app A (I saw changes in sharedPreferences) but when I launch app B again, it access to previous values instead of new ones.

It is quite strange. It looks like Android holds my sharedPreferences instance reference and when app B starts again, it does not go again to shared file through getSharedPreferences method.

This is related code in app A:

//Instance of SharedPreferences made in onCreate method
sharedPreferences = getSharedPreferences("my.configuration.file", Context.MODE_PRIVATE)
//Function to insert values in sharedPreferences
fun addConfigItemToSharedPreferences(itemToBeSaved: String){

            with (sharedPreferences.edit()) {
                putString(keyItemsList[configItemToBeAddedCounter], itemToBeSaved)
                commit()
            }
        }

And this is related code in app B (inside onCreate method):

val packageContext = createPackageContext("com.televes.angrod.configurationapp", 0)
val sharedPreferences = packageContext.getSharedPreferences("my.configuration.file", Context.MODE_PRIVATE)
val mapOfPreferences: Map<String, *> = sharedPreferences.all

Is my approach correct or shall I use another mechanism to share data between apps?

Upvotes: 2

Views: 1626

Answers (3)

I found my own solution!

Finally I decided to join all my apps to the same one. I just declared each activity in same manifest and then all of them are sharing same file directory and permissions. I also used a direct file access instead of shared preferences because of my problem with cached values. I also implemented a File Observer in order to get notified in each app about file changes:

class FileObserver(val context: MyWatchFaceSOSApp.Engine, absolutePath: String) : FileObserver(absolutePath, FileObserver.MODIFY) {

    val TAG = "FileObserver"

    override fun onEvent(event: Int, path: String?) {

        //data was written to a file
        if (FileObserver.MODIFY and event != 0) {
            Log.i(TAG, "Wohoo. El archivo se ha actualizado!")
            Log.i(TAG, "Iniciando actualización de parámetros del WATCHFACE")
            context.cancelAndPurgePresenceReportService()
            context.updateConfigurationParams()
            context.setUpTimeBetweenPresenceReportMsgs()
            context.startPresenceReportService()
        }
    }
}

So now I´m using a File Manager with to read/write functions. I found it in stack overflow and did my own changes. Just for sharing:

public class FileManagerUtility {

    private static String TAG = "FileManagerUtility";

    public static void writeSettings(Context context, Map<String, String> watchSettings) {

        File appDirectory = new File(context.getFilesDir(), "watchsettings");
        String file = "settings.txt";

        if (!appDirectory.exists()) {
            appDirectory.mkdirs();
        }

        File fileName = new File(appDirectory, file);

        FileOutputStream fos = null;
        ObjectOutputStream out = null;
        try {
            Log.i(TAG, "Escribiendo en fichero: " + fileName.getAbsolutePath());

            fos = new FileOutputStream(fileName);
            out = new ObjectOutputStream(fos);
            out.writeObject(watchSettings);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.flush();
                fos.close();
                if (out != null)
                    out.flush();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Log.i(TAG, "Se ha escrito correctamente en el fichero: " + fileName.getAbsolutePath());
            Log.i(TAG, "Contenido del fichero: " + watchSettings.toString());
        }
    }

    @SuppressWarnings("unchecked")
    public static Map<String, String> readSettings(Context context) {

        Map<String, String> watchSettings;

        File appDirectory = new File(context.getFilesDir(), "watchsettings");
        String file = "settings.txt";

        if (!appDirectory.exists()) return null; // File does not exist

        File fileName = new File(appDirectory, file);

        FileInputStream fis = null;
        ObjectInputStream in = null;
        try {
            Log.i(TAG, "Leyendo de fichero: " + fileName.getAbsolutePath());
            fis = new FileInputStream(fileName);
            in = new ObjectInputStream(fis);
            Map<String, String> myHashMap = (Map<String, String> ) in.readObject();
            watchSettings = myHashMap;
            return watchSettings;

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (StreamCorruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {

            try {
                if(fis != null) {
                    fis.close();
                }
                if(in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

And that´s it! I´m saving every configuration value in a Map to do quick access to data, which is also easy to read/write from files.

Hope it helps!

Upvotes: 0

smoothBlue
smoothBlue

Reputation: 203

Please use Content Providers to share data across apps. That's the recommended Android pattern: https://developer.android.com/guide/topics/providers/content-providers

Upvotes: 1

Akshar Patel
Akshar Patel

Reputation: 9378

Earlier you could solve this by changing MODE_PRIVATE to MODE_WORLD_READABLE or MODE_WORLD_WRITABLE but it has been deprecated now.

From official documentation (https://developer.android.com/training/data-storage/shared-preferences#GetSharedPreferences):

Caution: The MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE modes have been deprecated since API level 17. Starting with Android 7.0 (API level 24), Android throws a SecurityException if you use them. If your app needs to share private files with other apps, it may use a FileProvider with the FLAG_GRANT_READ_URI_PERMISSION. For more information, also see Sharing Files.

So, you have to use FileProvider now.

You can learn more about FileProvider here:

https://developer.android.com/reference/android/support/v4/content/FileProvider

Upvotes: 2

Related Questions