Blog Infos
Author
Published
Topics
, , , ,
Published
Image is taken from the stock.adobe

 

Along time ago I had an Android app as a pet project. And last year I decided to created the iOS version of the application. The best option for me was to use KMP technology since it allowed me to keep the UI part almost as it was, refactor the modules which contain the business logic, replace JVM dependencies with their analogues in pure Kotlin (or implement expect/actual mechanism) and create the UI part for the iOS.

I stored the key-value data in SharedPreferences and I wanted to find some similar solution but for KMP. Luckily, there’s such a library — multiplatform-settings. One of the biggest pros for me was that it can use default SharedPreferences which I already had in the Android app. So, you shouldn’t overthink data migration. Here’s the way how I implemented it.

In common sourceSet:

expect fun getSettings(context: AppContext?): Settings

In android sourceSet:

actual fun getSettings(context: AppContext?): Settings = SharedPreferencesSettings(
PreferenceManager.getDefaultSharedPreferences(context)
)

So, as you can see, Settings is just a wrapper over SharedPreferences in the Android part.

In the iOS part there’s nothing complicated too — Settings wrap old-good NSUserDefaults.

actual fun getSettings(context: AppContext?): Settings =
NSUserDefaultsSettings(NSUserDefaults.standardUserDefaults)
view raw Settings_ios.kt hosted with ❤ by GitHub

Again, using this library you shouldn’t worry about the data migration, since the app still uses same data sources. But is there any way to store the data in the secure way and still use this library? Sure, and here’s the way how you can do it. In the Android it’s more secure to use EncryptedSharedPreferences than default implementation of SharedPreferences. Since EncryptedSharedPreferences still implements SharedPreferences interface, you can rewrite the Android part of your code in the following way:

actual fun getSettings(context: AppContext?): Settings {
requireNotNull(context)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(KeyScheme.AES256_GCM)
.build()
val encryptedPreferences = EncryptedSharedPreferences.create(
context,
context.packageName + "_encrypted_preferences",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
return SharedPreferencesSettings(encryptedPreferences)
}

In iOS secure data can be stored in the keychain

@OptIn(ExperimentalSettingsImplementation::class)
actual fun getEncryptedSettings(context: AppContext?): Settings =
KeychainSettings("${NSBundle.mainBundle.bundleIdentifier}.AUTH")
view raw Settings_ios.kt hosted with ❤ by GitHub

At the end, I’d also like to share my experience with you in migration from default key-value storage to more secure implementation in each platform. I had some values, which had to be stored in the secure way. Since the library doesn’t provide any migration mechanism out of the box, here’s the way how I solved this issue — I modified the way of retrieving encrypted preferences (Keychain) in the following way:

In common sourceSet nothing was changed

In android sourceSet now code looks like this:

actual fun getNonEncryptedSettings(context: AppContext?): Settings = SharedPreferencesSettings(
PreferenceManager.getDefaultSharedPreferences(context)
)
actual fun getEncryptedSettings(context: AppContext?): Settings {
requireNotNull(context)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(KeyScheme.AES256_GCM)
.build()
val encryptedPreferences = EncryptedSharedPreferences.create(
context,
context.packageName + "_encrypted_preferences",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val oldPreferences = getNonEncryptedSettings(context)
return SharedPreferencesSettings(MigrationPreferences(listOf("key1", "key2"), oldPreferences, encryptedPreferences))
}
class MigrationPreferences(
keysToMigrate: List<String>,
oldPreferences: Settings,
private val encryptedPreferences: SharedPreferences
) : SharedPreferences by encryptedPreferences {
init {
encryptedPreferences.edit {
keysToMigrate.forEach { key ->
val value = oldPreferences.getStringOrNull(key)
value?.let {
putString(key, it)
oldPreferences.remove(key)
}
}
}
}
}

and here’s the iOS part:

actual fun getNonEncryptedSettings(context: AppContext?): Settings =
NSUserDefaultsSettings(NSUserDefaults.standardUserDefaults)
@OptIn(ExperimentalSettingsImplementation::class)
actual fun getEncryptedSettings(context: AppContext?): Settings =
MigrationSettings(
listOf("key1", "key2"),
getNonEncryptedSettings(context),
KeychainSettings("${NSBundle.mainBundle.bundleIdentifier}.AUTH")
)
@OptIn(ExperimentalSettingsImplementation::class)
private class MigrationSettings(
keysToMigrate: List<String>,
oldSettings: Settings,
val keychainSettings: KeychainSettings
) : Settings by keychainSettings {
init {
keysToMigrate.forEach { key ->
val value = oldSettings.getStringOrNull(key)
value?.let {
putString(key, it)
oldSettings.remove(key)
}
}
}
}

Hope, that this article would help you!

Thank you for reading! Feel free to ask questions and leave the feedback in comments or Linkedin.

This article was previously published on proandroiddev.com

OUR VIDEO RECOMMENDATION

,

Kotlin Multiplatform- From “Hello World” to the Real World

By now you’ve surely heard of Kotlin Multiplatform, and maybe tried it out in a demo. Maybe you’ve even integrated some shared code into a production app.
Watch Video

Kotlin Multiplatform- From “Hello World” to the Real World

Russell Wolf
Kotlin Multiplatform
Touchlab

Kotlin Multiplatform- From “Hello World” to the Real World

Russell Wolf
Kotlin Multiplatform ...
Touchlab

Kotlin Multiplatform- From “Hello World” to the Real World

Russell Wolf
Kotlin Multiplatform
Touchlab

Jobs

No results found.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu