41

I was wondering, what is the common method being used to save Enum to SharedPrefereces? Currently, I'm using gson to convert enum to String, and then save it to SharedPrefereces.

    Gson gson = new Gson();
    // country is an enum.
    String json_country = gson.toJson(country);
    sharedPreferences.edit().putString(COUNTRY, json_country);

I was wondering, is this a good way? Is there any better way?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

5 Answers5

51

You can use a simple String for it and then extract the value using the method valueOf. Here is an example:

public enum MyEnum {
    ENUM1, ENUM2, ENUM3, ENUM4;

    public static MyEnum toMyEnum (String myEnumString) {
        try {
            return valueOf(myEnumString);
        } catch (Exception ex) {
                // For error cases
            return ENUM1;
        }
    }
}

public void setMyEnum(Context context, MyEnum myEnum) {
    SharedPreferences sp = context.getPreferences(this.MODE_PRIVATE);
    SharedPreferences.Editor editor = sp.edit();
    editor.putString("MyEnum", myEnum.toString());
    editor.commit();
}

public MyEnum getMyEnum(Context context) {
    SharedPreferences sp = context.getPreferences(this.MODE_PRIVATE);
    String myEnumString = sp.getString("MyEnum", MyEnum.ENUM1.toString());
    return MyEnum.toMyEnum(myEnumString);
}

Here is the sample code which you can see how does it works. https://github.com/jiahaoliuliu/SavingEnumToSharedPreferences

jiahao
  • 3,373
  • 2
  • 35
  • 36
49

This is the same problem that Enterprise Java developers face when persisting an enum to a database. The problem with the existing answers is that they are fragile and not refactoring-friendly. Here's why (with an alternative at the bottom.)

Using an enum's toString() and valueOf() methods means that an enum value can't be renamed. Suppose I have a VehicleType enum with values CAR and TRUCK. If I store the string "TRUCK" as a preference value and then rename VehicleType.TRUCK to VehicleType.PICKUP_TRUCK in the next version of my app, the stored string no longer makes sense and valueOf() will throw an IllegalArgumentException.

Using a value's ordinal means that the enum values can't be re-ordered or your stored values will no longer match up. This scenario is arguably worse because your app will continue to run but may behave in unexpected ways, making the issue difficult to track down once it's finally noticed and likely impossible to correct. The same is true for adding a new value anywhere but the end. If I added a new MOTORCYCLE value at the top, a CAR stored as its ordinal (0) in the previous version of the app would come back as MOTORCYCLE after updating, and a TRUCK (ordinal 1) would become a CAR.

The alternative I use is to add a final field to the enum and use its value, like so:

public enum VehicleType {
    CAR("C"),
    TRUCK("T");

    private final String code;
    private static final Map<String,VehicleType> valuesByCode;

    static {
        valuesByCode = new HashMap<>(values().length);
        for(VehicleType value : values()) {
            valuesByCode.put(value.code, value);
        }
    }

    VehicleType(String code) {
        this.code = code;
    }

    public static VehicleType lookupByCode(String code) { 
        return valuesByCode.get(code); 
    }

    public String getCode() {
        return code;
    }
}

Store a value using something like preferences.putString("vehicle_type", vehicleType.getCode()) and retrieve it using something like vehicleType = VehicleType.lookupByCode(preferences.getString("vehicle_type", null)).

This approach requires a little extra code but in my opinion, it's the most robust solution.

spaaarky21
  • 6,524
  • 7
  • 52
  • 65
  • I don't see how the previous example is 'fragile'. It does not 'leak' the String used by the preferences. Why can't the simpler version be converted to this more complicated version if/when the enumeration names need to be changed? I am fairly new to Java, am I missing something? I guess the only downside is that you would have to remember the refactoring is needed using the first example... – WayneJ Apr 30 '14 at 21:50
  • 7
    @WayneJ If an enum's name or ordinal wasn't written to persistent memory and died with your VM, I would agree. **But** when your app stores a preference, it "leaks" the value in the sense that the version of your app that wrote the value may not be the version of your app that reads it at a later date. You certainly could refactor your code to use this approach later but like you said, then you would have to remember to do it... and other developers would have to know to refactor it... and also remember to. It's personal preference but I would rather follow this simple pattern from the start. – spaaarky21 Apr 30 '14 at 22:43
  • 1
    I prefer to use Enum.name() and add a proper comment in my enum declaration not to change the string values. – Yar Oct 23 '18 at 14:39
  • @Yar How about ProGuard? Its job is to rename your identifiers. :) – spaaarky21 Oct 27 '18 at 19:26
  • @spaaarky21 So far it has not, I have ProGuard off. I am writing to Shared Prefs enum.name() and reading enum.valueOf() and both strings are the same. But thanks for pointing it out. – Yar Oct 29 '18 at 10:14
