Get StartedAndroid SDK

Hikotest on Android

Install the Kotlin SDK, connect it with two copy-paste values — no GitHub token — and call over-the-air business logic. Powered by the Chicory pure-JVM WASM runtime; no JNI or NDK required.

What you'll need

Min SDK29 (Android 10) or higher
Compile SDK35 recommended
LanguageKotlin (Java interop works); the SDK targets JVM 11
Build systemGradle with Kotlin DSL (KTS) or Groovy
RepositoryJitPack (added alongside google() and mavenCentral())
RuntimeChicory — pure JVM WebAssembly runtime, no native libraries
To connectYour panel address + Project ID — copy them from Settings → SDK Integration. No GitHub token needed.
New to this? You never compile WebAssembly yourself. Your functions are compiled on the Hikotest side when you deploy from the panel; the SDK downloads the result and runs it. The whole integration is: add the dependency → paste two values → call your functions.

1Add the JitPack repository

The SDK is distributed through JitPack. Add it to your dependency repositories:

settings.gradle.kts (Kotlin DSL)
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}
settings.gradle (Groovy)
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

2Add the dependency

app/build.gradle.kts (Kotlin DSL)
dependencies {
    implementation("com.github.halil9393:hikotest-sdk:1.0.0")
}
app/build.gradle (Groovy)
dependencies {
    implementation 'com.github.halil9393:hikotest-sdk:1.0.0'
}

3Get your panel address and Project ID

In the panel, open your project and go to Settings → SDK Integration. You'll see two values with copy buttons:

Panel addressThe address of your Hikotest panel, e.g. https://your-panel.hikotest.app — this is panelBaseUrl
Project IDA long unique id for your project (a UUID) — this is projectId
These are not secrets. The Project ID is a public, device-facing identifier, so — unlike the old GitHub token — you can put these two values straight in your code. No local.properties, BuildConfig plumbing or secret store is needed for the panel path.

4Connect and initialize at startup

Create an Application subclass, configure the SDK once with your two values and kick off initialization. The SDK downloads (or loads the cached) release.wasm from the panel and starts its background update loop.

AppDelegate.kt
class AppDelegate : Application() {

    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    override fun onCreate() {
        super.onCreate()

        Hikotest.configure(
            HikotestConfig.Builder()
                .panelBaseUrl("https://your-panel.hikotest.app") // from Settings → SDK Integration
                .projectId("your-project-id")                    // from Settings → SDK Integration
                .build()
        )

        appScope.launch {
            runCatching { Hikotest.initialize(this@AppDelegate) }
        }
    }

    override fun onTerminate() {
        super.onTerminate()
        Hikotest.shutdown()
        appScope.cancel()
    }
}

Register it in the manifest:

AndroidManifest.xml
<application
    android:name=".AppDelegate"
    ... >
Why no token? The SDK talks to your panel, not to GitHub. The panel serves updates from its own edge cache and fetches the release files server-side, so no credential ever ships inside your APK.

5Observe readiness

The SDK exposes a StateFlow you can collect from a Composable, ViewModel or Activity:

In a Composable
val initState by Hikotest.initState.collectAsState()

when (initState) {
    is HikotestInitState.Idle,
    is HikotestInitState.Loading -> { /* show loading indicator */ }
    is HikotestInitState.Ready   -> { /* SDK ready, enable UI */ }
    is HikotestInitState.Error   -> { /* show error message */ }
}

6Call your functions

The recommended path is the generated Kotlin wrapper. Every deploy writes sdk/kotlin/HikotestFunctions.ktinto your project's release — parameter names, types and doc comments included. Copy it into your app and get compile-time-safe calls over OTA logic:

Using the generated wrapper
// Signatures and docs come from your panel deploy
val price = HikotestFunctions.applyCampaign(1000.0, "VIP30")

You can also call dynamically with a signature token — int, float, string and boolean are supported, with up to five named parameters per function:

Dynamic call
val result = Hikotest.execute(
    "applyCampaign",
    "(float,string)->float",
    1000.0, "VIP30"
)

Hybrid mode — OTA lock (device-side guarantee)

