1. はじめに
DataStore とは
DataStore は、SharedPreferences に代わるものとして改善された新しいデータ ストレージ ソリューションです。Kotlin のコルーチンと Flow に基づいて構築された DataStore には、次の 2 種類の実装があります。型付きオブジェクトを保存する Proto DataStore(プロトコル バッファによってサポートされます)と、Key-Value ペアを保存する Preferences DataStore です。データは、非同期で、一貫性を持ち、トランザクションとして保存されるため、SharedPreferences の欠点の一部が解消されます。
学習内容
- DataStore の概要と使用すべき理由。
- DataStore をプロジェクトに追加する方法。
- Preferences DataStore と Proto DataStore の違い、およびそれぞれの利点。
- Proto DataStore の使用方法。
- SharedPreferences から Proto DataStore に移行する方法。
作業内容
この Codelab で使用するサンプルアプリでは、完了したステータスでフィルタリングでき、優先度と期限で並べ替えができるタスクのリストを表示します。
[Show completed tasks] フィルタのブール値フラグはメモリに保存されます。並べ替え順序は、SharedPreferences
オブジェクトを使用してディスクに引き継がれます。
DataStore には Preferences DataStore と Proto DataStore という 2 種類の実装があるので、それぞれの実装で Proto DataStore を使用して次のタスクを行う方法を学習します。
- 完了ステータスのフィルタを DataStore に引き継ぐ。
- 並べ替え順序を SharedPreferences から DataStore に移行する。
両者の違いをより深く理解するために、Preferences DataStore Codelab も使用してみることをおすすめします。
必要なもの
- Android Studio Arctic Fox。
- 次のアーキテクチャ コンポーネントに精通していること: LiveData、ViewModel、ビュー バインディング、およびアプリ アーキテクチャ ガイドで提案されているアーキテクチャ。
- コルーチンと Kotlin Flow に精通していること。
アーキテクチャ コンポーネントの概要については、Room と View の Codelab をご覧ください。Flow の概要については、Kotlin Flow と LiveData による高度なコルーチンの Codelab をご覧ください。
2. 設定方法
このステップでは、Codelab 全体のコードをダウンロードし、その後、簡単なサンプルアプリを実行します。
できるだけすぐに開始できるように、たたき台として利用できるスターター プロジェクトを用意しています。
git がインストールされている場合は、以下のコマンドをそのまま実行できます。git がインストールされているかどうかを確認するには、ターミナルまたはコマンドラインで「git --version
」と入力し、正しく実行されることを確認します。
git clone https://github.com/googlecodelabs/android-datastore
初期状態は master
ブランチにあります。解答コードは proto_datastore
ブランチにあります。
git がない場合は、次のボタンをクリックして、この Codelab のすべてのコードをダウンロードできます。
- コードの ZIP ファイルを展開し、プロジェクトを Android Studio Arctic Fox で開きます。
- デバイスまたはエミュレータでアプリ実行構成を実行します。
アプリが実行され、タスクのリストが表示されます。
3.プロジェクト概要
このアプリで、タスクのリストを表示できます。各タスクには、名前、完了ステータス、優先度、期限というプロパティがあります。
作業に必要なコードを簡素化するため、アプリで実施可能な作業は次の 2 つのみとします。
- 完了したタスクの公開設定を切り替える - デフォルトでは、タスクは非表示になります
- タスクを優先度、期限、または期限および優先度で並べ替える
アプリは、アプリ アーキテクチャ ガイドで推奨されているアーキテクチャに沿って実行されます。各パッケージの内容を以下に示します。
data
Task
モデルクラスTasksRepository
クラス - タスクを提供します。わかりやすくするために、ハードコードされたデータを返し、それをFlow
を介して公開することで、より現実的なシナリオを再現しています。UserPreferencesRepository
クラス -enum
として定義されたSortOrder
を保持します。列挙値名に基づいて、現在の並べ替え順がString
として SharedPreferences に保存されます。これにより、並べ替え順を保存および取得する同期メソッドが公開されます。
ui
RecyclerView
を使用したActivity
の表示に関するクラス。TasksViewModel
クラスは、UI ロジックに関与しています。
TasksViewModel
- UI に表示される必要があるデータの構築に必要なすべての要素(タスクのリスト、showCompleted
フラグ、sortOrder
フラグ)を保持します。それらの要素は TasksUiModel
オブジェクトでラップされます。これらの値が変更されるたびに、新しい TasksUiModel
を構築し直す必要があります。そのために、次の 3 つの要素を組み合わせます。
Flow<List<Task>>
はTasksRepository
から取得されます。- 最新の
showCompleted
フラグを保持するMutableStateFlow<Boolean>
。これは、メモリにのみ保持されます。 - 最新の
sortOrder
値を保持するMutableStateFlow<SortOrder>
。
UI の正しい更新を確実に実施するために、アクティビティの開始時にのみ LiveData<TasksUiModel>
を公開します。
コードにはいくつかの問題があります。
UserPreferencesRepository.sortOrder
を初期化すると、ディスク IO の UI スレッドがブロックされます。その結果、UI ジャンクが発生することがあります。showCompleted
フラグはメモリにのみ保持されるため、ユーザーがアプリを開くたびにリセットされます。SortOrder
と同様に、これはアプリを閉じた後も効力を有するよう引き継がれる必要があります。- 現在はデータの引き継ぎに SharedPreferences を使用していますが、ここでは
MutableStateFlow
をメモリに保持することで、手動で変更し、変更の通知を受け取れるようにしています。アプリ内の他の場所で値が変更されると、これは機能しません。 UserPreferencesRepository
では、並べ替え順序を変更する 2 つのメソッド、enableSortByDeadline()
とenableSortByPriority()
を公開します。どちらのメソッドも現在の並べ替え順の値に依存していますが、一方が終了する前に他方が呼び出されると、最終的に誤った値になります。さらに、これらのメソッドは UI スレッドで呼び出されるので、UI ジャンクと厳格モードの違反を引き起こす可能性があります。
showCompleted
フラグと sortOrder
フラグはどちらもユーザー設定ですが、現時点では 2 つの異なるオブジェクトとして表現されています。したがって、今回の目標のひとつは、これら 2 つのフラグを UserPreferences
クラスの下に統合することです。
では、DataStore を使ってこうした問題に対処する方法を確認しましょう。
4. DataStore - 基本事項
小規模またはシンプルなデータセットの保存が必要になる状況に、皆さんもよく遭遇することでしょう。その場合、以前は SharedPreferences を使用したかもしれませんが、この API にはいくつかの欠点もあります。Jetpack DataStore ライブラリは、このような問題の解決を目指して、よりシンプルで安全な非同期のデータ格納 API を作成します。これは、次の 2 つの実装を提供します。
- Preferences DataStore
- Proto DataStore
機能 | SharedPreferences | PreferencesDataStore | ProtoDataStore |
非同期 API | ✅(リスナーを介して変更された値を読み取る場合のみ) | ✅( | ✅( |
同期 API | ✅(ただし、UI スレッドで安全に呼び出せない) | ❌ | ❌ |
UI スレッドで安全に呼び出せる | ❌(1) | ✅(処理は内部で | ✅(処理は内部で |
エラーのシグナルを送信できる | ❌ | ✅ | ✅ |
ランタイム例外から保護される | ❌(2) | ✅ | ✅ |
強整合性保証を備えたトランザクション API がある | ❌ | ✅ | ✅ |
データの移行を処理する | ❌ | ✅ | ✅ |
型の安全性 | ❌ | ❌ | ✅(プロトコル バッファを使用) |
(1)SharedPreferences には、UI スレッドで呼び出しても安全に見える同期 API がありますが、これは実際にはディスク I/O オペレーションを行います。さらに、apply()
は fsync()
で UI スレッドをブロックします。fsync()
呼び出しの保留は、サービスが開始または停止するたび、およびアプリのどこかでアクティビティが開始または停止するたびにトリガーされます。UI スレッドは、apply()
によってスケジュール設定された保留中の fsync()
呼び出しでブロックされ、多くの場合、ANR の発生元になります。
(2)SharedPreferences は、ランタイム例外として解析エラーをスローします。
Preferences DataStore と Proto DataStore
Preferences DataStore と Proto DataStore はどちらもデータを保存できますが、その方法はそれぞれ異なります。
- Preferences DataStore は、SharedPreferences と同様に、スキーマを事前に定義することなく、キーに基づいてデータにアクセスします。
- Proto DataStore は、プロトコル バッファを使用してスキーマを定義します。Protobuf を使用すると、厳密に型付けされたデータを保持できます。XML やその他類似のデータ形式よりも、高速で小さくシンプル、かつ具体的です。Proto DataStore では新しいシリアル化メカニズムを学ぶことが求められますが、Proto DataStore がもたらす強力な型付けという利点には充分な価値があると考えられます。
Room と DataStore
部分更新、参照整合性、大規模なデータセットや複雑なデータセットが必要な場合は、DataStore の代わりに Room を使用することを検討してください。DataStore は、小規模で単純なデータセットに適しており、部分更新や参照整合性をサポートしていません。
5. Proto DataStore - 概要
SharedPreferences と Preferences DataStore の欠点のひとつとして、スキーマを定義したりキーへのアクセスが正しい型によるものか確認したりする方法がないということが挙げられます。Proto DataStore では、プロトコル バッファを使用してスキーマを定義することで、この問題に対処します。Proto DataStore を使用すると、格納される型が認識され、その型のみが提供されるので、キーを使用する必要がなくなります。
Proto DataStore と Protobuf をプロジェクトに追加する方法、プロトコル バッファの概要、Proto DataStore での使用方法、SharedPreferences を DataStore に移行する方法について見ていきましょう。
依存関係を追加する
Proto DataStore を使用して Protobuf でスキーマのコードを生成するには、build.gradle ファイルにいくつかの変更を加える必要があります。
- Protobuf プラグインを追加する
- Protobuf と Proto DataStore の依存関係を追加する
- Protobuf を構成する
plugins {
...
id "com.google.protobuf" version "0.8.17"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
6. protobuf オブジェクトの定義と使用
プロトコル バッファは構造化データのシリアル化を行うメカニズムです。データを構造化する方法を定義すると、コンパイラによって、構造化データを簡単に読み書きするためのソースコードが生成されます。
proto ファイルを作成する
スキーマは proto ファイルで定義します。この Codelab では、show_completed
と sort_order
の 2 つのユーザー設定があります。これらは現時点では 2 つの異なるオブジェクトとして表現されています。したがって、今回の目標のひとつは、DataStore に格納される UserPreferences
クラスの下にこの 2 つのフラグを統合することです。このクラスを Kotlin で定義するのではなく、protobuf スキーマで定義します。
構文の詳細については、Proto 言語ガイドをご覧ください。この Codelab では、必要な型のみを取り上げます。
user_prefs.proto
という新しいファイルを app/src/main/proto
ディレクトリに作成します。このフォルダ構造が表示されない場合は、[Project] ビューに切り替えます。protobuf では、message
キーワードを使用して各構造が定義されており、構造の各メンバーは、型と名前に基づいてメッセージ内で定義され、1 から始まる順序が割り当てられます。ここでは、show_completed
というブール値を持つ UserPreferences
メッセージを定義します。
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
}
シリアライザを作成する
proto ファイルで定義したデータ型の読み書き方法を DataStore に指示するには、シリアライザを実装する必要があります。シリアライザでは、ディスク上にデータがない場合に返されるデフォルト値も定義されます。UserPreferencesSerializer
という新しいファイルを data
パッケージ内に作成します。
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserPreferences {
try {
return UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
7. Proto DataStore にデータを引き継ぐ
DataStore の作成
showCompleted
フラグは、TasksViewModel
ではメモリに保持されますが、UserPreferencesRepository
では DataStore インスタンスに格納される必要があります。
DataStore インスタンスを作成するには、dataStore
デリゲートを使用し、レシーバーとして Context
を使用します。このデリゲートには 2 つの必須パラメータがあります。
- DataStore が処理するファイルの名前。
- DataStore で使用する型のシリアライザ。ここでは、
UserPreferencesSerializer
とします。
わかりやすくするために、この Codelab では、TasksActivity
でこの操作を行いましょう。
private const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
private const val SORT_ORDER_KEY = "sort_order"
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer
)
dataStore
デリゲートにより、DataStore の単一インスタンスが、その名前でアプリ内に存在するようになります。現在、UserPreferencesRepository
はシングルトンとして実装されています。sortOrderFlow
が保持され、TasksActivity
のライフサイクルと関連付けられないようにしているためです。UserPreferenceRepository
は新しいオブジェクトを作成、保存せずに DataStore のデータを処理するだけであるため、シングルトン実装は削除することができます。
companion object
を削除するconstructor
を一般公開にする
UserPreferencesRepository
は、コンストラクタのパラメータとして DataStore
インスタンスを取得する必要があります。ここでは、Context
をパラメータとして保持できますが(SharedPreferences に必要なため)、後で削除します。
class UserPreferencesRepository(
private val userPreferencesStore: DataStore<UserPreferences>,
context: Context
) { ... }
次に、TasksActivity
の UserPreferencesRepository
の構造を更新し、dataStore
を渡しましょう。
viewModel = ViewModelProvider(
this,
TasksViewModelFactory(
TasksRepository,
UserPreferencesRepository(dataStore, this)
)
).get(TasksViewModel::class.java)
Proto DataStore からデータを読み取る
Proto DataStore は Flow<UserPreferences>
に格納されているデータを公開します。dataStore.data
が割り当てられるパブリック userPreferencesFlow: Flow<UserPreferences>
値を作成しましょう。
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
データ読み取り中の例外の処理
DataStore がファイルからデータを読み取る際、データの読み取り中にエラーが発生すると IOException
がスローされます。catch
Flow 変換を使用することでこれに対処し、エラーのみをログに記録することができます。
private val TAG: String = "UserPreferencesRepo"
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading sort order preferences.", exception)
emit(UserPreferences.getDefaultInstance())
} else {
throw exception
}
}
Proto DataStore にデータを書き込む
DataStore では、データ書き込み用に suspend 関数 DataStore.updateData()
が用意されています。ここで、UserPreferences
の現在の状態をパラメータとして取得します。更新するには、設定オブジェクトをビルダーに変換し、新しい値を設定してから、新しい設定をビルドする必要があります。
updateData()
は、アトミックな読み取り-書き込み-修正オペレーションでデータをトランザクションとして更新します。データがディスク内で引き継がれると、コルーチンが完了します。
updateShowCompleted()
という UserPreferences
の showCompleted
プロパティを更新できるようにする suspend 関数を作成してみましょう。この関数は、dataStore.updateData()
を呼び出して新しい値を設定します。
suspend fun updateShowCompleted(completed: Boolean) {
dataStore.updateData { preferences ->
preferences.toBuilder().setShowCompleted(completed).build()
}
}
この時点で、アプリのコンパイルは行われるはずですが、UserPreferencesRepository
で作成したばかりの機能は使用されません。
8. SharedPreferences から Proto DataStore への移行
proto に保存するデータを定義する
並べ替え順は SharedPreferences に保存されます。それを DataStore に移しましょう。そのためには、まず proto ファイル内の UserPreferences
を更新して、並べ替え順も保存されるようにします。SortOrder
は enum
であるため、UserPreference
で定義する必要があります。enums
は、Kotlin と同様に protobuf で定義されます。
列挙型の場合、デフォルト値は、列挙型の型定義でリストの最初に表示される値になります。しかし、SharedPreferences から移行するときは、取得した値がデフォルト値なのか SharedPreferences で設定済みのものなのかを把握する必要があります。そこで、SortOrder
列挙型に新しい値 UNSPECIFIED
を定義し、リストの最初に表示して、これをデフォルト値にできるようにしています。
user_prefs.proto
ファイルは次のようになります。
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
// defines tasks sorting order: no order, by deadline, by priority, by deadline and priority
enum SortOrder {
UNSPECIFIED = 0;
NONE = 1;
BY_DEADLINE = 2;
BY_PRIORITY = 3;
BY_DEADLINE_AND_PRIORITY = 4;
}
// user selected tasks sorting order
SortOrder sort_order = 2;
}
プロジェクトをクリーンアップおよび再ビルドし、新しいフィールドを含む新しい UserPreferences
オブジェクトが生成されるようにします。
proto ファイルで SortOrder
が定義されたので、UserPreferencesRepository
から宣言を削除できます。次を削除します。
enum class SortOrder {
NONE,
BY_DEADLINE,
BY_PRIORITY,
BY_DEADLINE_AND_PRIORITY
}
すべての場所で適切な SortOrder
インポートが使用されていることを確認します。
import com.codelab.android.datastore.UserPreferences.SortOrder
TasksViewModel.filterSortTasks()
では、SortOrder
型に基づいて、異なるアクションを行います。UNSPECIFIED
オプションも追加したので、when(sortOrder)
ステートメントのための別のケースを追加する必要があります。現在処理中のオプション以外は処理する必要がないので、他のケースでも UnsupportedOperationException
をスローできます。
filterSortTasks()
関数は次のようになります。
private fun filterSortTasks(
tasks: List<Task>,
showCompleted: Boolean,
sortOrder: SortOrder
): List<Task> {
// filter the tasks
val filteredTasks = if (showCompleted) {
tasks
} else {
tasks.filter { !it.completed }
}
// sort the tasks
return when (sortOrder) {
SortOrder.UNSPECIFIED -> filteredTasks
SortOrder.NONE -> filteredTasks
SortOrder.BY_DEADLINE -> filteredTasks.sortedByDescending { it.deadline }
SortOrder.BY_PRIORITY -> filteredTasks.sortedBy { it.priority }
SortOrder.BY_DEADLINE_AND_PRIORITY -> filteredTasks.sortedWith(
compareByDescending<Task> { it.deadline }.thenBy { it.priority }
)
// We shouldn't get any other values
else -> throw UnsupportedOperationException("$sortOrder not supported")
}
}
SharedPreferences から移行する
移行をサポートするために、DataStore は SharedPreferencesMigration
クラスを定義します。DataStore が作成される by dataStore
メソッド(TasksActivity
で使用)には、produceMigrations
パラメータも公開されています。このブロックでは、この DataStore インスタンスに対して実行する必要がある DataMigration
のリストを作成します。この例では、1 つの移行(SharedPreferencesMigration
)のみを行います。
SharedPreferencesMigration
を実装する場合、migrate
ブロックで次の 2 つのパラメータが提供されます。
SharedPreferencesView
。これにより、SharedPreferences からデータを取得できますUserPreferences
。現在のデータ
UserPreferences
オブジェクトを返す必要があります。
migrate
ブロックを実装する場合、次の手順を行う必要があります。
UserPreferences
でsortOrder
値を確認します。- これが
SortOrder.UNSPECIFIED
の場合、SharedPreferences から値を取得する必要があります。SortOrder
がない場合は、SortOrder.NONE
をデフォルトとして使用できます。 - 並べ替え順を取得した後で、
UserPreferences
オブジェクトをビルダーに変換し、並べ替え順を設定してから、build()
を呼び出してオブジェクトを再びビルドする必要があります。この変更は他のフィールドには影響しません。 UserPreferences
のsortOrder
値がSortOrder.UNSPECIFIED
でない場合は、移行がすでに正常に実行されているはずなので、migrate
で取得した現在のデータをそのまま返すことができます。
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer,
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(
context,
USER_PREFERENCES_NAME
) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
// Define the mapping from SharedPreferences to UserPreferences
if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
currentData.toBuilder().setSortOrder(
SortOrder.valueOf(
sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!!
)
).build()
} else {
currentData
}
}
)
}
)
移行ロジックを定義したので、DataStore にそれを使用するよう指示する必要があります。そのためには、DataStore ビルダーを更新し、SharedPreferencesMigration
のインスタンスを含む新しいリストを migrations
パラメータに割り当てます。
private val dataStore: DataStore<UserPreferences> = context.createDataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer,
migrations = listOf(sharedPrefsMigration)
)
DataStore に並べ替え順を保存する
enableSortByDeadline()
と enableSortByPriority()
の呼び出し時に並べ替え順を更新するには、次の操作を行う必要があります。
dataStore.updateData()
のラムダで、それぞれの機能を呼び出します。updateData()
は suspend 関数であるため、enableSortByDeadline()
とenableSortByPriority()
も suspend 関数にする必要があります。updateData()
から受け取った現在のUserPreferences
を使用して、新しい並べ替え順を作成します。UserPreferences
をビルダーに変換して更新し、新しい並べ替え順を設定してから、設定を再ビルドします。
enableSortByDeadline()
の実装は次のようになります。enableSortByPriority()
に関する変更はご自分で行ってください。
suspend fun enableSortByDeadline(enable: Boolean) {
// updateData handles data transactionally, ensuring that if the sort is updated at the same
// time from another thread, we won't have conflicts
dataStore.updateData { preferences ->
val currentOrder = preferences.sortOrder
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_PRIORITY) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_DEADLINE
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_PRIORITY
} else {
SortOrder.NONE
}
}
preferences.toBuilder().setSortOrder(newSortOrder).build()
}
}
これで、context
コンストラクタ パラメータと使用されているすべての SharedPreferences を削除できます。
9. TasksViewModel を更新して UserPreferencesRepository を使用する
UserPreferencesRepository
は、show_completed
フラグと sort_order
フラグの両方を DataStore に保存して、Flow<UserPreferences>
を公開します。これらが使用されるように TasksViewModel
を更新しましょう。
showCompletedFlow
と sortOrderFlow
を削除し、その代わりに userPreferencesRepository.userPreferencesFlow
で初期化される userPreferencesFlow
という値を作成します。
private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow
tasksUiModelFlow
を作成するときに、showCompletedFlow
と sortOrderFlow
を userPreferencesFlow
に置き換えます。必要に応じてパラメータを変更してください。
filterSortTasks
を呼び出すときは、userPreferences
の showCompleted
と sortOrder
を渡します。コードの内容は次のようになります。
private val tasksUiModelFlow = combine(
repository.tasks,
userPreferencesFlow
) { tasks: List<Task>, userPreferences: UserPreferences ->
return@combine TasksUiModel(
tasks = filterSortTasks(
tasks,
userPreferences.showCompleted,
userPreferences.sortOrder
),
showCompleted = userPreferences.showCompleted,
sortOrder = userPreferences.sortOrder
)
}
showCompletedTasks()
関数は更新され、userPreferencesRepository.updateShowCompleted()
を呼び出すようになりました。これは suspend 関数であるため、viewModelScope
に新しいコルーチンを作成します。
fun showCompletedTasks(show: Boolean) {
viewModelScope.launch {
userPreferencesRepository.updateShowCompleted(show)
}
}
userPreferencesRepository
関数の enableSortByDeadline()
と enableSortByPriority()
は、suspend 関数になったため、viewModelScope
で開始された新しいコルーチンでも呼び出す必要があります。
fun enableSortByDeadline(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByDeadline(enable)
}
}
fun enableSortByPriority(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByPriority(enable)
}
}
UserPreferencesRepository
をクリーンアップする
不要になったフィールドとメソッドを削除しましょう。以下を削除できます。
_sortOrderFlow
sortOrderFlow
updateSortOrder()
private val sortOrder: SortOrder
private val sharedPreferences
これでアプリのコンパイルが正常に行われるようになりました。実行して、show_completed
フラグと sort_order
フラグが正しく保存されているかを確認してみましょう。
Codelab repo の proto_datastore
ブランチを確認して、変更を比較します。
10. まとめ
これで、Proto DataStore への移行が完了しました。学習内容をおさらいしましょう。
- SharedPreferences にはいくつかのデメリットがあります。たとえば、UI スレッドで呼び出しても安全そうに見える同期 API、エラーを伝えるメカニズムの欠如、トランザクション API の不足などです。
- DataStore は、その API の欠点のほとんどに対応した、SharedPreferences に代わるものです。
- DataStore は、Kotlin のコルーチンと Flow を使用する完全非同期の API を備え、データの移行を処理し、データの整合性を保証して、データの破損を処理します。