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

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Making the Big Kotlin Multiplatform Decision

Why is adopting Kotlin Multiplatform Mobile not an easy decision to make? After all, it can potentially save a business millions of dollars by cutting down duplicate iOS and Android code and saving many developer…
Watch Video

Making the Big Kotlin Multiplatform Decision

Sumayyah Ahmed
Android Tech Lead
Square

Making the Big Kotlin Multiplatform Decision

Sumayyah Ahmed
Android Tech Lead
Square

Making the Big Kotlin Multiplatform Decision

Sumayyah Ahmed
Android Tech Lead
Square

Jobs

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
Menu