
# Hello World [Kotlin]

## Introduction

The Kotlin binding rides on the same [JNA](https://github.com/java-native-access/jna)
layer as Java, so it loads the prebuilt `libazul` native library directly. You write
idiomatic Kotlin — a data class, a `LayoutCallback` SAM that returns a `Dom`, and the
companion-object `App` factory — and the generated wrappers handle the FFI.

## Installation

You need **Kotlin 1.9+**, **JDK 17+**, **JNA 5.14+**, and the native `libazul` library.

Azul is not on Maven Central, but there is a self-hosted maven2 repository
served from azul.rs (generated by the release CI). Add it next to
`mavenCentral()` and depend on `rs.azul:azul` (the jar bundles the Java
bindings plus a Linux x86-64 `libazul.so` as a JNA resource; on other
platforms also do step 1 below). Only JNA itself
(`net.java.dev.jna:jna:5.14.0`) comes from Maven Central as usual:

```kotlin
repositories {
    mavenCentral()
    maven { url = uri("https://azul.rs/ui/maven") }
}
dependencies {
    implementation("net.java.dev.jna:jna:5.14.0")
    implementation("rs.azul:azul:0.2.0")
}
```

Alternatively, install manually:

1. Download the native library from the
   [release page](https://azul.rs/ui/release/0.2.0) (`libazul.dylib`
   / `libazul.so` / `azul.dll`).
2. Add the generated `Azul.kt` bindings (plus optional Gradle scaffolding)
   to your sources:

   ```sh
   wget https://azul.rs/ui/release/0.2.0/Azul.kt
   wget https://azul.rs/ui/release/0.2.0/build.gradle.kts
   wget https://azul.rs/ui/release/0.2.0/settings.gradle.kts
   ```

The native library must be discoverable via `-Djna.library.path` /
`DYLD_LIBRARY_PATH` / `LD_LIBRARY_PATH` / `PATH`.

## Simple "Counter" Example

```kotlin
package com.azul

import com.sun.jna.Pointer

// Plain data class - the "single source of truth" for app state.
class MyDataModel(var counter: Int)
private val MODEL = MyDataModel(5)

// Click callback: write the Update int through the out-pointer.
private val onClick = AzulNativeManaged.ButtonOnClickCallbackInvokerCallback { _, dataPtr, _, outPtr ->
    val m = AzulHostInvoker.refanyGet(dataPtr)
    val result = if (m is MyDataModel) { m.counter += 1; Update.RefreshDom.value }
                 else Update.DoNothing.value
    outPtr!!.setInt(0, result)
}

// Typed layout callback: returns a Dom directly; the bridge splices the bytes
// into the native out-pointer internally.
private val layout = AzulHostInvoker.LayoutCallback { _, dataPtr, _ ->
    val m = AzulHostInvoker.refanyGet(dataPtr)
    if (m !is MyDataModel) {
        Dom.createBody()
    } else {
        val label = Dom.createDiv()
            .withCss("font-size: 32px;")
            .withChild(Dom.createText(m.counter.toString()))
        val buttonDom = Button.create("Increase counter")
            .withButtonType(ButtonType.Primary.value)
            .onClick(m, onClick)
            .dom()
        Dom.createBody()
            .withChild(label)
            .withChild(buttonDom)
    }
}

fun main() {
    // `use { }` disposes the App (C-side delete) when the block exits.
    App.create(AzulHostInvoker.refanyWrap(MODEL), AppConfig.create()).use { app ->
        app.run(WindowCreateOptions.create(layout))
    }
}
```

Three things to notice.

- **`refanyWrap` / `refanyGet` with `is` smart-casts** — the same object instance is
  handed back to every callback; `if (m is MyDataModel)` both guards and smart-casts.
  On mismatch return `Dom.createBody()` / `Update.DoNothing.value`.
- **`LayoutCallback` SAM returns `Dom`** — the companion `WindowCreateOptions.create`
  factory hides the host-invoker register + JNA byte-splice. Note the `!!` on the
  nullable `Pointer?` out-pointer before `setInt`.
- **Fluent wrapper API** — `Dom.createBody().withChild(...)` and
  `Button.create(...).withButtonType(...).onClick(data, fn).dom()`. The click
  handler is the event's typed SAM (`ButtonOnClickCallbackInvokerCallback` for
  `Button.onClick`). `AzulString.toString()` decodes UTF-8 into `kotlin.String`.

## Build and run

```sh
kotlinc -J-Xmx4g -cp $JNA_JAR Azul.kt HelloWorld.kt \
    -include-runtime -d hello-world.jar
# macOS requires -XstartOnFirstThread (Cocoa main-thread rule).
DYLD_LIBRARY_PATH=. java -XstartOnFirstThread -Djna.library.path=. \
    -cp hello-world.jar:$JNA_JAR com.azul.HelloWorldKt
```

`$JNA_JAR` points at your `jna-5.14.0.jar` (from Maven Central,
`net.java.dev.jna:jna:5.14.0`). On Linux/Windows drop
`-XstartOnFirstThread` and use `LD_LIBRARY_PATH` / `PATH`.

Alternatively — and recommended — use the Gradle project from the
repository's `examples/kotlin/` directory
([`build.gradle.kts`](https://github.com/fschutt/azul/blob/master/examples/kotlin/build.gradle.kts)):
`gradle run` pulls JNA from Maven Central, compiles `Azul.kt` +
`HelloWorld.kt` with daemon caching (the 4 GB compiler heap is preset in
`gradle.properties`), and wires `jna.library.path` onto the run task for
you.

You should see the window pictured on the [hello-world landing page](../hello-world.md).

## Common errors

- **`UnsatisfiedLinkError`** — native library not on the JNA library path.
- **No window on macOS** — `-XstartOnFirstThread` missing.
- **Counter does not advance** — the click handler wrote `Update.DoNothing.value`.
- **`NullPointerException` on `outPtr`** — the `!!` unwrap on the SAM's nullable
  `Pointer?` arg is required; keep it.
- **Process hangs at exit on Windows** — the example builds and runs the whole
  headless layout, but the JVM may not terminate afterwards. This is a known
  JNA-on-Windows behaviour: the JVM exits only once **all non-daemon threads
  end** and the native event queue is drained, so a native (libazul) thread or
  window left on the JVM thread keeps it alive. The binding itself is fine — it
  passes the full run on macOS, and the Java binding (same JVM) runs on Windows
  — so a fix needs a Windows-host thread dump of the hung JVM. The E2E board
  reports Kotlin `⊘ SKIP` on Windows for this reason.

## Coming Up Next

- [Application Architecture](../architecture.md) — architecting a larger Azul application
- [Document Object Model](../dom.md) — the Dom tree: node types, hierarchy, and CSS
- [Hello World [Java]](java.md)
