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 SDK | 29 (Android 10) or higher |
| Compile SDK | 35 recommended |
| Language | Kotlin (Java interop works); the SDK targets JVM 11 |
| Build system | Gradle with Kotlin DSL (KTS) or Groovy |
| Repository | JitPack (added alongside google() and mavenCentral()) |
| Runtime | Chicory — pure JVM WebAssembly runtime, no native libraries |
| To connect | Your panel address + Project ID — copy them from Settings → SDK Integration. No GitHub token needed. |
1Add the JitPack repository
The SDK is distributed through JitPack. Add it to your dependency repositories:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}2Add the dependency
dependencies {
implementation("com.github.halil9393:hikotest-sdk:1.0.0")
}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 address | The address of your Hikotest panel, e.g. https://your-panel.hikotest.app — this is panelBaseUrl |
| Project ID | A long unique id for your project (a UUID) — this is projectId |
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.
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:
<application
android:name=".AppDelegate"
... >5Observe readiness
The SDK exposes a StateFlow you can collect from a Composable, ViewModel or Activity:
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:
// 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:
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:
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 GitHub— only 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:
hikotest.github.token=ghp_YOUR_GITHUB_PAT hikotest.repo.owner=your-github-username hikotest.repo.name=your-project-build-repo
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
}
}Hikotest.configure(
HikotestConfig.Builder()
.githubToken(BuildConfig.HIKOTEST_GITHUB_TOKEN)
.repoOwner(BuildConfig.HIKOTEST_REPO_OWNER)
.repoName(BuildConfig.HIKOTEST_REPO_NAME)
.build()
)