English

Abstract #

KDataStore is my local persistent Android storage solution based on Jetpack’s DataStore. The emphasis on optimizations is as follows:

  • Singleton mode.
  • Generating key through delegation.
  • Adapting MutableStateFlow for 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 to Multiplatform after IOS support.

Competitor comparison #

SharedPreferencesMMKVDataStoreKDataStore
PerformanceStartup: 2.5ms

Reading: negligible time consumption.

Writing with commit: blocking for 2.3ms

Writing 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.6msStartup: 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 safetyNoNoYesYes
Support for types other than common basic types, String, and Set<String>ParcelableCustomizable
However, it needs to be placed in an independent DataStore
KtSerializable (including common storage types)

Customizable
Exception during readingReturns a empty HashMap, which means returning defaults for allManual catchGets from backup file
IOException during writingReplaces 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
MultiprocessManual encapsulationSupportedIn 1.1.0-alphastageAfter DataStore 1.1.0 releases
MultiplatformNot supportedSupported
CryptoManual encapsulationSupportedManual encapsulationYou need to choose a crypto protocol and customize a cipher.
Additional advantagesData updated before ANR will not be lostSmall size, jar on Android side is only 12.6 kb
Additional disadvantagesAfter a power outage or system crash, a lot of data is likely to be lostQuite 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

  1. Observe Flow/LiveData in Activity/BasicActivity and bind the theme.
  2. Checked RadioButton will change with the user’s click. Just set the initial state according to isDarkMode.value rather than bind.
  3. Update the stored value in the RadioGroup listener.
collectOnResume is recommended to observe Flow in Fragment, of which the setup is included in the following.
  1. Observe LiveData in Activity/BasicActivity and bind the theme.
  2. Checked RadioButton will change with the user’s click. Just set the initial state according to isDarkMode.value rather than bind.
  3. Update the stored value in the RadioGroup listener.

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
}
Include KDataStoreInitializer::class.java in dependencies, 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 to null.

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.

Warning is for preventing misuse rather than exception risks.

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();