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().
| Type | Use for |
|---|---|
STRING | Any text, URLs, JSON arrays/objects |
INT | Integer number |
LONG | Large integer |
DOUBLE | Decimal number |
FLOAT | Float number |
BOOLEAN | true / 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 type | Kotlin | Swift | TypeScript |
|---|---|---|---|
STRING | String | String | string |
INT | Int | Int | integer number |
LONG | Long | Int64 | bigint |
DOUBLE | Double | Double | non-integer number |
FLOAT | Float | Float | use a serializer |
BOOLEAN | Boolean | Bool | boolean |
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 Xat build time). Always define custom types in their own file andimportthem.
Rules
remember(value)takes a single value and no type annotation. The key is the binding name, never written by hand.- Use
STRINGfor JSON arrays and objects, serialize withJSON.stringify()/JSON.parse()inside scripts. BOOLEANvariables can be bound to a block'sblockVisibilityKeyto 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 theNativeblocksbootstrap.
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()
}