mcpField
D
datastore

datastore

github.com/rcosteira79开发165138 次安装6 周前更新
代码仓库

关于 datastore

面向 Android 和 Kotlin Multiplatform 开发者的 Jetpack DataStore 技能,帮助选择并实现响应式存储。它比较 Preferences、类型化存储与 Room,并说明错误恢复和平台文件路径方面的注意事项。

主要功能

  1. 01Preferences 与类型化存储指南
  2. 02DataStore 与 Room 选型
  3. 03针对 IOException 的 Flow 处理
  4. 04类型化存储损坏恢复
  5. 05平台专属路径工厂

适用场景

  1. 01为设置和结构化数据选择存储
  2. 02安全处理 DataStore 读取错误
  3. 03恢复损坏的类型化存储文件
  4. 04在 Android、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,而非基于文件路径。)本参考介绍存储方案的选择,以及两个错误/路径陷阱,不涵盖 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
单个类型化对象、多个相关字段、架构演进Typed DataStore + Serializer<T>
关系型数据、索引、WHERE/JOIN、超过 100 条记录、分页Room
单次写入的数据超过约 50KBRoom/文件系统——DataStore 每次 edit 都会重写整个文件

经验法则:如果 WHERE 子句会有用,就使用 Room。

两个陷阱

.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 的损坏恢复由 CorruptionException 触发,而非 IOException。 序列化器的 readFrom 必须将解析失败包装为 CorruptionException,并且存储需要 ReplaceFileCorruptionHandler——没有它,一个损坏的文件就会导致每次读取都永久失败。

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)

(在可组合项中,绝不要对 DataStore 使用 runBlocking——它会在磁盘 I/O 期间阻塞主线程,可能导致 ANR,而且每次重组都会重新运行。请暴露一个 StateFlow,并使用 collectAsStateWithLifecycle 收集。)

评论

支持 Markdown