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

Initialise the SDK

This page starts the SDK when your app launches. You build a GronaOptions object, implement GronaHost so Grona can reach your cart and router, and call Grona.init once from your Application class. Every other Grona call does nothing until init has run.

Grona provides the workspace key, the URLs and the signing keys for your workspace. The values below are Acme Market’s; replace them with yours.

  1. Create an Application subclass, or open the one your app already has, and register it in AndroidManifest.xml:

    app/src/main/AndroidManifest.xml
    <application
    android:name=".AcmeMarketApp"
    ...>
    </application>
  2. Add the signed bundled configuration that Grona exports for your workspace to your assets, at app/src/main/assets/grona/config.json. The SDK applies it at launch and whenever it is offline.

  3. Build the options and call Grona.init from onCreate:

    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")
    .configUrl("https://api.grona.ai/v1/mobile/workspaces/acme-market/config")
    .bundledConfigAsset("grona/config.json")
    .configKey("acme-market-2026-10", "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...")
    .eventsUrl("https://api.grona.ai/v1/mobile/events")
    .previewUrl("https://api.grona.ai/v1/mobile/preview/pair")
    .build()
    Grona.init(this, options, AcmeGronaHost(this))
    }
    }
  4. Implement AcmeGronaHost (outline below), then pass the shopper’s consent, identity and basket as your app learns them.

  • Constant time. init queues its work on a Grona background thread and returns. It does no disk or network work on the main thread, apart from reading its process name on Android 8.1 and lower.
  • Never throws. If start-up fails, the SDK logs through your GronaLogger and stays inactive. Your app carries on.
  • Once only. A second call is ignored, so you cannot change options after launch.
  • Main process only. In any other process init is ignored and Grona stays inactive there. If your app runs extra processes, do not call Grona.init from them. When the process name cannot be read at all, the SDK assumes it is the main process.
  • Thread rules. Setters such as setBasket and identify are safe from any thread. Grona.decision must be called on the main thread. The SDK calls your GronaHost on the main thread only.

At start the SDK applies the bundled configuration, then fetches the remote one. It never refreshes the configuration more often than every 300 seconds. For how configurations are signed and checked, see Signed configuration.

Every option is set on GronaOptions.Builder. Only the workspace key and one configKey are required.

Option Default Validation What it does
Builder(workspaceKey) Required Matches [A-Za-z0-9._:-]{1,128} Your workspace key, for example acme-market. It is a public identifier, not a secret.
configUrl(url) None Must start with https:// Signed remote configuration. Without it the SDK runs on the bundled copy only.
bundledConfigAsset(path) None Asset path of the signed bundled configuration, applied at launch and offline.
configKey(kid, publicKeySpki) None At least one is required A trusted configuration signing key: the key id and the base64 DER SubjectPublicKeyInfo of a P-256 public key. Add the current and the next key.
grantKey(kid, publicKeySpki) None A trusted grant signing key, same format as configKey. Needed for offers.
eventsUrl(url) None Must start with https:// Events endpoint. Without it the SDK sends no events and keeps no event queue. Debuggable builds keep the last 200 events in memory for the debug view.
previewUrl(url) None Must start with https:// Preview pairing endpoint. Without it preview links can only clear pins.
activationSource(source) None A GronaActivationSource that can switch campaigns off. See Activation source.
grantSource(source) None A GronaGrantSource that fetches grants from your backend. See Offers and grants.
theme(theme) GronaTheme.Builder().build() Fonts, colours and corner radius for Grona components. See Theming.
logger(logger) GronaLogger.NONE Receives SDK diagnostics. Silent by default. See Test and debug.
maxAddQuantity(max) 5 1 to 20 Highest quantity one Grona action may add. See Handle actions.
maxAddLines(max) 5 1 to 20 Most lines one Grona action may add.
sendPreConsentEvents(enabled) false Send events buffered before consent once consent is granted. See Consent.
demoMode(enabled) false Demos only, never in production. See Offers and grants.

GronaHost is how Grona works through your app’s own code. The SDK never reaches into your app any other way. It calls these four methods on the main thread only:

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
class AcmeGronaHost(private val app: AcmeMarketApp) : GronaHost {
// A fast in-memory lookup in your catalogue; null when the product is unknown
override fun product(productId: String): GronaProduct? = app.catalog.gronaProduct(productId)
// Add lines to the cart after the shopper tapped a Grona call to action
override fun add(lines: List<GronaLine>, source: GronaSource): GronaAddResult = app.cart.addFromGrona(lines)
// Swap one cart line, for example an out-of-stock item for its substitute
override fun replace(productId: String, with: GronaLine, source: GronaSource): GronaAddResult =
app.cart.replaceFromGrona(productId, with)
// Open the route with your own router; false when the route is unknown
override fun navigate(route: GronaRoute, source: GronaSource): Boolean = app.router.open(route)
}

Here catalog, cart and router stand for your app’s own classes. Handle actions has a complete implementation and the rules for each return value.

An activation source lets an external campaign system switch Grona campaigns off. Each campaign can list external ids, such as CMP-1024. When your source reports which external ids are active, a campaign that lists ids stays active only while at least one of them is active. Activation can only switch campaigns off, never on, and campaigns without external ids ignore it.

import ai.grona.sdk.GronaActivationSource
class AcmeActivationSource(private val campaigns: AcmeCampaignClient) : GronaActivationSource {
// Called on a Grona background thread; may block for up to 10 seconds
override fun activeExternalIds(): Set<String>? =
try {
campaigns.fetchActiveIds() // for example setOf("CMP-1024")
} catch (e: java.io.IOException) {
null // unknown: Grona does not narrow anything
}
}
  • Return null when you do not know. Unknown never switches a campaign off.
  • The SDK polls the source every 5 minutes while the app is in the foreground. An answer is trusted for at most 10 minutes. A poll that fails, times out or finds the previous call still running makes activation unknown at once.
  • Past 10 seconds the SDK stops waiting and treats the answer as unknown. It calls the source again only once the earlier call has returned.
  • The source runs on Grona’s background threads, sometimes at the same time as your own code. It must be thread-safe and must not touch views.

For how activation fits the kill switch, see Campaign activation and kill switch.

The SDK refreshes on its own. When you need a refresh now, for example after a pull-to-refresh, call one of these:

Grona.refresh() // no completion signal
Grona.refresh {
// Main thread, called once, also on timeouts and failures
}

Both poll the activation source and ask for a configuration fetch, subject to the 300-second floor. The listener form waits for an activation answer requested after the call (10-second timeout), then for the fetch and re-evaluation, before it runs.

A campaign that was switched off stays off for the rest of the screen visit, even after it is switched back on. To show it at once, end the visit from the listener by cancelling the decision’s GronaSubscription or setting the slot’s screen to null, then bind again. See Render Grona components.