diff --git a/.gitignore b/.gitignore index f71b497..ed84d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,12 @@ /files/build/ /local-storage/build/ /shared-preferences/build/ +/bundle/build/ /json/build/ /json/json-files/build/ /json/json-local-storage/build/ -/json/json-shared-preferences/build +/json/json-shared-preferences/build/ +/json/json-bundle/ /.gradle/ /buildSrc/.gradle/ diff --git a/README.md b/README.md index 9447235..1e115d5 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,32 @@ fun main() { } ``` +### Android Bundle State +You can also store android app state with the library +```kotlin +class MainActivity : Activity() { + private val state = object : KBundleDataStorage() { + var score by int { 0 } // This will be automatically saved and restored + } + + override fun onCreate(bundle: Bundle?) = state.fillState(bundle) { + super.onCreate(bundle) + } + // OR + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + state.restoreInstanceState(bundle) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + state.saveInstanceState(outState) + } +} +``` +`property` are also allowed there with serialization to string followed by `bundle.putString` + + ### Mutate Example There is also an API to use mutable objects ```kotlin @@ -60,15 +86,15 @@ object MainStorage : ... { } // Launches an asynchronous commit after block() -fun addItem() = MainStorage.mutate { +fun editItem() = MainStorage.mutate { item.foo = ... } // Suspends until commit -suspend fun addItem() = MainStorage.mutateCommit { +suspend fun editItem() = MainStorage.mutateCommit { item.foo = ... } // Blocking mutation -fun addItem() = MainStorage.mutateBlocking { +fun editItem() = MainStorage.mutateBlocking { item.foo = ... } @@ -127,7 +153,14 @@ All `kds` packages are located at repository [maven.kotlingang.fun](https://mave **Platforms**: ![android][badge-android]
**Dependency**: `fun.kotlingang.kds:json-shared-preferences:$version`
-**Example**: Github [repo](https://github.com/kotlingang/kds-android-example) +**Example**: GitHub [repo](https://github.com/kotlingang/kds-android-example) + +### KBundleDataStorage +> KDataStorage sync [implementation](json/json-bundle) with android `Bundle` + +**Platforms**: ![android][badge-android]
+**Dependency**: `fun.kotlingang.kds:json-bundle:$version`
+**Example**: GitHub [repo](https://github.com/kotlingang/kds-android-example) ### Custom There **are** plans for other implementations (bundle, ns-user-default, etc.), but if you want to create your implementation, take a look at the following dependencies diff --git a/buildSrc/src/main/kotlin/AppInfo.kt b/buildSrc/src/main/kotlin/AppInfo.kt index 1d80030..dfc4e5f 100644 --- a/buildSrc/src/main/kotlin/AppInfo.kt +++ b/buildSrc/src/main/kotlin/AppInfo.kt @@ -1,6 +1,6 @@ object AppInfo { const val PACKAGE = "fun.kotlingang.kds" - const val VERSION = "5.3.0" + const val VERSION = "1.0.0" const val NAME = "Kotlin Data Storage" const val DESCRIPTION = "Multiplatform Coroutine-Based Kotlin Library for storing data via kotlinx.serialization" } diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt index 6cba355..4956f38 100644 --- a/buildSrc/src/main/kotlin/Modules.kt +++ b/buildSrc/src/main/kotlin/Modules.kt @@ -14,3 +14,4 @@ val DependencyHandler.json get() = project(":json") val DependencyHandler.core get() = project(":core") val DependencyHandler.`shared-preferences` get() = project(":shared-preferences") val DependencyHandler.`android-app-provider` get() = project(":android-app-provider") +val DependencyHandler.bundle get() = project(":bundle") diff --git a/bundle/build.gradle.kts b/bundle/build.gradle.kts new file mode 100644 index 0000000..9569f9c --- /dev/null +++ b/bundle/build.gradle.kts @@ -0,0 +1,31 @@ +import `fun`.kotlingang.deploy.DeployEntity + +plugins { + id(plugin.androidLibrary) + kotlin(plugin.android) +} + +configure { + componentName = "release" +} + +android { + // Workaround since explicitApi() does not work for android + kotlinOptions.freeCompilerArgs += "-Xexplicit-api=strict" + kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + + compileSdk = Version.COMPILE_SDK + + defaultConfig { + targetSdk = Version.COMPILE_SDK + minSdk = Version.MIN_SDK + } + + buildFeatures { + buildConfig = false + } +} + +dependencies { + api(core) +} diff --git a/bundle/src/main/AndroidManifest.xml b/bundle/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1ffd87f --- /dev/null +++ b/bundle/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bundle/src/main/java/fun/kotlingang/kds/InstanceStateManager.kt b/bundle/src/main/java/fun/kotlingang/kds/InstanceStateManager.kt new file mode 100644 index 0000000..afb08fb --- /dev/null +++ b/bundle/src/main/java/fun/kotlingang/kds/InstanceStateManager.kt @@ -0,0 +1,9 @@ +package `fun`.kotlingang.kds + +import android.os.Bundle + + +public interface InstanceStateManager { + public fun restoreInstanceState(bundle: Bundle?) + public fun saveInstanceState(bundle: Bundle) +} diff --git a/bundle/src/main/java/fun/kotlingang/kds/KPrimitiveBundleDataStorage.kt b/bundle/src/main/java/fun/kotlingang/kds/KPrimitiveBundleDataStorage.kt new file mode 100644 index 0000000..5b2dcc4 --- /dev/null +++ b/bundle/src/main/java/fun/kotlingang/kds/KPrimitiveBundleDataStorage.kt @@ -0,0 +1,91 @@ +package `fun`.kotlingang.kds + +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.extensions.bundle.contains +import `fun`.kotlingang.kds.storage.PrimitiveDataStorage +import android.os.Bundle + + +public open class KPrimitiveBundleDataStorage : PrimitiveDataStorage, InstanceStateManager { + private val bundle = Bundle() + + override fun restoreInstanceState(bundle: Bundle?) { + if(bundle != null) + this.bundle.putAll(bundle) + } + override fun saveInstanceState(bundle: Bundle) { + bundle.putAll(this.bundle) + } + + @RawSetterGetter + final override fun putString(key: String, value: String) { + bundle.putString(key, value) + } + + @RawSetterGetter + final override fun putInt(key: String, value: Int) { + bundle.putInt(key, value) + } + + @RawSetterGetter + final override fun putLong(key: String, value: Long) { + bundle.putLong(key, value) + } + + @RawSetterGetter + final override fun putFloat(key: String, value: Float) { + bundle.putFloat(key, value) + } + + @RawSetterGetter + final override fun putDouble(key: String, value: Double) { + bundle.putDouble(key, value) + } + + @RawSetterGetter + final override fun putBoolean(key: String, value: Boolean) { + bundle.putBoolean(key, value) + } + + @RawSetterGetter + final override fun getString(key: String): String? = + if(key in bundle) + bundle.getString(key) + else + null + + @RawSetterGetter + final override fun getInt(key: String): Int? = + if(key in bundle) + bundle.getInt(key) + else + null + + @RawSetterGetter + final override fun getLong(key: String): Long? = + if(key in bundle) + bundle.getLong(key) + else + null + + @RawSetterGetter + final override fun getFloat(key: String): Float? = + if(key in bundle) + bundle.getFloat(key) + else + null + + @RawSetterGetter + final override fun getDouble(key: String): Double? = + if(key in bundle) + bundle.getDouble(key) + else + null + + @RawSetterGetter + final override fun getBoolean(key: String): Boolean? = + if(key in bundle) + bundle.getBoolean(key) + else + null +} diff --git a/bundle/src/main/java/fun/kotlingang/kds/extensions/bundle/in.kt b/bundle/src/main/java/fun/kotlingang/kds/extensions/bundle/in.kt new file mode 100644 index 0000000..3987e13 --- /dev/null +++ b/bundle/src/main/java/fun/kotlingang/kds/extensions/bundle/in.kt @@ -0,0 +1,6 @@ +package `fun`.kotlingang.kds.extensions.bundle + +import android.os.Bundle + + +internal operator fun Bundle.contains(key: String) = containsKey(key) diff --git a/bundle/src/main/java/fun/kotlingang/kds/extensions/instance_state_manager/fillState.kt b/bundle/src/main/java/fun/kotlingang/kds/extensions/instance_state_manager/fillState.kt new file mode 100644 index 0000000..babfba8 --- /dev/null +++ b/bundle/src/main/java/fun/kotlingang/kds/extensions/instance_state_manager/fillState.kt @@ -0,0 +1,10 @@ +package `fun`.kotlingang.kds.extensions.instance_state_manager + +import `fun`.kotlingang.kds.InstanceStateManager +import android.os.Bundle + + +public inline fun InstanceStateManager.fillState(bundle: Bundle?, block: () -> Unit) { + restoreInstanceState(bundle) + block() +} diff --git a/json/json-bundle/build.gradle.kts b/json/json-bundle/build.gradle.kts new file mode 100644 index 0000000..1d13296 --- /dev/null +++ b/json/json-bundle/build.gradle.kts @@ -0,0 +1,32 @@ +import `fun`.kotlingang.deploy.DeployEntity + +plugins { + id(plugin.androidLibrary) + kotlin(plugin.android) +} + +configure { + componentName = "release" +} + +android { + // Workaround since explicitApi() does not work for android + kotlinOptions.freeCompilerArgs += "-Xexplicit-api=strict" + kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + + compileSdk = Version.COMPILE_SDK + + defaultConfig { + targetSdk = Version.COMPILE_SDK + minSdk = Version.MIN_SDK + } + + buildFeatures { + buildConfig = false + } +} + +dependencies { + api(json) + api(bundle) +} diff --git a/json/json-bundle/src/main/AndroidManifest.xml b/json/json-bundle/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1ffd87f --- /dev/null +++ b/json/json-bundle/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/json/json-bundle/src/main/java/fun/kotlingang/kds/KBundleDataStorage.kt b/json/json-bundle/src/main/java/fun/kotlingang/kds/KBundleDataStorage.kt new file mode 100644 index 0000000..5e28765 --- /dev/null +++ b/json/json-bundle/src/main/java/fun/kotlingang/kds/KBundleDataStorage.kt @@ -0,0 +1,95 @@ +package `fun`.kotlingang.kds + +import `fun`.kotlingang.kds.annotation.DelicateKDSApi +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.storage.PrimitiveDataStorage +import android.os.Bundle +import kotlinx.serialization.json.Json + + +public open class KBundleDataStorage private constructor ( + json: Json, + private val storage: KJsonElementDataStorage +): KJsonDataStorage(json, storage), PrimitiveDataStorage, InstanceStateManager { + public constructor ( + json: Json = Json + ) : this(json, KJsonElementDataStorage(json)) + + final override fun restoreInstanceState(bundle: Bundle?) { + storage.restoreInstanceState(bundle) + } + final override fun saveInstanceState(bundle: Bundle) { + storage.saveInstanceState(bundle) + } + + @RawSetterGetter + final override fun putString(key: String, value: String) { + storage.putString(key, value) + } + + @RawSetterGetter + final override fun putInt(key: String, value: Int) { + storage.putInt(key, value) + } + + @RawSetterGetter + final override fun putLong(key: String, value: Long) { + storage.putLong(key, value) + } + + @RawSetterGetter + final override fun putFloat(key: String, value: Float) { + storage.putFloat(key, value) + } + + @RawSetterGetter + final override fun putDouble(key: String, value: Double) { + storage.putDouble(key, value) + } + + @RawSetterGetter + final override fun putBoolean(key: String, value: Boolean) { + storage.putBoolean(key, value) + } + + @RawSetterGetter + final override fun getString(key: String): String? { + return storage.getString(key) + } + + @RawSetterGetter + final override fun getInt(key: String): Int? { + return storage.getInt(key) + } + + @RawSetterGetter + final override fun getLong(key: String): Long? { + return storage.getLong(key) + } + + @RawSetterGetter + final override fun getFloat(key: String): Float? { + return storage.getFloat(key) + } + + @RawSetterGetter + final override fun getDouble(key: String): Double? { + return storage.getDouble(key) + } + + @RawSetterGetter + final override fun getBoolean(key: String): Boolean? { + return storage.getBoolean(key) + } + + @OptIn(DelicateKDSApi::class, RawSetterGetter::class) + override fun commitBlocking() { + encodeReferences().forEach { (k, v) -> + storage.putJsonElement(k, v) + } + } + + final override fun setupBlocking() { + super.setupBlocking() + } +} diff --git a/json/json-bundle/src/main/java/fun/kotlingang/kds/KJsonElementDataStorage.kt b/json/json-bundle/src/main/java/fun/kotlingang/kds/KJsonElementDataStorage.kt new file mode 100644 index 0000000..169aab9 --- /dev/null +++ b/json/json-bundle/src/main/java/fun/kotlingang/kds/KJsonElementDataStorage.kt @@ -0,0 +1,23 @@ +package `fun`.kotlingang.kds + +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.storage.JsonElementDataStorage +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement + + +internal class KJsonElementDataStorage ( + private val json: Json +) : KPrimitiveBundleDataStorage(), JsonElementDataStorage { + @RawSetterGetter + override fun putJsonElement(key: String, value: JsonElement) { + putString(key, json.encodeToString(value)) + } + + @RawSetterGetter + override fun getJsonElement(key: String): JsonElement? { + return json.decodeFromString(string = getString(key) ?: return null) + } +} diff --git a/json/json-local-storage/src/main/kotlin/fun/kotlingang/kds/KLocalDataStorage.kt b/json/json-local-storage/src/main/kotlin/fun/kotlingang/kds/KLocalDataStorage.kt index e8473f5..48cfd74 100644 --- a/json/json-local-storage/src/main/kotlin/fun/kotlingang/kds/KLocalDataStorage.kt +++ b/json/json-local-storage/src/main/kotlin/fun/kotlingang/kds/KLocalDataStorage.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.json.Json public open class KLocalDataStorage ( json: Json = Json -) : KJsonDataStorage(json, JsonElementLocalDataStorage(json)), CommittableStorage { +) : KJsonDataStorage(json, JsonElementLocalDataStorage(json)) { @OptIn(DelicateKDSApi::class, RawSetterGetter::class) final override fun commitBlocking(): Unit = encodeReferences() .forEach { (k, v) -> KStringLocalDataStorage.putString(k, json.encodeToString(v)) } diff --git a/json/src/commonMain/kotlin/fun/kotlingang/kds/KJsonDataStorage.kt b/json/src/commonMain/kotlin/fun/kotlingang/kds/KJsonDataStorage.kt index ce53ddc..62e3f0a 100644 --- a/json/src/commonMain/kotlin/fun/kotlingang/kds/KJsonDataStorage.kt +++ b/json/src/commonMain/kotlin/fun/kotlingang/kds/KJsonDataStorage.kt @@ -8,6 +8,7 @@ import `fun`.kotlingang.kds.storage.JsonElementDataStorage import `fun`.kotlingang.kds.storage.SerializableDataStorage import `fun`.kotlingang.kds.sync.platformSynchronized import `fun`.kotlingang.kds.optional.Optional +import `fun`.kotlingang.kds.storage.CommittableStorage import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @@ -15,10 +16,10 @@ import kotlinx.serialization.serializer import kotlin.reflect.KType -public open class KJsonDataStorage ( +public abstract class KJsonDataStorage ( final override val json: Json, private val storage: JsonElementDataStorage -) : SerializableDataStorage { +) : SerializableDataStorage, CommittableStorage { /** * Any mutation/iteration/etc should be wrapped with synchronization diff --git a/settings.gradle.kts b/settings.gradle.kts index ffe634c..fdf6781 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,9 +8,11 @@ include ( "files", "local-storage", "shared-preferences", + "bundle", "json", "json:json-files", "json:json-local-storage", - "json:json-shared-preferences" + "json:json-shared-preferences", + "json:json-bundle" )