Encatch
Welcome to Encatch Docs
Mobile & Native SDKs

Android SDK

Complete integration guide for the Encatch native Android SDK — in-app feedback and survey collection for Android apps

The Encatch Android SDK lets you collect in-app feedback and surveys in native Android apps. Display forms as a modal WebView overlay or inline in your layout, identify users, track screens and events, and submit responses to the Encatch backend.


Overview


Installation

// build.gradle.kts
dependencies {
    implementation("com.encatch:android:0.1.0")
}
// build.gradle
dependencies {
    implementation 'com.encatch:android:0.1.0'
}

The com.encatch:android artifact pulls in com.encatch:core (the platform-agnostic business logic — networking, storage, session management) automatically and adds the classic-Views UI: the modal form overlay and the WebView bridge wiring.


Quick Start

1. Initialization

Install the form UI once in your Application.onCreate. EncatchFormHost.install tracks the current foreground Activity so modal forms have a host to attach to, and wires foreground retry-queue flushing and completion-CTA handling.

import android.app.Application
import com.encatch.android.EncatchFormHost

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        EncatchFormHost.install(this)
    }
}

Then initialize the SDK. All main SDK entry points are suspend functions, so call them from a coroutine — for example lifecycleScope.launch:

import androidx.lifecycle.lifecycleScope
import com.encatch.core.Encatch
import kotlinx.coroutines.launch

lifecycleScope.launch {
    Encatch.init("your-api-key")
}

EncatchFormHost is required for modal forms

Without EncatchFormHost.install(application), showForm calls that resolve to the modal presentation have no Activity to attach to and nothing will be displayed. Inline forms (see below) attach through EncatchInlineFormView instead, but installing the host is still recommended as the fallback presenter.

For inline forms, add EncatchInlineFormView to your screen layout separately.

Pass an optional EncatchConfig to customize SDK behavior:

import com.encatch.core.Encatch
import com.encatch.core.EncatchConfig
import com.encatch.core.Theme

lifecycleScope.launch {
    Encatch.init(
        "your-api-key",
        EncatchConfig(
            theme = Theme.SYSTEM,
            debugMode = true,
            isFullScreen = false,
            apiBaseUrl = "https://api.encatch.com",
            appVersion = "1.2.3",
            onBeforeShowForm = { payload ->
                // Return false to prevent the form from showing
                true
            },
        ),
    )
}

Prop

Type

Calling init again with a new API key or config reconfigures the SDK in place — useful for switching environments at runtime.

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("…"))).

lifecycleScope.launch {
    Encatch.identifyUser("user@example.com")
}

Trait values in set / setOnce are JsonElements — wrap primitives with JsonPrimitive:

import com.encatch.core.UserTraits
import kotlinx.serialization.json.JsonPrimitive

lifecycleScope.launch {
    Encatch.identifyUser(
        "user@example.com",
        traits = UserTraits(
            set = mapOf(
                "name" to JsonPrimitive("Alice"),
                "plan" to JsonPrimitive("team"),
            ),
        ),
    )
}
import com.encatch.core.UserTraits
import kotlinx.serialization.json.JsonPrimitive

lifecycleScope.launch {
    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

IdentifyOptions fields:

FieldTypeDescription
localeString?Preferred language for this user (persisted)
countryString?ISO 3166 country code (persisted)
secureSecureOptions?Server-generated HMAC signature for verified identification

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 (for example the string form of System.currentTimeMillis() from your server). 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.core.IdentifyOptions
import com.encatch.core.SecureOptions

lifecycleScope.launch {
    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.core.ResetMode
import com.encatch.core.ShowFormOptions

lifecycleScope.launch {
    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. ContextValue is a sealed class with StringValue, NumberValue, BooleanValue, and DateValue (epoch millis) variants:

import com.encatch.core.ContextValue

lifecycleScope.launch {
    Encatch.showForm("feedback-form", ShowFormOptions(
        reset = ResetMode.ALWAYS,
        context = mapOf(
            "plan" to ContextValue.StringValue("team"),
            "feature" to ContextValue.StringValue("checkout"),
            "seats" to ContextValue.NumberValue(12.0),
            "trial" to ContextValue.BooleanValue(false),
            "signedUpAt" to ContextValue.DateValue(System.currentTimeMillis()),
        ),
    ))
}

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.

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 Android views or composables 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: intercept the form, render your own UI, then submit through the SDK.

1. Intercept the form. Set onBeforeShowForm in EncatchConfig and return false to suppress the SDK's WebView. The payload carries everything you need to render natively — payload.formConfig.feedbackConfigurationId (required for submission) and payload.formConfig.questionnaireFields (the question definitions):

import com.encatch.core.ShowFormResponse

var pendingNativeForm: ShowFormResponse? = null

lifecycleScope.launch {
    Encatch.init(
        "your-api-key",
        EncatchConfig(
            onBeforeShowForm = { payload ->
                pendingNativeForm = payload.formConfig
                // Hand off to your own UI (e.g. post to a StateFlow your screen observes)
                false // Suppress the SDK's WebView form
            },
        ),
    )
}

2. Emit lifecycle events (optional). The WebView normally reports lifecycle events; with a native UI you emit them yourself so dashboards and Encatch.on listeners stay accurate:

import com.encatch.core.EventPayload
import com.encatch.core.EventType

val configId = formConfig.feedbackConfigurationId
Encatch.emitEvent(EventType.FORM_SHOW, EventPayload(formId = configId, timestamp = 0))
// ... later, as the user interacts:
Encatch.emitEvent(EventType.FORM_STARTED, EventPayload(formId = configId, timestamp = 0))

(emitEvent stamps the current timestamp for you.)

3. Build and submit the response. Collect answers from your UI as NativeFormResponse entries (questionId, question type wire value, and the value), then convert them with buildSubmitRequest and send with Encatch.submitForm:

import com.encatch.core.BuildSubmitRequestOptions
import com.encatch.core.NativeFormResponse
import com.encatch.core.buildSubmitRequest

lifecycleScope.launch {
    val responses = listOf(
        NativeFormResponse("q1", "rating", 5),
        NativeFormResponse("q2", "short_answer", "Great product!"),
        NativeFormResponse("q3", "multiple_choice_multiple", listOf("option-a", "option-b")),
        NativeFormResponse("q4", "yes_no", true),
    )

    val request = buildSubmitRequest(
        BuildSubmitRequestOptions(
            formConfigurationId = formConfig.feedbackConfigurationId,
            completionTimeInSeconds = 42,
        ),
        responses,
    )
    Encatch.submitForm(request)

    Encatch.emitEvent(
        EventType.FORM_COMPLETE,
        EventPayload(formId = formConfig.feedbackConfigurationId, timestamp = 0),
    )
}

buildSubmitRequest maps every supported question type (rating, NPS, CSAT, opinion scale, text types, choice types, ranking, yes/no, consent, date, matrix types, and structured types like signature, file upload, phone number, address, video/audio, scheduler, QnA with AI, and UPI payments) to the wire format the backend expects. Numeric scale values are rounded to integers; unknown types fall back to short_answer for forward-compatibility.

Value shapes

The value passed to NativeFormResponse depends on the question type: numbers for scales (rating, nps, csat, opinion_scale), strings for text types, String or List<String> for choice/ranking types, Boolean for yes_no/consent, Map for matrix types, and the matching Kotlin data class (SignatureAnswer, PhoneNumberAnswer, AddressAnswer, etc.) for structured types.


Support

Was this page helpful?