14

I've used shared preferences many times, but for some reason, changes aren't being saved in a new app that I'm testing. Here's a snippet of the important code:

SharedPreferences sp = getSharedPreferences(getString(R.string.key_preferences), MODE_PRIVATE);
Set<String> widgets = sp.getStringSet(getString(R.string.key_widgets), (new HashSet<String>()));
widgets.add(name + " " + Integer.toString(appWidgetId) + " " + address);
sp.edit().putStringSet(getString(R.string.key_widgets), widgets).commit();

I've used logging to check that the widget is added to the set, but the updated set is never saved. If I change the last line to...

sp.edit().putStringSet(getString(R.string.key_widgets), widgets).putString("testkey", "testvalue").commit();

...then everything saves just fine. What am I missing?

*UPDATE:

I found out that this also works:

SharedPreferences sp = getSharedPreferences(getString(R.string.key_preferences), MODE_PRIVATE);
Set<String> widgets = sp.getStringSet(getString(R.string.key_widgets), (new HashSet<String>()));
Set<String> newWidgets = new HashSet<String>();
for (String widget : widgets) newWidgets.add(widget);
newWidgets.add(name + " " + Integer.toString(appWidgetId) + " " + address);
sp.edit().putStringSet(getString(R.string.key_widgets), newWidgets).commit();

Perhaps I missed something in the documentation about needing to create a new object for the editor to save the prefs.

*UPDATE 2:

It makes no difference if I create an editor object:

SharePreferences.Editor spe = sp.edit();
spe.putStringSet(getString(R.string.key_widgets), widgets)
spe.commit();
Cœur
  • 37,241
  • 25
  • 195
  • 267
piusvelte
  • 1,596
  • 2
  • 15
  • 18

6 Answers6

19

We just need to read the documentation more carefully

According to getStringSet

Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

In fact it should have been noted in SharedPreferences.Editor not to send putStringSet a set that may modified afterwards. Make a copy of the set returned from getStringSet before modifying, and make a copy of your set before sending it to putStringSet.

SharedPreferences myPrefs = getSharedPreferences(myPrefName, MODE_PRIVATE);
HashSet<String> mySet = new HashSet<string>(myPrefs.getStringSet(mySetKey, new HashSet<string()));
....
SharedPreferences.Editor myEditor = myPrefs.edit();

Then one of

myEditor.putStringSet(mySetKey, new HashSet<String>(mySet));

or

myEditor.putStringSet(mySetKey, (Set<String>) mySet.clone());
Chike
  • 306
  • 3
  • 5
10

I would like to improve Chike's answer a bit.

Cause

The reason why commit() did not save to disk is obvious if we take a look at the implementation(unchanged since v4.0.1), which commit to memory first and then waits for disk writes to complete:

public boolean commit() {
    MemoryCommitResult mcr = commitToMemory();
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

The point is, no changes will be detected if we call putStringSet() with the same instance from getStringSet():

// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
    MemoryCommitResult mcr = new MemoryCommitResult();
    ...
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                ...
                    if (mMap.containsKey(k)) {
                        Object existingValue = mMap.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mMap.put(k, v);
                }

                mcr.changesMade = true;
                ...
            }
            ...
        }
    }
    return mcr;
}

Since there is no change made, writeToFile() gladly skips disk writing and set a successful flag:

private void writeToFile(MemoryCommitResult mcr) {
    // Rename the current file so it may be used as a backup during the next read
    if (mFile.exists()) {
        if (!mcr.changesMade) {
            // If the file already exists, but no changes were
            // made to the underlying map, it's wasteful to
            // re-write the file.  Return as if we wrote it
            // out.
            mcr.setDiskWriteResult(true);
            return;
        }
        ...
    }
    ...
}

Solution

Please refer to Chike's answer, make a copy of the String set before saving it to the same SharedPreference key.

Community
  • 1
  • 1
Roger Huang
  • 597
  • 1
  • 8
  • 15
1

You need to save the Editor object and then call commit() (before Android 2.3) or apply() (for Android 2.3 and above).

SharedPreferences.Editor editor = sp.edit();
editor.put...
editor.commit();
Ran
  • 4,117
  • 4
  • 44
  • 70
1

I'm experiencing the same problem that you describe.

My boolean and string preferences are stored without problem, but when I tried to store a string set, the problems begin.

When I add the first element to the string set and store it, it works. But, when I try to add a second one, it is added in memory (I can refresh a view that shows all the elements stored in that preference). If I force close my application and restart it again, the shared preference only have the first element added.

I debugged my code and see that the HashSet where I stored my strings is ok when I add a new one, but the changes are not written to persistent storage

JoseLSegura
  • 3,830
  • 3
  • 20
  • 27
0

Use the following structure for Shared Preferences :

1. Create an Object

SharedPreferences myPrefs = context.getSharedPreferences(PREF_NAME, MODE_WORLD_READABLE);

2. To save values in Shared Preferences :

Create an Editor for the Shared Preference :

SharedPreferences.Editor prefsEditor = myPrefs .edit();

Then put values in Editor :

prefsEditor.putString("KEY", VALUE);

Now commit the changes to save Shared Preference :

prefsEditor.commit();
Vipul Purohit
  • 9,807
  • 6
  • 53
  • 76
0

I put the below code in onCreate method, so it will reset everytime I restart the app.

    preferences.edit().putInt(...).apply();

Use the method stated in here instead: https://stackoverflow.com/a/28789934/11266070

    SharedPreferences sharedPrefs = getSharedPreferences("sp_name", MODE_PRIVATE);
    SharedPreferences.Editor ed;
    if(!sharedPrefs.contains("initialized")){
        ed = sharedPrefs.edit();

        //Indicate that the default shared prefs have been set
        ed.putBoolean("initialized", true);

        //Set some default shared pref
        ed.putString("myDefString", "wowsaBowsa");

        ed.commit();
    }  

Hope this will remind someone...

jonijeng
  • 195
  • 3
  • 9