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はデータを「識別子と値のペア」で記録します。
Preferences DataStoreは、キー(文字列)を識別子にした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オブジェクトを取得し、データの読み出しを行います。
ストリームデータは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 } }
関連記事: