😨

M1でJetpack proto datastoreのインストールがままならなかったので共有

2022/06/29に公開

❗️❗️❗️ この記事は Jetpack 初心者が書いています!ご了承ください ❗️❗️❗️

結論

Jetpack proto datastore は M1 Mac では 3.17.3 以降でないと動かないよ!!! バージョンを上げよう

Jetpack proto datastore とは

Android でデータを保存して永続化したときに使うライブラリです。今までは SQLite や SharedPreferences なんかで対応していたらしいですが、Jetpack 勉強中なので proto datastore を触ってみることに。

使用方法は以下の通りです。

  1. 諸々インストール
  2. proto ファイルというスキーマを定義
  3. プロジェクトをビルドする。すると便利なクラスが自動生成される
  4. 生成されたクラスを使用してデータを取得したり保存したりする

公式ドキュメントに従ってインストールしてみようとしてみました。がトラブル続きで大変な目にあいましたのでこの記事で共有させていただきます。

https://developer.android.com/topic/libraries/architecture/datastore?hl=ja

Jetpack datastore には 2 種類あるらしい

datastore には,Preferences DataStoreProto DataStore の 2 種類があるようです。
Preferences DataStore は SharedPreferences のようにキーバリューで保存するのかな?
Proto DataStore は Protocol Buffers とやらを使って多言語対応でシリアライズ(オブジェクトやインスタンスなどをバイナリ形式で保存する)できるらしい。便利そうじゃん。
ということで Proto DataStore を使うことにしました。(正直 Preferences DataStore の良さがわかってない)

ドキュメント通りに進めてみる

build.gradle
dependencies {
  implementation("androidx.datastore:datastore:1.0.0")
}

dependencies {
  implementation("androidx.datastore:datastore-core:1.0.0")
}

app/src/main/proto/settings.proto

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 から対応...だと?!

色々ググっているとこんな記事を見つけました。

http://blog.64p.org/entry/2021/11/17/112322

びっくりするくらい簡潔。見習わないと...

まあとりあえずバージョンをあげればいい事がわかったのであげてみる

build.gradle
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 を作るために必要なので作っておきます。

app/src/main/java/プロジェクト/data/SettingsSerializer.kt
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 として入っています。

ActivityのonCreateなどで
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.dataexampleCounterFlow は Flow という形で取り出されます。Flow って何?な方は以下の記事を参照

https://qiita.com/tonionagauzzi/items/12aa1a4400256cece72c

書き

dataStore.updateData メソッドを使います。

ActivityのonCreateなどで
class MainActivity : ComponentActivity() {
  suspend fun incrementCounter() {
    this.settingsDataStore.updateData { currentSettings ->
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.exampleCounter + 1)
        .build()
    }
  }
}

データの永続化ができて、とても幸せな気分になれました。

Discussion