Skip to content

kotlin_multiplatform

guoling edited this page Jul 30, 2026 · 1 revision

license PRs Welcome Release Version Platform

MMKV for Kotlin Multiplatform

MMKV provides experimental Kotlin Multiplatform support for Android and iOS starting from v2.4.1. The KMP API and published artifact layout may change in a future release.

The KMP wrapper exposes the same MMKV files and native implementation as the platform SDKs while allowing storage calls to live in shared Kotlin code.

Supported Targets

The v2.4.1 package publishes these targets:

  • Android (API level 23 or later, 64-bit ABIs)
  • iosArm64 (iOS 13.0 or later)
  • iosSimulatorArm64 (iOS 14.0 or later)
  • iosX64 (iOS 13.0 or later)

Android projects must have AndroidX enabled:

android.useAndroidX=true

Installation

Make sure Maven Central is available to dependency resolution:

dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}

Add the root KMP package to the shared module:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.tencent:mmkv-kmp:2.4.1")
        }
    }
}

Depend only on com.tencent:mmkv-kmp:2.4.1. Gradle metadata selects the correct target artifact automatically:

Target Selected artifact
Common metadata com.tencent:mmkv-kmp:2.4.1
Android com.tencent:mmkv-kmp-android:2.4.1
iOS device com.tencent:mmkv-kmp-iosarm64:2.4.1
Apple Silicon iOS simulator com.tencent:mmkv-kmp-iossimulatorarm64:2.4.1
Intel iOS simulator com.tencent:mmkv-kmp-iosx64:2.4.1

Do not add the target-specific artifacts directly.

Initialization

Initialization is platform-specific and must happen before any shared MMKV code runs. Import com.tencent.mmkv.kmp.initialize in the platform source set.

Android

Initialize MMKV from your Application or another startup entry point:

import android.app.Application
import com.tencent.mmkv.kmp.MMKV
import com.tencent.mmkv.kmp.initialize

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        val rootDir = MMKV.initialize(this)
        println("MMKV root: $rootDir")
    }
}

Register the Application class in AndroidManifest.xml when your project does not already have one:

<application
    android:name=".App"
    ... />

To use a custom root directory:

MMKV.initialize(applicationContext, rootDir = customRootDir)

The overload also accepts logLevel and an MMKVHandler.

iOS

Initialize MMKV on the main thread from the iOS startup path:

import com.tencent.mmkv.kmp.MMKV
import com.tencent.mmkv.kmp.initialize

fun initializeMMKV() {
    val rootDir = MMKV.initialize()
    println("MMKV root: $rootDir")
}

The default root is the app's Documents directory under mmkv. A custom root can be supplied with MMKV.initialize(rootDir = customRootDir).

For multi-process access between an app and its extensions, enable the same App Group entitlement on every target and initialize with its container:

import platform.Foundation.NSFileManager

val groupDir = NSFileManager.defaultManager
    .containerURLForSecurityApplicationGroupIdentifier("group.example.app")
    ?.path

checkNotNull(groupDir)
MMKV.initialize(groupDir = groupDir)

Every participating process must initialize MMKV with the same App Group container.

Basic Usage

After platform initialization, MMKV operations can live in commonMain:

import com.tencent.mmkv.kmp.MMKV

val kv = MMKV.defaultMMKV()

kv.encodeBool("bool", true)
val boolValue = kv.decodeBool("bool")

kv.encodeInt("int", Int.MIN_VALUE)
val intValue = kv.decodeInt("int")

kv.encodeLong("long", Long.MAX_VALUE)
val longValue = kv.decodeLong("long")

kv.encodeDouble("double", 3.14)
val doubleValue = kv.decodeDouble("double")

kv.encodeString("string", "Hello from MMKV KMP")
val stringValue = kv.decodeString("string")

kv.encodeBytes("bytes", "Hello".encodeToByteArray())
val bytesValue = kv.decodeBytes("bytes")

Supported value types are Boolean, Int, Long, Float, Double, String, and ByteArray.

Delete and Query

val kv = MMKV.defaultMMKV()

if (kv.containsKey("string")) {
    kv.removeValueForKey("string")
}

kv.removeValuesForKeys(listOf("int", "long"))
println("keys: ${kv.allKeys}")
println("count: ${kv.count}")

kv.clearAll()

Named and Configured Instances

Use a unique ID to isolate data:

val kv = MMKV.mmkvWithID("account")

Use MMKVConfig for all instance options:

import com.tencent.mmkv.kmp.MMKVConfig
import com.tencent.mmkv.kmp.MMKVMode

val config = MMKVConfig(
    mode = MMKVMode.MULTI_PROCESS,
    cryptKey = "MyEncryptKey",
    expectedCapacity = 8L * 1024,
    enableCompareBeforeSet = true,
)

val kv = MMKV.mmkvWithID("shared-account", config)

Available configuration fields include mode, encryption, AES-256, custom root path, expected capacity, expiration, compare-before-set, recovery strategy, and item-size limit.

Use MMKVMode.READ_ONLY for a read-only instance. Modes are flags and can be combined when needed:

