Preferences DataStore

投稿日:  更新日:

Preferences DataStoreの使い方を、まとめます。

DataStoreはPreferencesとProto DataStoreの2つがあります。

Preferences DataStoreは、データの識別子にキー(文字列)を使うタイプです。

手軽・簡素で、Proto DataStoreに比べて、使い勝手がよいです。

あえて、Proto DataStoreを使う理由が無いのであれば、Preferences DataStoreで十分です。

※環境:Android Studio Hedgehog | 2023.1.1 Patch 2
    androidx.datastore:datastore-preferences:1.1.7

スポンサーリンク

Preferences DataStore

DataStoreはデータを「識別子と値のペア」で記録します。

DataStoreのデータフォーマット

Preferences DataStoreは、キー(文字列)を識別子にしたDataStoreです。

キーを指定して、データのアクセスを行います。

Preferences DataStoreのデータ管理イメージ

データはPreferencesクラス内にPairクラスで格納されています。

スポンサーリンク

環境設定

build.gradleへDataStoreのライブラリを指定します。
※ライブラリについては「ライブラリ>DataStore」を参照

plugins {
    ...
}

android {
    ...
}

dependencies {
    implementation("androidx.datastore:datastore-preferences:1.1.7")

    ...
}
スポンサーリンク

DataStoreの例

アプリの設定(状態)を保存する例です。

ポイントは4つです。

  • (1)DataStoreインスタンスの作成
  • (2)キーの定義
  • (3)コルーチンでデータの読み出し
  • (4)コルーチンでデータの書き込み
// ↓↓ (1)DataStoreインスタンスの作成
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

val APP_STATE0 = intPreferencesKey("app_state0")   // (2)キーの定義
val APP_STATE1 = intPreferencesKey("app_state1")
val APP_STATE2 = intPreferencesKey("app_state2")
private const val UNDEFINE = -1

class MainActivity : ComponentActivity() {

    private var state0: Int = UNDEFINE
    private var state1: Int = UNDEFINE
    private var state2: Int = UNDEFINE

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        val state0Flow: Flow<Int> = dataStore.data  // (3)データの読み出し
            .map { preferences ->
                preferences[APP_STATE0] ?: 1000
            }
        // 常に更新、通信経路を維持(スレッドをブロックしない)
        lifecycleScope.launch {
            state0Flow.collect { state0 = it }
        }

        val state1Flow: Flow<Int> = dataStore.data
            .map { preferences ->
                preferences[APP_STATE1] ?: 1100
            }
        // 実行で更新(スレッドをブロックしない)
        lifecycleScope.launch(Dispatchers.Default) {
            state1 = state1Flow.first()
        }

        val state2Flow: Flow<Int> = dataStore.data
            .map { preferences ->
                preferences[APP_STATE2] ?: 1200
            }
        // 実行で更新(スレッドをブロックする)
        state2 = runBlocking { state2Flow.first() }

        setContent { ... }
    }
}
                                ...
                                lifecycleScope.launch(Dispatchers.Default) { // (4)データの書き込み
                                    dataStore.edit { settings ->
                                        settings[APP_STATE0] = _nextState0
                                    }
                                }
								...
                                lifecycleScope.launch(Dispatchers.Default) { // 記述はState0と同じ
                                    dataStore.edit { settings ->
                                        settings[APP_STATE1] = _nextState1
                                    }
                                }
								...
                                lifecycleScope.launch(Dispatchers.Default) { // 記述はState0と同じ
                                    dataStore.edit { settings ->
                                        settings[APP_STATE2] = _nextState2
                                    }
                                }
								...
スポンサーリンク

(1)DataStoreインスタンスの作成

DataStoreインスタンスを作成します。

この時、データの書き込み先のファイル名を指定します。

// ↓↓ (1)DataStoreインスタンスの作成
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
//

インスタンスはアプリケーション内に1つだけ作成します。ここでは、Contextインスタンスの拡張プロパティに代入しています。それにより、「アプリケーション内に1つ」を実現しています。

preferencesDataStore( )は引数にファイル名を伴って、DataStoreインスタンスを返します。委譲プロパティなので、プロパティへの代入は初回の参照時です。

ちなみに、書き込み先のファイルは以下の場所にあります。

# pwd
/data/user/0/パッケージ名/files/datastore
# ls -l
total 16
-rw------- 1 u0_a174 u0_a174 19 2025-09-08 01:02 settings.preferences_pb

場所はサンドボックス内(アプリ固有の領域)です。

スポンサーリンク

(2)キーの定義

キーの定義を行います。

val APP_STATE0 = intPreferencesKey("app_state0")   // (2)キーの定義
val APP_STATE1 = intPreferencesKey("app_state1")
val APP_STATE2 = intPreferencesKey("app_state2")

