SharedPreferencesのラッパークラスを作ろう

これは[Androidその２ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/android_second) 12/20の投稿です。

# はじめに
今回はキーと値のペアを保存、取得することができる`SharedPreferences`をより安全、簡単に使うためにラッパークラスを作る話をします。
基本的な話となるため、`SharedPreferences`の使用における自分なりのプラクティスが無い人向けの投稿となります。
ご了承ください。
# 基本的な`SharedPreferences`使用法の再確認
標準的な`SharedPreferences`を使用した値の書き込み、及び読み出しは以下のコードとなるかと思われます。

```java
SharedPreferences sp = context.getSharedPreferences("hoge", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("fuga", false);
editor.apply();
```

```java
SharedPreferences sp = context.getSharedPreferences("hoge", Context.MODE_PRIVATE);
boolean fuga = sp.getBoolean("fuga", false);
```
# 問題点
上記コードから問題点を洗い出してみます。
1. `getSharedPreferences`, `putBoolean`, `getBoolean`メソッドの第一引数を忘れたり、スペルミスしたりしてしまう可能性がある。
2. `apply`メソッドを呼び忘れる可能性がある。
3. 全体的にもっとスマートに記述したい。

とりあえずはこのくらいでしょうか。
早速問題解決していきましょう。
# 実践
## データファイルをenum管理
まず`SharedPreferences`のデータファイルをenumで管理します。

```PreferenceNames.java
public enum PreferenceNames {
    DEFAULT("default");
    HOGE("hoge");

    private final String name;

    PreferenceNames(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
```
データファイルを増やしたい場合は、このenumに追加するだけです。
簡単ですね。

## `SharedPreferences`のラッパークラスを作成する
次に、上記enumを取り扱うことが出来るように`SharedPreferences`のラッパークラスを作成し、コンストラクタを記述します。

```MySharedPref.java
public class MySharedPref{
    private final SharedPreferences sp;
    private final PreferenceNames preferenceNames;

    public MySharedPref(Context context, PreferenceNames preferenceNames) {
        if (preferenceNames == PreferenceNames.DEFAULT) {
            this.sp = PreferenceManager.getDefaultSharedPreferences(context);
        } else {
            this.sp = context.getSharedPreferences(preferenceNames.getName(), Context.MODE_PRIVATE);
        }
        this.preferenceNames = preferenceNames;
    }
}
```
ここでは、第二引数に`PreferenceNames.DEFAULT`を設定した場合は`getDefaultSharedPreferences`メソッドをコールします。

これで`getSharedPreferences`メソッドの第一引数の忘れ、及びスペルミス問題は解決しました。

## キーのenum管理
次に、キーをenumで管理します。

```PreferenceKeys.java
public enum PreferenceKeys {
    FUGA(PreferenceNames.HOGE, Boolean.class, true);

    private final PreferenceNames preferenceNames;
    private final Class<?> valueClass;
    private final String key;

    PreferenceKeys(PreferenceNames preferenceNames, Class<?> valueClass, String key) {
        this.preferenceNames = preferenceNames;
        this.valueClass = valueClass;
        this.key = key;
    }

    public PreferenceNames getPreferenceNames() {
        return preferenceNames;
    }

    public Class<?> getValueClass() {
        return valueClass;
    }

    public String getKey() {
        return key;
    }
}
```
`SharedPreferences`のキーを増やしたい場合は、このenumに追加するだけです。
一列でそのキーが何のデータファイルで使用され、紐付けられる値のクラスは何かを把握することが出来るという点でもこのenumは優れています。

## `putXXX`メソッドの作成
`putBoolean`メソッドを以下のように作成します。


```MySharedPref.java
public void putBoolean(PreferenceKeys preferenceKeys, boolean value) {
    if (preferenceKeys.getPreferenceNames() == preferenceNames && preferenceKeys.getValueClass() == Boolean.class) {
        sp.edit().putBoolean(preferenceKeys.getKey(), value).apply();
    } else {
        throw new IllegalArgumentException();
    }
}
```
同時に`apply`メソッドの呼び出し忘れ問題も解決することが出来ました。
一度にたくさんの値を保存する場合はまた別のソリューションを考える必要がありますが、値を一度に変更するような機能に関して、SharedPreferencesはあまり使用されないでしょう。

## `getXXX`メソッドの作成
`getBoolean`メソッドは以下のように作成します。

```MySharedPref.java
public boolean getBoolean(PreferenceKeys preferenceKeys) {
    if (preferenceKeys.getPreferenceNames() == preferenceNames && preferenceKeys.getValueClass() == Boolean.class) {
        return sp.getBoolean(preferenceKeys.getKey(), false);
    } else {
        throw new IllegalArgumentException();
    }
}
```
こちらも初期値を都度変更したい場合は、引数として渡してあげるといいでしょう。
そこら辺は人やプロジェクトによると思われるため、自由にカスタマイズしてみてください。

また、`putXXX`, `getXXX`メソッドはboolean以外にもStringやintなどが必要になるはずですので、同様に実装してみてください。

## おまけ
ついでに`clear`メソッドも作成してみましょう。

```MySharedPref.java
public void clear() {
    sp.edit().clear().apply();
}
```

ラッパークラスとenumの作成は以上です。

# 比較
以下、ラッパークラスを使わないバージョンと使うバージョンでデータの書き込み、及び読み出しのサンプルを比較してみました。

```java
// Use no wrapper
SharedPreferences sp = context.getSharedPreferences("hoge", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("fuga", false);
editor.apply();

// Use wrapper
MySharedPref sp = new MySharedPref(this, PreferenceNames.HOGE);
sp.putBoolean(PreferenceKeys.FUGA, true);
```


```java
// Use no wrapper
SharedPreferences sp = context.getSharedPreferences("hoge", Context.MODE_PRIVATE);
boolean fuga = sp.getBoolean("fuga", false);

// Use wrapper
MySharedPref sp = new MySharedPref(getActivity(), PreferenceNames.HOGE);
boolean fuga = sp.getBoolean(PreferenceKeys.FUGA);
```

# まとめ
さて、ラッパーを作る意義が感じられましたでしょうか。
今回は、基本的な機能しか実装していませんが、個人個人でカスタマイズして自分なりのSharedPreferencesラッパークラスを作成してみてください。