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)?
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.
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.
Related
I have created a service that writes some information about a widget once a user places it on home screen(the info is picked up from the confutation activity)..i also write down the number of widgets the user has set up.
Once the user removes the widget i delete that info in the shared preferences.
What i have experienced is that if user places for example 2 widgets, then removes one, then places one again, doing all those actions fast, the shared preferences file gets inconsistent values in it. Sometimes it works ok but most of the time i get stuck with wrong values in it.
I am using apply(), i've tried with commit but same thing happens.
The values i store in the shared preferences are crucial for the system to work, without it the widgets are useless since they are backed up by info from internet based on the user configuration which is written in preferences.
Is switching to a database solution more reliable or any other viable solution which will fix this "race condition"? (maybe forcing my own mechanism of synchronization, but as far as i've understood from docs, apply() is already synchronized, and the read/write should first go to RAM which should make it fast and i shouldnt be experiencing any problems like this since the user cant physically manage to delete a widget and place a new one faster then 2-3 seconds top!)
Try using the synchronized keyword in working with the SharedPreferences itself. For example, here is a method that could be used when setting an application String in the SharedPreferences of an Android app:
public synchronized static void setAppString(Context context, String pref,
String val) {
SharedPreferences sp = context.getSharedPreferences(
APP_PREFS_UNIQUE_ID, Context.MODE_PRIVATE);
Editor editor = sp.edit();
editor.putString(pref, val);
editor.commit();
}
For few/simple key-value pairs, you might not need the overhead of a database paradigm.
When I originally wrote and published my app, I was using a custom written activity to handle application settings. I used custom file name to store shared preferfences, like this:
getSharedPreferences("custom_settings_file",MODE_PRIVATE);
But now I'm refactoring my app and I would like to implement PreferenceActivity or PreferenceFragment and an xml file with PreferenceScreen section. Every tutorial or example that I've seen is using
getDefaultSharedPreferences(context);
to retrieve shared preferences, because PreferenceActivity assumes default filename to store preferences and there's no way to tell it to use a different one(at least I couldn't find one after an hour of searching and reading documentation).
So now I have a problem. If I just simply use the new default file, existing users of my app will lose their settings when they update the app, because the new application will not know anything about "custom_settings_file". What would be the best way to move the data from an old file to a new one on app update?
Here are the possible options that I could come up with:
Extend Application class and implement a piece of code in onCreate() so that every time my app is launched, it would check for existence of "custom_settings_file" and move it's contents to the new one. But running a block of code on every app launch seems like wasting too much processing resources for an operation that only needs to run once.
Just notify the user that their old settings are gone. But obviously this is not acceptable.
Is there a better solution, than option 1? Perhaps someone has already faced a similar problem?
What is preventing you from doing number 1 only once?
Just add a "migration" boolean to the new sharedpreferences.
If you also load the xml preference file then you can try this:
PreferenceManager.setDefaultValues(context, YOUR_PREFERENCE_NAME, MODE_PRIVATE, R.xml.preference_file, false);
If not (you want to add each preference item dynamically in your code) then you can do like this:
PreferenceManager pm = getPreferenceManager();
pm.setSharedPreferencesMode(MODE_PRIVATE);
pm.setSharedPreferencesName(YOUR_PREFERENCE_NAME);
In case you still want to use the defaultSharedPreference and process the migration then ... I'm writing this and I see Nicklas's answer, so I'm done here.
Could you add value in your new SharedPreferences that records whether you are a new install or an upgrade. If you don't have the setting in your sharedpreferences, check to see if you have an old preferences file in the way you were before. Then convert those preferences to your new method, and set your private setting indicating that it's been upgraded. Then just set the new value indicating the new state and you won't need to check your old preferences any more.
I am having an odd issue in which the SharedPreferences are not being updated upon returning to an app. Here's the scenario:
I have two projects that use the same shared preferences. Project1 and Project2. They are separate but related apps. They are signed with the same key and use sharedUserId to share information.
Project1 opens Project2.
Project2 retrieves the SharedPreferences file and writes to it via this method:
Context prefsContext = c.createPackageContext(packageNameOfProject1, Context.CONTEXT_IGNORE_SECURITY);
SharedPreferences prefs = prefsContext.getSharedPreferences(fileName, Context.MODE_PRIVATE);
SharedPreferences.editor editor = prefs.edit();
editor.putBool("bool1", value1);
editor.putBool("bool2", value2);
...
editor.putBool("boolN", valueN);
editor.apply();
Once that is done, I return to Project1 by calling finish().
Project1 then reads the data like so:
SharedPreferences prefs = getSharedPreferences(getPreferencesFileName(), Context.MODE_PRIVATE);
Boolean value1 = prefs.getBoolean(fileName, false);
Boolean value2 = prefs.getBoolean(fileName, false);
...
Boolean valueN = prefs.getBoolean(fileName, false);
Map<String, ?> mappings = prefs.getAll();
Set<String> keys = mappings.keySet();
for(String key : keys) {
log.d(TAG, "_____");
log.d(TAG, "Key = " + key);
log.d(TAG, "Value = " + mappings.get(key));
}
The problem is the values are not updated in Project1. I can tell based off the logs at the end that the file isn't even generating mappings. However, I can verify that the xml is being updated. If I force stop the app then restart it, all the mappings are there in Project1. All the values are correct. However, I need them updated when the user leaves Project2. I feel like there's something I'm missing here but can not spot it.
The only things I have been able to find on the subject is:
SharedPreferences.Editor not being updated after initial commit
SharedPreferences value is not updated
These don't help as I'm already doing that.
I have WRITE_EXTERNAL_STORAGE set in both manifests. The fileName is the same (else I wouldn't be able to read the file when I reenter the app).
EDIT:
I should note that I did try to do editor.commit() instead of editor.apply() as I thought I was facing a race condition. The problem still persisted. I'm thinking that for some reason, the old reference to the SharedPreference in Project1 is being used instead of a new one even though I'm lazy-loading it each time.
EDIT2:
Ok, to further test to see what id going on. I decided to try the opposite direction.
In Project1 I do:
Float testFloat (float) Math.random();
Log.d("TEST_FLOAT", "Project1: TEST_FLOAT = " + testFloat);
prefs.edit().putFloat("TEST_FLOAT", testFloat).commit();
In Project2 I do:
Log.d("TEST_FLOAT", "Project2: TEST_FLOAT = " + prefs.getFloat("TEST_FLOAT", 0.0f));
I then go back and forth between the two like so: Project1->Project2->Project1->Project2->Project1->Project2 and here is the logcat result:
Project1: TEST_FLOAT = 0.30341884
Project2: TEST_FLOAT = 0.30341884
Project1: TEST_FLOAT = 0.89398974
Project2: TEST_FLOAT = 0.30341884
Project1: TEST_FLOAT = 0.81929415
Project2: TEST_FLOAT = 0.30341884
In other words, it's reading and writing to the same file. However, it's keeping the mapping that it had when it was first opened it in the project. Even though I close the project, the mapping remains until the application is force stopped.
EDIT: I'm still getting upvotes on this answer even though it recommends a method that has since been deprecated. If you need consistent data through multi-process, then you need to use something other than SharedPreferences like a ContentProvider backed by a file system or database.
https://developer.android.com/reference/android/content/Context#MODE_MULTI_PROCESS
Final answer:
Replace
getSharedPreferences(fileName, Context.MODE_PRIVATE);
with
getSharedPreferences(fileName, Context.MODE_MULTI_PROCESS);
As per document:
Context.MODE_MULTI_PROCESS
SharedPreferences loading flag: when set, the file on disk will be
checked for modification even if the shared preferences instance is
already loaded in this process. This behavior is sometimes desired in
cases where the application has multiple processes, all writing to the
same SharedPreferences file. Generally there are better forms of
communication between processes, though.
This was the legacy (but undocumented) behavior in and before
Gingerbread (Android 2.3) and this flag is implied when targeting
such releases. For applications targeting SDK versions greater than
Android 2.3(Gingerbread), this flag must be explicitly set if desired.
I knew there was a simple oversight in this.
Try to call editor.commit(); instead of editor.apply();. Normally they should do the same, but I noticed there some weird behaviour sometimes.
From the SharedPreferences documentation the method "apply()" writes asynchronously (delayed) to the file and the method "commit()" writes the information synchronously (immediatly) to the file.
Also from the documentation, they say that you don't need to wary about the activity life cycle while using any of the above methods, as they ensure the "apply()" writes are completed before status changes, if they are running in the same system process.
However, as you are using two different projects, they run in two different processes and you can't be sure that "apply()" on project 2 will be concluded before the "onResume()" starts on project 1.
I suggest that you try "commit()" instead of "apply()" to force synchronous write. If this don't solve the issue, you can add a delay of a couple of seconds before reading the preferences in project 1, just to check if the issue is related to this delayed write.
--EDITED--
To debug the issue let's do the following:
1-In Eclipse Select/Add the view "File Explorer" and navigate to the directory:
/data/data/[your package name]/shared_prefs
your package name should be something like "com.myproject.shared"
2-Select the file with your saved preferences and press the button "download to PC".
3-Check if file contents match your expectations.
good luck.
I just implemented a preferences screen for one of the sample apps that I am creating. I followed the guide given on google site. All is fine and I loaded up a ListPreference and I am able to store it and persist it also. There is also one minor problem here. I have already defined a custom preference file for the app but this automatic handling of the preference screeen seems to be creating a preference file on its own. For now I was trying to get this code to work but it is not getting the custom preference file.
final Preference customPrefs = getPreferenceScreen().findPreference(Utils.PREFS_NAME);
customPrefs.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
customPrefs.getEditor().commit();
return true;
}
});
Is there a way to bind the custom preference file with this auto preference class in anyway ?
You can set a custom name for the file used to store the preferences, used in your PreferenceActivity, by calling its getPreferenceManager().setSharedPreferencesName("file_name") method. Just remember that you need to set that before calling addPreferencesFromResource, otherwise your UI will still change stuff back to the wrong file (the default one).
As I said, you don't need to do that, since you can use the default file provided by the system. If/when you need to read the preferences elsewhere, you can then just call PreferenceManager.getDefaultSharedPreferences(Context context).
I already addressed those remarks in another question.
Also remember that some of the methods are deprecated. If you're writing new code, try to avoid them and conform to the new "Fragment Way" of doing things.
have been away from Android for more than a year, trying to pick up on it again, but confused about having a simple default preference.
My app relies heavily on a SQLite3 db, which has hundreds of tables. The launch activity has to build and load a good amount of data at launch.
I just need a simple way to read two strings from a preferences file. The thing is that I want to have, the very first time the application opens, two default string values.
If the user changes that I will save back to it.
Been reading two books, SharedPreferences, File I/O, etc. just that all the examples seem a tad complicated for what I need.
So, If I create a res/pref/preferences.xml all I have seen around are PreferenceScreens as the root element
For my need, I think I just need a root preference such as:
<?xml version="1.0" encoding="utf-8"?>
<Preference
xmlns:android="http://schemas.android.com/apk/res/android">
</Preference>
how do I add two simple string key/value pairs that I would read at the very first launch, and only when needed write to it?
any help is appreciated, sorry for the noob'iness
I think you're over complicating what you need to do, sharedpreferences are perfect for this situation, and easily implemented. Just do the check in the onCreate() when the app starts :)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//"Hello World" is the name of your preferences
SharedPreferences settings = getSharedPreferences("HelloWorld", 0);
//to get a value from pref - (NAME_OF_PREF,RETURN_IF_NOT_FOUND)
boolean enteredDetails = settings.getBoolean("details", false);
string foo = settings.getString("foo_name","no name");
//to write to them - (NAME_OF_PREF,VALUE)
SharedPreferences.Editor editor = settings.edit();
editor.putString("foo_name", "Joe Bloggs");
editor.putBoolean("details", true);
editor.commit();
}