⌘K

Variable

Variable types, declaration, type inference, custom serializers, and runtime rules in the Nativeblocks DSL.

Variables are the only way to carry dynamic values in a frame. You declare one with remember(value). The binding name becomes the variable key, the key you use to read and write the value at runtime. There is no type annotation and no separate string key to maintain.


Types

Every variable resolves to one of six wire types. You never write the type, it is inferred from the value you pass to remember().

TypeUse for
STRINGAny text, URLs, JSON arrays/objects
INTInteger number
LONGLarge integer
DOUBLEDecimal number
FLOATFloat number
BOOLEANtrue / false. Also controls block visibility.

All variable values are stored and passed as strings at runtime. Always convert explicitly inside scripts: Number() / String() (the runtime is JavaScript).


Type inference

remember(value) infers the wire type from the value's native type:

Wire typeKotlinSwiftTypeScript
STRINGStringStringstring
INTIntIntinteger number
LONGLongInt64bigint
DOUBLEDoubleDoublenon-integer number
FLOATFloatFloatuse a serializer
BOOLEANBooleanBoolboolean

A TypeScript number cannot distinguish LONG/FLOAT: an integer becomes INT, a non-integer becomes DOUBLE. Use a bigint for LONG.


Declaration

Declare variables inside the frame lambda, before rootBlock { }. Use by remember(...) and no type annotation.

val title    by remember("default")  // STRING
val count    by remember(0)          // INT
val big      by remember(0L)         // LONG
val ratio    by remember(0.0)        // DOUBLE
val weight   by remember(0.0f)       // FLOAT
val visible  by remember(true)       // BOOLEAN

Custom variable types

remember accepts any value, not just primitives. To store a custom type, write a VariableSerializer: it maps your value to one of the six wire types plus a string. Register it once in the project bootstrap, then pass instances of your type straight to remember(). The CLI resolves them to (wire type, string) at frame-build time, the wire type never carries your custom class.

The bootstrap is a conventional file named Nativeblocks that exposes a setup() method. The CLI instantiates it and calls setup() once before building any frame, so you never register serializers manually inside a frame.

Define the type and its serializer in their own file, then register it in src/<package/path>/Nativeblocks.kt (the root of your base package, the only place the CLI looks).

// model/User.kt - its own file, never inside a frame
data class User(val name: String, val age: Int)

class UserSerializer : VariableSerializer<User> {
    override val type = VariableType.STRING
    override fun serialize(value: User): String = "${value.name} (${value.age})"
}
// src/com/example/app/Nativeblocks.kt - the project bootstrap
package com.example.app

import com.example.app.generated.shared.VariableSerializers
import com.example.app.model.UserSerializer

class Nativeblocks {
    fun setup() {
        VariableSerializers.register(UserSerializer())
    }
}
// any frame - import the type, never declare it in the frame
import com.example.app.model.User

val user by remember(User("Alex", 30))   // resolves to STRING "Alex (30)" at build time

The runner inlines the frame body, so a type declared inside a frame loses its package and won't match the serializer registered against its real package (you get No VariableSerializer registered for X at build time). Always define custom types in their own file and import them.


Rules

  • remember(value) takes a single value and no type annotation. The key is the binding name, never written by hand.
  • Use STRING for JSON arrays and objects, serialize with JSON.stringify() / JSON.parse() inside scripts.
  • BOOLEAN variables can be bound to a block's blockVisibilityKey to control show/hide.
  • Variable keys must be unique within a frame.
  • Never hardcode user-facing text, always use a variable.
  • For any type that is not a built-in primitive, write a VariableSerializer. Never register one manually inside a frame, register it in the Nativeblocks bootstrap.

Runtime conversion examples

Scripts run as JavaScript at runtime. Read a value with the getVariable helper (it produces the getVariable("key") call) and convert it explicitly. See Action for the full script reference.

The script build closure returns the JavaScript body string. getVariable(v) splices in the read expression; v.key gives the variable's key.

// Numeric
script { getVariable, _, _ ->
    """updateVariable("${count.key}", String(Number(${getVariable(count)}) + 1));"""
}

// Boolean toggle
script { getVariable, _, _ ->
    """updateVariable("${flag.key}", String(${getVariable(flag)} !== "true"));"""
}

// JSON list
script { getVariable, _, _ ->
    """
    let list = JSON.parse(${getVariable(items)});
    list.push({ id: "new" });
    updateVariable("${items.key}", JSON.stringify(list));
    """.trimIndent()
}