quick-store
0.1.0-alpha01indexedFast MMKV-compatible key-value store offering a unified, type-safe API, MMKV binary format, drop-in preferences adapters, null-on-miss getters, synchronous C++ core and no reflection or annotation processors.
Fast MMKV-compatible key-value store offering a unified, type-safe API, MMKV binary format, drop-in preferences adapters, null-on-miss getters, synchronous C++ core and no reflection or annotation processors.
A fast, MMKV-compatible key-value store for Kotlin Multiplatform — Android and iOS from a single API.
QuickStore is a thin Kotlin Multiplatform wrapper over a C++ MMKV-compatible storage engine. It exposes a unified, type-safe API for persisting primitive values on Android (AAR + JNI) and iOS (XCFramework). The C++ core is frozen and battle-tested; QuickStore adds the Kotlin and Swift layers.
null on miss (no sentinel values)SharedPreferences adapter for Android migration| Platform | Minimum version |
|---|---|
| Android | minSdk 24 (Android 7.0) |
| iOS | iOS 13+ |
Add the dependency to your module's build.gradle.kts:
dependencies {
implementation("io.github.santimattius:quickstore:0.1.0-alpha01")
}
Make sure mavenCentral() is in your repository list.
Native bridge: QuickStore ships its JNI layer as a separate artifact (
quickstore-native). Gradle resolves it automatically as a transitive dependency — you do not need to declare it explicitly. Both artifacts are versioned together and published simultaneously.
In Xcode: File > Add Package Dependencies, enter the URL:
https://github.com/santimattius/quick-store
Select the QuickStore product and add it to your target.
Alternatively, add to your Package.swift:
dependencies: [
.package(url: "https://github.com/santimattius/quick-store", from: "0.1.0-alpha01")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["QuickStore"]
)
]
Note: On the
mainbranch,Package.swiftcarries a placeholder and is NOT directly consumable. The release workflow () computes the real checksum and the release URL on each tag, then commits the resolved back to . Always depend on a tagged version (), never on .
iOS — Kotlin extension functions: The Kotlin extensions in
QuickStoreExtensions.kt(putString,getInt, default-value overloads, …) are exposed to Objective-C/Swift as top-level functions on the generatedQuickStoreExtensionsKtclass, with mangled selectors such as . The exact selector names are generated by Kotlin/Native and may shift across versions — always consult the generated in the XCFramework for the authoritative signatures rather than hardcoding mangled names.
QuickStoreSharedPreferences is a drop-in adapter that implements the SharedPreferences interface backed by a QuickStore instance. Swap one line:
// Before
val prefs: SharedPreferences = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
// After — same interface, faster storage
val store = QuickStore(mmkvId = "app_prefs", rootDir = context.filesDir.absolutePath)
val prefs: SharedPreferences = QuickStoreSharedPreferences(store)
All existing getString, getInt, getLong, getFloat, getBoolean, contains, edit(), , and calls continue to work without change.
See Known Limitations for unsupported operations.
Replace UserDefaults calls with the equivalent QuickStore primitives:
// UserDefaults
UserDefaults.standard.set("santiago", forKey: "username")
let name = UserDefaults.standard.string(forKey: "username")
// QuickStore — equivalent
_ = store.putString(key: "username", value: )
name: ? store.getString(key: )
Numeric migration:
// UserDefaults
UserDefaults.standard.set(42, forKey: "count")
// QuickStore
_ = store.setLong(key: "count", value: 42)
let count = store.getLong(key: "count") // Int64?
| Signature | Platform | Description |
|---|---|---|
QuickStore(mmkvId: String, rootDir: String) | Both | Opens (or creates) a store with the given ID in rootDir. Throws on failure. |
true on successnull when key not foundcommonMain)Requires
@OptIn(ExperimentalQuickStoreApi::class). See Experimental API stability.
@OptIn(ExperimentalQuickStoreApi::class)
fun example(store: QuickStore) {
// Write several counters in a single native call
store.batchSetLongs(mapOf("views" to 1_000L, "clicks" to , to ))
counts: Map<String, ?> = store.batchGetLongs(listOf(, , ))
println(counts)
}
The @ExperimentalQuickStoreApi marker indicates that the shape of an API (its name, signature, or existence) may change in any minor release before 1.0 WITHOUT a deprecation cycle. The underlying storage behavior is stable and MMKV-compatible — only the Kotlin surface is provisional.
To suppress the compiler warning, opt in at the call site:
@OptIn(ExperimentalQuickStoreApi::class)
fun myFunction() { /* use batch APIs here */ }
Or propagate the opt-in to a whole class or file:
@file:OptIn(ExperimentalQuickStoreApi::class)
| Class / Method | Description |
|---|---|
QuickStoreSharedPreferences(store: QuickStore) | Wraps a QuickStore as a SharedPreferences. Drop-in replacement for getSharedPreferences(...). |
QuickStore is a Kotlin Multiplatform wrapper over a frozen C++ MMKV-compatible storage engine. On Android it ships as an AAR with a JNI bridge (libquickstore_jni.so, packaged in the quickstore-native artifact); on iOS it ships as an XCFramework built via Kotlin/Native cinterop. The Kotlin layer uses expect/actual declarations so commonMain holds the API contract and each platform target (androidMain, ) provides the platform implementation.
Why it is fast: The C++ core uses an mmap-backed memory region and an append-only write-log. Writes append a type-tagged record to the end of the log; reads are served from the mapped memory region without a system call. Periodic calls to trim() compact the log by rewriting only live entries, reclaiming any space accumulated from overwritten or removed keys.
Type model: Every value carries a type tag in the core. A getter of the wrong type (e.g., calling getLong on a key that stores a boolean) returns null rather than a corrupt value. This is the source of the nullability contract documented throughout the API.
Two-artifact publishing: The library publishes as two Maven coordinates — quickstore (the Kotlin layer) and quickstore-native (the Android JNI AAR). Both are versioned together and must be published simultaneously. See Known Limitations for details.
For tests, a FakeQuickStore in-memory double exists in the source tree under commonTest. It is not part of the published artifact — copy it into your own test source set if you want it.
Copyright 2026 Santiago Mattiauda
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance the License.
You may obtain a copy of the License at
https:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.
REPLACE_WITH_CHECKSUMpublish.ymlv*.*.*Package.swiftmainfrom: "x.y.z"branch: "main"import com.quickstore.QuickStore
import com.quickstore.putString
import com.quickstore.getString
// Open a store — mmkvId is the store name, rootDir is the storage directory
val store = QuickStore(
mmkvId = "app_prefs",
rootDir = context.filesDir.absolutePath
)
// Write
store.putString("username", "santiago")
store.setLong("login_count", 42L)
store.setBool("onboarding_done", true)
// Read
val username: String? = store.getString("username")
val count: Long? = store.getLong("login_count")
val done: Boolean? = store.getBool("onboarding_done")
// Default-value overloads
val safeUsername: String = store.getString("username", "guest")
val safeCount: Long = store.getLong("login_count", 0L)
// Cleanup
store.close()
import QuickStore
// Open a store
let store = QuickStore(
mmkvId: "app_prefs",
rootDir: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path
)
// Write
_ = store.setLong(key: "login_count", value: 42)
_ = store.setBool(key: "onboarding_done", value: true)
// Read (returns Optional)
let count: Int64? = store.getLong(key: "login_count")
let done: Bool? = store.getBool(key: "onboarding_done")
// Cleanup
store.close()
QuickStoreExtensionsKt.putStringForStore_key_value_QuickStore.hcommit()apply()| Method | Platform | Description |
|---|
setBool(key: String, value: Boolean): Boolean | Both | Stores a boolean value. |
setLong(key: String, value: Long): Boolean | Both | Stores a 64-bit integer. |
setDouble(key: String, value: Double): Boolean | Both | Stores a 64-bit float. |
setBytes(key: String, value: ByteArray): Boolean | Both | Stores a raw byte array. |
| Method | Platform | Description |
|---|
getBool(key: String): Boolean? | Both | Retrieves a boolean, or null if absent. |
getLong(key: String): Long? | Both | Retrieves a 64-bit integer, or null if absent. |
getDouble(key: String): Double? | Both | Retrieves a 64-bit float, or null if absent. |
getBytes(key: String): ByteArray? | Both | Retrieves a byte array, or null if absent. |
| Method | Description |
|---|
putString(key: String, value: String): Boolean | Stores a UTF-8 string (encoded as bytes). |
getString(key: String): String? | Retrieves a UTF-8 string, or null if absent. |
getString(key: String, default: String): String | Retrieves a string, falling back to default. |
putInt(key: String, value: Int): Boolean | Stores an integer (promoted to Long). |
getInt(key: String): Int? | Retrieves an integer, or null if absent. |
getInt(key: String, default: Int): Int | Retrieves an integer, falling back to default. |
putFloat(key: String, value: Float): Boolean | Stores a float (promoted to Double). |
getFloat(key: String): Float? | Retrieves a float, or null if absent. |
getFloat(key: String, default: Float): Float | Retrieves a float, falling back to default. |
getBool(key: String, default: Boolean): Boolean | Retrieves a boolean, falling back to default. |
getLong(key: String, default: Long): Long | Retrieves a Long, falling back to default. |
getDouble(key: String, default: Double): Double | Retrieves a Double, falling back to default. |
| Method | Description |
|---|
contains(key: String): Boolean | Returns true if the key exists in the store. |
remove(key: String): Boolean | Removes the entry for the given key. Returns true on success. |
count(): Long | Returns the total number of entries in the store. |
allKeys(): List<String> | Returns all keys currently stored. |
trim() | Reclaims unused storage space (equivalent to MMKV trim). |
clear() | Removes all entries from the store. |
close() | Closes the store and releases native resources. |
| Method | Description |
|---|
batchGetLongs(keys: List<String>): Map<String, Long?> | Reads many longs in one round-trip; absent keys map to null. |
batchGetBools(keys: List<String>): Map<String, Boolean?> | Reads many booleans in one round-trip. |
batchGetDoubles(keys: List<String>): Map<String, Double?> | Reads many doubles in one round-trip. |
batchSetLongs(pairs: Map<String, Long>) | Writes many longs in one round-trip. |
batchSetBools(pairs: Map<String, Boolean>) | Writes many booleans in one round-trip. |
batchSetDoubles(pairs: Map<String, Double>) | Writes many doubles in one round-trip. |
StringSet support: getStringSet / putStringSet throw UnsupportedOperationException. The C ABI has no multi-value encoding. Use putString with JSON serialization if you need sets.registerOnSharedPreferenceChangeListener / unregisterOnSharedPreferenceChangeListener throw UnsupportedOperationException. The frozen C++ core has no notification hook.commit() == apply(): Both are synchronous. commit() always returns true. There is no deferred write queue.QuickStoreFactory does not exist: The library does not ship a factory object. Instantiate QuickStore(mmkvId, rootDir) directly.io.github.santimattius:quickstore and io.github.santimattius:quickstore-native at the same version. The main artifact's POM declares quickstore-native as a transitive dependency; publishing only one will cause a resolution failure for consumers.iosMainSurfaced from shared tags and platforms — no rankings paid for.