diff --git a/gradle.properties b/gradle.properties index cd3542d..c73c0ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ kotlin.native.binary.memoryModel=experimental #POM CONFIGURATION POM_NAME=nimbus -VERSION_NAME=1.0.0-alpha5 +VERSION_NAME=1.0.0-alpha POM_GROUP=br.com.zup.nimbus POM_DESCRIPTION=A framework to help implement Server-Driven UI in your apps natively. POM_INCEPTION_YEAR=2022 diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/Nimbus.kt b/src/commonMain/kotlin/com/zup/nimbus/core/Nimbus.kt index 5ae977d..aeaa86e 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/Nimbus.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/Nimbus.kt @@ -15,7 +15,7 @@ import com.zup.nimbus.core.ui.coreUILibrary /** * The root scope of a nimbus application. Contains important objects like the logger and the httpClient. */ -class Nimbus(config: ServerDrivenConfig): CommonScope( +open class Nimbus(config: ServerDrivenConfig): CommonScope( parent = null, states = (config.states ?: emptyList()) + ServerDrivenState("global", null), ) { diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/ComponentDeserializer.kt b/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/ComponentDeserializer.kt index 0c69a94..4a4224c 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/ComponentDeserializer.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/ComponentDeserializer.kt @@ -1,6 +1,7 @@ package com.zup.nimbus.core.deserialization import com.zup.nimbus.core.log.Logger +import com.zup.nimbus.core.tree.ServerDrivenEvent import com.zup.nimbus.core.tree.ServerDrivenNode class ComponentDeserializer(val logger: Logger, val node: ServerDrivenNode) { @@ -58,8 +59,8 @@ class ComponentDeserializer(val logger: Logger, val node: ServerDrivenNode) { return deserializer.asMap(key) } - fun asAction(key: String): (Any?) -> Unit { - return deserializer.asAction(key) + fun asEvent(key: String): ServerDrivenEvent { + return deserializer.asEvent(key) } fun asStringOrNull(key: String): String? { @@ -90,7 +91,7 @@ class ComponentDeserializer(val logger: Logger, val node: ServerDrivenNode) { return deserializer.asMapOrNull(key) } - fun asActionOrNull(key: String): ((Any?) -> Unit)? { - return deserializer.asActionOrNull(key) + fun asEventOrNull(key: String): ServerDrivenEvent? { + return deserializer.asEventOrNull(key) } } diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/MapDeserializer.kt b/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/MapDeserializer.kt index 3686e3c..aec3c82 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/MapDeserializer.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/deserialization/MapDeserializer.kt @@ -1,5 +1,8 @@ package com.zup.nimbus.core.deserialization +import com.zup.nimbus.core.tree.ServerDrivenEvent +import com.zup.nimbus.core.tree.dynamic.DynamicEvent + class MapDeserializer { private var errors = ArrayList() private var current: Map? = null @@ -72,13 +75,12 @@ class MapDeserializer { return emptyMap() } - private fun getAction(key: String, nullable: Boolean): ((Any?) -> Unit)? { + private fun getEvent(key: String, nullable: Boolean): ServerDrivenEvent? { val value = current?.get(key) if (nullable && value == null) return null - @Suppress("UNCHECKED_CAST") - if (value is Function1<*, *>) return value as (Any?) -> Unit - addTypeError("an array of Actions", key, value) - return { _ -> } + if (value is ServerDrivenEvent) return value + addTypeError("an event, i.e. an array of actions.", key, value) + return DynamicEvent("Unknown") } fun start(map: Map?) { @@ -134,8 +136,8 @@ class MapDeserializer { return getMap(key, false)!! } - fun asAction(key: String): (Any?) -> Unit { - return getAction(key, false)!! + fun asEvent(key: String): ServerDrivenEvent { + return getEvent(key, false)!! } fun asStringOrNull(key: String): String? { @@ -166,7 +168,7 @@ class MapDeserializer { return getMap(key, true) } - fun asActionOrNull(key: String): ((Any?) -> Unit)? { - return getAction(key, true) + fun asEventOrNull(key: String): ServerDrivenEvent? { + return getEvent(key, true) } } diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/network/DefaultHttpClient.kt b/src/commonMain/kotlin/com/zup/nimbus/core/network/DefaultHttpClient.kt index bac67ff..2f5c244 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/network/DefaultHttpClient.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/network/DefaultHttpClient.kt @@ -13,9 +13,7 @@ import io.ktor.http.Headers import io.ktor.http.HttpMethod import kotlin.collections.set -class DefaultHttpClient internal constructor ( - engine: HttpClientEngine? = null, -): com.zup.nimbus.core.network.HttpClient { +class DefaultHttpClient (engine: HttpClientEngine? = null): com.zup.nimbus.core.network.HttpClient { constructor() : this(null) private val client = if (engine == null) HttpClient() else HttpClient(engine) diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/scope/Scope.kt b/src/commonMain/kotlin/com/zup/nimbus/core/scope/Scope.kt index 4c71dd9..fd4ccb0 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/scope/Scope.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/scope/Scope.kt @@ -55,7 +55,7 @@ interface Scope { /** * Returns the closest Scope with the specified type. */ -internal inline fun Scope.closestScopeWithType(): T? { +inline fun Scope.closestScopeWithType(): T? { var current: Scope? = this while (current != null && current !is T) current = current.parent return current?.let { current as T } diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/tree/dynamic/node/IfNode.kt b/src/commonMain/kotlin/com/zup/nimbus/core/tree/dynamic/node/IfNode.kt index 3e7de11..843df75 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/tree/dynamic/node/IfNode.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/tree/dynamic/node/IfNode.kt @@ -3,11 +3,17 @@ package com.zup.nimbus.core.tree.dynamic.node import com.zup.nimbus.core.ServerDrivenState import com.zup.nimbus.core.utils.valueOfKey -// fixme: normally, in UI frameworks, if-else blocks completely remove the other branch from the tree and rebuilds it +// fixme(1): normally, in UI frameworks, if-else blocks completely remove the other branch from the tree and rebuilds it // when the condition changes. This is not being done here. On the positive side, states will never be lost when // switching from true to false. On the other hand, we won't free up the memory for the if-else branch not currently // rendered until the associated RootNode is unmounted. If we decide this to be a feature and not a bug, remove this // commentary. +// +// fixme(2): we shouldn't try to run the components of Else if the condition is true; or the components of Then when +// the condition is false. Example: we may use an operation that expects something not to be null and this can only be +// guaranteed by the if's condition. By initializing both Then and Else no matter the condition value, we'll end up +// processing way more than we need and risk running operations that expect values different than the ones we have. +// PS: fixing the first issue automatically fixes this. /** * IfNode is a polymorphic DynamicNode that chooses only one of its original subtree (json) to render at a time. The * chosen subtree depends on the value of the property "condition". When "condition" is true and "Then" is a child of diff --git a/src/commonMain/kotlin/com/zup/nimbus/core/ui/UILibrary.kt b/src/commonMain/kotlin/com/zup/nimbus/core/ui/UILibrary.kt index eec9bbb..3f7db10 100644 --- a/src/commonMain/kotlin/com/zup/nimbus/core/ui/UILibrary.kt +++ b/src/commonMain/kotlin/com/zup/nimbus/core/ui/UILibrary.kt @@ -29,22 +29,22 @@ open class UILibrary( private val actionObservers = mutableListOf() private val operations = mutableMapOf() - fun addAction(name: String, handler: ActionHandler): UILibrary { + open fun addAction(name: String, handler: ActionHandler): UILibrary { actions[name] = handler return this } - fun addActionInitializer(name: String, handler: ActionInitializationHandler): UILibrary { + open fun addActionInitializer(name: String, handler: ActionInitializationHandler): UILibrary { actionInitializers[name] = handler return this } - fun addActionObserver(observer: ActionHandler): UILibrary { + open fun addActionObserver(observer: ActionHandler): UILibrary { actionObservers.add(observer) return this } - fun addOperation(name: String, handler: OperationHandler): UILibrary { + open fun addOperation(name: String, handler: OperationHandler): UILibrary { operations[name] = handler return this } @@ -65,11 +65,6 @@ open class UILibrary( return operations[name] } - /** - * Merges the given UILibrary into this UILibrary. This alters the current UILibrary. - * - * Attention: remember to override this method when extending this class. - */ open fun merge(other: UILibrary): UILibrary { actions.putAll(other.actions) actionInitializers.putAll(other.actionInitializers)