How can I force SharedPreferences to save? - android

As far as I can tell, values aren't stored in Android's SharedPreferences until they're explicitly accessed. That is, while they may have default values in XML, no value is placed in a SharedPreferences store until an accessor method is called, which is why all the accessors have "default" parameters included.
While this isn't a huge deal for simply pulling values out of the preference store, it prevents any attempt to get all the preference keys that are used in the application, even if they are stored in XML. The keys don't appear when SharedPreferences#getAll() is called unless the preference has already been explicitly accessed.
Is there any way to force all preferences defined in XML to be saved into a SharedPreferences store? The nearest solution I can find is to manually parse the Preference XML files, find all keys and defaults, and save the default value for each one. Is there a cleaner approach?
UPDATE
After looking at this in more depth, I've been getting a partial list of preferences for a different reason. When the defaults are set, only EditTextPreference and ListPreference values are saved. The other two, a custom preference and a CheckBoxPreference, are completely ignored. Here's an example of the CheckBoxPreference that's being ignored:
<CheckBoxPreference
android:defaultValue="false"
android:key="PREF_NAME"
android:summary="Summary text"
android:title="Title" />
Any idea why not all defaults are being set?

You can use PreferenceManager::setDefaultValues. For instance, at your Application::onCreate method.
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
If the last argument is false, this will only set the default values if this method has never been called in the past.

The reason for the missing preferences in my case was actually an Android bug. The workaround was to manually set the missing preferences to their default values, as indicated in a duplicate question: Android CheckBoxPreference Default Value

Related

What does Preference.setSummary() do?

In my code I've used the Preference.setSummary() method to change the summary line of a setting to a certain string. In my main activity when I need to retrieve this setting's value I call the getString(key) method on the SharedPreferences object, where key is the key of an EditTextPreference.
Now I really wonder if setSummary also sets the value of a preference in SharedPreferences by the same key to the value that I pass to setSummary, because I really never created a SharedPreferences.Editor object and called a putString method on it explicitly.
The docs don't say anything specific other than:
Sets the summary for this Preference with a CharSequence.
Parameters
summary The summary for the preference.
Reference
Firstly I'm sorry that I asked this question even though one of the next lectures in the course (by Google) provided the answer, but I think it was worth it as there existed no question on SO about Preference.setSummary() also saving values in SharedPreferences.
The answer is that when the summary is changed so is the value in SharedPreferences as quoted in this video by Google's official Android Course:
When the user selects an option, it's saved into SharedPreferences.
Providing some context, "user selects an option" refers to choosing an option in a ListPreference which is then set as its summary.

Cannot load default values from custom preference

I used Mark Murphy's code to built a custom preference, a TimePreference with a TimePicker in particular. I have a problem with getting and storing the default values in onSetInitialValue method, where null is returned. I also read that PreferenceManager.setDefaultValues(this, R.xml.preferences, false); does not work with strings, which in fact does not work in my case. Is there a way to get and set the default values without using the PreferenceManager's getDefaultSharedPreferences method?
I deleted the preference file from the emulator and everything is back to normal

What happens to the existing data of Android user preferences when preferences structure changes in a new version?

For example, if a few entries of preferences are added or deleted, how does Android handle the existing preference data when the app is updated with the new preferences structure?
I am sorry for this rudimentary question, but my diligent search and reading could not find the answer.
The shared preferences are stored in the xml file in the folder data/data/your.application.package/shared_prefs/. The file is called your.application.package_preferences.xml;
When you retrieve the shared preferences, you call the Context.getSharedPreferences method. It creates the SharedReferences object and invokes the SharedReferences.startLoadFromDisk method.
If you open this method, you will see that the xml file with preferences (mFile) is parsed and preferences are loaded into the memory storage (map).
BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
From then you will always read your preferences from the memory. More exactly from the private Map<String, Object> mMap variable.
Also the application can call the startReloadIfChangedUnexpectedly method and if the underlying file has been changed, it will be parsed and a new HashMap will be created.
As to your question, there are the following cases:
You added a preference item in a new version. Then the default value specified as the second parameter will be returned. Note: the attribute android:defaultValue is not used, so be aware.
String v = (String)mMap.get(key); // not found => v = null
return v != null ? v : defValue; // return defValue
You removed a preference item in a new version. The xml file and map object will contain some redundant data, but it will be fixed when the user saves preferences next time.
You changed the key of a preference item to some key which wasn't used. Then the default value will be returned. The same result as p.1.
You removed one preference item (with the key pref1_key, for example) and changed the key of another item so that it refers to the first item (from pref2_key to pref1_key). Then the second preference item will have the value of the first item.
You changed a type of a preference item (from boolean to int, for example). Then it will throw the CastException because of this and similar code: (Integer)mMap.get(key);. But you can change, for example, EditTextPreference to ListPreference, because they both have the String type.
Maybe there are some more test cases, but so far I've made up only 5.
Also here is the example of the preferences file with ListPreference, EditTextPreference and CheckBoxPreference:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="pref_theme_key">THEME_LIGHT</string>
<string name="pref_homepage_key">test</string>
<boolean name="pref_display_name_key" value="true" />
</map>
how does Android handle the existing preference data when the app is updated with the new preferences structure?
It is unclear what you mean by "preferences structure".
You can think of a SharedPreferences as being a persistent HashMap. You store various values under various keys, and you can get them back later. Android has no a priori knowledge of the keys or the types of values that will be stored under those keys, until you actually store something.
My guess is that by "preference structure", you mean "preference screen definitions in XML". In that case:
if you add new preferences, Android will handle those normally, just as if the preferences has been around all along but the user had never set those preferences before
if you remove preferences that you had used before, the old data remains, unless you elect to get rid of it, as Android has no way to know that you will never use that data again
if you re-key a preference (i.e., replace keyA with key1 for what logically is the same preference), Android will treat key1 as a totally new preference, and so you will need to put smarts in your code somewhere to update your SharedPreferences data to reflect the key change, if you so choose
There is no SharedPreferencesOpenHelper equivalent of SQLiteOpenHelper to manage preference "schema versions" and help you migrate data. You are welcome to create such a system, if you so choose, if you feel that it will help you manage frequently-changing "preference structure".

