3

Kotlin Multiplatform — The Ultimate Guide to Key-Value Storage

 1 year ago
source link: https://blog.protein.tech/kotlin-multiplatform-the-ultimate-guide-to-key-value-storage-ded5cc17cd42
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Kotlin Multiplatform — The Ultimate Guide to Key-Value Storage

Using Settings library…

0*sgwepiJJS4XrO2V_
Photo by Jan Antonin Kolar on Unsplash

In this article, I will show you the equivalent of SharedPreferences in Android & UserDefaults in iOS to store key-value data using Settings library. Specifically to be able to…

  • #1 — Store/Retrieve primitive data types like String, Int, etc.
  • #2 — Store/Retrieve custom classes
  • #3 — Listen to value changes using Coroutines flows

Let’s get started 🤩

  1. Add Settings library and then sync your project…
// settings.gradle.kts
repositories {
// make sure you have mavenCentral in your repositories
mavenCentral()
}

// build.gradle.kts in shared module
plugins {
// #2 - For custom class serialization
kotlin("plugin.serialization") version "1.8.10"
}

val multiplatformSettings = "1.0.0"
val coroutinesVersion = "1.6.4"
sourceSets {
val commonMain by getting {
dependencies {
// #1 - Basic settings
implementation("com.russhwolf:multiplatform-settings-no-arg:$multiplatformSettings")

// #2 - For custom class serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("com.russhwolf:multiplatform-settings-serialization:$multiplatformSettings")

// #3 - For observing values as flows
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("com.russhwolf:multiplatform-settings-coroutines:$multiplatformSettings")
}
}
}

Note: You can skip implementing #2 and #3 if you don’t need them.

2. Define a KeyValueStorage interface (to increase testability and your project is not coupled to this library)

interface KeyValueStorage {
// #1 - Primitive type
var token: String?

// #2 - Custom type
var loginInfo: LoginInfo?

// #3 - Observer token value changes
val observableToken: Flow<String>

fun cleanStorage()
}

@Serializable
data class LoginInfo(val username: String, val password: Int)

3. Create an enum for Storage keys

Since we need keys for our values to store/retrieve the values, I think it’s better to create an enum for every key instead of using hardcoded strings because it’s less error-prone and you can add some functionality later.

enum class StorageKeys {
TOKEN,
LOGIN_INFO;

val key get() = this.name
}

4. Implement KeyValueStorage (Finally)

class KeyValueStorageImpl : KeyValueStorage {
private val settings: Settings by lazy { Settings() }
private val observableSettings: ObservableSettings by lazy { settings as ObservableSettings }

// #1 - store/retrive primitive type
override var token: String?
get() = settings[StorageKeys.TOKEN.key]
set(value) {
settings[StorageKeys.TOKEN.key] = value
}

// #2 - store/retrive custom types
override var loginInfo: LoginInfo?
get() = settings.decodeValueOrNull(LoginInfo.serializer(), StorageKeys.LOGIN_INFO.key)
set(value) {
if (value != null) {
settings.encodeValue(LoginInfo.serializer(), StorageKeys.LOGIN_INFO.key, value)
} else {
settings.remove(StorageKeys.TOKEN.key)
}
}

// #3 - listen to token value changes
override val observableToken: Flow<String>
get() = observableSettings.getStringFlow(StorageKeys.TOKEN.key, "")

// clean all the stored values
override fun cleanStorage() {
settings.clear()
}
}

I think the code is pretty self-explanatory but here are some important notes and tips…

#1 — To store primitive values you can also use settings.putType() in our case it’s putString(key, value)

#1 — To retrieve primitive nullable values you can also use settings.get<Type>(key) or settings.getTypeOrNull(key) and for non-nullable values use settings.getType(). for ex:settings.getString(key)

#2 — To get non-nullable custom types you can use settings.decodeValue()

That’s it, now you can use KeyValueStorage anywhere — not in the UI-Layer unless you wanna get fired :) — here is a simple usage example…

// You can use DI for injecting keyValueStorage  
val keyValueStorage: KeyValueStorage = KeyValueStorageImpl()

// store new token
keyValueStorage.token = "Token" + (1..10).random()

// get stored value
val token = keyValueStorage.token

// observe token changes
coroutineScope {
keyValueStorage.observableToken.collectLatest {token ->
println("New token $token")
}
}

Important Note: The Settings library is way more powerful then this basics so please check it out to learn more…

That’s it and I hope you loved the blog 💜

See ya :)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK