〈kotlin〉Preferences DataStore

Android studioでkotlinを使ってAndroidアプリ作成の勉強中。

だいぶ前に小さいデータを保存しておくSharedPreferencesの記事を書いたが、
Jetpack DataStoreに移行することを検討してください」となっていて、
以下のcodelabを元にPreferences DataStoreについて勉強したのでまとめてみる。
サンプルコードも以下のcodelabから。
Preferences DataStore  |  Android Developers

Jetpack DataStore

シンプルな小規模なデータを非同期で安全に保存できる。
以下の2種類がある。

Preferences DataStore スキーマを定義せず、キーを使用してデータを保存・アクセスする。
データがkey-valueで保存できるほどシンプルな場合簡単に保存できる。
Proto DataStore プロトコル バッファを使用してスキーマを定義してデータを保存する。
設定が必要だがタイプセーフで高速。

今回はPreferences DataStoreについてのみを書く。

Preferences DataStore

設定
dependencies {
        implementation("androidx.datastore:datastore-preferences:1.0.0")
}
DataStoreを作成

Kotlinファイルの最上位でpreferencesDataStoreデリゲートを使用してDataStoreインスタンスを作成し、
アプリの他の部分ではこのプロパティを介してアクセスする。
これによりDataStoreをシングルトンで保持する。
preferencesDataStoreのnameにはDataStoreの名前を設定する。

private const val LAYOUT_PREFERENCES_NAME = "layout_preferences"
val Context.dataStore :DataStore<Preferences> by preferencesDataStore(
    name = LAYOUT_PREFERENCES_NAME
)


この先のコードはSettingsDataStoreクラスとして記述していく。

保存するキーの定義

スキーマを定義しないので、対応するキー型の関数を使用して保存するキーと値の型を定義する。

class SettingsDataStore(context: Context) {
    private val IS_LINEAR_LAYOUT_MANAGER = booleanPreferencesKey("is_linear_layout_manager")
    //is_linear_layout_managerにboolean型の値を保存
}
書き込み

DataStore内のデータをトランザクションとして更新するsuspend関数 edit()を使用して、
上記で定義したキーに対して値を保存する。
edit関数にはtransform パラメータがあり、ここに値を更新するコードブロックを渡す。

class SettingsDataStore(context: Context) {
    private val IS_LINEAR_LAYOUT_MANAGER = booleanPreferencesKey("is_linear_layout_manager")

    suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager:Boolean, context: Context){
        context.dataStore.edit {preferences ->
            preferences[IS_LINEAR_LAYOUT_MANAGER] = isLinearLayoutManager
        }
    }
}
読み込み

DataStoreは設定が変更されるたびに出力される Flow<Preferences>に保存されたデータを公開する。
ここからキーを使用して目的の値を取り出す。
DataStore がファイルに対してデータを読み書きする際に、
データへのアクセス時に IOExceptions が発生することがあるためキャッチしておくと良い。

class SettingsDataStore(context: Context) {
    private val IS_LINEAR_LAYOUT_MANAGER = booleanPreferencesKey("is_linear_layout_manager")

    val preferenceFlow : Flow<Boolean> = context.dataStore.data
        .catch {
            if (it is IOException){
                it.printStackTrace()
                emit(emptyPreferences())
            }else{
                throw it
            }
        }
        .map { preferences ->
            preferences[IS_LINEAR_LAYOUT_MANAGER] ?: true
            //初回は空のため、デフォルト値を渡しておく
        }
}
DataStoreを使う
class LetterListFragment : Fragment() {

    private lateinit var settingsDataStore: SettingsDataStore
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        settingsDataStore = SettingsDataStore(requireContext())
        settingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner) { value ->
           //Flowで保存されているのでLiveDataに変換して監視できる
           //変更時にしたい処理あれば書く
        }
    }

     override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_switch_layout -> {
                ...
                //コルーチンで呼び出す
                lifecycleScope.launch{
                    settingsDataStore.saveLayoutToPreferencesStore(isLinearLayoutManager, requireContext())
                }
                return true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

参考
アプリ アーキテクチャ: データレイヤー - DataStore - デベロッパー向け Android  |  Android デベロッパー  |  Android Developers