Abstract #
KDataStore is my local persistent Android storage solution based on Jetpack’s DataStore.
The emphasis on optimizations is as follows:
- Singleton mode.
- Generating
keythrough delegation. - Adapting
MutableStateFlowfor real-time observation, synchronous reading and writing with memory, and asynchronous writing to disk. - Backing up data to handle exceptions.
- Supports extensive types, e.g.
Kotlin Serializable,Customized,Nullable.
See the GitHub repository (includes demo) for more information.
Java support is for the initial promotion and not considered in my other works.This page will be moved toMultiplatformafterIOSsupport.
Competitor comparison #
| SharedPreferences | MMKV | DataStore | KDataStore | |
|---|---|---|---|---|
| Performance | Startup: 2.5ms Reading: negligible time consumption. Writing with commit: blocking for 2.3msWriting time consumption with applyis negligible, but it is unsure if the data has been successfully written to disk asynchronously. | Startup: 2.3ms Reading and writing: negligible time consumption | All are performed asynchronously, so only response time consumption is measured: 8.6ms | Startup: 13.5ms This impact is not significant when the file size increases substantially, and it can be also resolved by calling the KDataStore subclass asynchronously in the Application.Reading and writing time consumption is negligible. |
| Type safety | No | No | Yes | Yes |
Support for types other than common basic types, String, and Set<String> | Parcelable | Customizable However, it needs to be placed in an independent DataStore | KtSerializable (including common storage types)Customizable | |
| Exception during reading | Returns a empty HashMap, which means returning defaults for all | Manual catch | Gets from backup file | |
| IOException during writing | Replaces with the backup file without the data, and do not write again. Returns false if via commit | Only catch without handling. | Records at once. Update from backup file at next startup | |
| Multiprocess | Manual encapsulation | Supported | In 1.1.0-alphastage | After DataStore 1.1.0 releases |
| Multiplatform | Not supported | Supported | ||
| Crypto | Manual encapsulation | Supported | Manual encapsulation | You need to choose a crypto protocol and customize a cipher. |
| Additional advantages | Data updated before ANR will not be lost | Small size, jar on Android side is only 12.6 kb | ||
| Additional disadvantages | After a power outage or system crash, a lot of data is likely to be lost | Quite new Modeling requires Kotlin. Java is only supported on caller side. | ||
The test results above are based on 30 sets of String data of Meizu 18s. Source code is in KDataStore.benchmark.
There are serious errors in most storage scheme comparison analysises from other information sources. The official website is relatively accurate, but very one-sided. If you want to explore further, I recommend you test it yourself and view the source code for deep exploration.
Basic Usage #
Take the example of switching darkTheme with the stored Boolean.
Model #
Set an independent Android module, commonly named settings.
All namings in the following documents refer to settings.
About KDSFlow

actual KDSFlow on Android

Call #
Call in other modules
- Observe
Flow/LiveDatainActivity/BasicActivityand bind thetheme. - Checked
RadioButtonwill change with the user’s click. Just set the initial state according toisDarkMode.valuerather than bind. - Update the stored value in the
RadioGrouplistener.

collectOnResume is recommended to observeFlowinFragment, of which thesetupis included in the following.
- Observe
LiveDatainActivity/BasicActivityand bind thetheme. - Checked
RadioButtonwill change with the user’s click. Just set the initial state according toisDarkMode.valuerather than bind. - Update the stored value in the
RadioGrouplistener.
Bind state and theme

Update stored value at RadioButton

Setup #
Configure build.gradle/build.gradle.kts as below, or reference the demo in Github
which uses version catalog.
Root Directory #
plugins{
...
id 'org.jetbrains.kotlin.plugin.serialization' version "$version_kt" apply false
}
plugins{
...
id ("org.jetbrains.kotlin.plugin.serialization") version "$version_kt" apply false
}
Model module #
plugins {
...
id 'kotlinx-serialization'
}
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1'
implementation 'io.github.shawxingkwok:kt-util:1.0.2'
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
}
plugins {
...
id ("kotlinx-serialization")
}
dependencies {
...
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation ("io.github.shawxingkwok:kt-util:1.0.2")
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
}
Caller Side #
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach{
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}
dependencies {
...
implementation 'io.github.shawxingkwok:android-util-view:1.0.8'
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
implementation project(':modelModuleName') // or remote library
}
dependencies{
...
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
implementation project(':modelModuleName') // or remote library
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach{
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}
dependencies {
...
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
implementation ("io.github.shawxingkwok:android-util-view:1.0.8")
implementation (project(":modelModuleName")) // or remote library
}
dependencies{
...
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
implementation (project(":modelModuleName")) // or remote library
}
IncludeKDataStoreInitializer::class.javaindependencies, in case this caller module introduced startup-runtime.
Type Support #
kotlinx.serialization
usage is similar to Java Serializable, but multiplatform and more than twice as fast.
Classes annotated with Serializable, basic types, enum, Pair, IntArray, List’s default implementation and some others
can be considered as Serializable.

- When
non-null, a default value must be declared. - When
nullable, the default value is restricted tonull.
Edit in a stored object would not trigger the disk update.
Migration #
Migrate from other storage repositories with this format (judge existence -> migrate -> delete) in which appContext
is from KDataStore.
Take the example of migrating from SharedPreferences.

Additionally, there are two built-in functions, delete and exist, that assist in migrating from KDataStore to other places.


Optional arguments #


For the cipher part, you need to choose a crypto protocol from Java standard library,
or other libraries to customize it.
Data isolation was introduced in Android API 29, making it relatively safe without crypto which approximately doubles the startup time.
Reset #
All #


Partial #
Take the example of isDarkMode.
Settings.isDarkMode.reset()
Settings.isDarkMode().reset();
