Skip to content

makeevrserg/klibs.kstorage

Repository files navigation

version kotlin_version

KStorage

KStorage is a lightweight Kotlin Multiplatform library that provides a unified and type-safe interface for key-value storage across different platforms. It simplifies the process of storing and retrieving data by abstracting the underlying storage mechanisms.

✨ Features

  • Multiplatform Support: Works across Android, iOS, Jvm and other Kotlin-supported platforms.
  • Type-Safe API: Ensures compile-time type checking for stored values.
  • Lightweight: Minimal overhead with a focus on simplicity and performance.
  • Extensible: Easily integrate with various storage backends.

🚀 Installation

Version catalogs

[versions]
klibs-kstorage = "<latest-version>"

[libraries]
klibs-kstorage = { module = "ru.astrainteractive.klibs:kstorage", version.ref = "klibs-kstorage" }

Gradle

implementation("ru.astrainteractive.klibs:kstorage:<version>")
// or version catalogs
implementation(libs.klibs.kstorage)

🛠️ Basic Usage

Blocking API

class SettingsApi(private val intSettingsMap: MutableMap<String, Int>) {
    /**
     * The intSettingsMap["INT_KEY"] may return null
     * But since we have factory = { 0 }, the krate will be non-null!
     */
    val mutableKrate: MutableKrate<Int> = DefaultMutableKrate(
        factory = { 0 },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value -> intSettingsMap["INT_KEY"] = value }
    )

    // Want to cache your value?
    val cachedMutableKrate: CachedKrate<Int> = mutableKrate.asCachedMutableKrate()

    // Want even more and make it StateFlow?
    val stateFlowKrate: StateFlowMutableKrate<Int> = mutableKrate.asStateFlowMutableKrate()

    // Have a nullable krate somewhere, but also somewhere need it non-null?
    val nonNullableMutableKrate: MutableKrate<Int> = DefaultMutableKrate(
        factory = { null },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value ->
            if (value == null) intSettingsMap.remove("INT_KEY")
            else intSettingsMap["INT_KEY"] = value
        }
    ).withDefault { 102 }

    // Don't want to call krate.cachedValue every time? Use delegates!
    val cachedKrateValue by cachedMutableKrate
    val cachedStateFlowKrateValue by stateFlowKrate
}

Suspend API

Want suspend-style api? It's almost the same!

class SuspendSettingsApi(private val intSettingsMap: MutableMap<String, Int>) {
    val suspendMutableKrate: SuspendMutableKrate<Int> = DefaultSuspendMutableKrate(
        factory = { 0 },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value -> intSettingsMap["INT_KEY"] = value }
    )

    val stateFlowSuspendMutableKrate: StateFlowSuspendMutableKrate<Int> = DefaultStateFlowSuspendMutableKrate(
        factory = { 0 },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value -> intSettingsMap["INT_KEY"] = value }
    )

    val nonNullableStateFlowSuspendMutableKrate = DefaultStateFlowSuspendMutableKrate(
        factory = { null },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value ->
            if (value == null) intSettingsMap.remove("INT_KEY")
            else intSettingsMap["INT_KEY"] = value
        }
    ).withDefault { 102 }
    
    val nullableSuspendMutableKrate: SuspendMutableKrate<Int?> = DefaultSuspendMutableKrate(
        factory = { null },
        loader = { intSettingsMap["INT_KEY"] },
        saver = { value ->
            if (value != null) intSettingsMap["INT_KEY"] = value
            else intSettingsMap.remove("INT_KEY")
        }
    )
    val nullableStateFlowSuspendMutableKrate = nullableSuspendMutableKrate.asStateFlowMutableKrate()
    val nullableStateFlowSuspendMutableKrate = nullableSuspendMutableKrate.asStateFlowMutableKrate { 10 }
}

Flow API

Want to use Jetpack DataStore? There's Flow API for your needs!

You can even integrate it with ROOM or on-disk files if you want!

class FlowApi {
    private val key = intPreferencesKey("KEY_2")

    // Create Jetpack DataStore instance
    private val dataStore: DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath {
        val path = FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "$key.preferences_pb"
        val file = path.toFile()
        if (file.exists()) file.delete()
        path
    }

    // Create your krate
    val flowKrate = DefaultFlowMutableKrate(
        factory = { 10 },
        loader = { dataStore.data.map { it[key] } },
        saver = { value ->
            dataStore.edit { preferences ->
                preferences[key] = value
            }
        }
    )

    // Make it StateFlow if you need
    val stateFlowValue = flowKrate.stateFlow(GlobalScope)
}

🧪 Advanced usage

🏷️ Class-based Krate Example

class IntKrate(
    key: String,
    settingsMap: MutableMap<String, Int>
) : MutableKrate<Int?> by DefaultMutableKrate(
    factory = { null },
    loader = { settingsMap[key] },
    saver = { value -> settingsMap[key] = value }
)

🧱 Custom Data Type Example

data class CustomClass(val number: Int)

class CustomClassKrate(
    key: String,
    settingsMap: MutableMap<String, Int>
) : MutableKrate<CustomClass> by DefaultMutableKrate(
    factory = { CustomClass(0) },
    loader = {
        val intValue = settingsMap[key]
        if (intValue != null) CustomClass(intValue)
        else null
    },
    saver = { value -> settings[key] = value.number }
)

🔄 Dynamic Keys

fun krateForUser(userId: String): MutableKrate<Int> {
    return DefaultMutableKrate(
        factory = { 0 },
        loader = { settings[userId] ?: 0 },
        saver = { value -> settings[key] = value }
    )
}

About

Kotlin wrapper for key-value storage libraries

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages