Android Proto DataStore徹底解説:SharedPreferencesから切り替える価値はあるのか?
数年前、Googleは長年定番として使われてきたSharedPreferencesの後継となるDataStoreを発表しました。
アプリでSharedPreferencesを使ったことがある方なら、乗り換えを検討しているかもしれません。しかし何事にもコストがつきものです。ここで最も重要な疑問は「開発コストはどれくらいかかるのか?」という点でしょう。
DataStoreには多くのメリットがありますが、オブジェクトを型安全に保存できるのはProto DataStoreだけです。
ところが、Proto DataStoreの公式ドキュメントはやや古く、重要な手順が欠けている部分もあります。そこで本記事では、Proto DataStoreをアプリに統合する具体的な手順を丁寧に解説し、思っているほど導入の手間ではないことをお見せします。
DataStoreとは?
Jatpack DataStoreには、以下の2種類があります。
- Preferences DataStore
- Proto DataStore
Preferences DataStoreはSharedPreferencesとよく似ており、すでに広く解説されているため、本記事では割愛します。ここでは「Proto DataStore」の「Proto」が何を意味するのかを見ていきましょう。
Protoとは、GoogleがProtocol Buffers(プロトコルバッファー)を表すために選んだ名称です。Protocol Buffersは構造化データをシリアライズするためのGoogleの仕組みで、特定のプログラミング言語に依存しません。一般的には、扱いたいデータの型を定義すると、そのデータを読み書きするためのコードが自動生成されます。
✋ 本記事ではProto 3を使用します。
.protoファイルの定義方法
定義は以下のように記述します。
message MyItem {
string itemName = 1;
int32 itemId = 2;
}
まずmessageキーワードでオブジェクトを定義し、その中に関連するフィールドを列挙します。各フィールド末尾の番号はフィールドを識別するために使われるもので、一度設定してオブジェクトが使用され始めたら変更できません。
では、複数のオブジェクトを.protoファイルに定義したい場合はどうすればよいのでしょうか?オブジェクト同士に関連性がある場合、messageオブジェクトを追加するだけで簡単に実現できます。
message MyItem {
string itemName = 1;
int32 itemId = 2;
}
message MyListOfItems {
repeated MyItem items = 1;
}
上記の例では、先に定義したMyItemオブジェクトに依存する別のmessageオブジェクトを追加しています。オブジェクトのリストを定義する際は、repeatedキーワードを使用します。
Proto DataStoreのセットアップ方法
まず、アプリレベルのbuild.gradleに以下の依存関係を追加します。
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
次に、プロジェクト内にprotoディレクトリを作成します。このディレクトリは、プロジェクト構造上Javaフォルダと同じ階層に配置する必要があります。
protoディレクトリ内に.protoファイルを作成します。このファイルは、Proto DataStoreに保存したいデータ型を生成する役割を担います。
今回はTodoリストを表すオブジェクトを扱うため、ファイル名をtodo.protoとします。内容は以下の通りです。
syntax = "proto3";
option java_package = "com.yourPackageName.todo";
option java_multiple_files = true;
message TodoItem {
string itemId = 1;
string itemDescription = 2;
}
message TodoItems {
repeated TodoItem items = 1;
}
ここでは2つのmessageオブジェクトを定義しました。
- TodoItem – 個々のTodoアイテムを定義
- TodoItems – TodoItemオブジェクトのリストを定義
次にプロジェクトをビルドすると、TodoItemとTodoItemsのクラスが自動生成されます。
Serializerクラスの作成
データオブジェクトを定義したら、それらをシリアライズするクラスを作成する必要があります。このクラスは、DataStoreに対してオブジェクトの読み書き方法を指示するものです。
// 1
object TodoItemSerializer: Serializer<TodoItems> {
// 2
override val defaultValue: TodoItems = TodoItems.getDefaultInstance()
// 3
override suspend fun readFrom(input: InputStream): TodoItems {
try {
return TodoItems.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
// 3
override suspend fun writeTo(
t: TodoItems,
output: OutputStream
) = t.writeTo(output)
}
このクラスのポイントを確認しましょう。
- クラス宣言時に、対象オブジェクトを型(T)としてSerializerインターフェースを実装する
- ファイルがまだ作成されていない場合に備え、シリアライザーのデフォルト値を定義する
- readFrom/writeToメソッドをオーバーライドし、対象オブジェクトをデータ型として指定する
DataStoreのインスタンス化
.protoファイルとシリアライザーが揃ったら、次はDataStoreをインスタンス化します。dataStoreが提供するプロパティデリゲートを使用し、データ保存先のファイル名とシリアライザークラスを指定します。
private const val DATA_STORE_FILE_NAME = "todo.pb"
private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)
このコードは、任意のクラスのクラス定義よりも上部に配置する必要があります。具体的には以下のような位置关系です。
private const val DATA_STORE_FILE_NAME = "todo.pb"
private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)
class YourClassName {
}
アプリの他の場所からこのオブジェクトにアクセスするには、Contextを使用します。例えば、ViewModelクラスでApplication経由で取得する例は以下の通りです。
class MyViewModel(application: Application): AndroidViewModel(application) {
val todoDataStore = application.todoItemDataStore
//...
}
Kotlin Flowの使い方
DataStoreのセットアップが完了したので、実際にどう操作するかを見ていきましょう。データの読み書きを行いますが、その方法はSharedPreferencesとは大きく異なります。
上で定義したDataStoreにはdataフィールドがあり、DataStoreに定義したプロパティをFlowとして公開しています。
🚰 Flowに馴染みがない方は、まず公式ドキュメントの入門ガイドをチェックすることをおすすめします。
val todoItemFlow: Flow<TodoItems> = todoItemDataStore.data
.catch { exception ->
if (exception is IOException) {
emit(TodoItems.getDefaultInstance())
} else {
throw exception
}
}
上記のコードは、Proto DataStoreからデータを収集するFlowを定義する方法を示しています。例外発生に備えてcatchブロックを追加しています。このロジックはDataStoreを定義したクラスに配置し、ViewModel側では以下のように利用できます。
val todoItemsFlow: LiveData<TodoItems> = todoItemsRepository.todoItemFlow.asLiveData()
ここでFlowをLiveDataに変換しているのには、2つの理由があります。
- Flowはアクティビティやフラグメントに依存せず、動作し続けることができる
- LiveDataは多くの開発者にとって馴染み深く、サンプルをできるだけ分かりやすくするため
この変換を行うには、build.gradleに以下の依存関係を追加してください。
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
アクティビティやフラグメントのクラスでは、このLiveDataを以下のように監視できます。
myViewModel.todoItemFlow.observe(LocalLifecycleOwner.current) { todoItems ->
// DataStoreからデータへアクセスする処理
}
DataStoreを使うべき理由とタイミング
ここまで確認してきたところで、いよいよ本題に入りましょう。既存または次のプロジェクトでDataStore(PreferencesまたはProto)を使うべきなのでしょうか?
結論から言えば、答えは「Yes」です。Google自身がSharedPreferencesから離れつつあることに加え、DataStoreにはデータ永続化の煩雑さではなく、アプリ開発そのものに集中できる多くのメリットがあります。
- UIスレッドから安全にアクセス可能:I/O処理は自動的にワーカースレッドへ移譲されるため、ANRのリスクを抑えられます。
- Flowの活用を促進:リアクティブなデータ管理の恩恵をすべて享受できます。
- 移行が容易:SharedPreferencesからPreferences DataStoreへの移行ツールも用意されています。
また、「Proto DataStoreの代わりにRoomを使うべきか?」という疑問を持つ方もいるでしょう。これはユースケース次第です。保存するデータ量が比較的少なく、部分的な更新が必要ないのであれば、Proto DataStoreが最適です。一方、大規模または複雑なデータセットを扱う場合は、Roomを選ぶべきでしょう。
本記事のコード全体が実際のアプリでどのように動作するか確認したい方は、サンプルリポジトリをご覧ください。その他の筆者の記事についても、ぜひチェックしてみてください。
最後までお読みいただきありがとうございました!
参考文献
- Protocol Buffers Documentation(proto 3)
- Working With Proto DataStore Codelab
- DataStore Documentation
無料でプログラミングを学ぼう。freeCodeCampのオープンソースカリキュラムは、これまで4万人以上の人々を開発者としての就職へと導いてきました。
-
無料で使えるおすすめオープンソースAndroidアプリ12選
Androidはオープンソースのオペレーティングシステムですが、その上で動くアプリやサービスの多くはそうとは限りません。フリーかつオープンソースのソフトウェアを愛するユーザーは、一体どうすればよいのでしょうか?実は、選択肢は想像以上に豊富にあります。Playストアとまったく同じことはできなくても、スマホを十分に活用しながら、洗練されたマテリアルデザインを採用したソフトウェアを使うことは可能です。ここでは、特におすすめのオープンソースAndroidアプリを12個、順不同で紹介します。すべてF-Droidで入手可能で、多くはPlayストアにもあります(もちろん、F-Droid限定の優秀なアプリも数
-
Google Play以外からインストールしたAndroidアプリを自動でアップデートする方法
Androidユーザーにとって、Google Playストアはアプリを入手するための中心的な場所です。最新ゲームから生産性向上アプリ、SNSの公式アプリまで、必要なものはほぼすべてここで揃います。しかし、欲しいアプリが必ずしもGoogle Playにあるとは限りません。利用規約違反でストアから削除されたアプリや、Googleへの依存を極力減らしたいというケースもあるでしょう。幸い、Androidには「サイドロード」と呼ばれる仕組みがあり、APKファイルを直接インストールすることで、Google Playを経由せずにアプリを導入できます。実は、サイドロードしたアプリのアップデートを毎回手動で確認