Guide 3 of 9

Render A2UI in Compose

Add the Maven artifact, connect a host, paint a surface in Jetpack Compose.

Install

One Maven artifact, at.exponential:ui-compose (minSdk 26). Its POM brings the primitives, Compose, coroutines and JNA, and the AAR carries the Rust core for arm64-v8a, armeabi-v7a and x86_64. R8 needs no extra rules.

Not on the registries yetNothing is published yet: every package publishes on the first ui-v* tag. Until it is on Maven Central, publish it to your local Maven repository from a checkout:
shell
bash apps/desktop/crates/exponential-ui-ffi/build-android.sh
cd packages/exponential-ui-compose
./gradlew :ui-compose:publishReleasePublicationToMavenLocal :ui-compose-primitives:publishReleasePublicationToMavenLocal

Then add mavenLocal() to your repositories and the one dependency:

guides/compose/app/build.gradle.kts
kotlin (gradle)
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")
}

android {
    namespace = "at.exponential.guide"
    compileSdk = 36

    defaultConfig {
        applicationId = "at.exponential.guide"
        minSdk = 26
        targetSdk = 36
        versionCode = 1
        versionName = "0.1.0"
    }

    buildFeatures {
        compose = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

kotlin {
    compilerOptions {
        jvmTarget = JvmTarget.JVM_17
    }
}

dependencies {
    // The SDK: the painter + host runtime (its POM brings the primitives,
    // Compose, coroutines and JNA with the native core).
    implementation("at.exponential:ui-compose:0.1.0")

    implementation("androidx.activity:activity-compose:1.12.4")
    implementation(platform("androidx.compose:compose-bom:2024.12.01"))
    implementation("androidx.compose.foundation:foundation")
    implementation("androidx.compose.material3:material3")
}

Paint a surface

One ExponentialHost per activity, connected in onCreate and disposed in onDestroy; HostSurface paints a surface by id. The emulator reaches the host machine's server at 10.0.2.2.

guides/compose/app/src/main/kotlin/at/exponential/guide/MainActivity.kt
kotlin
package at.exponential.guide

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import at.exponential.ui.ExponentialUi
import at.exponential.ui.host.ExponentialHost
import at.exponential.ui.host.HostOptions
import at.exponential.ui.host.HostSurface
import at.exponential.ui.host.JsonlStreamTransport
import at.exponential.ui.theme.Mode
import at.exponential.ui.theme.ThemeHandle

class MainActivity : ComponentActivity() {
    // One host per app: A2UI messages in over a transport, actions back out.
    private val host = ExponentialHost(
        HostOptions(
            transport = JsonlStreamTransport(url = "http://10.0.2.2:4300/a2ui.jsonl", postUrl = "http://10.0.2.2:4300/action"),
            theme = ThemeHandle.builtin("exponential"),
            mode = Mode.Dark,
        ),
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ExponentialUi.appContext = applicationContext // links open through it
        host.connect()
        setContent {
            // The surface the agent creates, as tall as its content: you own the scroller.
            Column(Modifier.safeDrawingPadding().verticalScroll(rememberScrollState()).padding(16.dp)) {
                HostSurface(host, "main", fallback = { Text("Waiting for the agent…") })
            }
        }
    }

    override fun onDestroy() {
        host.dispose()
        super.onDestroy()
    }
}

Host plugin

The lower-level SurfaceModel + ExponentialSurface take a HostPlugin (every member has a default; ClosureHost builds one from lambdas): icons painted under LocalContentColor, onAction, onInput (Change debounced with a revision, Commit on blur or IME Done), openUrl, fontFamily, markdown. Register theme fonts with ExponentialUi.registerFont(family, FontFamily). Details in the Compose library's README.

Compiled in CI

Every file on this page is a real file in apps/ui-site/guides/compose. CI copies it into an empty directory and builds it from scratch, the way you would, so the example cannot drift from the SDK:

shell
bun apps/ui-site/guides/check.ts compose

For a complete app on this platform, see the samples: four hosts rendering the same surface from a local A2UI server, in a third-party theme, with a custom extension component.

Next guideRender A2UI in gpui