13

You can assotiate your enums with integers and store simple int, look this:

Cast Int to enum in Java (second answer) - also in same way you can do enumToInt

Community
  • 1
  • 1
dilix
  • 3,761
  • 3
  • 31
  • 55
3

Considering all the options above I decided to use another solution for the problem.

I need only a set of values, without any logic on them. In this case, it is possible to use such tools as @IntDef and @StringDef instead of using enum.

The solution is safe to refactor both in terms of variables names and variables order. The only thing that should not be changed - actual values. However, default refactoring tools won't ask you to change them in case of renaming, so it is not so easy to make it just automatically (and @IntDef is better from this point of view than @StringDef).

  @Retention(SOURCE)
  @IntDef({NOT_REQUESTED, GIVEN, PROHIBITED})
  public @interface CustomPermission {}

  public static final int NOT_REQUESTED = -1;
  public static final int PROHIBITED = 0;
  public static final int GIVEN = 1;

  @CustomPermission
  public int getPermission() {
    return getSharedPreferences(SHARED_PREFS_FILE)
          .getInt(STATISTICS_COLLECTION_PERMISSION, NOT_REQUESTED);
  }

  public void setPermission(@CustomPermission int flag) {
    getSharedPreferences(SHARED_PREFS_FILE)
      .edit()
      .putInt(STATISTICS_COLLECTION_PERMISSION, flag)
      .apply();
  }

And here is the official doc

Gaket
  • 6,533
  • 2
  • 37
  • 67
1

Here's a nice utils class I wrote for this. It's very generalized to fit a broad spectrum of requirements.

import android.content.Context;
import android.content.SharedPreferences;

public class PreferencesUtils
{
    enum StorageMode {
        /** Store enum by ordinal. */
        ORDINAL,

        /** Store enum by name. */
        NAME
    }

    private static final String PREFERENCES_NAME = "your_prefs_name";

    /**
     * Put an enum in SharedPreferences with the given key.
     * @param context The context
     * @param key The preference key
     * @param enumm The enum to store
     * @param mode The mode to store the enum - by name or ordinal
     */
    public static void putEnum(final Context context, final String key, final Enum enumm, final StorageMode mode)
    {
        final SharedPreferences sp = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
        final SharedPreferences.Editor editor = sp.edit();
        switch (mode)
        {
            default:
            case ORDINAL:
                editor.putInt(key, enumm.ordinal());
                break;
            case NAME:
                editor.putString(key, enumm.name());
                break;
        }
        editor.apply();
    }

    /**
     * Get the enum stored in SharedPreferences with the given key.
     * @param context The context
     * @param key The preference key
     * @param enumType The type of Enum stored
     * @param defaultEnumm Enum returned if a preference stored with key does not exist. Can be null.
     * @param mode The mode by which the enum was originally stored
     * @param <T> The type of Enum stored
     * @return The Enum stored as a preference with the given key
     */
    public static <T extends Enum<T>> Enum getEnum(final Context context, final String key, final Class<T> enumType,
                                                   final Enum defaultEnumm, final StorageMode mode)
    {
        final SharedPreferences sp = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
        final T[] values = enumType.getEnumConstants();

        switch (mode)
        {
            default:
            case ORDINAL:
                final int ord = sp.getInt(key, -1);
                return ord == -1 ? defaultEnumm : values[ord];
            case NAME:
                final String name = sp.getString(key, null);
                for (final T value : values)
                {
                    if (value.name().equals(name))
                    {
                        return value;
                    }
                }
        }
        return defaultEnumm;
    }
}
Chris Sprague
  • 3,158
  • 33
  • 24