Skip to content
The mobile SDKs are available on request. Email support@grona.ai to get access.

Quickstart: Android

This quickstart adds the Grona Android SDK to your app, starts it when the app launches, and shows Grona content in the home_top placement on your home screen. At the end you check the result in the SDK’s debug view. It takes one Application class, one host interface, one view in a layout and one line of code on the screen.

The Android SDK is at version 0.1.0. It supports Android 7.0 (API level 24) and later.

Grona sets up your workspace for apps and gives you these values. The examples on this page use Acme Market’s.

Value Example
Workspace key acme-market
Config URL https://api.grona.ai/v1/mobile/workspaces/acme-market/config
Events URL https://api.grona.ai/v1/mobile/events
Preview pairing URL https://api.grona.ai/v1/mobile/preview/pair
Configuration key id and public key acme-market-2026-10 and a base64 public key
Signed bundled configuration A config.json file
  1. Add the dependencies to your app module. grona-debug adds the debug view to debug builds only.

    app/build.gradle.kts
    dependencies {
    implementation("ai.grona:grona-core:0.1.0")
    debugImplementation("ai.grona:grona-debug:0.1.0")
    }

    For the repository setup and the build requirements (compileSdk, Java and Kotlin versions), see Install the SDK.

  2. Add the bundled configuration. Save the signed file from Grona as app/src/main/assets/grona/config.json. The SDK applies it at launch and when the device is offline, before the first download from the config URL.

  1. Implement GronaHost. The SDK calls it on the main thread to look up products, add them to the basket, replace a basket line and open screens. This minimal version knows no products and accepts no actions, which is enough for a first banner or message without a call to action:

    app/src/main/java/com/example/acmemarket/AcmeGronaHost.kt
    package com.example.acmemarket
    import ai.grona.sdk.GronaAddResult
    import ai.grona.sdk.GronaHost
    import ai.grona.sdk.GronaLine
    import ai.grona.sdk.GronaProduct
    import ai.grona.sdk.GronaRoute
    import ai.grona.sdk.GronaSource
    // A minimal host. The full version in Handle actions takes your Application.
    class AcmeGronaHost : GronaHost {
    override fun product(productId: String): GronaProduct? = null
    override fun add(lines: List<GronaLine>, source: GronaSource): GronaAddResult = GronaAddResult.REJECTED
    override fun replace(productId: String, with: GronaLine, source: GronaSource): GronaAddResult = GronaAddResult.REJECTED
    override fun navigate(route: GronaRoute, source: GronaSource): Boolean = false
    }

    Content that names products or adds them to the basket shows nothing until product returns your products from a fast in-memory lookup and add adds them. See Handle actions.

  2. Call Grona.init in Application.onCreate. Build the options with the values from Grona:

    app/src/main/java/com/example/acmemarket/AcmeMarketApp.kt
    package com.example.acmemarket
    import ai.grona.sdk.Grona
    import ai.grona.sdk.GronaOptions
    import android.app.Application
    class AcmeMarketApp : Application() {
    override fun onCreate() {
    super.onCreate()
    val options = GronaOptions.Builder("acme-market")
    .bundledConfigAsset("grona/config.json")
    .configUrl("https://api.grona.ai/v1/mobile/workspaces/acme-market/config")
    .eventsUrl("https://api.grona.ai/v1/mobile/events")
    .previewUrl("https://api.grona.ai/v1/mobile/preview/pair")
    .configKey("acme-market-2026-10", CONFIG_PUBLIC_KEY)
    .build()
    Grona.init(this, options, AcmeGronaHost())
    }
    private companion object {
    // Base64 DER SubjectPublicKeyInfo from Grona; a public key, not a secret
    const val CONFIG_PUBLIC_KEY = "REPLACE_WITH_CONFIG_PUBLIC_KEY_FROM_GRONA"
    }
    }

    build() throws IllegalArgumentException if there is no configKey, if a URL does not start with https://, or if the workspace key is not valid. After that, the SDK never throws into your app. Grona.init returns at once and does its work off the main thread. It is ignored outside your app’s main process and on repeat calls.

  3. Register the Application class in your manifest:

    app/src/main/AndroidManifest.xml
    <application
    android:name=".AcmeMarketApp"
    ... >
  4. Pass the shopper’s consent answer. Consent starts as UNKNOWN: events stay in memory and no events are sent. Call setConsent after init with the answer from your own consent flow, and again whenever it changes:

    import ai.grona.sdk.Grona
    import ai.grona.sdk.GronaConsent
    fun onAnalyticsConsentChanged(allowed: Boolean?) {
    Grona.setConsent(
    when (allowed) {
    true -> GronaConsent.GRANTED
    false -> GronaConsent.DENIED
    null -> GronaConsent.UNKNOWN
    },
    )
    }

    Consent controls events, not rendering: Grona content still shows under DENIED, and nothing is collected. See Consent.

  1. Add a GronaSlotView where the content should appear, with app:gronaPlacement set to home_top:

    app/src/main/res/layout/activity_home.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ai.grona.sdk.GronaSlotView
    android:id="@+id/gronaHomeTop"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:gronaPlacement="home_top" />
    <!-- The rest of your home screen -->
    </LinearLayout>
  2. Set the slot’s screen when the screen is created. Setting screen starts the slot; until then it shows nothing.

    app/src/main/java/com/example/acmemarket/HomeActivity.kt
    package com.example.acmemarket
    import ai.grona.sdk.GronaScreen
    import ai.grona.sdk.GronaSlotView
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_home)
    findViewById<GronaSlotView>(R.id.gronaHomeTop).screen = GronaScreen.HOME
    }
    }
  3. Run the app. If the configuration has a campaign for home_top and you are in its treatment arm, the component appears at the top of the home screen.

Open the debug view from a debug-only menu or button in your app:

Grona.openDebugView(this)

It shows the configuration the SDK applied, the slots on screen, each campaign with your arm, and recent events. It opens only when grona-debug is in the build and the app is debuggable; otherwise the call does nothing.

If the slot is empty, check your arm for the campaign. Control and holdout show nothing by design. To see the treatment on your device, pin the variant locally from the debug view or open a preview link.