キーの定義で使用するxxxPreferencesKey( )関数は、キーへデータ型を関連付ける役割も持ちます。

サンプルはInt型に関連付けしています。

その他、次のような関数が準備されています。

public fun intPreferencesKey(name: String): Preferences.Key<Int>
public fun doublePreferencesKey(name: String): Preferences.Key<Double>
public fun stringPreferencesKey(name: String): Preferences.Key<String>
public fun booleanPreferencesKey(name: String): Preferences.Key<Boolean>
public fun floatPreferencesKey(name: String): Preferences.Key<Float>
public fun longPreferencesKey(name: String): Preferences.Key<Long>
public fun stringSetPreferencesKey(name: String): Preferences.Key<Set<String>>
public fun byteArrayPreferencesKey(name: String): Preferences.Key<ByteArray>
//

Preferences DataStoreは関数の準備されているデータ型をサポートします。

スポンサーリンク

(3)コルーチンでデータの読み出し

Flow経由でReferencesオブジェクトを取得し、データの読み出しを行います。

Preferences DataStoreのFlow

ストリームデータはReferencesオブジェクトです。

ファイルが更新される度に、データが送信される点に注意して下さい。

受信したReferencesオブジェクトから、キーを使って、データを読み出します。

Flow.map(中間関数)を使って、Flowを個別データへ割り振り直すと便利です。
※中間関数については「Coroutine:Flowのストリームデータ変更(中間演算)」を参照

スポンサーリンク

collectを使って

collectを使って受信する例です。

Flowの通信経路は維持されます。受信データが無い場合は、スレッドを休止して待ちます。

        val state0Flow: Flow<Int> = dataStore.data  // (3)データの読み出し
            .map { preferences ->
                preferences[APP_STATE0] ?: 1000
            }
        // 常に更新、通信経路を維持(スレッドをブロックしない)
        lifecycleScope.launch {
            state0Flow.collect { state0 = it }
        }

経路が維持される限り、「ファイルの更新⇒データの送信⇒データの受信⇒設定(状態)の更新」を繰り返すので、アプリの設定は常に最新の値になります。

キー対応する値がファイルに存在しなければ、nullを返します。nullの場合は初期値に置き換えています。

first()を使って

firstを使って受信する例です。

最初のデータを受け取るまで待機し、受信後キャンセル信号を送信機へ送ります。

ですので、一回限りの読み出しで、Flowの通信経路は閉じられます。

        val state1Flow: Flow<Int> = dataStore.data
            .map { preferences ->
                preferences[APP_STATE1] ?: 1100
            }
        // 実行で更新(スレッドをブロックしない)
        lifecycleScope.launch(Dispatchers.Default) {
            state1 = state1Flow.first()
        }

ちなみに、Flowはホットストリームなので、Flowの通信経路はfirstを実行する度に作成されます。

runBlockingを使って

runBlockingを使って受信する例です。

スレッドはデータを受信するまでブロックされます。サンプルの場合はMainスレッドがブロックされます。

        val state2Flow: Flow<Int> = dataStore.data
            .map { preferences ->
                preferences[APP_STATE2] ?: 1200
            }
        // 実行で更新(スレッドをブロックする)
        state2 = runBlocking { state2Flow.first() }

ANR(Application Not Responding)を引き起こす可能性があるので、お勧めしません。

スポンサーリンク

(4)コルーチンでデータの書き込み

editを使うと、MutablePreferences(変更可能なPreferences)が取り出せます。

取り出したMutablePreferencesオブジェクトへ、キーを使って、データを書き込みます。

                                lifecycleScope.launch(Dispatchers.Default) { // (4)データの書き込み
                                    dataStore.edit { settings ->
                                        settings[APP_STATE0] = _nextState0
                                    }
                                }
スポンサーリンク

関連記事:

「DataStore」はAndroid Jetpackで提供されているAPIです。 永続的なデータを保存するための仕組みです。 「SharedPreferences」の代替ツールとして登場し、「SharedPreferences」に代わって使用が推奨されています。 ここでは「DataStoreの概要」について、まとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2 ...
Proto DataStoreの使い方を、まとめます。 DataStoreはPreferencesとProto DataStoreの2つがあります。 Proto DataStoreは、データの識別子にカスタムデータクラスのフィールドを使うタイプです。 カスタムデータクラスはprotoスキーマの定義から自動生成される仕組みになっています。 このカスタムデータクラスの作成に一手間かかるため、使い勝手はPreference DataStoreよりも劣ります。 あえて、Proto DataStoreを使う理由が無いのであれば、Preferences DataStoreで十分です。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     com.google.protobuf 0.9.5     androidx.datastore:datastore:1.1.7     com.google.protobuf:protobuf-javalite:4.32.0 ...
スポンサーリンク