Encatch
Welcome to Encatch Docs
Mobile & Native SDKs

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:

OperationTypeDescription
setMap<String, JsonElement>?Set user attributes (overwrites existing values)
setOnceMap<String, JsonElement>?Set user attributes only if they don't already exist
incrementMap<String, Double>?Increment numeric user attributes
decrementMap<String, Double>?Decrement numeric user attributes
unsetList<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

ResetModeBehavior
ResetMode.ALWAYSReset pre-fill and response data on every form display
ResetMode.ON_COMPLETEReset only after the form is completed
ResetMode.NEVERNever 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:

  1. Intercept the form with onBeforeShowForm and return false. The ShowFormInterceptorPayload includes formConfigJson — the JSON encoding of the full form configuration (including questionnaireFields), so you can render your own UI from the real form definition.
  2. Render your own UI from the payload.
  3. 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

Was this page helpful?