KotlinでSharedPreferenceへのアクセスを楽にするライブラリを作ってみた

**（2015/08/07）** 色々と追記しました。

KotlinではDelegateでプロパティへの代入／参照時の動作を委譲することができます。
これを利用して代入／参照時にプロパティ名をキーにしてSharedPreferenceに書込／読込をできるようになるかなーと思ったので試しに作ってみました。

## できたもの

https://github.com/chibatching/Kotpref

## 使い方

### インストール

```
compile 'com.chibatching:kotpref:0.1.0'
```

### モデルを作成

元になるモデルクラスを継承してobjectなりclassを作る。

```kotlin
public object UserInfo : KotprefModel() {
    var name: String by stringPrefVar()
    var age: Int by intPrefVar()
    var highScore: Long by longPrefVar()
    var threshold: Float by floatPrefVar()
    var prizes: MutableSet<String> by stringSetPrefVar(default = TreeSet<String>())
}
```

### セットアップ

コンテキストを渡してあげる

```kotlin
    Kotpref.init(context)
```

### Read/Write

プロパティへの代入でSharedPreferenceへの書き込み、値を参照すればSharedPreferenceから読み込み

```kotlin
    UserInfo.name = "chibatching"
    UserInfo.age = 30
    UserInfo.highScore = 49219902
    UserInfo.threshold = 49.21F
    UserInfo.prizes.add("Bronze")
    UserInfo.prizes.add("Silver")
    UserInfo.prizes.add("Gold")

    Log.d(javaClass.getSimpleName(), "User name: ${UserInfo.name}")
    Log.d(javaClass.getSimpleName(), "User age: ${UserInfo.age}")
    Log.d(javaClass.getSimpleName(), "User high score: ${UserInfo.highScore}")
    Log.d(javaClass.getSimpleName(), "User threshold: ${UserInfo.threshold}")
    UserInfo.prizes.forEachIndexed { i, s -> Log.d(javaClass.getSimpleName(), "prize[$i]: ${s}") }
```

### XML

SharedPreferenceのディレクトリにモデルクラス名でXMLが作られる。今回の例だと「UserInfo.xml」になる。

```xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <long name="highScore" value="49219902" />
    <set name="prizes">
        <string>Bronze</string>
        <string>Gold</string>
        <string>Silver</string>
    </set>
    <string name="name">chibatching</string>
    <int name="age" value="30" />
    <float name="threshold" value="49.21" />
</map>
```

### オプション

#### デフォルト値の変更

Delegateの引数`default`を指定する。

```kotlin
var age: Int by intPrefVar(18)
```

または

```kotlin
var age: Int by intPrefVar(default = 18)
```


#### SharedPreferenceのファイル名を変更する

`kotprefName`プロパティをオーバーライドする。デフォルトはモデルのクラス名。

```kotlin
public object UserInfo : KotprefModel() {
    override val kotprefName: String = "user_info"
```

#### SharedPreferenceのモードを変更する

`kotprefMode`プロパティをオーバーライドする。デフォルト値は`Context.MODE_PRIVATE`。

```kotlin
public object UserInfo : KotprefModel() {
    override val kotprefMode: Int = Context.MODE_MULTI_PROCESS
```

## なにをやっているのか

プロパティの宣言時は次の関数を呼んでvar変数に相当する`ReadWriteProperty`を返しています。

```kotlin
protected fun stringPrefVar(default: String = "")
        : ReadWriteProperty<KotprefModel, String> = StringPrefVar(default)
```

`ReadWriteProperty`は`set`と`get`を持ったインターフェースになっており、値の参照時には`get`が、値の代入時には`set`が呼ばれます。
また、呼び出し時にはプロパティを持っているオブジェクトの型とプロパティのメタデータが渡されます。
これを利用して呼び出し元のオブジェクトが持っているSharedPreferenceを取得、プロパティ名で読込書込しています。

```kotlin
private class StringPrefVar(val default: String) : ReadWriteProperty<KotprefModel, String> {

    override fun get(thisRef: KotprefModel, desc: PropertyMetadata): String {
        // プロパティの名前が分かるので名前をキーにSharedPreferenceにアクセスする
        // SharePreferenceのインスタンスはプロパティを持っているオブジェクトから取得
        return thisRef.kotprefPreference.getString(desc.name, default)
    }

    override fun set(thisRef: KotprefModel, desc: PropertyMetadata, value: String) {
        thisRef.kotprefPreference.edit().putString(desc.name, value).apply()
    }
}
```

## これから

パフォーマンス（特にStringSet）に不安あり。改善していきたい。