I am trying to read the value of a field in sharedpreference using two different instances of sharepreferences. While read by using the first instance is giving the correct result, the second read operation using second instance is returning the default values.Why is it so?Am I missing some important concept here?
Code:
public void testMethod(){
SharedPreferences pref1=myContext.getSharedPreferences(PreferenceHelper.MY_PREF, myContext.MODE_PRIVATE);
//Correct value is obtained here...
String value1=pref1.getString("KEY", "");
SharedPreferences pref2=myContext.getSharedPreferences(PreferenceHelper.MY_PREF, myContext.MODE_PRIVATE);
//Incorrect value is obtained here...
String value2=pref2.getString("KEY", "");
}
I am doubting this is due to multiple instances of the same preference.Android documentation states:
Only one instance of the SharedPreferences object is returned to any callers for the same name, meaning they will see each other's edits as soon as they are made.
Does my case relates to concept in this sentence?
Since your invoking commit() and not apply(), one of them isn't saving and you're getting the wrong answer. Check out the docs:
Unlike commit(), which writes its preferences out to persistent storage synchronously, apply() commits its changes to the in-memory SharedPreferences immediately but starts an asynchronous commit to disk and you won't be notified of any failures. If another editor on this SharedPreferences does a regular commit() while a apply() is still outstanding, the commit() will block until all async commits are completed as well as the commit itself.
The above from http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()
Related
I'm reading SharedPreferences in my app at startup, and after a few runs it will crash and tell me that I'm trying to cast from a String to a Boolean. Below is the code that I use to read and write this value.
// Checks if the realm has been copied to the device, and copies it if it hasn't.
private void copyRealm() {
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (!sharedPreferences.getBoolean(getString(R.string.pref_copied), false)) {
// Copy the realm to device.
final String path = copyBundledRealmFile(getResources().openRawResource(R.raw.realm), getString(R.string.realm_name));
// Save the path of the realm, and that the realm has been copied.
sharedPreferences.edit()
.putBoolean(getString(R.string.pref_copied), true)
.putString(getString(R.string.pref_path), path)
.apply();
}
}
The two strange things are that it doesn't start happening for a few builds, and so far it has only happened on a simulator. I haven't been able to check a physical device yet, but I've also been running this code without change for several months and had no trouble.
Why would I be getting this message?
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
at android.app.SharedPreferencesImpl.getBoolean(SharedPreferencesImpl.java:293)?
Take a look at this question Android getDefaultSharedPreferences.
It seems it's a better idea to just use
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
or
SharedPreferences references1=getSharedPreferences("some_name",MODE_PRIVATE);
instead of using
SharedPreferences preferences= getDefaultSharedPreferences(this);
From the documentation:
getPreferences(MODE_PRIVATE) retrieves a SharedPreferences object for
accessing preferences that are private to this activity. This simply
calls the underlying getSharedPreferences(String, int) method by
passing in this activity's class name as the preferences name.
I regularly use one of these two approaches and had no problem of any kind so far.
I am storing the data in the Shared Preferences by
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("menu_bar","abcd");
editor.apply();
and I am fetching the data from Shared Preferences in fragment by
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String name = preferences.getString("menu_bar","");
if(!name.equalsIgnoreCase("")){
Toast.makeText(getActivity(), name, Toast.LENGTH_SHORT).show();
It is working when the app is removed from the stack.
But on the first time it is not working. Getting NULL in the first time but working fine from the second time. I also tried with editor.commit() when saving it.
Use getSharedPreferences("MyPref", Context.MODE_PRIVATE) and then commit to reflect changes instantly
SharedPreferences preferences = getSharedPreferences("MyPref", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("menu_bar","abcd");
editor.commit();
Official Documentation
Commit your preferences changes back from this Editor to the SharedPreferences object it is editing. This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
Note that when two editors are modifying preferences at the same time, the last one to call apply wins.
Unlike commit(), which writes its preferences out to persistent storage synchronously, apply() commits its changes to the in-memory SharedPreferences immediately but starts an asynchronous commit to disk and you won't be notified of any failures. If another editor on this SharedPreferences does a regular commit() while a apply() is still outstanding, the commit() will block until all async commits are completed as well as the commit itself.
As SharedPreferences instances are singletons within a process, it's safe to replace any instance of commit() with apply() if you were already ignoring the return value.
You don't need to worry about Android component lifecycles and their interaction with apply() writing to disk. The framework makes sure in-flight disk writes from apply() complete before switching states.
The SharedPreferences.Editor interface isn't expected to be implemented directly. However, if you previously did implement it and are now getting errors about missing apply(), you can simply call commit() from apply().
Try using the same instance of SharedPreferences. Easiest would be an explicitly named one:
class MyActivity {
private static final String PREFS_NAME = "myActivityPrefs";
private static SharedPreferences sharedPrefs = null;
public static SharedPreferences prefs(Context context) {
if (sharedPrefs == null) {
sharedPrefs = context.getSharedPreferences(PREFS_NAME, 0);
}
return sharedPrefs;
}
}
You then use it in the activity like this:
prefs(this).edit().putString("menu_bar","abcd").apply()
And in the fragment:
String name = MyActivity.prefs(getActivity()).getString("menu_bar","");
This is a recommended mode of operation that would give you good performance without waiting for I/O on your main thread - apply() does that for you but requires using the same instance to be consistent.
To use separate instances (sometimes you're forced to - for example through separate processes) always use commit(), which can freeze your main thread at times, and still doesn't guarantee consistency due to how filesystems work, I've seen instances where commit()ed data wasn't immediately available to a separate process, often on specific devices that had a FS configuration quirk.
THE SCENARIO
I have a class that makes use of a request list set by the user. The request list is stored in SharedPreferences. The dilemma I'm facing is to whether to keep an instance of the request list or to read from SharedPreferences every time the request list is needed (which is very frequent).
Also not that Gson is used to deserialize the object.
The code goes like this:
public List<PrayerTimesCalculator.Time> getDefaultRequestList() {
if (mRequestList != null) return mRequestList;
// Try getting request list from preferences;
Gson gson = new Gson();
String json = mSharedPref.getString(KEY_PREF_REQUEST_LIST, null);
Type listType = new TypeToken<List<Time>>() {
}.getType();
mRequestList = gson.fromJson(json, listType);
if (mRequestList != null) return mRequestList;
// Create default list;
mRequestList = Arrays.asList(
Time.DAWN,
Time.MORNING,
Time.AFTERNOON,
Time.EVENING,
Time.MID_NIGHT);
return mRequestList;
}
THE GOAL
My concern is that if I keep around an instance of the request list, and there are multiple instances of this class, an update to the request list in one instance of the class would not be reflected in the rest of the instances until they are recreated.
Thus, I'm leaning towards reading from SharedPreferences unless there is a better way to keep the request list objected updated in all instances.
THE QUESTION
(1) So, how efficient is it to read the same key from SharedPreferences quite frequently by multiple instances of the object? and (2) Is there a better way to keep the request list objected updated in all instances?
So there are a couple of approaches you can take to this.
First, your object is small - re-reading SharedPreferences thousands of times would hardly be noticeable. It's not like SharedPreferences is on a remote drive or has a "bad connection."
Second, if you don't like that answer, then you need a DAO (Data Access Object). SharedPreferences is a form of this already. It provides a means to store and retrieve data with confidence that you have the most recent data available. But, if you feel like you can improve on it's optimization (because it's generic, and this is your app), then you can provide access to you data through a static object that performs both "read" and "write" operations. This will guarantee that access to the object is done with the most recent data. Of course, you will need to be thread aware, etc. (something that is not always guaranteed by SharedPreferences).
Next, you could persist your data in a database and use Cursors or other built-in or custom DAOs. This requires another level of complexity and a lot of overhead, but is useful when several components of your app might need access to the data, provide updates or needs real-time monitoring of changes because background threads or other objects may make modifications that will change your app behavior or result in UI updates.
Last, you could use more complex data stores like a Content Provider. This is really required for cases where you want/need other apps to access data provided by your app (and your app may also consume the data). That's a complex solution and implementation is well outside the scope of this question.
But I mention it because you seem interested in being certain that frequent reads of SharedPreferences is acceptable. It definitely is acceptable - otherwise there would be something else besides it, databases and Content Providers.
The onSharedPreferenceChanged() listener does not have a boolean type to return, as the onPreferenceChanged() listener has.
So how can one reject a change after validation?
The only way that occurs to me is to keep all shared preferences stored in local variables and if the validation fails, restore the value from the local variable, if it passes update the local variable.
Is this doing double work? Is there a built-in mechanism for reject?
Is this doing double work?
I think so. If one part of the code is going to reject this change, why did another part of the code permit it?
Is there a built-in mechanism for reject?
The user input should be validated in onPreferenceChange before it is committed. It looks like the purpose of onSharedPreferenceChanged is not validation but to receive a read-only live update when a change has been committed.
Since other code could receive this callback and act on it, it's too late to validate during this callback.
Reference (the Preference javadoc):
This class provides the View to be displayed in the activity and associates with a SharedPreferences to store/retrieve the preference data
Basically you use setText to return the value to default if it does not meet your requirements. Then you can show a Toast or whatever to inform the user of the issue.
Here is the solution:
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
if ("mypref".equals(key)) {
String value = sharedPreferences.getString(key, "");
try {
int v = Integer.parseInt(value);
// do whatever is needed
} catch (RuntimeException e) {
String deflt = ... # fetch default value
EditTextPreference p = (EditTextPreference) findPreference(key);
p.setText(def);
}
}
}
Credit: http://androidfromscratch.blogspot.ca/2010/11/validating-edittextpreference.html
You could use the editor: (http://developer.android.com/reference/android/content/SharedPreferences.html#edit()) which allows to make changes atomically and then call commit only if everything is right. I hope this helps.
So guys here is the question, I looked up at this one
http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit%28%29
and it mentions that "Note that when two editors are modifying preferences at the same time, the last one to call commit wins."
Say my activity is like this :
SharedPreferences.Editor editor();
onCreate(....)
{
//start the instance of editor
......
if(condition1)
editor.put...
editor.commit()
}
onPause()
{
if(condition1)
{
editor.commit()
}
}
Will this work? Because in my application I have to store the user credentials while online and do few submissions back to server, where the user id is recorded, when offline(i.e condition1), its not. The onPause is where i do it. So could anyone confirm this please. Thanks.
**The putBoolean() seems to work fine. this is a humongous code piece, so I might be doing a mistake somewhere with user credentials logic. So, I just want to confirm with editor.commit() usage.*
If you have a class member SharedPreferences.Editor editor, then yes, you can use it in your whole class without worry. Also, look at the method signature:
public abstract boolean commit()
You can check the result of commit to be sure the values have been written successfully.
boolean result = editor.commit();
Yes, in the majority of cases this will work in the example you have provided (work as in the correct order). If you want to absolutely make sure that the modifications are all performed then you can synchronize them..
For example:
private void someSaveMethod() {
synchronized(this) {
//TODO perform your retrieval of the PreferencesEditor
editor.commit();
}
}