Skip to content

Commit

Permalink
1.1.0 (#33)
Browse files Browse the repository at this point in the history
Integrations implemented
  • Loading branch information
y9san9 committed Jun 14, 2021
2 parents 494a87f + d4decb0 commit 60f2a84
Show file tree
Hide file tree
Showing 19 changed files with 449 additions and 69 deletions.
21 changes: 3 additions & 18 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
/.idea/

/build/
/buildSrc/build/
/core/build/
/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-bundle/

/.gradle/
/buildSrc/.gradle/
.idea
build
.gradle

/data/
/json/json-files/data/

/local.properties
/deploy.properties
/android-app-provider/build/
140 changes: 95 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,79 @@ fun main() {

Note that the library is written in a way where you may **fully** customize it (add xml format for files/etc, implement java.Serializable support and so on, interfaces are common, so you may still use delegates, commits, mutations on it)

## Integrations
Library integrates with some other libraries providing convenient API for storing androidx `MutableState`. Integrations still require including storage dependency.

<details>
<summary>Expand</summary>

### Androidx MutableState
<details>
<summary>Expand</summary>

```kotlin
object ComposeStorage : ... {
val username by mutableState<String>()
}
...
@Composable
fun UserNameText() {
val username by remember { ComposeStorage.username }
Text (
text = username
)
}
```

</details>

### KVision ObservableValue
<details>
<summary>Expand</summary>

```kotlin
object AppData : KLocalDataStorage() {
val clicks by observableValue { 0 }
}

class App : Application() {
override fun start() {
root(id = "root") {
vPanel {
h1(AppData.clicks) { clicks ->
+ "Clicked $clicks times"
}
button(text = "Click!") {
onClick {
// Changes still handled by storage
clicks.value++
}
}
}
}
}
}

fun main() = startApplication(::App)
```

</details>

### Coroutines MutableStateFlow
<details>
<summary>Expand</summary>

```kotlin
object CoroutinesStorage : ... {
// Use it everywhere you need to save state flow values
val stateFlow by mutableStateFlow<String>()
}
```

</details>

</details>

## Implementation
> When targeting JS, only IR is supported
Expand Down Expand Up @@ -229,8 +302,28 @@ All `kds` packages are located at repository [maven.kotlingang.fun](https://mave
**Dependency**: `fun.kotlingang.kds:json-bundle:$version` <br>
**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
## Integrations implementation

### Androidx Extensions
> MutableState [implementation](extensions/extensions-androidx/src/main/java/fun/kotlingang/kds/compose/mutable_state/StorageMutableState.kt)
**Platforms**: ![android][badge-android] <br>
**Dependency**: `fun.kotlingang.kds:extensions-androidx:$version`

### Coroutines Extensions
> MutableStateFlow [implementation](extensions/extensions-coroutines/src/commonMain/kotlin/fun/kotlingang/kds/coroutines/mutable_state_flow/StorageMutableStateFlow.kt)
**Platform**: ![android][badge-android] ![jvm][badge-jvm] ![js][badge-js] ![nodejs][badge-nodejs]
**Dependency**: `fun.kotlingang.kds:extensions-coroutines:$version`

### KVision Extensions
> ObservableState [implementation](extensions/extensions-kvision/src/main/kotlin/fun/kotlingang/kds/kvision/observable_value/StorageObservableValue.kt)
**Platform**: ![js][badge-js]
**Dependency**: `fun.kotlingang.kds:extensions-kvision:$version`

## Custom
If you want to create your own implementation, take a look at the following dependencies

#### Core
> The core module with delegates and main interfaces
Expand All @@ -244,49 +337,6 @@ There **are** plans for other implementations (bundle, ns-user-default, etc.), b
**Platforms**: Any<br>
**Dependency**: `fun.kotlingang.kds:json:$version`

## Plans
There are a lot of possibilities to customize the library, the main goal, for now, is a stabilization of user API.

**Ideas**: <br>
I think it may be cool to integrate the library with [kvision](https://github.com/rjaros/kvision), `compose`, etc.

### KVision integration example
<details>
<summary>Expand</summary>
<p>

```kotlin
object AppData : KLocalDataStorage() {
val clicks by kvisionState<Int>()
}

class App : Application() {
override fun start() {
root(id = "root") {
vPanel {
h1(AppData.clicks) { clicks ->
+ "Clicked $clicks times"
}
button(text = "Click!") {
onClick {
// Changes still handled by storage
clicks.value++
}
}
}
}
}
}

fun main() = startApplication(::App)
```

</p>
</details>

**Near future**: <br>
I would separate the `json` module (add `refs-proxy` module to proxy references) and `files` (add `content-storage` module to add abstraction over storages that converting data to Map<String, String> and then serializing it to content)

[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat
[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/AppInfo.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object AppInfo {
const val PACKAGE = "fun.kotlingang.kds"
const val VERSION = "1.0.1"
const val VERSION = "1.1.0"
const val NAME = "Kotlin Data Storage"
const val DESCRIPTION = "Multiplatform Coroutine-Based Kotlin Library for storing data via kotlinx.serialization"
}
6 changes: 6 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// Used in library
const val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Version.SERIALIZATION}"
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.COROUTINES}"
const val nodejsExternals = "org.jetbrains.kotlinx:kotlinx-nodejs:${Version.NODEJS_EXTERNALS}"

// Used for integrations
const val composeRuntime = "androidx.compose.runtime:runtime:${Version.COMPOSE}"
const val kvision = "io.kvision:kvision:${Version.KVISION}"

5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/Version.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ object Version {

const val SERIALIZATION = "1.2.1"
const val COROUTINES = "1.5.0"

const val KVISION = "4.8.1"

const val NODEJS_EXTERNALS = "0.0.7"

const val COMPOSE = "1.0.0-beta08"
}
6 changes: 5 additions & 1 deletion buildSrc/src/main/kotlin/fun.kotlingang.deploy/Deploy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ class Deploy : Plugin<Project> {
name.set(entity.name ?: error("shouldn't be null"))
description.set(entity.description ?: error("shouldn't be null"))
}
from(components[entity.componentName])

if(components.asMap.containsKey(entity.componentName))
from(components[entity.componentName])
else
System.err.println("Trying to create component from ${entity.componentName}, but this component not found in project ${project.name}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package `fun`.kotlingang.kds.annotation
@RequiresOptIn (
message = """
This function uses unsafe `KType to value` association, so you can provide
KType associated with Int and value with different type, be careful!
KType associated with one type and value with different type, be careful!
""",
level = RequiresOptIn.Level.WARNING
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface KTypeDataStorage {
public fun putWithKType(key: String, type: KType, value: Any?)

/**
* May be you would use inline [getWithKType]?
* Maybe you would use inline [getWithKType]?
*/
@RawSetterGetter
@UnsafeKType
Expand Down
2 changes: 2 additions & 0 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Extensions
> Modules in this directory contains extensions to use with other frameworks
3 changes: 3 additions & 0 deletions extensions/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
configure<`fun`.kotlingang.deploy.DeployEntity> {
ignore = true
}
30 changes: 30 additions & 0 deletions extensions/extensions-androidx/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id(plugin.androidLibrary)
kotlin(plugin.android)
}

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
}
}

configure<`fun`.kotlingang.deploy.DeployEntity> {
componentName = "release"
}

dependencies {
api(core)
implementation(composeRuntime)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="fun.kotlingang.kds"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package `fun`.kotlingang.kds.compose.mutable_state

import `fun`.kotlingang.kds.annotation.RawSetterGetter
import `fun`.kotlingang.kds.annotation.UnsafeKType
import `fun`.kotlingang.kds.optional.getOrDefault
import `fun`.kotlingang.kds.storage.KTypeDataStorage
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.structuralEqualityPolicy
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.typeOf


public inline fun <reified T> KTypeDataStorage.mutableState (
policy: SnapshotMutationPolicy<T?> = structuralEqualityPolicy(),
): StorageMutableStateProvider<T?> = mutableState(policy) { null }


@OptIn(ExperimentalStdlibApi::class, UnsafeKType::class)
public inline fun <reified T> KTypeDataStorage.mutableState (
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(),
noinline defaultValue: () -> T,
): StorageMutableStateProvider<T> = StorageMutableStateProvider (
storage = this,
type = typeOf<T>(),
defaultValue = defaultValue,
policy = policy
)


@OptIn(UnsafeKType::class)
public class StorageMutableStateProvider<T> @UnsafeKType constructor (
private val storage: KTypeDataStorage,
private val type: KType,
private val defaultValue: () -> T,
private val policy: SnapshotMutationPolicy<T>
) {
private lateinit var name: String
private val state by lazy { StorageMutableState(storage, name, type, defaultValue, policy) }

public operator fun getValue(thisRef: Any?, property: KProperty<*>): StorageMutableState<T> {
name = property.name
return state
}

}

@OptIn(RawSetterGetter::class, UnsafeKType::class)
public class StorageMutableState<T> @UnsafeKType constructor (
private val storage: KTypeDataStorage,
private val key: String,
private val type: KType,
defaultValue: () -> T,
policy: SnapshotMutationPolicy<T>
) : MutableState<T> {
private val state = mutableStateOf (
storage.getWithKType<T>(key, type).getOrDefault(defaultValue),
policy = policy
)

override var value: T
get() = state.value
set(value) {
storage.putWithKType(key, type, value)
state.value = value
}

override fun component1(): T = value

override fun component2(): (T) -> Unit = { value = it }

public operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
public operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
28 changes: 28 additions & 0 deletions extensions/extensions-coroutines/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@file:Suppress("UNUSED_VARIABLE")

plugins {
kotlin(plugin.multiplatform)
}

kotlin {
explicitApi()

jvm()
js(IR) {
nodejs()
browser()
}

sourceSets {
val commonMain by getting {
dependencies {
api(core)
implementation(coroutines)
}
}

all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
}
}
}
Loading

0 comments on commit 60f2a84

Please sign in to comment.