val config = MMKVConfig(
    mode = MMKVMode.MULTI_PROCESS or MMKVMode.READ_ONLY,
)
val readOnly = MMKV.mmkvWithID("shared-account", config)

Encryption

Configure an encrypted instance when creating it:

val encrypted = MMKV.mmkvWithID(
    "secure",
    MMKVConfig(
        cryptKey = "MyEncryptKey",
        aes256 = false,
    ),
)

Use reKey() to add, replace, or remove encryption:

encrypted.reKey("Key_seq_1")
encrypted.reKey("Key_Seq_Very_Looooooooong", aes256 = true)
encrypted.reKey(null)

Store encryption keys separately from the MMKV files. MMKV encryption does not replace platform key-management facilities.

Auto Expiration

Enable a default expiration duration for an instance:

import com.tencent.mmkv.kmp.MMKVExpireDuration

val kv = MMKV.mmkvWithID(
    "cache",
    MMKVConfig(
        enableKeyExpire = true,
        expiredInSeconds = MMKVExpireDuration.InDay,
    ),
)

Override the duration for an individual value:

kv.encodeString("short-lived", "value", MMKVExpireDuration.InHour)
kv.encodeString("persistent", "value", MMKVExpireDuration.Never)

Expiration durations are unsigned seconds. Enabling expiration changes the on-disk format and is not backward-compatible with MMKV v1.2.16 or earlier.

NameSpace and Custom Directories

Use MMKVNameSpace to manage a group of instances under one root:

import com.tencent.mmkv.kmp.MMKVNameSpace

val namespace = MMKVNameSpace.of(customRootDir)
val kv = namespace.mmkvWithID("account")

kv.encodeString("name", "MMKV")

The namespace also provides root-specific backupOneToDirectory(), restoreOneFromDirectory(), isFileValid(), checkExist(), and removeStorage() operations.

Backup and Restore

Back up or restore one instance:

val backedUp = MMKV.backupOneToDirectory(
    mmapID = "account",
    dstDir = backupDirectory,
)

val restored = MMKV.restoreOneFromDirectory(
    mmapID = "account",
    srcDir = backupDirectory,
)

Back up or restore every instance in the default root:

val backupCount = MMKV.backupAllToDirectory(backupDirectory)
val restoreCount = MMKV.restoreAllFromDirectory(backupDirectory)

Do not read or write an instance while restoring its files.

Error Handling and Logs

Register one common handler for recovery decisions, log redirection, content-change notifications, and successful-load notifications:

import com.tencent.mmkv.kmp.MMKVHandler
import com.tencent.mmkv.kmp.MMKVLogLevel
import com.tencent.mmkv.kmp.MMKVRecoverStrategic

val handler = object : MMKVHandler() {
    override fun onMMKVCRCCheckFail(mmapID: String) =
        MMKVRecoverStrategic.OnErrorRecover

    override fun onMMKVFileLengthError(mmapID: String) =
        MMKVRecoverStrategic.OnErrorRecover

    override fun wantLogRedirect() = true

    override fun mmkvLog(
        level: MMKVLogLevel,
        file: String,
        line: Int,
        function: String,
        message: String,
    ) {
        println("MMKV [$level] $message")
    }
}

MMKV.registerHandler(handler)

Call MMKV.unRegisterHandler() when the application no longer wants callbacks.

Lifecycle and close()

close() permanently destroys the native MMKV instance. All references backed by the same native instance become invalid immediately. The caller must ensure that no operation is running and no reference is used afterward. Discard every reference before reopening the same ID.

A second close() on the same KMP wrapper is harmless, and other calls on that closed wrapper fail. This does not make aliases safe: another wrapper backed by the same native instance is invalid after either wrapper closes it. Do not use close() as scoped ownership and do not retain aliases across it.

Most applications can keep MMKV instances for the process lifetime and do not need to call close(). MMKV.onExit() is also optional.

Native Packaging

The Android target delegates to the native com.tencent:mmkv:2.4.1 AAR.

The iOS KLIBs embed MMKV Core through the C bridge. KMP consumers do not need CocoaPods, Swift Package Manager, or a source build for MMKV Core. Do not also link the native MMKV CocoaPod or SwiftPM product into the same iOS binary, because both contain MMKV Core and can produce duplicate native symbols.

Troubleshooting

  • Dependency cannot be resolved: Confirm that mavenCentral() is available and that only com.tencent:mmkv-kmp:2.4.1 is declared.
  • AndroidX classes cannot be resolved: Add android.useAndroidX=true to gradle.properties.
  • MMKV reports that it is not initialized: Run the platform-specific initializer before shared code creates an instance.
  • The iOS simulator target cannot link: Declare the target matching the host Mac: iosSimulatorArm64 on Apple Silicon or iosX64 on Intel.
  • Duplicate MMKV symbols on iOS: Remove the separate native MMKV CocoaPods or SwiftPM dependency from that binary.
  • An iOS extension cannot see the app's data: Configure matching App Group entitlements, pass the shared group container to initialization in every process, and open the instance in multi-process mode.

Sample and Source

Change Log

See the v2.4.1 change log for release details.

Clone this wiki locally