当前位置:网站首页>Ctrip ticket app KMM cross end kV repository mmkv kotlin | open source
Ctrip ticket app KMM cross end kV repository mmkv kotlin | open source
2022-06-26 11:29:00 【JetBrains China】
Author's brief introduction
Yu ang , Senior engineer of Ctrip mobile terminal , Focus on Kotlin Mobile end cross platform field .Kotlin Core members of the Chinese community , Shanghai KUG(Kotlin User Group) Organizer , The book 《Kotlin Programming practice 》 translator , official account :“Kotlin Maintenance workshop ”.
One 、 background
The R & D team of Ctrip ticket mobile terminal started from 2021 Has been practicing on the mobile terminal since Kotlin Multiplatform technology ( See the reference link 1). Due to the present Kotlin Multiplatform Ecology is still in its infancy , Most of the Kotlin Open source libraries are JVM only Of , Therefore, we urgently need some support in the daily development process of our team KMM(Kotlin Multiplatform Mobile) The basic library or framework of .
In the development of native mobile terminals ,Android SDK Provides SharedPreferences,iOS Provides NSUserDefaults be used for KV Storage function , But these two can not meet the demand in the case of high performance requirements . Later, though Google Launched Jetpack Datastore Used for replacement SharedPreferences, But it only supports Android platform .
After a series of evaluations, Ctrip's basic framework team decided to use Tencent's open source library MMKV ( Reference link 2) Used to satisfy Ctrip App Of KV Storage requirements . Compare with SharedPreferences And NSUserDefaults,MMKV Have more powerful performance ; Compare with Jetpack Datastore,MMKV Support multiple platforms at the same time , The consistency of dual end business logic will be better ; Besides ,MMKV Other advantages include : Support multi process access 、 When the process is suddenly killed, the storage can still take effect . therefore , Ctrip ticket mobile terminal R & D team decided to base on MMKV Secondary development , send MMKV Support Kotlin Multiplatform Technology stack .
MMKV-Kotlin So it came into being , It has a very convenient way of integration , And MMKV Highly similar API And so on . For having MMKV For the original mobile end developers with experience , The cost of learning transfer is very low . After more than half a year of on-line experiments have proved its stability and functional integrity , Ctrip ticket R & D team decided to open source it , by Kotlin Multiplatform Open source ecological building blocks .MMKV-Kotlin Github See the reference link for the address 3.
Two 、 Easy to use
Let's give you a brief introduction MMKV-Kotlin Usage of , It is convenient for readers to have a more intuitive understanding , It is also convenient to discuss its internal design later .
2.1 Installation and import
about KMM developer , stay common source set Import MMKV-Kotlin, stay Gradle Script (kts) Add :
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin:1.0.0")
}If you are using Kotlin Writing pure Android Users of the program , The import method is in Gradle Script (kts) Add :
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-android:1.0.0")
}For pure Android For developers , Although there is no cross platform requirement , but MMKV-Kotlin Of API Targeted Kotlin Grammar optimization .
Be careful , By the time the article was published ,MMKV-Kotlin The latest version of is 1.2.0, be based on Kotlin 1.7.0,MMKV 1.2.13.
2.2 initialization
MMKV Initialization is required before use , because MMKV-Android Strong dependence on Context type , therefore MMKV-Kotlin The initialization API There are differences at both ends , Need to be in Android And iOS The main works or KMM Platform related source set Respectively initialize :
Android:
import com.ctrip.flight.mmkv.initialize
// In Android source set
fun initializeMMKV(context: Context) {
val rootDir = initialize(context)
Log.d("MMKV Path", rootDir)
}iOS:
import com.ctrip.flight.mmkv.initialize
// In iOS source set
fun initializeMMKV(rootDir: String) {
initialize(rootDir)
Log.d("MMKV Path", rootDir)
}2.3 Simple read and write operations
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")}")
}Usage and MMKV Of Java And Objective-C API Highly similar .
3、 ... and 、 Architecture design
MMKV core use C++ To write , Most of its functions are core Realization . for example mmap Memory provided - File mapping 、 The data is based on protobuf Protocol serialization and deserialization 、 Multi process implementation, etc .core Direct external exposure C++ API, stay Win32、POSIX And other systems can be directly used by developers . stay core The outer layer of MMKV Provides multi language packaging , Used to support multiple technology stacks . for example :Java(Android)、Objective-C(iOS/macOS)、Dart(Flutter)、 JavaScript(React-Native, Non Tencent development and maintenance ).
MMKV-Kotlin At the bottom, you need to rely on and call MMKV, Hope to expose and MMKV Allied API And do some encapsulation that conforms to the language characteristics .
MMKV-Kotlin Need to be relevant on both platforms source set Separate integration MMKV. stay Android source set in , If directly integrated MMKV core It needs to be written manually JNI To do it JVM Layer and C++ Interaction , The input-output ratio is too small , So we choose to go directly to Gradle The script passes Maven rely on MMKV-Android, stay Android source set Directly call its Java API. And in the iOS source set in , because Kotlin At present only with C and Objective-C Have relatively complete interoperability , Therefore, it directly relies on the provision of C++ API Of MMKV core Nor is it appropriate , We choose to Gradle The script passes CocoaPods rely on MMKV-iOS, stay iOS source set Through its Objective-C API Finish right MMKV Call to .
MMKV-Kotlin See the following figure for the overall design of the :