Initializing Preferences

Android Guide recommends defining preferences in XML files, And from there, these can be loaded in PreferenceActivity/PreferenceFragment etc for viewing and editing by user. But in real scenario, User Interacts with other activities first, then (maybe) with Preferences UI.
What if the starter activity needs some of these preferences ? They'll be not loaded yet, because preferences resources has not been inflated yet. Is there a way to pre-access preferences in XML files ?
Yes. When you first request the preference you can provide it with a default value. E.g. if you are loading a preference of type Int, then you can do so in the following manner from an activity:
SharedPreferences defaultSettings = PreferenceManager.getDefaultSharedPreferences(this);
int preferenceValue = defaultSettings.getInt("PreferenceName", 7);
This would load your preferenceValue to be 7 (without this preference ever being initialized yet). This is assuming that in your XML preference file, you have a preference of key "PreferenceName". If you plan on editing this preference in the activity before the Preference activity has been ran, be sure you commit your changes with a SharedPreferenceEditor:
// ... change to preferenceValue occurs prior to this code
SharedPreferences.Editor defaultEditor = defaultSettings.edit();
defaultEditor.putInt("PreferenceName", preferenceValue);
defaultEditor.commit();
We probably want to avoid "PreferenceName" in a hardcoded matter though, and instead use it as a string in the strings.xml file. This way it can be grabbed both from the initial code when the preference has not been saved yet and from the Preference XML file as well. This means that our above code would substitute the string "PreferenceName" with something like the following:
getResources().getString(R.string.pref_name)
And in your Preference XML file you may would reference the key in the following way:
android:key="#string/pref_name"
android:defaultValue="7"
This should cover "pre-loading" the preference as well as trying to keep most of the application settings within one place. There may indeed be overlap in terms of whether or not the XML preference was created/loaded before the initial Activity occurred, but I haven't tested that out yet.
EDIT: It turns out instead of using the above code, you can directly load the XML file (with its default preference) by the following method:
PreferenceManager.setDefaultValues(this, R.xml.preference, false);
More information about this method can be found in the documentation for the PreferenceManager: http://developer.android.com/reference/android/preference/PreferenceManager.html
If you look at SharedPreference API, you will see this
getString(String key, String defValue)
So, you can actually in fact define a default value if it's not already existed.
Source: http://developer.android.com/reference/android/content/SharedPreferences.html
You can also predefine default value in XML using
android:defaultValue="SOMETHING"

SharedPreferences.getInt() results in ClassCastException - Why?

I have a simple (not user-editable) numerical setting defined in a preferences XML as follows:
<EditTextPreference
android:key="#string/numeric_val"
android:defaultValue="0" />
And I read it using this simple statement:
sharedPrefs.getInt(getString(R.string.numeric_val), 3)
It works, but when I try to read it, for the first time after application install, it generates a ClassCastException.
The documentation says that getInt() "Throws ClassCastException if there is a preference with this name that is not an int." - and I know that this preference is clearly defined as an <EditTextPreference> (a string?) but, if this is the reason for the exception, how I am supposed to use SharedPreferences.getInt()?
I know I can use SharedPreferences.getString() instead and then do the parsing/conversion myself, but then what is the purpose of SharedPreferences.getInt()?
You can store preferences as sharedPreferences.edit().putInt(..).commit() (as an example);
And then get them as getInt. But if you use EditTextPreference it will set the type of the preference to string. So if you use EditTextPreference to store some data, use Integer.valueOf(getString) to get it back.
If, you put it manually, use getInt().
As a workaround, you can set onPreferenceChangeListener on this EditTextPreference , and whenever user changes it, you will manually save it as an int, so then, getInt will work normally.
android:defaultValue="0"
is a string.
There is no way to declare an actual int in the xml of your prefs

Categories

Resources