Can't Control Order of String Set in Shared Preferences


耗尽温柔 提交于 2019-12-05 00:13:11

问题


This is my first stackoverflow question. I have done lot of googling on this. On Hashsets, Treesets, LinkedHashSets, Collections, Stacks (Stack class is deprecated?)... I realize I could just use SQLite but I'm trying to avoid that for the time being.

I'm working on an app in Android Studio. The app deals with people, listing them and contacting them in different ways. App users can maintain and control three types of lists: recently contacted, blocked, and favorites. These lists are saved as string sets in shared preferences so they survive the app being closed and reopened. The individual strings act as primary keys when the lists are populated using an online database. I am most concerned with the "recently contacted" list because the order of this list matters.

The problem is that as far as I understand, when I declare a string set it is as follows:

Set<String> faveArray = new LinkedHashSet<String>;

As I understand I can only use HashSet, LinkedHashSet, or TreeSet on the right hand side.

Since I used LinkedHashset, I was expecting that when I reopen the app and pull the data back out of sharedpreferences, that the strings would be pulled out starting with the most recently added (LIFO / stack), and therefore when I populate a listview, they will show with the "most recently contacted" person at the top of the list and so on... or at least I was expecting to have some kind of predictable order/behavior that I can work with.

So....... I've attached my code for my shared preferences I/O class.

From my main app, I do something like:

static SharedPrefUTIL sharedPrefUTIL;

sharedPrefUTIL = new SharedPrefUTIL(this);

sharedPrefUTIL.addRecent("derp");
sharedPrefUTIL.addRecent("nerp");
sharedPrefUTIL.addRecent("gerp");
sharedPrefUTIL.addRecent("herp");

At this point, the code

Log.i(TAG, set + " committed to " + key);

in the commit() method of class SharedPrefUTIL Logs:

" [derp, nerp, gerp, herp] committed to recentArray "

Bur after closing and reopening the app, if we perform:

sharedPrefUTIL.toastContents("R");

The result is seemingly random order. <<<< that is my problem.

Any help greatly appreciated.

package com.secretsoft.booberbunz;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;

/**
* Created by Programming on 2/22/2016.
*/
public class SharedPrefUTIL {

protected static final String TAG = "CXX SharedPrefUTIL";

Context context;

SharedPreferences sharedPreferences;

Set<String> faveArray;
Set<String> blockArray;
Set<String> recentArray;

public static final Set<String> DEFAULT = new HashSet<String>(Arrays.asList("empty"));


public SharedPrefUTIL(Context context){

    this.context = context;

    // load shared prefs into static arrays (new objects to prevent problems)

    sharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);

    recentArray = new LinkedHashSet<String>(sharedPreferences.getStringSet("recentArray",DEFAULT));
    blockArray = new LinkedHashSet<String>(sharedPreferences.getStringSet("blockArray",DEFAULT));
    faveArray = new LinkedHashSet<String>(sharedPreferences.getStringSet("faveArray",DEFAULT));

    Log.i(TAG, "SharedPrefUTIL instance created");


    if (recentArray.contains("empty")) {
        recentArray.clear();

        Log.i(TAG, "recentArray contains the string -empty- and was cleared");
    }

    if (blockArray.contains("empty")) {
        blockArray.clear();

        Log.i(TAG, "blockArray contains the string -empty- and was cleared");
    }

    if (faveArray.contains("empty")) {
        faveArray.clear();

        Log.i(TAG, "faveArray contains the string -empty- and was cleared");
    }

}

public void toastLength(String type){

    if (type == "R"){

        String temp = type + " array is this long: " + recentArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);


    }

    else if (type == "B"){

        String temp = type + " array is this long: " + blockArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);

    }

    else if (type == "F"){

        String temp = type + " array is this long: " + faveArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);

    }

    else {
        Log.i(TAG, "invalid type param given to toastLength()");
    }

}


public void toastContents(String type){

    if (type == "R"){

        for (String temp : recentArray) {

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, "recentArray contains: " + temp);

    }

    }

    else if (type == "B"){

        for (String temp : blockArray) {

            Toast.makeText(context, temp,
                    Toast.LENGTH_LONG).show();

            Log.i(TAG, "blockArray contains: " + temp);

        }

    }

    else if (type == "F"){

        for (String temp : faveArray) {

            Toast.makeText(context, temp,
                    Toast.LENGTH_LONG).show();

            Log.i(TAG, "faveArray contains: " + temp);

        }
    }

    else {
        Log.i(TAG, "invalid type param given to toastContents()");
    }



}

