Thanks to the fact that it can run on multiple platforms, Kotlin is gaining more and more popularity in multiplatform development. Recently we have started a dedicated R&D working group in Netguru in order to establish best practices for multiplatform development and to evaluate how much code reusability we can achieve using this technology. We’ve started to work on an online, multiplayer game project consisting of a Spring backend module along with iOS and Android client apps.
One of the first features we needed to implement was an encrypted storage for auth tokens on both mobile platforms. Quick research proved there is no such multiplatform solution available yet in Kotlin. That's why we've decided to build it on our own. It was also a perfect opportunity to dive more deeply into the Kotlin multiplatform capabilities. We wanted the library to allow us to store data securely on both mobile platforms, and decided to use Binary Prefs and Keystore on Android and iOS Keychain under the hood, and to expose a unified API which could be consumed in the mobile apps’ common codebase. Sounds interesting? Read on to learn more!
If you’d like to use the library, all you need to do is obtain an instance of the Kissme type like this:
val storage = Kissme(name = "my_secure_store")
The name parameter is optional, and you can omit it if you want to use the default storage. The library allows you to store and persist multiple data types: String, Int, Long, Float, Double, and Boolean. For example, if you’d like to store a String value, just call:
storage.putString(key = "someKey", value = "value")
And if you’d like to read the stored value, call:
storage.getString(key = "someKey", defaultValue = "default")
That’s it! All the get() functions will return the defaultValue parameter if the storage doesn't contain a given key.
There are also more functions you can call on the Kissme instance which support operations like data removing, storage clearing, checking if a specific key is stored, and more. You can read more about those functions in the project’s GitHub Readme.
How it works
Under the hood, Kissme allows storing key-value pairs in a platform-specific way. The Android implementation uses the Binary Prefs library which is rapidly fast and lightweight re-implementation of SharedPreferences. Key-value pairs are also stored securely - they are encrypted using XorKeyEncryption and AesValueEncryption under the hood. Moreover, Kissme allows all encryption keys to be generated automatically and stored securely with Keystore. The BinaryPreferences instance is initialized lazily when calling one of the available functions using the application Context, which is provided automatically by ContentProvider. On the iOS side, the library uses the Keychain service API which stores the data in an encrypted database.
The Android and iOS storage implementations are based on different platform-specific services. However, they both implement a common interface which provides a unified API across both platforms that can be used in the Kotlin common module painlessly.
We faced many challenges during the development of the Kotlin Multiplatform Storage library. We’ve spent a lot of hours solving numerous problems. In most cases, the solutions were quite easy, but some weren’t that obvious and we needed community support.
We wanted to create a cross-platform library that uses platform-specific ways to store key-value pairs. Our first goal was to create Android and iOS implementations.
After consultations with an iOS dev, we discovered that UserDefaults isn't the best place to store app secrets and we need to involve one more platform-specific developer who will provide a Keychain implementation.
The first step was to create a common interface. After that, we discovered that it is impossible to implement Keychain fully in Kotlin due to platform limitations. We found out that we have to develop an external library wrapping Keychain API in Objective-C and link it using c-interop tool to the Kotlin iOS module!
Cross-team cooperation was crucial here. Without that, our library wouldn’t follow best practices to store key-value pairs on iOS.
Immature technology, lack of resources and documentation
Kotlin Multiplatform is still in development. Moreover, this technology is quite fresh, so it’s understandable that some documentation is missing and it’s not easy to find a solution for some issues on StackOverflow. Sometimes you have to use an older version of a Gradle plugin because publishing an Android library (AAR) as a part of a multiplatform library has not been implemented in the newest one yet. Sometimes you have to spend a lot of time on project configuration, but it’s worth it! Every new release provides improvements and it’s really fun to play with Kotlin Multiplatform. Moreover, the Kotlin community is great! You can find very helpful people and answers to almost all questions on the Kotlin Slack, so I would definitely encourage you to use it!
Kotlin and Objective-C interoperability
After discovering that it is impossible to fully implement Keychain in Kotlin due to platform limitations, we decided that we will create an Objective-C library and then we will link it to our Multiplatform Storage library. Sounds easy?
Nope. We had a lot of problems with configuring the c-interop tool to work correctly with the Gradle build script. There’s almost no documentation available and we have spent hours getting through the kotlin-native plugin’s source code and consulting with the JetBrains community. But finally, everything worked out well.
We are happy to present to you our first Kotlin multiplatform library! Feel free to use it, contribute, and stay tuned for more.
Are you already interested in multiplatform development in Kotlin? Maybe you’re working on your own Kotlin multiplatform project? Reach out to us, we are happy to discuss it!