DialogPreference is not saving the preference when I expect it to? - android

I have written a bare bones standard DialogPreference which is working fine, except that it is not saving the preference to default shared preferences when I expected it to.
1) open the app, and main activity shows value of foo from default shared preferences = 1
2) go to settings
3) click on foo setting which opens my DialogPreference and shows value = 1
4) enter value 3
5) close my DialogPreference using Ok button
***** default shared preferences foo should now be 3
6) click on foo setting which opens my DialogPreference and shows value = 1
***** so my DialogPreference didn't save the preference to default shared preferences?
7) cancel dialog
8) go back to main activity which shows value of foo from default shared preferences = 3
***** so my DialogPreference did save the preference to default shared preferences
9) go to settings
10) click on foo setting which opens my DialogPreference and shows value of 3
Why isn't the value of default shared preferences foo = 3 at step (6)?
It seems that the preference is only being saved to default shared preferences when the flow returns to the main activity from the settings list, which is counter intuitive to saving the preference in the onDialogClosed method of DialogPreference.
MyDialogPreference
public class MyDialogPreference extends DialogPreference
{
private static final String DEFAULT_VALUE = "0";
private String value = DEFAULT_VALUE;
private EditText editText;
public MyDialogPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
setDialogLayoutResource(R.layout.constrained_integer_preference);
}
#Override
public void onBindDialogView(View view)
{
super.onBindDialogView(view);
editText = (EditText) view.findViewById(R.id.edit);
editText.setText("" + value);
}
#Override
protected void onDialogClosed(boolean positiveResult)
{
if (positiveResult)
{
persistString(editText.getText().toString());
}
super.onDialogClosed(positiveResult);
}
#Override
protected Object onGetDefaultValue(TypedArray typedArray, int index)
{
return typedArray.getString(index);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
{
if (restorePersistedValue)
{
value = getPersistedString(DEFAULT_VALUE);
}
else
{
value = (String) defaultValue;
if (shouldPersist())
{
persistString(value);
}
}
}
}
EDIT: So it appears that the preference I am handling with my DialogPreference has no key, which is causing all the problems. But I have specified the key in the preferences.xml file for this DialogPreference. I have tried everything to force the key to be recognised but nothing is working.
Can anyone tell me how I get a DialogPreference to receive the android:key from the preferences.xml file to work?
preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<org.mycompany.myproject.MyDialogPreference
android:defaultValue="11"
android:dialogLayout="#layout/my_preference"
android:inputType="number"
android:key="MY_KEY"
android:selectAllOnFocus="true"
android:singleLine="true"
android:summary="summary"
android:title="My Preference" />
</PreferenceScreen>

You'd have to implement the OnPreferenceChangeListener and/or call to notifyChanged().
Unless you'd provide the code of that DialogPreference, it's difficult to reproduce the issue.

At some point I always feel like I'm hacking Android, and this is definitely a hack.
Initially I thought the problem I was fighting was that the framework ignores my android:key, because getKey() returns an empty string, but that can't be true because it gets the persistent value when it starts the PreferenceScreen, and saves my changed values to shared preferences when I close the DialogPreference.
So it seems the problem I am fighting is that the framework reads the preferences persistent values in to internal members, and then uses the internal members until the flow returns out of the preferences framework, without refreshing them after a DialogPreference has closed.
But I have finally found a way of getting the PreferenceScreen to refresh the preferences persistent values it holds in it's internal members. Although it's not really a refresh, it's a hack.
So what I do is basically throw away the PreferenceScreen and create a new one. I do this by adding the following code to my SettingsFragment.onCreate method directly before addPreferencesFromResource(R.xml.preferences).
SharedPreferences.OnSharedPreferenceChangeListener prefListener = (prefs, key) ->
{
setPreferenceScreen(null);
addPreferencesFromResource(R.xml.preferences);
};
PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()).registerOnSharedPreferenceChangeListener(prefListener);
This is probably bad. I have tested it, repeatedly, though not thouroghly, and have not witnessed any adverse effects so far.
So with this hack, I can now repeatedly open a DialogPreference from the PreferenceScreen, save a new value, then go back in to the DialogPreference with the previously updated value.
I don't believe my hack is the intended way of achieving this outcome, but after days of searching the source code and google for answers, I have not found anything else.
I am leaving this answer here for anyone else that faces the same problem and is brave enough to try my hack. I'll update the answer if (and probably when) I find any problems with the hack.
Better yet, if anyone else can provide a preferred solution, pointing out what I have done wrong that caused the problem, please do.
EDIT: After working for so long, that hack eventually broke, and consistently.
But while removing the hack, I approached the problem with a fresh mind, and using the fact that I determined the dialog is getting the preference key, I used this fix for the problem, which is working perfectly.
I added this line of code to the start of onBindDialogView
value = getSharedPreferences().getString(getKey(), "-1");
Which makes the calls to onGetDefaultValue and onSetInitialValue redundant, but they just don't work as intended, at least not for me.
EDIT:
omg, I hate this!
I did not notice that during an earlier refactor the line of code that updates the DialogPreference internal value in onDialogClosed was removed.
It's usually something simple, and with everything else I was checking, I missed that small change.
I only just noticed it during a code review on the repo, and now I feel silly. So no additional code was required in the end.

