2

I've the following sample code. The application is installed successfully at first time. However, it throws an error on reinstalling.

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LinkedHashSet<String> planets = new LinkedHashSet<String>();
        planets.add("Earth");
        SharedPreferences prefs = getPreferences(0);
        prefs.edit().putStringSet("planets", planets).commit();
        prefs = getPreferences(0);
        planets = (LinkedHashSet<String>) prefs.getStringSet("planets", new  LinkedHashSet<String>());
    }
}

I've pasted the error produced on reinstalling the application below.

Caused by: java.lang.ClassCastException: java.util.HashSet cannot be cast to java.util.LinkedHashSet at com.example.test.MainActivity.onCreate(MainActivity.java:12)

I want to understand why a saved LinkedHashSet can not be casted back to LinkedHashSet. And why is it automaically being converted to HashSet by Android?

Ibungo
  • 1,137
  • 12
  • 23

2 Answers2

8

@Ibungo, I think you've misunderstood what SharedPreferences do. You can NOT ask the prefs to save a LinkedHashSet set - you can only ask it to save a general Set. And in return you can get a general Set after that. There are no guarantees that it'll return the same set implementation as yours.

The preferences are just iterating over the set of items you gave them and writing them to the XML store for your application. Thus when you ask for those items it is reading from the XML and creating a new Set - HashSet since it is the most commonly used one.

The only reason you might want a LinkedHashSet, I can think of, is if you want to preserve the insertion order. If this is the case, you might try converting your collection to ArrayList and store it - there is an example here. Alternatively you could save your data to a JSONArray and store it as a String.

Community
  • 1
  • 1
Samuil Yanovski
  • 2,837
  • 17
  • 19
1

Change your code to use a basic HashSet:

HashSet<String> planets = new LinkedHashSet<String>();
planets.add("Earth");
SharedPreferences prefs = getPreferences(0);
prefs.edit().putStringSet("planets", planets).commit();
prefs = getPreferences(0);
planets = prefs.getStringSet("planets", new  LinkedHashSet<String>());

Side note: Keep in mind that SharedPreferences.getStringSet() is available in API11+ only.

The reason why your Set is converted to a HashSet can be found in Android's source code:

public Editor putStringSet(String key, Set<String> values) {
    synchronized (this) {
        mModified.put(key,
            (values == null) ? null : new HashSet<String>(values));
        return this;
    }
}
flx
  • 14,146
  • 11
  • 55
  • 70
  • Hi @flx, I want to know why I can't use LinkedHashSet. – Ibungo Sep 13 '13 at 06:16
  • Because `SharedPreferences.getStringSet()` is returning a `HashSet`, not a `LinkedHashSet`. It's that simple. – flx Sep 13 '13 at 06:18
  • But why? I'm asking it to save a `LinkedHashSet`, so why does it return me `HashSet`? – Ibungo Sep 13 '13 at 06:19
  • You didn't. You asked it to save a `HashSet`: https://developer.android.com/reference/android/content/SharedPreferences.Editor.html#putStringSet(java.lang.String, java.util.Set) // you can not change the signature of a API method with your plain imagination. – flx Sep 13 '13 at 06:21
  • Hi @flx, as per Android API we can pass any class's object which implements the `Set` interface. Both `HashSet` and `LinkedHashSet` implement `Set` interface, and as per OP's code, he is asking API to save a `LinkedHashSet`. So why is it automatically being converted to `HashSet` when he is trying to get the same object back? Full disclosure: I'm OP's colleague. – Vikrant Chaudhary Sep 13 '13 at 06:58
  • Because the underlying API is serializing the `Set` and later it is deserializing it to a `HashSet`. Here is the line of the Android API which converts your `Set` into a `HashSet`: https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/core/java/android/app/SharedPreferencesImpl.java#L316 – flx Sep 13 '13 at 07:06
  • still not satisfied with the answer? – flx Sep 13 '13 at 07:32
  • This makes sense now. Specially after @SamuilYanovski's answer and your link to the source code. – Vikrant Chaudhary Sep 14 '13 at 06:43