Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save enum to SharedPreference

Tags:

android

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?

like image 364
Cheok Yan Cheng Avatar asked Feb 07 '13 12:02

Cheok Yan Cheng


4 Answers

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

like image 110
jiahao Avatar answered Nov 05 '22 17:11

jiahao


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.

like image 41
spaaarky21 Avatar answered Nov 05 '22 18:11

spaaarky21


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

like image 13
dilix Avatar answered Nov 05 '22 19:11

dilix


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

like image 3
Gaket Avatar answered Nov 05 '22 19:11

Gaket