Kotlin Multiplatform SDK
Complete integration guide for the Encatch Kotlin Multiplatform SDK — in-app feedback and survey collection for KMP apps targeting Android and iOS
The Encatch Kotlin Multiplatform SDK (com.encatch:kmp-sdk) lets you collect in-app feedback and surveys from shared commonMain code. One Encatch object gives you the full SDK — initialize, identify users, track screens and events, show modal forms, and submit responses — with the same call site on Android and iOS and zero platform-bridging code of your own.
Under the hood it is a thin platform-routing layer over the two native Encatch SDKs, not a reimplementation: on Android it forwards 1:1 to the native Android SDK (Android's native language is Kotlin), and on iOS it forwards through Kotlin/Native cinterop to the pure-Swift iOS SDK.
Building with Compose Multiplatform?
This module is pure business logic with no UI layer. If your app uses Compose Multiplatform and you also want a ready-made inline-form composable, use the Compose Multiplatform SDK (com.encatch:compose-sdk) instead — it depends on this module, re-exports the same Encatch API, and adds EncatchInlineForm plus fully automatic modal-host setup.
Overview
- Package:
com.encatch:kmp-sdk(Maven Central) - Version: 0.1.0
- Platforms: Android (minSdk 24), iOS (
iosArm64,iosSimulatorArm64) - Repository: github.com/get-encatch/encatch-android
- License: MIT
Installation
Add the dependency to your shared module's commonMain source set:
// build.gradle.kts (shared module)
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.encatch:kmp-sdk:0.1.0")
}
}
}Platform setup
Install the modal form host once, typically in your Application.onCreate. This module cannot do it for you automatically — it has no Context/Application reference available from commonMain (unlike com.encatch:compose-sdk, which can do this lazily via Compose's LocalContext):
import android.app.Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
com.encatch.android.EncatchFormHost.install(this)
}
}Without this call, showForm cannot present the modal overlay on Android.
Nothing to do — Encatch.init(...) installs the modal form host automatically the first time it's called.
Quick Start
1. Initialization
Call Encatch.init once at app startup from any coroutine scope. It's a suspend function — all subsequent calls (identifyUser, showForm, tracking) silently no-op until initialization completes.
import com.encatch.sdk.Encatch
// commonMain — same call site on both platforms
scope.launch {
Encatch.init("your-api-key")
}Check Encatch.isInitialized to guard against double-initialization, e.g. on process restarts:
if (!Encatch.isInitialized) {
Encatch.init("your-api-key")
}Pass an optional EncatchConfig to customize SDK behavior:
import com.encatch.sdk.Encatch
import com.encatch.sdk.EncatchConfig
import com.encatch.sdk.Theme
scope.launch {
Encatch.init(
"your-api-key",
EncatchConfig(
theme = Theme.SYSTEM,
debugMode = true,
isFullScreen = false,
appVersion = "1.2.3",
onBeforeShowForm = { payload ->
// Return false to prevent the form from showing
true
},
),
)
}Prop
Type
2. Identify users
Identify the current user. The userName is required (can be a username, email, or unique identifier). Traits and options are optional.
Username format
userName must be an ASCII identifier: 1–50 characters, using only letters A–Z / a–z, digits 0–9, and ., _, @, -. Spaces and non-English characters (Unicode, accented letters, emoji, etc.) are not supported. Use an email address, internal user ID, or ASCII username — for example user@example.com or user_123. To store a display name in another language, pass it as a trait instead (e.g. set = mapOf("display_name" to JsonPrimitive("…"))).
Encatch.identifyUser("user@example.com")Trait values are kotlinx.serialization JsonElements — use JsonPrimitive for strings, numbers, and booleans:
import com.encatch.sdk.UserTraits
import kotlinx.serialization.json.JsonPrimitive
Encatch.identifyUser(
"user@example.com",
traits = UserTraits(
set = mapOf(
"name" to JsonPrimitive("Alice"),
"plan" to JsonPrimitive("team"),
),
),
)import com.encatch.sdk.UserTraits
import kotlinx.serialization.json.JsonPrimitive
Encatch.identifyUser(
"user@example.com",
traits = UserTraits(
set = mapOf("name" to JsonPrimitive("Alice"), "plan" to JsonPrimitive("team")),
setOnce = mapOf("firstSeen" to JsonPrimitive("2026-08-05T12:00:00Z")),
increment = mapOf("loginCount" to 1.0),
decrement = mapOf("credits" to 5.0),
unset = listOf("trialEndDate"),
),
)Prop
Type
User traits support the following operations:
| Operation | Type | Description |
|---|---|---|
set | Map<String, JsonElement>? | Set user attributes (overwrites existing values) |
setOnce | Map<String, JsonElement>? | Set user attributes only if they don't already exist |
increment | Map<String, Double>? | Increment numeric user attributes |
decrement | Map<String, Double>? | Decrement numeric user attributes |
unset | List<String>? | Remove user attributes |
Recommended
Using the secure option with a server-generated signature is recommended to verify that identification requests come from your backend. Keep your secret key on the server only — never expose it in client-side code.
Pass a server-generated HMAC signature so Encatch can validate the request. generatedDateTimeInUtc must be milliseconds since the Unix epoch (the string form of your server's epoch-millis timestamp). When your publishable key has a session timeout, use the same value in HMAC-SHA256(userName + epochMs, secretKey). It is sent as the X-User-Signature-Time header and limits the signature's lifespan.
import com.encatch.sdk.IdentifyOptions
import com.encatch.sdk.SecureOptions
Encatch.identifyUser(
"user@example.com",
options = IdentifyOptions(
secure = SecureOptions(
signature = "your-hmac-signature",
generatedDateTimeInUtc = "1741867200000", // ms since epoch (2025-03-13T12:00:00Z)
),
),
)3. Show a form manually
Show a specific form by slug or ID.
import com.encatch.sdk.ResetMode
import com.encatch.sdk.ShowFormOptions
Encatch.showForm("feedback-form")
Encatch.showForm("feedback-form", ShowFormOptions(reset = ResetMode.ALWAYS))Prop
Type
Prop
Type
| ResetMode | Behavior |
|---|---|
ResetMode.ALWAYS | Reset pre-fill and response data on every form display |
ResetMode.ON_COMPLETE | Reset only after the form is completed |
ResetMode.NEVER | Never reset response data |
Pass caller context when showing a form. Context values use the ContextValue sealed class (StringValue, NumberValue, BooleanValue, DateValue):
import com.encatch.sdk.ContextValue
import com.encatch.sdk.ResetMode
import com.encatch.sdk.ShowFormOptions
Encatch.showForm(
"feedback-form",
ShowFormOptions(
reset = ResetMode.ALWAYS,
context = mapOf(
"plan" to ContextValue.StringValue("team"),
"feature" to ContextValue.StringValue("checkout"),
),
),
)Other actions
Inline Forms
Inline forms are a way to show Encatch in-app feedback without a modal — the survey renders directly in your layout instead of as a full-screen overlay.
No UI layer in this module
com.encatch:kmp-sdk ships no views or composables. Inline forms are rendered by embedding the platform-native inline form view in each platform's UI code. If you use Compose Multiplatform, prefer the Compose Multiplatform SDK, which wraps both native views in a single EncatchInlineForm composable you call from commonMain.
Build Your Own Form UX & UI
If your feedback flow uses a fixed, predictable question set — the same fields and workflow every time — you can build the form with your own native UI and submit responses through the SDK. That keeps typography, spacing, colors, and interaction patterns aligned with the rest of your app, so the survey feels like a native screen rather than an embedded web page.
The flow has three parts, all available from commonMain:
- Intercept the form with
onBeforeShowFormand returnfalse. TheShowFormInterceptorPayloadincludesformConfigJson— the JSON encoding of the full form configuration (includingquestionnaireFields), so you can render your own UI from the real form definition. - Render your own UI from the payload.
- Submit with
buildSubmitRequest+Encatch.submitForm.
import com.encatch.sdk.BuildSubmitRequestOptions
import com.encatch.sdk.Encatch
import com.encatch.sdk.EncatchConfig
import com.encatch.sdk.NativeFormResponse
import com.encatch.sdk.buildSubmitRequest
// 1. Intercept: block the SDK's own rendering for this form
Encatch.init(
"your-api-key",
EncatchConfig(
onBeforeShowForm = { payload ->
if (payload.formId == "my-native-form") {
showMyNativeForm(payload.formId, payload.formConfigJson)
false // block the SDK form — we render our own
} else {
true
}
},
),
)
// 3. Submit: convert your native answers and post them to Encatch
suspend fun submitMyNativeForm(formConfigurationId: String) {
val responses = listOf(
NativeFormResponse("q1", "rating", 5),
NativeFormResponse("q2", "short_answer", "Great product!"),
NativeFormResponse("q3", "multiple_choice_multiple", listOf("option-a", "option-b")),
)
val requestJson = buildSubmitRequest(
BuildSubmitRequestOptions(formConfigurationId = formConfigurationId),
responses,
)
Encatch.submitForm(requestJson)
}NativeFormResponse.value's expected shape depends on the question type: numeric scales (rating, nps, csat, opinion_scale) take a Number or numeric String; text types take String; choice and ranking types take String or List<String>; boolean types (yes_no, consent) take Boolean. All 33 Encatch question types are supported; unknown types fall back to short_answer for forward-compatibility.
Prop
Type
Support
- Maven Central:
com.encatch:kmp-sdk - Issues: github.com/get-encatch/encatch-android/issues
Was this page helpful?