Skip to content

Quick Start

Add Halogen to your Compose Multiplatform project and generate your first LLM-powered theme.


1. Add Dependencies

// build.gradle.kts
dependencies {
    implementation("me.mmckenna.halogen:halogen-core:0.1.0")
    implementation("me.mmckenna.halogen:halogen-compose:0.1.0")
    implementation("me.mmckenna.halogen:halogen-engine:0.1.0")
    implementation("me.mmckenna.halogen:halogen-provider-nano:0.1.0")

    // Optional: persistent cache that survives process death
    implementation("me.mmckenna.halogen:halogen-cache-room:0.1.0")
}
// build.gradle.kts (commonMain)
commonMain.dependencies {
    implementation("me.mmckenna.halogen:halogen-core:0.1.0")
    implementation("me.mmckenna.halogen:halogen-compose:0.1.0")
    implementation("me.mmckenna.halogen:halogen-engine:0.1.0")

    // Optional: persistent cache that survives process death (Android, iOS, JVM — not wasmJs)
    implementation("me.mmckenna.halogen:halogen-cache-room:0.1.0")
}

2. Initialize Halogen

val halogen = Halogen.Builder()
    .provider(GeminiNanoProvider())
    .defaultTheme(HalogenDefaults.materialYou())
    .cache(HalogenCache.memory())
    .build()
val halogen = Halogen.Builder()
    .provider(OpenAiProvider(apiKey = BuildConfig.OPENAI_KEY))
    .defaultTheme(HalogenDefaults.light())
    .cache(HalogenCache.memory())
    .build()

Persistent caching across app restarts

By default, HalogenCache.memory() provides an in-memory LRU cache that is lost on process death. To persist themes across app restarts, add the optional halogen-cache-room dependency and use HalogenRoomCache:

// Android only: call once in Application.onCreate()
HalogenRoomCache.initialize(applicationContext)

val halogen = Halogen.Builder()
    .provider(GeminiNanoProvider())
    .cache(HalogenRoomCache.create()) // persistent cache
    .build()

Room cache is available on Android, iOS, and JVM (not wasmJs). On non-Android platforms, no initialize() call is needed.


3. Wrap Your UI

Wrap your content with HalogenTheme, passing the active spec from the engine:

val spec by halogen.activeTheme.collectAsState()

HalogenTheme(spec = spec) {
    App()
}

HalogenTheme wraps MaterialTheme - all standard M3 accessors (MaterialTheme.colorScheme, .typography, .shapes) work as expected. When a theme is generated, the entire tree recomposes with the new values.

Animated Transitions

Theme changes animate smoothly by default - all 49 M3 color roles and shape corner radii transition with a 400ms tween. No extra code needed.

To customize the animation:

// Custom spring animation
HalogenTheme(
    spec = spec,
    colorAnimationSpec = spring(stiffness = Spring.StiffnessLow),
    shapeAnimationSpec = spring(stiffness = Spring.StiffnessMedium),
) {
    MyApp()
}

// Disable animation (instant snap)
HalogenTheme(
    spec = spec,
    colorAnimationSpec = snap(),
    shapeAnimationSpec = snap(),
) {
    MyApp()
}

Typography (font family, weight, letter spacing) snaps instantly to avoid text reflow during transitions.


4. Generate a Theme

Trigger theme generation:

// Generate and apply a theme
val result = halogen.resolve(
    key = "coffee",
    hint = "warm coffee shop, cozy browns, soft shapes"
)

when (result) {
    is HalogenResult.Success -> { /* Generated by LLM, cached, applied */ }
    is HalogenResult.Cached  -> { /* Loaded from cache, no LLM call */ }
    is HalogenResult.FromServer -> { /* Fetched from server, cached */ }
    is HalogenResult.ParseError -> { /* JSON parse failed, default theme active */ }
    is HalogenResult.Unavailable -> { /* No provider available */ }
    is HalogenResult.QuotaExceeded -> { /* Rate limited, try later */ }
}

The key is a cache key - any string you choose. The hint is the natural language prompt sent to the LLM. If the key already exists in cache, the hint is ignored and the cached theme is returned instantly.


5. Platform Notes

Gemini Nano is Android-only

halogen-provider-nano depends on ML Kit and only works on Android. Supported devices include Pixel 9+ and Samsung Galaxy S24+. On unsupported devices, availability() returns UNAVAILABLE.

Use a cloud provider for cross-platform

For iOS, Desktop, and Web targets, implement HalogenLlmProvider with a cloud LLM (OpenAI, Claude, Ollama, etc.). See the Provider Guide for examples.

One LLM call = light + dark

A single theme generation produces both light and dark color schemes. The system dark mode toggle switches between them instantly - no second LLM call, no cache miss.