3

I am creating a song playback app. I have a Set that I keep in SharedPreferences. I want to add and remove file names from it, and keep them in the same order as I added them in for example:

recentsp = getSharedPreferences("recentkey", Context.MODE_PRIVATE);

    recent = recentsp.getStringSet("recent", recent);
    recentedited = recent;

    if (recentedited.contains(string) {
        recentedited.remove(string);
        Log.i(TAG, "Recent exists, removing song");
        SharedPreferences.Editor editor = recentsp.edit();
        editor.clear();
        editor.putStringSet("recent", recentedited);
        editor.commit();
    }

        recentedited.add(string);
    Log.i(TAG, "adding song to recent");
        SharedPreferences.Editor editor = recentsp.edit();
        editor.clear();
        editor.putStringSet("recent", recentedited);
        editor.commit();

But the problem arises when I view these in a ListView. I want them in the order that I add them so that I can have a recently played section, but sometimes they don't move at all, or maybe they end up in the beginning. It seems random. Am I missing something?

Edit:

I check to make sure the SharedPreferences is not null by doing this in an initialization step...

Edit:

Even with using a LinkedHashSet I still don't get proper ordering. I only call this if the SharedPreferences is null, so I'm not exactly sure how to make sure I am using a LinkedHashSet.

        recentsp = getSharedPreferences("recentkey", Context.MODE_PRIVATE);
    recent = recentsp.getStringSet("recent", null);
    if (recent == null) {

        recent = new LinkedHashSet<String>();
        SharedPreferences.Editor editor = recentsp.edit();
        editor.clear();
        editor.putStringSet("recent", recent);
        editor.commit();
    }
MJDude
  • 73
  • 10
  • A quick glance at the docs of `getStringSet` says: "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." So you should be copying them into a new `Set` implementation, modifying that, and putting that in. Additionally, it looks like you're supposed to be able to control the `Set` implementation -- what implementation are you putting in initially? – Louis Wasserman Feb 02 '16 at 23:05
  • Won't I encounter the same problem when I pull it out again? If I modify by putting it in to a new set and rewriting the old set. When I call `getStringSet` won't it just spit out my Set in the wrong order? – MJDude Feb 02 '16 at 23:08
  • Possibly; from the API it looks like maintaining any sort of order may just not be an available feature. But you should probably _first_ modify your code to avoid doing exactly what the documentation tells you not to do, and then work on this bug. – Louis Wasserman Feb 02 '16 at 23:10
  • I don't understand what you mean by controlling the Set implementation. – MJDude Feb 02 '16 at 23:11
  • what is `recent` initially set to, before this code happens? – Louis Wasserman Feb 02 '16 at 23:13
  • I just edited the question to include how I implement the set. – MJDude Feb 02 '16 at 23:19
  • `HashSet`s have undefined, sort-of-random ordering. If you want ordering, use a `LinkedHashSet`; see if that helps. But yeah, `HashSet` is what you use when you specifically don't care about order. – Louis Wasserman Feb 02 '16 at 23:20
  • Am I copying my `Set` in to a new implementation properly by using the `recentedited`? – MJDude Feb 02 '16 at 23:41
  • it's not clear to me you're copying it at all? You still seem to be modifying the set returned by `getStringSet`. And, I confess, it's not obvious just from the API where there's _any_ way to preserve order. – Louis Wasserman Feb 02 '16 at 23:42
  • How would you go about copying it? – MJDude Feb 02 '16 at 23:49
  • `recentedited = new LinkedHashSet(recent)` would do it. `recentedited = recent` does nothing useful. – Louis Wasserman Feb 02 '16 at 23:50

3 Answers3

6

If you are looking for an implementation of the Set interface that preserves insertion order, switch from HashSet to: LinkedHashSet. All of the standard Set behavior remains intact, with the addition of insertion-order iteration.

From the associated JavaDoc:

Hash table and linked list implementation of the Set interface, with predictable iteration order. This implementation differs from HashSet in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is the order in which elements were inserted into the set (insertion-order). Note that insertion order is not affected if an element is re-inserted into the set. (An element e is reinserted into a set s if s.add(e) is invoked when s.contains(e) would return true immediately prior to the invocation.)

This implementation spares its clients from the unspecified, generally chaotic ordering provided by HashSet, without incurring the increased cost associated with TreeSet. It can be used to produce a copy of a set that has the same order as the original, regardless of the original set's implementation

Community
  • 1
  • 1
Sean Mickey
  • 7,618
  • 2
  • 32
  • 58
  • I just edited my answer with my changes, but it still doesn't seem to be preserving my order. – MJDude Feb 02 '16 at 23:39
  • @MJDude Rather than attempting to save each song title as a separate `SharedPreference`, why not save the list as a single value? It might feel more natural to save a playlist as a different type of resource; I'm not trying to be critical, but it's not really a setting value that controls how your application functions at runtime - it really is just a bit of saved state. Then you would have full control of ordering in the way you really want. – Sean Mickey Feb 03 '16 at 04:03
  • I was already putting the song titles in to a single list. But in what other way would you suggest saving it? – MJDude Feb 03 '16 at 21:16
  • Rather than creating a `Set`, where each `String` is a separate song title, and then adding the `Set` as a (set of) `SharedPreference` values, what I am trying to describe is just a single `String`, added as a single `Preference`, and constructing that single value so that it contains all of the song titles separated by a delimiter. The value of that single `Preference`, if you chose a semicolon as the delimiter, would end up being something like: "Song Title 1;Song Title 2;Very, Very Long Song Title 3;Song Title 4", etc. – Sean Mickey Feb 03 '16 at 22:10
  • Thanks, That's what I did! – MJDude Feb 08 '16 at 21:35
2

I have a Set that I keep in SharedPreferences. I want to add and remove file names from it, and keep them in the same order

That is not possible. You do not control the Set implementation, and there is no requirement for a Set to be ordered. Your choices are:

  1. Do not worry about the order, or

  2. Do not use string sets with SharedPreferences, but instead store your values in a single string (e.g., encode to JSON), or

  3. Do not use SharedPreferences, but instead use some other data storage option (e.g., SQLite database, JSON file)

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
0

Maybe just use an ArrayList instead of a Set - it looks like you already have code to make sure the item is removed and added if it's already in the collection - that pattern should work with the ArrayList.

If you need code to serialize the list to a preference this older question has an example Save ArrayList to SharedPreferences or use something like GSON to store and retrive Store a List or Set in SharedPreferences

Community
  • 1
  • 1
jt-gilkeson
  • 2,661
  • 1
  • 30
  • 40