M1でJetpack proto datastoreのインストールがままならなかったので共有
❗️❗️❗️ この記事は Jetpack 初心者が書いています!ご了承ください ❗️❗️❗️
結論
Jetpack proto datastore は M1 Mac では 3.17.3 以降でないと動かないよ!!! バージョンを上げよう
Jetpack proto datastore とは
Android でデータを保存して永続化したときに使うライブラリです。今までは SQLite や SharedPreferences なんかで対応していたらしいですが、Jetpack 勉強中なので proto datastore を触ってみることに。
使用方法は以下の通りです。
- 諸々インストール
- proto ファイルというスキーマを定義
- プロジェクトをビルドする。すると便利なクラスが自動生成される
- 生成されたクラスを使用してデータを取得したり保存したりする
公式ドキュメントに従ってインストールしてみようとしてみました。がトラブル続きで大変な目にあいましたのでこの記事で共有させていただきます。
Jetpack datastore には 2 種類あるらしい
datastore には,Preferences DataStore と Proto DataStore の 2 種類があるようです。
Preferences DataStore は SharedPreferences のようにキーバリューで保存するのかな?
Proto DataStore は Protocol Buffers とやらを使って多言語対応でシリアライズ(オブジェクトやインスタンスなどをバイナリ形式で保存する)できるらしい。便利そうじゃん。
ということで Proto DataStore を使うことにしました。(正直 Preferences DataStore の良さがわかってない)
ドキュメント通りに進めてみる
dependencies {
implementation("androidx.datastore:datastore:1.0.0")
}
dependencies {
implementation("androidx.datastore:datastore-core:1.0.0")
}
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
【独り言】 ドキュメントの書き方が...
スキーマを定義する
Proto DataStore では、app/src/main/proto/ ディレクトリの proto ファイル内に定義済みスキーマが必要です。このスキーマは、Proto DataStore で保持するオブジェクトの型を定義します。proto スキーマの定義の詳細については、protobuf 言語ガイドをご覧ください。
ドキュメントでは上のように説明があったが、この表現では app/src/main/proto/proto
を作るのか app/src/main/proto/.proto
を作るのか app/src/main/proto/ほげほげ.proto
を作るのか汲み取りづらかった。例を示してほしいなぁっていうのは甘えだろうか...?
そこで再ビルド最中に以下のエラーに遭遇しました。(ようやく本題)
エラーに遭遇
> Could not resolve all files for configuration ':protobufToolsLocator_protoc'.
> Could not find protoc-3.15.0-osx-aarch_64.exe (com.google.protobuf:protoc:3.15.0).
Searched in the following locations:
https://中略/com/google/protobuf/protoc/3.15.0/protoc-3.15.0-osx-aarch_64.exe
':protobufToolsLocator_protoc'が解決できない? 何故 M1Mac なのに exe ファイルを探しに行ってるんだ? まあとりあえずprotoc
とやらが何か悪いのか?
M1 は 3.17.3 から対応...だと?!
色々ググっているとこんな記事を見つけました。
びっくりするくらい簡潔。見習わないと...
まあとりあえずバージョンをあげればいい事がわかったのであげてみる
def protobufVersion = '3.17.3'
dependencies {
implementation "com.google.protobuf:protobuf-javalite:${protobufVersion}" //ここを変える
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${protobufVersion}" //ここを変える
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
改めて再ビルドすると...
BUILD SUCCESSFUL in 6s
37 actionable tasks: 37 executed
Build Analyzer results available
久々にSUCCESSFUL
という単語に喜びを覚えました ☺️
またこのタイミングで app/src/main/proto/settings.proto
に記述したスキーマをもとに Settings
クラスが生成されます。後でこれを使ってデータを操作します。
dataStore を作る
ドキュメントでは指定がないですがどこかの記事でjava/プロジェクト/data
につくっている例を見たので例に倣って SettingsSerializer
を実装します。シリアライザーは バイナリ->kotlinのインスタンス
または kotlinのインスタンス->バイナリ
を行うメソッドを定義するオブジェクトです。dataStore を作るために必要なので作っておきます。
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}
val Context.settingsDataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
これでようやくデータを読み書きできるようになりました。
データの読み書き
store の定義で Context.settingsDataStore
とあるように、settingsDataStore メソッドが Context クラスに追加されたので(Context とは Activity などのことです)、 (Activityなどのインスタンス).settingsDataStore
というふうに呼び出せます。そうして呼び出した dataStore を使って読み書きします。
読み
データは settingsDataStore.data に Flow として入っています。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val exampleCounterFlow: Flow<Int> = this.settingsDataStore.data
.map { settings ->
// The exampleCounter property is generated from the proto schema.
settings.exampleCounter
}
}
}
取得した this.settingsDataStore.data
や exampleCounterFlow
は Flow という形で取り出されます。Flow って何?な方は以下の記事を参照
書き
dataStore.updateData メソッドを使います。
class MainActivity : ComponentActivity() {
suspend fun incrementCounter() {
this.settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
}
データの永続化ができて、とても幸せな気分になれました。
Discussion