Some functions (payments, pricing, authorization) should never change over-the-air. Download a release you trust from the panel (release.wasm + manifest.json), ship it in your app's assets/ and pass it at configure time:

Hybrid setup
Hikotest.configure(
    HikotestConfig.Builder()
        .panelBaseUrl("https://your-panel.hikotest.app")
        .projectId("your-project-id")
        .localBundle(
            assets.open("release.wasm").readBytes(),
            assets.open("manifest.json").bufferedReader().readText(),
        )
        .lockedFunctions("applyPayment") // device-side lock list (optional)
        .build()
)
  • A function runs from the embedded local bundle when the live manifest marks it ota:false (locked in the panel) or it is in your lockedFunctions list. Everything else keeps hot-reloading from the live OTA bundle.
  • The device wins. The union is one-way: even if the panel unlocks a function remotely, anything in lockedFunctions keeps running from the embedded bundle until you ship an app update.
  • A locked function missing from the embedded bundle raises a clear error — there is no silent OTA fallback.
  • Fully offline mode: configure only localBundle (no panel, no repo) and call Hikotest.initialize() — no context needed, the SDK never touches the network.
  • Hikotest.isLocallyPinned(name) tells you where calls are served from.

How OTA updates behave

  • Every deploy (and rollback) from the panel publishes an immutable release with a bumped bundle version (e.g. v1.0.7).
  • The SDK periodically asks the panel for the latest version in the background; when a new one appears, the bundle is downloaded, cached and hot-loaded — no app restart, no store release.
  • The last downloaded bundle is cached locally, so the app starts offline with the most recent logic.
  • Wrapper code only changes when a function signature changes; internal logic updates arrive without touching your codebase.

Troubleshooting

  • 404 / "project not found": re-copy panelBaseUrl and projectId from Settings → SDK Integration — a wrong Project ID is the usual cause. Make sure the project has at least one deployed function.
  • "Missing export" errors on string functions: the bundle predates the current string ABI — redeploy the project from the panel to regenerate it.
  • Stuck in Loading: initialization needs network on the first run; afterwards the cached bundle is used.

Connect via GitHub token (legacy)

Older setup — read releases straight from GitHubonly if you were already using it

Before the panel path existed, the SDK read releases directly from your project's build repository on GitHub, which required a GitHub token stored securely. It still works, but the panel path above is preferred: no token, no rate-limit worries, nothing secret in your app. Use either the panel values or the GitHub values, never both.

Keep the token out of version control in local.properties (gitignored by default) and inject it into BuildConfig:

local.properties
hikotest.github.token=ghp_YOUR_GITHUB_PAT
hikotest.repo.owner=your-github-username
hikotest.repo.name=your-project-build-repo
app/build.gradle.kts
import java.util.Properties

val localProps = Properties()
val localPropsFile = rootProject.file("local.properties")
if (localPropsFile.exists()) localProps.load(localPropsFile.inputStream())

android {
    defaultConfig {
        buildConfigField("String", "HIKOTEST_GITHUB_TOKEN", "\"${localProps.getProperty("hikotest.github.token", "")}\"")
        buildConfigField("String", "HIKOTEST_REPO_OWNER",   "\"${localProps.getProperty("hikotest.repo.owner", "")}\"")
        buildConfigField("String", "HIKOTEST_REPO_NAME",    "\"${localProps.getProperty("hikotest.repo.name", "")}\"")
    }
    buildFeatures {
        buildConfig = true
    }
}
AppDelegate.kt (legacy config)
Hikotest.configure(
    HikotestConfig.Builder()
        .githubToken(BuildConfig.HIKOTEST_GITHUB_TOKEN)
        .repoOwner(BuildConfig.HIKOTEST_REPO_OWNER)
        .repoName(BuildConfig.HIKOTEST_REPO_NAME)
        .build()
)
Never hardcode the token in source files or commit it. Minimum token scope: repo for private build repositories, public_repo for public ones. The import java.util.Properties line must be the very first line of the build script, before the plugins {} block.
Not ready for over-the-air updates? The standalone guide shows how to ship release.wasm inside your app and call it without the SDK — no network access, no tokens.