MMKV‑Kotlin: A Kotlin Multiplatform Key‑Value Storage Library – Design, Usage, Testing and Maven Publishing
This article introduces MMKV‑Kotlin, a Kotlin Multiplatform wrapper for Tencent's high‑performance MMKV key‑value storage, covering its background, simple integration steps, architectural decisions, platform‑specific implementations, comprehensive unit and instrumented testing, and the process of publishing the library to Maven Central.
The Ctrip flight mobile team has been using Kotlin Multiplatform (KMM) since 2021 and needed a cross‑platform KV storage solution; they chose Tencent's open‑source MMKV for its performance, multi‑process support, and cross‑platform consistency.
Simple usage
Add the appropriate dependency in the common source set:
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin:1.0.0")
}For pure Android projects:
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-android:1.0.0")
}Initialize MMKV on each platform. Android requires a Context :
import com.ctrip.flight.mmkv.initialize
fun initializeMMKV(context: Context) {
val rootDir = initialize(context)
Log.d("MMKV Path", rootDir)
}iOS initialization uses a root directory string:
import com.ctrip.flight.mmkv.initialize
fun initializeMMKV(rootDir: String) {
initialize(rootDir)
Log.d("MMKV Path", rootDir)
}Basic read/write operations mirror MMKV’s Java/Objective‑C APIs:
import com.ctrip.flight.mmkv.defaultMMKV
fun demo() {
val kv = defaultMMKV()
kv.set("Boolean", true)
kv.set("Int", Int.MIN_VALUE)
kv.set("String", "Hello from mmkv")
println("Boolean: ${kv.takeBoolean("Boolean")}")
println("Int: ${kv.takeInt("Int")}")
println("String: ${kv.takeString("String")}")
}Architecture design
MMKV core is written in C++ and provides memory‑mapped file storage, protobuf serialization, and multi‑process capabilities. Platform‑specific wrappers expose the core to Java (Android), Objective‑C (iOS), Dart, and JavaScript. MMKV‑Kotlin adds a thin layer that forwards calls to the native MMKV instances while keeping a unified API.
The common MMKV_KMP interface is defined as:
interface MMKV_KMP {
operator fun set(key: String, value: String): Boolean
operator fun set(key: String, value: Boolean): Boolean
fun takeString(key: String, default: String = ""): String
fun takeBoolean(key: String, default: Boolean = false): Boolean
fun close()
// ... other functions and properties
}Android implementation delegates to the Java MMKV API:
class MMKVImpl internal constructor(val platformMMKV: MMKV) : MMKV_KMP {
override fun set(key: String, value: String) = platformMMKV.encode(key, value)
override fun set(key: String, value: Boolean) = platformMMKV.encode(key, value)
override fun takeString(key: String, default: String) = platformMMKV.decodeString(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean) = platformMMKV.decodeBool(key, default)
override fun close() = platformMMKV.close()
// ... other functions
}iOS implementation uses the Objective‑C MMKV API:
class MMKVImpl internal constructor(val platformMMKV: MMKV) : MMKV_KMP {
override fun set(key: String, value: Int) = platformMMKV.setInt32(value, key)
override fun set(key: String, value: Boolean) = platformMMKV.setBool(value, key)
override fun takeString(key: String, default: String) = platformMMKV.getStringForKey(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean) = platformMMKV.getBoolForKey(key, default)
override fun close() = platformMMKV.close()
// ... other functions
}Factory functions created via expect/actual return a MMKV_KMP instance after constructing the platform‑specific MMKV object.
Unit testing
Common tests are written with kotlin-test and cover CRUD operations, default values, and type‑specific behavior. Example test class:
class MMKVKotlinTest {
companion object { const val KEY_NOT_EXIST = "Key_Not_Exist" }
lateinit var mmkv: MMKV_KMP
fun setUp() { mmkv = mmkvWithID("unitTest", cryptKey = "UnitTestCryptKey") }
fun tearDown() { mmkv.clearAll() }
fun testBoolean() {
assertEquals(true, mmkv.set("Boolean", true))
assertEquals(true, mmkv.takeBoolean("Boolean"))
assertEquals(false, mmkv.takeBoolean(KEY_NOT_EXIST))
assertEquals(true, mmkv.takeBoolean(KEY_NOT_EXIST, true))
}
// ... other type tests
}iOS uses @BeforeTest / @AfterTest annotations, while Android requires instrumented tests because the native .so cannot run on the host JVM. Android instrumented test example:
@RunWith(AndroidJUnit4ClassRunner::class)
@SmallTest
class MMKVKotlinTestAndroid {
private lateinit var mmkvTest: MMKVKotlinTest
@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext
()
initialize(context)
mmkvTest = MMKVKotlinTest().apply { setUp() }
}
@After
fun tearDown() { mmkvTest.tearDown() }
@Test
fun testCommon() = with(mmkvTest) { testBoolean() /* ... */ }
// ... IPC and Parcelable tests
}Maven Central publishing
The library is released to Maven Central using the maven-publish and signing plugins. The publishing block configures Javadoc, POM metadata, repository credentials, and GPG signing:
publishing {
publications.withType
{
artifact(javadocJar)
pom { /* pom settings */ }
}
repositories {
maven {
credentials { username = NEXUS_USERNAME; password = NEXUS_PASSWORD }
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
}
}
signing { sign(publishing.publications) }
}Only the release variant is published for Android, while iOS produces three klib artifacts (iosArm64, iosSimulatorArm64, iosX64). The article also notes a pitfall where overriding artifactId does not affect the Android artifact.
Future plans
MMKV‑Kotlin now supports macOS (Intel and Apple‑silicon) via additional Maven coordinates. Support for other Apple platforms (watchOS, tvOS) is under investigation, while Windows and Linux are not planned due to limited Kotlin/Native C++ interop. The Ctrip team will continue to evolve the library alongside MMKV and Kotlin updates.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.