Search code examples
androidsharedpreferencesdialog-preference

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


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>

Solution

  • 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.