Four 、 Implementation profile
stay 《 Ctrip ticket App KMM Cross end production practice 》( Reference link 1) Penniless 2.2 In the section, we used to MMKV As demo To introduce KMM Of expect-actual technology . But this open source version is for the robustness and practicality of the code , The specific implementation method is adjusted , This section will discuss in detail .
4.1 Initialization function
2.2 Section demonstrates MMKV-Kotlin The initialization , So its initialization function is in Android、iOS Two source set Defined and implemented respectively in .
Have a look first Android:
import android.content.Context
import com.tencent.mmkv.MMKV
fun initialize(context: Context): String = MMKV.initialize(context)
fun initialize(context: Context, rootDir: String): String = MMKV.initialize(context, rootDir)
fun initialize(context: Context, loader: MMKV.LibLoader): String = MMKV.initialize(context, loader)
fun initialize(context: Context, logLevel: MMKVLogLevel): String = MMKV.initialize(context, logLevel.rawValue)
fun initialize(context: Context, rootDir: String, loader: MMKV.LibLoader): String = MMKV.initialize(context, rootDir, loader)
fun initialize(context: Context, rootDir: String, logLevel: MMKVLogLevel): String = MMKV.initialize(context, rootDir, logLevel.rawValue)
fun initialize(context: Context, loader: MMKV.LibLoader, logLevel: MMKVLogLevel): String = MMKV.initialize(context, loader, logLevel.rawValue)
fun initialize(context: Context, rootDir: String, loader: MMKV.LibLoader, logLevel: MMKVLogLevel): String = MMKV.initialize(context, rootDir, loader, logLevel.rawValue) The implementation of the initialization function simply calls MMKV Java API Medium initialize function .Android The initialization of the platform is strongly dependent Context type , It also provides LibLoader Type as parameter , Used to load... During initialization so library . We hope to satisfy as much as possible Android Various requirements of the platform , So it will MMKV-Android Initialization in API All exposed .
I want to see others iOS:
import cocoapods.MMKV.MMKV
fun initialize() = MMKV.initialize()
fun initialize(rootDir: String): String = MMKV.initializeMMKV(rootDir)
fun initialize(rootDir: String, logLevel: MMKVLogLevel): String = MMKV.initializeMMKV(rootDir, logLevel.rawValue)
fun initialize(rootDir: String, groupDir: String, logLevel: MMKVLogLevel): String = MMKV.initializeMMKV(rootDir, groupDir, logLevel.rawValue) by comparison iOS Less platforms Context The type and LibLoader type , Therefore, the overload of initialization function is much less .
4.2 MMKV type
stay MMKV Of Java And Objective-C In the version ,MMKV The type is specific CRUD Function implementation class . stay Java In the version , Write a function as a series of encode Overloaded functions or uniformly named putXXX, among putXXX Internal call encode function , They just have different return types , The read function is uniformly named decodeXXX or getXXX Function of , The two act in unison . and Objective-C In the version , Write functions are uniformly named setXXX function , The read function is uniformly named getXXX function . Although the platforms are different , But the number of arguments to a function that has the same function 、 type , And return types are highly uniform . So this gives us the definition common source set Medium MMKV Types bring convenience .
We need to be in common Layer declaration MMKV type ( To avoid confusion caused by the same name , We will common Layer of MMKV The type is named MMKV_KMP), And the specific implementation in each platform source set in ,MMKV Instances of type need to hold Java or Objective-C Of MMKV Instance of type , And will CURD Operations are delegated to them . Our implementation has two options :
•
MMKV_KMPDeclare as class, adopt expect-actual The mechanism is implemented in the platform related layer .•
MMKV_KMPDeclare as interface, Write its implementation class in the platform related layer .
In the end, we chose option two , The reason lies in : In platform related source set Written in class When you need to instantiate, you need to build Java/Objective-C Of MMKV example , And the best way is to pass in its constructor as a parameter . and Java And Objective-C Of MMKV Are two independent types that have no relationship at all , So we have common source set Unification of China MMKV_KMP The constructor of is very inconvenient . secondly , stay MMKV In the original design ,MMKV The instance itself is not created by the constructor , Instead, it is created through a series of factory methods , So we don't have to common Layer defines its constructor .
After determining the basic design , Let's see. MMKV_KMP The definition of :
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()
// More other functions and properties
}The implementation of dual platforms is as follows ,Android:
import com.tencent.mmkv.MMKV
class MMKVImpl internal constructor(internal val platformMMKV: MMKV) : MMKV_KMP {
override operator fun set(key: String, value: String): Boolean = platformMMKV.encode(key, value)
override operator fun set(key: String, value: Boolean): Boolean = platformMMKV.encode(key, value)
override fun takeString(key: String, default: String): String = platformMMKV.decodeString(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean): Boolean = platformMMKV.decodeBool(key ,default)
override fun close() = platformMMKV.close()
// More other functions and properties
iOS:
import cocoapods.MMKV.MMKV
import platform.Foundation.NSSet
@Suppress("UNCHECKED_CAST")
class MMKVImpl internal constructor(internal val platformMMKV: MMKV) : MMKV_KMP {
override operator fun set(key: String, value: Int): Boolean = platformMMKV.setInt32(value, key)
override operator fun set(key: String, value: Boolean): Boolean = platformMMKV.setBool(value, key)
override fun takeString(key: String, default: String): String = platformMMKV.getStringForKey(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean): Boolean = platformMMKV.getBoolForKey(key, default)
override fun close() = platformMMKV.close()
// More other functions and properties
} Finally, create MMKV_KMP Factory function of type , We just need to go through expect-actual The mechanism can be implemented , The return type of these factory functions is specified as MMKV_KMP, On the platform source set Call in Java And Objective-C The corresponding factory function of , obtain MMKV The instance is constructed by the constructor MMKVImpl Instance and return . The specific code is omitted here , Can be found in Github View in .
4.3 Platform specific API
stay Kotlin/Native in ,Kotlin Basic types and String Some collection types can be mapped to Objective-C The corresponding type in . for example Kotlin Of String It can be done with Objective-C Of NSString Mapping each other , It is considered the same type when writing code . therefore common source set Chinese support CURD The data type of is MMKV-Android And MMKV-iOS Support CURD Intersection of types , Include :
•
Boolean、Int、Long、Float、Double、String、UInt、ULong、ByteArray、Set<String>
One of the things to note is ,Kotlin Of ByteArray Not with Objective-C Of NSData Direct mapping , But the two can be converted by handwritten code , So in iOS Read / write in ByteArray It is also based on such manual conversion , The final reading and writing is NSData. and Set<String> The type is MMKV-Android I've always supported , But in iOS source set Chinese is through reading and writing NSCoding To achieve ,Set<String> Can be directly mapped to NSSet, and NSSet again NSCoding The implementer of the protocol .
besides ,MMKV-Android And MMKV-iOS Some platform specific types are also supported , for example Android Extra support Parcelable Implementer of interface , and iOS Extra support NSCoding The implementer of the protocol and NSDate , These additional types of support are available on the platform source set By extending the function , So as to keep as complete as possible MMKV The original function , And let developers on the platform source set Use them in .
5、 ... and 、 unit testing
Unit testing is an essential part of open source projects , Whereas MMKV-Kotlin Of API And MMKV They are roughly the same , Therefore, the design of unit test also refers to MMKV Unit test .
5.1 API A functional test
Kotlin A set of kotlin-test Unit test framework , Can be in common And iOS source set Use in . And in the Android source set We still use JUnit. Usually we just need to common source set Write a set of unit test code , And platform related source set You don't even need to add any code to complete the construction of unit tests . After running, the framework will run tests against the added platforms . But in MMKV-Kotlin in initialize Functions are implemented on different platforms , So we will take API The core code of the test is placed in common, stay Android/iOS source set initialization MMKV And build tests .
Common The test code of layer is aimed at MMKV-Kotlin API Test of , Refer to the MMKV The design of the , Here is a simple example :
class MMKVKotlinTest {
companion object {
const val KEY_NOT_EXIST = "Key_Not_Exist"
}
lateinit var mmkv: MMKV_KMP
private set
fun setUp() {
mmkv = mmkvWithID("unitTest", cryptKey = "UnitTestCryptKey")
}
fun testDown() {
mmkv.clearAll()
}
fun testBoolean() {
val result = mmkv.set("Boolean", true)
assertEquals(result, true)
val value0 = mmkv.takeBoolean("Boolean")
assertEquals(value0, true)
val value1 = mmkv.takeBoolean(KEY_NOT_EXIST)
assertEquals(value1, false)
val value2 = mmkv.takeBoolean(KEY_NOT_EXIST, true)
assertEquals(value2, true)
}
// Other type test......
}setUp、testDown Responsible for, respectively, MMKV_KMP Object instantiation and cleaning up after testing . Tests for each specific data type are independent of testXXX Within the function , For normal writing and reading 、 Read null value and whether the default value is effective when reading null value .
We are on the platform source set Build specific tests in , And by calling common Layer test code to complete the test ,iOS A simple example of the platform code is as follows :
class MMKVKotlinTestIos {
private lateinit var mmkvTest: MMKVKotlinTest
@BeforeTest
fun setUp() {
initialize()
mmkvTest = MMKVKotlinTest().apply {
setUp()
}
}
@AfterTest
fun setDown() {
mmkvTest.testDown()
}
@Test
fun testCommon() = with(mmkvTest) {
testBoolean()
// Call other test functions
}
// Test NSDate and NSCoding......
} We build tests through annotations , And call common Layer code performs specific tests , Finally, you need to write only iOS platform-supported NSDate And NSCoding Types of tests ( The code is omitted from the above example ), Unit tests are built .
5.2 Android Pile insertion test
MMKV-Kotlin Pure unit testing in Android The platform is not working properly , The reason lies in Android Unit tests for do not support tests that contain native binary code . As mentioned before ,MMKV core yes C++ Compiling , stay Android The construction product of the platform is so library .MMKV-Android Built aar as well as MMKV-Kotlin Built aar All contain this so library . But it's time to so The library is for Android Platform binaries , It is not commonly used by developers Windows or Mac Run on computer . So we need to build an insert pile test (instrumented test) Package our test code into tests APK Running on a real machine , The code of the test class is as follows :
@RunWith(AndroidJUnit4ClassRunner::class)
@SmallTest
class MMKVKotlinTestAndroid {
private lateinit var mmkvTest: MMKVKotlinTest
@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
initialize(context)
mmkvTest = MMKVKotlinTest().apply {
setUp()
}
}
@After
fun setDown() {
mmkvTest.testDown()
}
@Test
fun testCommon() = with(mmkvTest) {
testBoolean()
// Call other test functions
}
// Test Parcelable......
@Test
fun testIPCUpdateInt() { ... }
@Test
fun testIPCLock() { ... }
} The test is built in the same way 5.1 Subsection iOS Is built in the same way . In addition to testing common types and Android Platform specific Parcelable Outside , Also added right Android Platform cross process access test , namely testIPCUpdateInt And testIPCLock function . To improve cross process testing , We also need to define an additional..., which runs in other processes Service( See the reference link for the code 4). The design of cross process access test also fully refers to MMKV, See the reference link 5.
stay Android Studio Click on the “Make Project”( The icon is a small hammer ) Drop down option bar on the right , And then click “Edit Configurations...” Options , In the pop-up window, click... In the upper left corner “+” And then choose “Android Instrumented Test”, You can start configuring the instrumentation test . A screenshot of the configuration is as follows :