public void clearList(String type){
    if (type == "R"){
        recentArray.clear();

        commit("recentArray", recentArray);

        Toast.makeText(context,"recent list has been cleared.", Toast.LENGTH_LONG);
    }

    else if (type == "B"){

        blockArray.clear();

        commit("blockArray", blockArray);

        Toast.makeText(context,"blacklist has been cleared.", Toast.LENGTH_LONG);

    }

    else if (type == "F"){

        faveArray.clear();

        commit("faveArray", faveArray);

        Toast.makeText(context,"favorites have been cleared.", Toast.LENGTH_LONG);

    }

    else {
        Log.i(TAG, "invalid type param given to clearList()");
    }

}

public void addRecent(String newRecent){
    recentArray.add(newRecent);
    commit("recentArray", recentArray);
    Log.i(TAG, newRecent + " added to recentArray");
}

public void addBlocked(String newBlocked, String  nick){
    blockArray.add(newBlocked);
    commit("blockArray", blockArray);
    Toast.makeText(context, nick + " has been blacklisted!", Toast.LENGTH_SHORT);
}

public void remBlocked(String remBlocked, String nick){
    blockArray.remove(remBlocked);
    commit("blockArray", blockArray);
    Toast.makeText(context, nick + " has been unblocked.", Toast.LENGTH_SHORT);
}

public void addFave(String newFave, String nick){
    faveArray.add(newFave);
    commit("faveArray", faveArray);
    Toast.makeText(context, nick + " added to favorites!", Toast.LENGTH_SHORT);

}

public void remFave(String remFave, String nick){
    faveArray.remove(remFave);
    commit("faveArray", faveArray);
    Toast.makeText(context, nick + " removed from favorites.", Toast.LENGTH_SHORT);
}

public void commit(String key, Set<String> set){
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putStringSet(key,set);
    editor.commit();
    Log.i(TAG, set + " committed to " + key);
}




}

回答1:


It is unfortunate, but you have simply found a limitation of SharedPreferences.

While you are using an orderd hash, it does not load them ordered when you call getStringSet.

The quickest simplest way I have found of doing this is to convert your array into text, ordered, and then save that into the SharedPreferences. Android comes with an object JSONArray that can do this.

http://developer.android.com/reference/org/json/JSONArray.html

Here is some pseudo code that will do what you want:

public void saveOrderedCollection(Collection collection, String key){
    JSONArray jsonArray = new JSONArray(collection);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putString(key, jsonArray.toString());
    editor.commit();
}

public Collection loadOrderedCollection(String key){
    ArrayList arrayList = new ArrayList;
    SharedPreferences.Editor editor = sharedPreferences.edit();
    JSONArray jsonArray = new JSONArray(editor.getString(key, "[]"));
    for (int i = 0; i < jsonArray.length(); i++) {
        arrayList.put(jsonArray.get(i));
    }
    return arrayList;
}

Here are some other posts that I used to make this:

Is it possible to add an array or object to SharedPreferences on Android

In shared preferences how to store string array in android application




回答2:


I've implemented the changes suggested (using JSON strings instead of string sets when saving to shared prefs) and here's the new code for my shared pref class in case anyone else is trying to do the same thing. Probably overkill on the troubleshooting logs but thats how I roll since I don't know how to debug properly.

package [];

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;

import java.util.LinkedHashSet;
import java.util.Set;


/**
 * Created by Programming on 2/22/2016.
 */
public class SharedPrefUTIL {

protected static final String TAG = "CXX SharedPrefUTIL";

Context context;

SharedPreferences sharedPreferences;

Set<String> recentArray;
Set<String> blockArray;
Set<String> faveArray;

JSONArray recentJArray;
JSONArray blockJArray;
JSONArray faveJArray;


public SharedPrefUTIL(Context context){

    this.context = context;

    // START load arrays from shared prefs

    sharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);

    try {
        recentJArray = new JSONArray(sharedPreferences.getString("recentArray","[]"));
        blockJArray = new JSONArray(sharedPreferences.getString("blockArray","[]"));
        faveJArray = new JSONArray(sharedPreferences.getString("faveArray","[]"));
    } catch (JSONException e) {
        e.printStackTrace();
        Log.i(TAG, e.toString());
    }

    Log.i(TAG, "length of recentJArray is " + recentJArray.length());

    recentArray = new LinkedHashSet<String>();
    blockArray = new LinkedHashSet<String>();
    faveArray = new LinkedHashSet<String>();

    for (int i = 0; i < recentJArray.length(); i++) {

        try {
            recentArray.add(recentJArray.getString(i));
        } catch (JSONException e) {
            e.printStackTrace();
            Log.i(TAG, e.toString());
        }

    }

