Android开发拾遗:DataStore与JSON结合
在本系列的上一篇中我介绍了Android的Proto DataStore
的用法,但是我对protobuf
的schema定义并不熟悉,所以就想着有没有使用JSON格式结合DataStore存储数据,事实证明这是可能的。
首先注意到DataStore
与protobuf
相关类是解耦的,dataStore
函数需要的参数是一个文件名——持久化数据存放位置,以及一个serializer
对象。现在尝试改动serializer来实现用JSON存储配置数据。
先安装依赖:
plugins {
// ...
alias(libs.plugins.jetbrains.kotlin.serialization)
}
dependencies {
// datastore还是必要的
implementation(libs.androidx.datastore)
// 用于序列化/反序列化
implementation(libs.kotlinx.serialization.json)
}
# libs.versions.toml
[versions]
datastore = "1.1.1"
kotlin = "1.9.0"
kotlinxSerializationJson = "1.1.0"
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
[plugins]
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
下一步是创建一个用于序列化/反序列化的数据类(之前Proto DataStore中这个类是自动生成的):
import kotlinx.serialization.Serializable
@Serializable
data class Settings(
val theme: Theme = Theme.SYSTEM,
val isDebugMode: Boolean = false,
)
enum class Theme { SYSTEM, LIGHT, DARK }
接下来设置好Serializer
对象:
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings
get() = Settings()
override suspend fun readFrom(input: InputStream): Settings {
return try {
Json.decodeFromString(
deserializer = Settings.serializer(),
string = input.readBytes().decodeToString()
)
} catch (e: SerializationException) {
e.printStackTrace()
defaultValue
}
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
withContext(Dispatchers.IO) {
output.write(
Json.encodeToString(
serializer = Settings.serializer(),
value = t
).encodeToByteArray()
)
}
}
}
将其设置到Context
上:
val Context.dataStore by dataStore("settings.json", SettingsSerializer)
简单写个UI试验下:
@Composable
fun MainScreen() {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
val context = LocalContext.current
val settings by context.dataStore.data.collectAsState(initial = Settings())
val scope = rememberCoroutineScope()
Surface(modifier = Modifier.padding(innerPadding)) {
Column {
Text(if (settings.isDebugMode) "Debug" else "Release")
Button(onClick = {
scope.launch {
context.dataStore.updateData {
it.copy(isDebugMode = !it.isDebugMode)
}
}
}) {
Text(text = "Toggle")
}
}
}
}
}
完成!