Related

PreferenceManager.setDefaultValues does not work for custom Preferences

I'm working on a little app with a lot of modifiable preferences, most of them being SeekBarPreferences.
It happens that, since I'm quite not happy with Android default SeekBarPreferences, I'm using the very good MaterialSeekBarPreference library which unfortunately have not been updated for 2 years.
Here is an example of code used by this library:
<com.pavelsikun.seekbarpreference.SeekBarPreference
android:key="#string/param_maxEvent"
android:title="Blahlblahblahblahblah"
android:summary="Blahlblahblahblahblah too"
android:defaultValue="2"
custom:msbp_minValue="0"
custom:msbp_maxValue="5"
custom:msbp_measurementUnit="events"
custom:msbp_interval="1"
custom:msbp_dialogEnabled="false"/>
As you can see, you can use the android:defaultValue xml attribute, and it works perfectly with the UI.
Since I need to load all these default values at app initialization, I use the PreferenceManager.setDefaultValues method:
public class App extends Application {
#Override public void onCreate() {
super.onCreate();
PreferenceManager.setDefaultValues(this, R.xml.preferences, true);
}
}
This works fine with all default preferences (SwitchPreference, ListPreference, Preference), but unfortunately not with these custom SeekBarPreference.
Loading the preferences activity does not set up thoses default values either.
Is there any workaround for this problem ? Else, if I was up to edit the library, what should I change ?
I don't use the method PreferenceManager.getDefaultSharedPreferences(this, R.xml.preferences, true);. Instead, I use preference.setDefaultValue(object); in the Fragment.

Programatically updated ListPreferences is not displayed

When the user updates a shared preference through a subclass of PreferenceActivity I check whether or not the new value is valid at the given time. If not the value should be changed back in the onSharedPreferenceChanged method.
This works so far. I set an OnSharedPreferenceChangedListener, the method gets called. The user-set value will be overwritten and the new value will be used in the app, however when I open this specific preferences value (in this case a ListPreference) again the wrong list item will be selected (the one the user selected, not the one set in the Listener). I tried overwriting the value with both:
mPrefs.edit().putString("answers", value.toString()).commit();
mPrefs.edit().putString("answers", value.toString()).apply();
Are there additional steps I need to take to update the ListPreference? After restarting the PreferenceActivity the value will be displayed correctly.
try to use Override method SharedPreferenceChanged
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals(KEY)) {
Preference ServicePref = findPreference(key);
// Set summary to be the user-description for the selected value
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
}
}
I found a way to solve this issue by setting the value manually as described here:
http://liquidlabs.ca/2011/08/25/update-preference-value-without-reloading-preferenceactivity/

MultiSelectListPreference not storing values?

