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.
Start Grona at launch
Section titled “Start Grona at launch”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.
-
Create an
Applicationsubclass, or open the one your app already has, and register it inAndroidManifest.xml:app/src/main/AndroidManifest.xml <applicationandroid:name=".AcmeMarketApp"...></application> -
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. -
Build the options and call
Grona.initfromonCreate:AcmeMarketApp.kt package com.example.acmemarketimport ai.grona.sdk.Gronaimport ai.grona.sdk.GronaOptionsimport android.app.Applicationclass 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))}} -
Implement
AcmeGronaHost(outline below), then pass the shopper’s consent, identity and basket as your app learns them.
How init behaves
Section titled “How init behaves”- Constant time.
initqueues 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
GronaLoggerand 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
initis ignored and Grona stays inactive there. If your app runs extra processes, do not callGrona.initfrom them. When the process name cannot be read at all, the SDK assumes it is the main process. - Thread rules. Setters such as
setBasketandidentifyare safe from any thread.Grona.decisionmust be called on the main thread. The SDK calls yourGronaHoston 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.
Options
Section titled “Options”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. |
Implement GronaHost
Section titled “Implement GronaHost”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:
package com.example.acmemarket
import ai.grona.sdk.GronaAddResultimport ai.grona.sdk.GronaHostimport ai.grona.sdk.GronaLineimport ai.grona.sdk.GronaProductimport ai.grona.sdk.GronaRouteimport 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.
Activation source
Section titled “Activation source”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
nullwhen 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.
Refresh on demand
Section titled “Refresh on demand”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.
Next steps
Section titled “Next steps”- Identify shoppers when they sign in.
- Set consent from your consent flow.
- Push the basket and context.