⌘K

Block & Action

Annotate, register, and provide custom blocks and actions with the Nativeblocks SDK.

Blocks are UI components. Actions are business logic handlers. Both are annotated in your code, compiled into schemas, and registered with the SDK at runtime.


Step 1: Add the Compiler

The compiler reads your annotations and generates JSON schemas that the CLI syncs to Nativeblocks.

Add to your module build.gradle:

build.gradle
dependencies {
    implementation("io.nativeblocks:nativeblocks-android:1.8.1")
    implementation("io.nativeblocks:nativeblocks-compiler-android:1.3.2")
    ksp("io.nativeblocks:nativeblocks-compiler-android:1.3.2")
}

Also add the Gradle plugin to sync schemas via the CLI:

build.gradle
plugins {
    id("io.nativeblocks.nativeblocks-gradle-plugin") version "1.2.1"
}

Step 2: Annotate a Block

@NativeBlock(
    name = "Custom Button",
    keyType = "myOrg/CUSTOM_BUTTON",
    description = "A configurable button block",
    version = 1,
    versionName = "1.0.0"
)
@Composable
fun CustomButton(
    @NativeBlockData(description = "Button label")
    text: String,
    @NativeBlockProp(
        description = "Size variant",
        valuePicker = NativeBlockValuePicker.DROPDOWN,
        valuePickerOptions = [
            NativeBlockValuePickerOption("S", "Small"),
            NativeBlockValuePickerOption("M", "Medium"),
            NativeBlockValuePickerOption("L", "Large"),
        ]
    )
    size: String = "M",
    @NativeBlockSlot(description = "Leading icon")
    onLeadingIcon: @Composable (index: BlockIndex) -> Unit,
    @NativeBlockEvent(description = "On click")
    onClick: () -> Unit,
) {
    // your Composable implementation
}

After building, the compiler generates a provider. Register it after SDK init:

DemoBlockProvider.provideBlocks()

Annotation reference

AnnotationTargetPurpose
@NativeBlockFunction / StructMarks the component as a block with a unique keyType
@NativeBlockDataParameterRuntime variable bound to frame state
@NativeBlockPropParameterStatic property configured via the CLI; supports valuePicker, valuePickerGroup, valuePickerOptions, and defaultValue
@NativeBlockSlotParameterSlot where child blocks can be nested
@NativeBlockEventParameterEvent the block can fire (tap, change, etc.); supports dataBinding
@NativeBlockValuePickerOptionDefines a selectable option (id, text) for DROPDOWN or COMBOBOX_INPUT pickers
@NativeBlockValuePickerPositionGroups related properties under a named section in the CLI UI

Step 3: Annotate an Action

@NativeAction(
    keyType = "myOrg/NAVIGATE_ACTION",
    name = "Navigate",
    description = "Navigates to a new screen",
    version = 1,
    versionName = "1.0.0"
)
class NavigateAction {

    @NativeActionParameter
    data class Parameter(
        @NativeActionProp(description = "Destination route")
        val route: String = "",
        @NativeActionData(description = "Error message stored in a variable")
        val error: String = "",
        @NativeActionEvent(then = Then.SUCCESS)
        val onSuccess: () -> Unit,
        @NativeActionEvent(then = Then.FAILURE, dataBinding = ["error"])
        val onFailure: (String) -> Unit,
    )

    @NativeActionFunction
    suspend fun navigate(parameter: Parameter) {
        try {
            // perform navigation
            parameter.onSuccess()
        } catch (e: Exception) {
            parameter.onFailure(e.message ?: "Unknown error")
        }
    }
}

Register after SDK init:

DemoActionProvider.provideActions()

Annotation reference

AnnotationTargetPurpose
@NativeActionClassMarks the class as an action with a unique keyType
@NativeActionParameterClassMarks a class as a parameter holder passed to the action at runtime
@NativeActionFunctionFunctionMarks the function that executes the action logic
@NativeActionPropParameterStatic property configured via the CLI; supports valuePicker, valuePickerGroup, valuePickerOptions, and defaultValue
@NativeActionDataParameterRuntime variable bound to frame state
@NativeActionEventParameterOutcome event the action can emit; then controls flow (SUCCESS, FAILURE, NEXT, END)
@NativeActionValuePickerOptionDefines a selectable option (id, text) for DROPDOWN pickers
@NativeActionValuePickerPositionGroups related properties under a named section in the CLI UI

Step 4: Register at Runtime

You can also register blocks and actions manually:

// Block
NativeblocksManager.getInstance().provideBlock(
    blockType = "myOrg/CUSTOM_BUTTON",
    block = { props -> CustomButton(props) }
)

// Action
NativeblocksManager.getInstance().provideAction(
    actionType = "myOrg/NAVIGATE_ACTION",
    action = NavigateAction()
)

Type Converters

Frame properties are stored as JSON strings in the server. A type converter bridges between that raw string and a typed Kotlin or Swift value so you can use rich types directly in your block properties.

Important: Type converters only apply to @NativeBlockProp (static properties configured via the CLI). They do not apply to @NativeBlockData (runtime data variables bound to frame state).

Implement a converter

Implement INativeType<T> with two methods: toString to serialise your type to a string, and fromString to deserialise it back. A common property use case is a Color stored as a hex string in the frame definition.

class ColorNativeType : INativeType<Color> {
    override fun toString(value: Color): String {
        val argb = value.toArgb()
        return String.format("#%08X", argb)
    }

    override fun fromString(value: String): Color {
        return try {
            Color(android.graphics.Color.parseColor(value))
        } catch (e: Exception) {
            Color.Unspecified
        }
    }
}

Register a converter

Call provideTypeConverter after SDK initialisation, before any blocks are rendered.

NativeblocksManager.getInstance().provideTypeConverter(
    type = Color::class,
    converter = ColorNativeType()
)

Use it in a block property

Once registered, annotate a block property with your custom type. The SDK calls the converter automatically when reading from frame JSON.

@NativeBlock(name = "Custom Button", keyType = "myOrg/CUSTOM_BUTTON", version = 1, versionName = "1.0.0")
@Composable
fun CustomButton(
    @NativeBlockData(description = "Button label")
    text: String,
    @NativeBlockProp(description = "Background color as hex (e.g. #FF6200EE)")
    backgroundColor: Color = Color.Blue,
) {
    Box(modifier = Modifier.background(backgroundColor)) {
        Text(text = text)
    }
}