datastore について
AndroidおよびKotlin Multiplatform開発者向けのJetpack DataStoreスキルです。Preferences、型付きストレージ、Roomを比較し、エラー復旧とプラットフォーム別ファイルパスの注意点を示します。
主な機能
- 01Preferencesと型付きストレージの指針
- 02DataStoreとRoomの選択
- 03IOExceptionに限定したFlow処理
- 04型付きストアの破損復旧
- 05プラットフォーム別パスファクトリー
活用シーン
- 01設定や構造化データの保存先選択
- 02DataStore読み取りエラーの安全な処理
- 03破損した型付きストアファイルの復旧
- 04Android、iOS、JVMでのストア作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill datastoreインストール後、Claude Code で/datastoreを入力して読み込まれたか確認します。
詳細
Android および KMP 向け Jetpack DataStore
リアクティブなコルーチンベースのキーと値/型付きストレージです。同じ androidx.datastore:datastore-preferences-core が Android、iOS、JVM で動作し、プラットフォーム固有なのはファイルパスの生成処理だけです。(Web のサポートは 1.3.0-alpha のみで、ファイルパスベースではなく、sessionStorage/OPFS を使用します。)このリファレンスでは、ストレージの選択と、2 つのエラー/パスに関する落とし穴を取り上げます。Preferences のキー、edit { }、Serializer<T> の基本は扱いません。Meet-Miyani/compose-skill を基にしています。MIT。関連項目: android-skills:android-data-layer、android-skills:kmp-boundaries、android-skills:kotlin-flows。
Preferences、Typed、Room の比較
| 必要なもの | ストレージ |
|---|---|
| キーと値のフラグ(テーマ、ロケール、オンボーディング完了など) | Preferences DataStore |
| 1 つの型付きオブジェクト、多数の関連フィールド、スキーマの進化 | Typed DataStore + Serializer<T> |
リレーショナルデータ、インデックス、WHERE/JOIN、100 件を超えるエントリー、ページング | Room |
| 書き込みあたり約 50KB を超えるペイロード | Room/ファイルシステム — DataStore は edit のたびにファイル全体を書き換えます |
目安:WHERE 句が役立つなら、Room を使ってください。
2 つの落とし穴
.catch は IOException のみに一致させてください。広範な catch は決して使わないでください。 何も指定しない catch { emit(emptyPreferences()) } は CancellationException を握りつぶし(構造化並行性を損ないます)、シリアライザー/破損エラーを、何も通知しない空の状態に隠してしまいます。
val settings: Flow<UserSettings> = dataStore.data
.catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
.map { p -> UserSettings(p[Keys.DARK_MODE] ?: false, p[Keys.LOCALE] ?: "en") }
Typed DataStore の破損復旧がトリガーされるのは IOException ではなく CorruptionException です。 シリアライザーの readFrom は解析エラーを CorruptionException でラップする必要があり、ストアには ReplaceFileCorruptionHandler が必要です。これがないと、ファイルが 1 つ破損しただけで、以後すべての読み取りが永久に失敗します。
object AppSettingsSerializer : Serializer<AppSettings> {
override val defaultValue = AppSettings()
override suspend fun readFrom(input: InputStream): AppSettings =
try { Json.decodeFromString(input.readBytes().decodeToString()) }
catch (e: SerializationException) { throw CorruptionException("Cannot read AppSettings", e) }
override suspend fun writeTo(t: AppSettings, output: OutputStream) =
output.write(Json.encodeToString(t).encodeToByteArray())
}
val store = DataStoreFactory.create(
serializer = AppSettingsSerializer,
corruptionHandler = ReplaceFileCorruptionHandler { AppSettings() },
produceFile = { File(context.filesDir, "app_settings.json") },
)
KMP ファクトリー — プラットフォームごとのパス(テンポラリディレクトリーではありません)
// commonMain
fun createPreferencesDataStore(producePath: () -> String): DataStore<Preferences> =
PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
// android: context.filesDir.resolve(PREFS_FILE).absolutePath
// ios: NSDocumentDirectory via NSFileManager
// jvm: File(System.getProperty("user.home"), ".myapp") — NOT java.io.tmpdir (the OS may wipe it on reboot)
(Composable 内で DataStore に runBlocking を使用しないでください。ディスク I/O 中にメインスレッドを停止させて ANR のリスクを高め、再コンポジションのたびに再実行されます。StateFlow を公開し、collectAsStateWithLifecycle で収集してください。)