    for (int i = 0; i < blockJArray.length(); i++) {

        try {
            blockArray.add(blockJArray.getString(i));
        } catch (JSONException e) {
            e.printStackTrace();
            Log.i(TAG, e.toString());
        }

    }

    for (int i = 0; i < faveJArray.length(); i++) {

        try {
            faveArray.add(faveJArray.getString(i));
        } catch (JSONException e) {
            e.printStackTrace();
            Log.i(TAG, e.toString());
        }

    }

    // END load arrays from shared prefs

    Log.i(TAG, "SharedPrefUTIL instance created");

}

public void toastLength(String type){

    if (type == "R"){

        String temp = type + " array is this long: " + recentArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);


    }

    else if (type == "B"){

        String temp = type + " array is this long: " + blockArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);

    }

    else if (type == "F"){

        String temp = type + " array is this long: " + faveArray.size();

        Toast.makeText(context, temp,
                Toast.LENGTH_LONG).show();

        Log.i(TAG, temp);

    }

    else {
        Log.i(TAG, "invalid type param given to toastLength()");
    }

}


public void toastContents(String type){

    if (type == "R"){

        for (String temp : recentArray) {

            Toast.makeText(context, temp, Toast.LENGTH_LONG).show();

            Log.i(TAG, "recentArray contains: " + temp);

        }

    }

    else if (type == "B"){

        for (String temp : blockArray) {

            Toast.makeText(context, temp,
                    Toast.LENGTH_LONG).show();

            Log.i(TAG, "blockArray contains: " + temp);

        }

    }

    else if (type == "F"){

        for (String temp : faveArray) {

            Toast.makeText(context, temp,
                    Toast.LENGTH_LONG).show();

            Log.i(TAG, "faveArray contains: " + temp);

        }
    }

    else {
        Log.i(TAG, "invalid type param given to toastContents()");
    }



}

public void clearList(String type){
    if (type == "R"){
        recentArray.clear();

        commit("recentArray", recentArray);

        Toast.makeText(context,"recent list has been cleared.", Toast.LENGTH_LONG);
    }

    else if (type == "B"){

        blockArray.clear();

        commit("blockArray", blockArray);

        Toast.makeText(context,"blacklist has been cleared.", Toast.LENGTH_LONG);

    }

    else if (type == "F"){

        faveArray.clear();

        commit("faveArray", faveArray);

        Toast.makeText(context,"favorites have been cleared.", Toast.LENGTH_LONG);

    }

    else {
        Log.i(TAG, "invalid type param given to clearList()");
    }

}

public void addRecent(String newRecent){
    recentArray.add(newRecent);
    commit("recentArray", recentArray);
    Log.i(TAG, newRecent + " added to recentArray");
}

public void addBlocked(String newBlocked){
    blockArray.add(newBlocked);
    commit("blockArray", blockArray);
    Toast.makeText(context, newBlocked + " has been blacklisted!", Toast.LENGTH_SHORT);
}

public void remBlocked(String remBlocked){
    blockArray.remove(remBlocked);
    commit("blockArray", blockArray);
    Toast.makeText(context, remBlocked + " has been unblocked.", Toast.LENGTH_SHORT);
}

public void addFave(String newFave){
    faveArray.add(newFave);
    commit("faveArray", faveArray);
    Toast.makeText(context, newFave + " added to favorites!", Toast.LENGTH_SHORT);

}

public void remFave(String remFave){
    faveArray.remove(remFave);
    commit("faveArray", faveArray);
    Toast.makeText(context, remFave + " removed from favorites.", Toast.LENGTH_SHORT);
}

public void commit(String key, Set<String> set){

    // convert set into JSON

    JSONArray jsonArray = new JSONArray(set);

    Log.i(TAG, "During commit, jsonArray(set) is this long: " + jsonArray.length());

    //-------------------------------------------


    // commit changes
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putString(key,jsonArray.toString());
    editor.commit();
    Log.i(TAG, jsonArray.toString() + " committed to " + key);

}

}



回答3:


Using kotlin, you could simply write extension functions to store a list of strings which keeps the ordering as you've added them:

   fun SharedPreferences.Editor.putStringList(key: String, list: List<String>) {
        this.putString(key, list.joinToString(";"))
    }

   fun SharedPreferences.getStringList(key: String): List<String> {
        return this.getString(key, "")?.split(";") ?: listOf()
    }

You can then store your values like this:

sharedPrefs.edit().putStringList("SOME_KEY", listOf("A", "B", "C"))

And receive them likes this:

val myList = sharedPrefs.getStringList("SOME_KEY")


来源:https://stackoverflow.com/questions/35567517/cant-control-order-of-string-set-in-shared-preferences

工具导航Map