はじめに
Android Jetpackファミリーに DataStoreの1.0.0-alpha01が追加されました。
公式ページを抜粋しますと、
Jetpack DataStore is a data storage solution that allows you to store key-value
pairs or typed objects with protocol buffers. DataStore uses Kotlin coroutines and
Flow to store data asynchronously, consistently, and transactionally.
If you're currently using SharedPreferences to store data, consider migrating to
DataStore instead.
Jetpack DataStoreはデータストレージソリューションの一種で、キー-値のペア方式で保存したり、
ProtocolBufferを使った型情報付きのデータの保存ができたりするよ。DataStoreはKotlinコルーチンと
Flowを使っていて、データの保存をトランザクション的に一貫性を保ちながら非同期に行うこともできるんだ。
もしいま君がSharedPreferenceを使ってデータを保存しているならDataStoreへの移行を検討してみてよ。
とのことす(適当な位置での改行&クソ邦訳:筆者)。
というわけで、少し触ってみましたのでその気づきメモです。
なるべく上記のページに書いていないことを書きます。
誤記・欠落
公式ページにいくつか誤記・欠落があった(2020/09/04時点)ので補足します。
セットアップの欠落
Preferences DataStoreだけ使う場合
公式ページではモジュール階層の build.gradle
の dependencies
にライブラリーを追加するだけしか書いていませんが、実際にはJavaのバージョン指定も要るようです。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions.jvmTarget = "1.8"
...
}
dependencies {
...
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
...
}
Proto DataStoreだけ使う場合
前節の内容に加え、Protocol Buffer関係のモジュールの追加が必要です。
これらがないとprotoファイルからクラスが自動生成されません。
今回はProtocol Buffer公式のJava実装およびそのGradleプラグインを使ってクラスを自動生成するようにします。
最初に、プロジェクト階層のbuild.gradleです。
buildscript {
...
dependencies {
...
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.13'
...
}
...
}
...
つぎに、モジュール階層のbuild.gradleです。
...
+ apply plugin: 'com.google.protobuf'
android {
...
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions.jvmTarget = "1.8"
...
}
dependencies {
...
+ // Preferences DataStore
+ implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"
+ // Proto DataStore
+ implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
...
}
サンプルのprotoの誤記
int
-> int32
ですね。
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
+ //int example_counter = 1;
+ int32 example_counter = 1;
}
設計思想
DataStore自体は任意のファイル形式・任意のデータ形式に対応した設定管理モジュールとして振る舞えるよう設計されています。
1.0.0-alpha01では、スキーマレス形式の代表兼SharedPreferenceの代替として PreferenceDataStore
を、 スキーマフル形式の代表として ProtoDataStore
が用意されています。
関数
SharedPreferences
の edit
と違い、DataStore
のedit
は suspend function
です。
よって呼び出す側も suspend function
またはコルーチンコンテキストで実行を強制されます。
メインスレッドで叩いてしまうのをコンパイラレベルで防ぐことができるというわけですね。
保存できる型
PreferenceDataStore
デフォルトのシリアライザーで対応している型はSharedPreferencesと同じ。つまり、
Boolean
Float
Int
Long
String
Set<String>
の6つとなります。Double、List、Map、Parcelableは保存できない一方、整数型はIntとLongの2種類に分かれています。
また、nullは保存できません。
2020/11/27追記
1.0.0-alpha03でDouble型が追加されました。
https://developer.android.com/jetpack/androidx/releases/datastore#1.0.0-alpha03
ProtoDataStore
Protocol Buffer(protobuf) を使っているので、IDLであるprotoが許す限り自由な型を扱えます。
ほかの形式
シリアライザーはインタフェース化されており、DataStoreの生成時に引数として指定することができます。
シリアライザーさえ書けば任意の型・任意の形式で読み書きできます(後述)。
ファイル仕様
読み書き形式
デフォルトでは、protobuf(バイナリー)形式で読み書きされます。
これはPreferenceDataStoreであっても同じです。
「protobufだったらIDLが必要なんじゃないの?」と思いましたが、保存時にMap変数が抱えている値の型を調べ、protoファイル相当のスキーマデータを作っています。これによりスキーマレスのように扱えるという仕組みです。 嘘でした。
内部実装として このようなprotoファイル およびそこから生成されたクラスを持っており、これを使ってシリアライズ/デシリアライズを行っています。
前述のとおり、シリアライザーを入れ替えれば任意の形式で読み書きすることができます。
保存パス
PreferenceDataStore
デフォルトでは <filesDir>/datastore/<name>.preferences_pb
に保存されます(nameは引数で指定する値)。
例:パッケージ名が com.example.hoge
、nameに settings
を指定した場合、 /data/data/com.example.hoge/files/datastore/settings.preferences_pb
。
保存する場所やファイル名は引数で変更できますが、拡張子は .preferences_pb
でないと例外送出されます。
ProtoDataStore
デフォルトでは <filesDir>/datastore/<fileName>
に保存されます(fileNameは引数で指定する値)。
例:パッケージ名が com.example.hoge
、nameに settings.pb
を指定した場合、 /data/data/com.example.hoge/files/datastore/settings.pb
。
PreferenceDataStoreと違い、こちらは拡張子まで含めて指定するようです。
感想
- 筆者の感じたメリット
- Flowでデータを扱えるのでRxプログラミングしやすい
- ProtoDataStoreは、設定値もスキーマを使って厳密に扱いたいけどRoom(SQLite)を使うほどでもないという「帯に短し襷に長し」の問題にちょうどいいかもしれない
- 筆者の感じたデメリット
-
保存形式がprotobufのバイナリーであり、JSONやYAMLのように気軽に人間が読み書きできない(PreferenceDataStoreであっても!)-> デメリットというほどの問題ではありませんでした。詳しくは DataStoreの保存形式をProtocol Bufferを使いつつJSONにする をご覧ください - Jetpack Securityが対応していない=SharedPreferenceのように暗号化して管理できない
- PreferenceDataStoreで保存可能な型がSharedPreference同等なせいでListもMapもParcelableも保存できない。
かといってProtoDataStoreを使うにはprotobuf関係のモジュールのセットアップ、protoファイルの管理など面倒くさすぎる
-
例えばいま書いてるアプリではSharedPreferenceの値をFlowで扱わなきゃいけないような大仰な状況には直面していないので、SharedPreferenceから入れ替えるメリットはないかなーといった感じです。
PreferenceDataStoreがList・Map・Parcelableを扱えるならワンチャンあったんですけどもね。
かといってProtoDataStoreはちょっとヘビーですし、ProtoDataStoreより軽くとなるとKotlinx SerializationやMoshiを使って自分でシリアライザーを書き下ろす必要があるんですが、いやそれなら最初からJSONで読み書きすればええやん!となるのでやはり必要なさそう。
とはいえ、まだアルファ版でこれから機能が充実していくライブラリーです。
筆者が感じたような問題も当然解決されていくでしょうししばらく動向を見守りたいと思います。