Connect the real machine , Then run it .
6、 ... and 、Maven Central Release
Maven Central is Android And Java A key link in the distribution of projects in the technical field , Open source authors should open source their code to Github outside , Usually, the build products of the project are also published to Maven Central, So that users can integrate open source libraries in the most convenient way . Use Gradle Common processes for publishing are as follows :
register sonatype JIRA account number , After logging in, submit a issue It is used to register and publish group id.
Local installation GPG suit Generate key after , Then upload the public key .
stay Gradle The script introduces
maven-publishAndsigningplugin.Write and publish / Signature script , Configure publishing parameters .
perform publish task.
Sign in Nexus repository manager( Reference link 6, Later referred to as" Nexus) Process release request .
After publishing successfully , Users can go to Gradle as well as Maven And other building tools to import your open source library through a line of code .
I believe that this process has Maven Release experience Android And Java It's no stranger to developers . But for the Kotlin Multiplatform For developers , Some details are different , And there are few online materials , Here we will record the pit records .
Kotlin Multiplatform The common release method of a project is to release all the build products uniformly , These include Android Platform aar file ,JVM Platform jar file ,Kotlin/Native The product of construction klib Documents, etc. . For example, once publish after ,Nexus The content directory structure published on is as follows :

We can see the co ownership 5 A catalog , among mmkv-kotlin representative common layer , Usually Multiplatform The project only needs to be completed in common source set Add dependencies to it in , On each platform source set Automatically get dependencies in .
and mmkv-kotlin-android representative Android Products of the platform , Its internal core is aar file , With any pure Android There is no difference in the structure of the library . because Android stay Gradle There is a complete build release system in , therefore Android aar The release of requires manual configuration of the released variants , for example (kts):
kotlin {
android {
publishLibraryVariants("release")
}
// ......
}We have configured publish only release variant , It can also be passed in at the same time "debug" Parameters , take debug Variations are released together .
The other three are iOS Build products , They correspond to each other :iphone Real machine (iosarm64)、M1 & M2 Chip Mac Upper iOS Simulator (iossimulatorarm64)、Intel Chip Mac Upper iOS Simulator (iosx64). Their core is klib file ,klib Is pure Kotlin Special format for cross reference between projects , for example target by iOS Pure of the system Kotlin/Native Projects can be added separately to these iOS klib Dependence , To use MMKV-Kotlin. But considering that Kotlin/Native stay iOS It seems that there are no actual use scenarios and requirements in single platform development , therefore MMKV-Kotlin These are not included in the document klib The dependent code of is listed .
Finally, take a look at Gradle Release the script (kts):
publishing {
publications.withType<MavenPublication> {
artifact(javadocJar)
with(pom) {
// pom setting......
}
}
repositories {
maven {
credentials {
username = NEXUS_USERNAME
password = NEXUS_PASSWORD
}
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
}
}
signing {
sign(publishing.publications)
}
} In the script, we configured javadoc、pom Information 、 Warehouse information ( user name 、 password 、 Uploaded address ) And signature . Above kts Code added to gradle.build.kts After the document ,sync project , And then run publish Gradle task, You can complete the release .
Finally, there is a pit to be noted , If you don't want to use your project name as artifact id, Can be in publications.withType<MavenPublication> { ... } Configure and overwrite , Just give artifactId Property can be re assigned . But at present, it is actually measured , When this attribute is overridden, only multiplatform And iOS Of artifact id It will change , Yes Android Invalid (Gradle 7.2,Kotlin 1.6.10、1.6.21),Android Will always use the project name as artifact id. This pit needs special attention , avoid Android Of artifact id It is different from other platforms .
7、 ... and 、 Summary and future plans
MMKV-Kotlin Take advantage of Kotlin In each native platform, it can communicate with “ Indigenous languages ”(Java、C、Objective-C, And Swift The interaction of is under development ) The nature of direct interaction , Will originally support running on multiple platforms MMKV Transplanted to Kotlin Multiplatform Technology stack . To make the original MMKV Users have less migration learning costs ,MMKV-Kotlin Of API And MMKV Maintain a high degree of consistency , However, from the perspective of avoiding duplicate names and other factors , part API Some changes have been made to the name of . for example :MMKV To MMKV_KMP, encode To set wait .MMKV-Kotlin And try to keep it as complete as possible MMKV Platform specific features , It's convenient Kotlin Multiplatform Developers in platform related source set Use in . Besides ,MMKV-Kotlin Also designed with MMKV Similar unit tests , Covering most of the core API, And in Android A pile test is designed on the platform to test the correctness of multi process access .
Kotlin Multiplaform And MMKV Not only support Android/iOS Two platforms . At first ,MMKV-Kotlin Only support Android And iOS Two mobile end platforms , But in 1.1.1 The version has added a pair of macOS( Include Intel And M1&M2 Chip architecture ) Support for . Import in Kotlin/Native engineering Gradle Script (kts) Add :
dependencies {
// Intel chip
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-macosx64:1.1.1")
// M1&M2 chip
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-macosarm64:1.1.1")
}If your Kotln/Native Engineering is an executable program , Remember in CocoaPods Add a pair of MMKV Dependence , And add a pair of MMKV And MMKVCore Of link To configure , For details, see MMKV-Kotlin Of README( Reference link 7).
because macOS Version of MMKV Also through Objective-C expose API, And it can also be done through CocoaPods Integrate , So add macOS Your support is only needed in Gradle Add the corresponding... In the build script source set that will do , It's not difficult to achieve . other Apple operating system ( watchOS、tvOS)MMKV Not directly supported yet , therefore MMKV-Kotlin Their support is still being demonstrated , If possible , In the future, all Apple The platform is included in the support plan .
because Win32、Linux Equal platform MMKV adopt C++ expose API, Whereas Kotlin/Native And C++ The interoperability of is not perfect , as well as JetBrains Official future to C++ Negative attitude towards interoperability development ( Has been removed Kotlin roadmap), And now Kotlin Developers' development needs for these two platforms are not so urgent , Therefore, it is not considered to be included in the support plan for the time being .
because MMKV And Kotlin Will update the version from time to time , therefore MMKV-Kotlin Will follow both iterations . if MMKV or Kotlin It's upgraded ,MMKV-Kotlin Follow up and upgrade will be carried out in the future , Please ensure that MMKV-Kotlin Rely on the MMKV or Kotlin The version is compatible with the version you are using .
In the follow-up, the R & D team of Ctrip ticket mobile terminal will also continue to plough deeply Kotlin Multiplatform technical field , Bring more dry goods and contributions to the entire technology community .
8、 ... and 、 Reference link
Ctrip ticket App KMM Cross end production practice
https://mp.weixin.qq.com/s/gQNPO5iNFH1OQ-ygqjNfTA
MMKV
https://github.com/Tencent/MMKV
MMKV-Kotlin
https://github.com/ctripcorp/mmkv-kotlin
MMKV-Kotlin Cross process testing Service
https://github.com/ctripcorp/mmkv-kotlin/blob/main/mmkv-kotlin/src/androidTest/kotlin/com/ctrip/flight/mmkv/MMKVTestService.kt
MMKV Cross process testing Service
https://github.com/Tencent/MMKV/blob/master/Android/MMKV/mmkv/src/androidTest/java/com/tencent/mmkv/MMKVTestService.java
Nexus repository manager
https://oss.sonatype.org/
MMKV-Kotlin Chinese for README
https://github.com/ctripcorp/mmkvkotlin/blob/main/README_CN.md
More cases


This article is from WeChat official account. - JetBrains(JetBrainsChina).
If there is any infringement , Please contact the [email protected] Delete .
Participation of this paper “OSC Source creation plan ”, You are welcome to join us , share .
边栏推荐
- Black squares in word
- 机器学习线性回归——实验报告
- openresty 概述
- leetcode 715. Range 模块 (hard)
- Random numbers in leetcode 710 blacklist [random numbers] the leetcode path of heroding
- word中涂黑的方块
- How to calculate flops and params in deep learning
- Laravel admin hidden button, and set button display, default sequence, form form form non modifiable value
- 02 linked list of redis data structure
- Please advise tonghuashun which securities firm to choose for opening an account? Is it safe to open a mobile account?
猜你喜欢
女性科学家的流失

利用 Repository 中的方法解决实际问题

Sqli-labs靶场1-5

Excel operation of manual moving average method and exponential smoothing method for time series prediction

【北邮果园微处理器设计】10 Serial Communication 串口通信笔记

介绍一下实现建模中可能用到的时间序列预测之线性二次移动平均,Excel的简单操作

FastRCNN

9、 Beautify tables, forms, and hyperlinks

HUST network attack and defense practice | 6_ IOT device firmware security experiment | Experiment 3 freertos-mpu protection bypass

MOS管基本原理,单片机重要知识点
随机推荐
Laravel admin uses native JS to realize sound prompt and automatic playback
Build document editor based on slate
Compréhension approfondie de l'expérience de port série stm32 (registre) [Tutoriel de niveau nounou]
我想知道,十大劵商如何开户?在线开户安全么?
laravel-admin 非自增ID获取, 及提交隐藏表单
TCP面试
[Beiyou orchard microprocessor design] 10 serial communication serial communication notes
Pre knowledge of hash table -- binary search tree
开通证券账户需要注意事项 开户安全吗
.net中,日志组件 Nlog,SerialLog, Log4Net的用法
Laravel writes native SQL statements
flannel的host-gw与calico
我想知道同花顺是炒股的么?在线开户安全么?
3、 Linked list exercise
【Redis 系列】redis 学习十六,redis 字典(map) 及其核心编码结构
【Redis 系列】redis 学习十六,redis 字典(map) 及其核心编码结构
Using the methods in the repository to solve practical problems
I want to know how the top ten securities firms open accounts? Is online account opening safe?
Machine learning SVM - Experimental Report
统计遗传学:第一章,基因组基础概念