I'm rather new to Android App developing so maybe I'm just making a simple newbie mistake, but here's my problem:
I have 2 simple Activities, MainActivity and SettingsActivity. In MainActivity I have a button which displays SettingsActivity. Within SettingsActivity I include a PreferenceFragment SettingsFragment and display a ButtonBar at the bottom of the Activity. Within the SettingsFragment I have a MultiSelectListPreference defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory
android:title="#string/title_schedule_settings">
<MultiSelectListPreference
android:key="#string/key_list_schedule"
android:title="#string/title_schedule_list"
android:dialogTitle="#string/title_schedule_list"
android:entries="#array/list_weekdays"
android:entryValues="#array/list_weekdays"
android:defaultValue="#array/empty_list"
android:persistent="true"/>
</PreferenceCategory>
</PreferenceScreen>
Now when I select that Preference it shows me the list with all the entries as defined in the array, I can select multiple entries and when I confirm the dialog the values are in fact stored in the SharedPreferences under the defined key. But if I now show the Preference again it will show me previously selected items as selected, but the values are no longer stored within the SharedPreferences, and after some fiddling around I had to realize that the values in the SharedPreferences apparently get wiped as soon as the dialog is shown.
So now my questions are: is this normal/intended behavior or is this a bug? And how can I work around this?
I already tried making my own implementation of MultiSelectListPreference and override the onPrepareDialogBuilder method like this
#Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder)
{
super.onPrepareDialogBuilder(builder);
Log.i("mmslp", Arrays.deepToString(PreferenceManager.getDefaultSharedPreferences(getContext()).getStringSet(getKey(), new HashSet<String>()).toArray()));
setValues(PreferenceManager.getDefaultSharedPreferences(getContext()).getStringSet(getKey(), new HashSet<String>()));
}
but the values are apparently wiped already at this point.
I spent some time yesterday on this and am now convinced that it is not intended like that, but actually broken - someone confused reference by pointer and by value. ;-)
Seems to be fixed in the more current versions of Android (since 4.1) though:
https://code.google.com/p/android/issues/detail?id=22807
The way I solved it now for the previous versions of Android is to override the setValues method in my implementation of MultiSelectListPreference and just copy the values into a new object:
#Override
public void setValues( Set<String> values ) {
//Workaround for https://code.google.com/p/android/issues/detail?id=22807
final Set<String> newValues = new HashSet<String>();
newValues.addAll( values );
super.setValues( newValues );
}

Saving data between activities using external class

I'm developing an app that has to share strings between activities. I'm trying to get the seperate activities to call a public class with set and get methods. The calling the methods part works and I manage to get a response although the set value has to be rememberd by the set and get class. Here's a link to my set and get class, it's pretty basic: http://pastebin.com/0WabNKz3
Now my question is this: How do I make the set and get class to remember my values between sessions? Feel free ask questions if there's anything you didn't understand.
Thanks!
You need to use SharedPreferences. That's the way to save data even after the app is closed and you can access it from anywhere:
public void savePrefrences(String key, String value)
{
SharedPreferences prefs = context.getSharedPreferences(context.getPackageName(), 0);
prefs.edit().putString(key, value).commit();
}
public String getPrefrences(String key)
{
SharedPreferences prefs = context.getSharedPreferences(context.getPackageName(), 0);
return prefs.getString(key, "");
}
Save the prefrence when and whereever you want and get it whenever and from wherver you want.
The value will not delete when you close the app.
I ended up creating invisible EditTextPreference that now hold the data that I want to keep because they can be shared easily.
When you say saving between sessions, do you mean between the app being paused, or closed completely?
A good resource for lifecycle and storing data across sessions is:
//developer.android.com/training/basics/activity-lifecycle/index.html

Android: Is there a way to show DialogPreference from code?

I want to open some preferences (which are made by extending DialogPreference) on first app startup. Also, these preferences are used as usual preferences.
Is there a way of accomplishing this?
EDIT:
I have my custom preference, made like this:
public class CitySelectPreference extends DialogPreference {
// Some code here
}
And as the solution I want it to be shown from the code, without the need of user getting to preference screen.
Just do this :
CitySelectPreference preference = (CitySelectPreference) findPreference("city_pref_key")
//You have to set a key to yout PreferenceScreen
PreferenceScreen screen = (PreferenceScreen) findPreference("screen_pref_key");
//Retrieve the index of the preference in preferenceScreen
int index = preference.getOrder();
//Perform a click
screen.onItemClick(null, null, index, 0);

Categories

Resources