AndroidKotlinMobile SDK

How to Embed Forms in Your Android App with FlexForm SDK (Kotlin + Compose)

Atul Kumar· June 4, 2026· 11 min read
FlexForm backendGET /forms/:id?snapshot=truepages, theme, navigation44 element typesconditional logic + branchesSchema fetched at runtimeJSON schemaYour Android AppFlexFormView(formId = "abc12")Full nameWork emailCountryPlanSubmitNative ComposeOne Composable.44 element types.Offline drafts via Room.No Play Store releaseto ship form changes.
Server-driven UI: the form schema arrives over the wire; FlexFormView renders it in Compose.

Adding forms to an Android app is one of those jobs that always takes longer than the estimate. Layouts, field validation, page navigation, draft saving, file uploads — each one a corner case. The FlexForm Kotlin Multiplatform SDK handles all of it server-side: you add one Gradle dependency, drop FlexFormView(formId = "...") into a Composable, and the form renders natively in Jetpack Compose. Schema, theme, navigation, and conditional logic all live in the FlexForm backend.

This guide walks through the full Android integration: Gradle setup, the minimum Composable you need, how offline drafts work, how to handle file uploads and the camera permission, how to theme the form to match your app, and the server-driven UI loop that lets you ship form changes without a Play Store release. Everything below is grounded in the actual SDK structure shipped in the io.flexform:flexform-sdk artifact.

Requirements

  • Android minSdk 28 (Android 9 Pie+)
  • Kotlin 2.0+
  • Jetpack Compose enabled
  • Compose Multiplatform compatible
  • Internet permission in AndroidManifest

What you get

  • 44 element types rendered natively in Compose
  • Offline draft storage via Room (KMP)
  • Server-driven UI — no app release for form changes
  • Conditional logic + page branching evaluated client-side
  • Theme JSON applied automatically

Step 1 — Add the Gradle dependency

The SDK ships as a Kotlin Multiplatform library published under io.flexform:flexform-sdk. Add it to your app module's build.gradle.kts:

// app/build.gradle.kts
dependencies {
    implementation("io.flexform:flexform-sdk:1.0.0")

    // Standard Compose + Material 3 (already in most projects)
    implementation(platform("androidx.compose:compose-bom:2026.04.01"))
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.9.0")
}

The SDK pulls KotlinX Serialization and Room as transitive dependencies, so you don't need to add them separately. KSP is required for Room — most Android projects already have it enabled.

Step 2 — Render the form with one Composable

The minimum integration is a single Composable in your Activity or Fragment. Pass the formId (visible in your FlexForm dashboard) and an onSubmit callback:

@Composable
fun FeedbackScreen() {
    FlexFormView(
        formId = "abc12",
        modifier = Modifier.fillMaxSize(),
        onSubmit = { submission ->
            // submission.id, submission.values, submission.metadata
            Log.d("FlexForm", "Submitted: ${submission.id}")
        },
        onError = { error ->
            Log.e("FlexForm", "Failed: ${error.message}")
        }
    )
}

That's the entire integration for a basic form. The Composable handles fetching the schema, rendering all 44 element types, validating inputs, evaluating conditional logic, navigating between pages, and posting the final submission. The onSubmit callback fires after the server confirms write — at that point the submission already exists in your FlexForm dashboard with full metadata attached.

Step 3 — Offline drafts and partial saving

Mobile forms get filled on subway rides, in basements, on planes. The SDK persists draft answers to Room on every page navigation. When the device returns online, queued submissions sync to the FlexForm backend with exponential backoff. The same session_id is preserved across reconnects, so a partially completed form resumes exactly where the user left off.

Partial saving is on by default for any form configured with enablePartialSaving: true in the form settings. To disable it for a specific embed (for example, a one-page intake where draft saving isn't useful):

FlexFormView(
    formId = "abc12",
    config = FlexFormConfig(
        enableOfflineDrafts = false,
        clearDraftOnSubmit  = true,
    ),
    onSubmit = { /* ... */ }
)

Step 4 — File uploads and camera permission

When a form contains a file_uploader element, the SDK uses the Android Activity Result API to launch the system photo picker or document picker. No READ_EXTERNAL_STORAGE permission is required on Android 13+ thanks to the Photo Picker API — older Android versions fall back to the documents UI which also avoids the storage permission prompt.

If a form includes camera capture (a signature pad or a photo question), add the camera permission to your manifest:

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

File size limits come from the FlexForm plan attached to the form. Starter forms cap at 5MB per file; Founders forms cap at 50MB per file; Enterprise at 100MB per file. The SDK enforces the limit client-side before upload, so users get an immediate "file too large" message instead of a delayed server rejection.

Step 5 — Theme the form to match your app

The form's appearance comes from the theme JSON configured in the FlexForm dashboard. Colors, fonts, button radius, logo placement — all server-side. The SDK reads the theme on form open and applies it to the rendered Composables; no per-app overrides needed.

For apps that need stricter visual control (Material You, a custom design system), FlexFormView accepts a themeOverride parameter that takes a FlexFormTheme. Anything you set here wins over the server-side theme:

FlexFormView(
    formId = "abc12",
    themeOverride = FlexFormTheme(
        colors = FlexFormColors(
            primary       = MaterialTheme.colorScheme.primary,
            questionsText = MaterialTheme.colorScheme.onSurface,
            background    = MaterialTheme.colorScheme.surface,
        ),
        typography = FlexFormTypography(
            fontFamily = Typography.brand,
        ),
    ),
    onSubmit = { /* ... */ }
)

Server-driven UI — the architectural win

Every form change normally costs a Play Store review cycle. Adding a field, fixing a label typo, reordering pages, swapping a theme color — each one a release. The SDK's server-driven model removes that loop for everything below the structural surface.

ChangeNative formFlexForm SDK
Add a fieldApp releaseDashboard publish
Fix a label typoApp releaseDashboard publish
Reorder pagesApp releaseDashboard publish
Add conditional branchApp releaseDashboard publish
Swap brand colorsApp releaseDashboard publish
Add a new element typeApp releaseSDK update
Bug fix in rendering pipelineApp releaseSDK update

SDK updates still ship through the normal Gradle path, but the cadence is once per quarter, not once per form change. For teams iterating on intake forms weekly, that's the difference between shipping a fix the same day and waiting for the next sprint's release train.

Common use cases

Five integration patterns that ship today on the Android SDK:

  1. In-app feedback — a tappable "Feedback" entry in your settings screen opens a FlexForm in a bottom sheet. Submissions route to Slack via FlexForm's native integration.
  2. Field-ops intake — inspection forms used offline by technicians. Drafts sync when the device returns to a signal. File uploads (photos of the equipment) queue locally.
  3. KYC and onboarding — multi-page intake with conditional branching by country. The SDK's page navigation handles the branching with no app code.
  4. Restaurant and gym intake — POS or check-in tablets that need a quick form. The same form can run on the customer's phone too via the public hosted URL.
  5. Customer-success kickoff — long onboarding forms with partial saving. The Android version stays in sync with the web version because both render from the same schema.

Verifying the integration

After integrating, three quick checks confirm the SDK is wired up correctly:

  1. First render — open the screen and confirm the form appears within a second on a warm cache. Cold start (first launch) typically renders in 1 to 2 seconds depending on schema size and network.
  2. Offline draft — fill in two pages, kill network, navigate to a third page, kill the app, relaunch. The form should resume on page 3 with your earlier answers preserved.
  3. Submission landing — submit a test entry. The submission should appear in your FlexForm dashboard within 2 seconds, with device, OS, and app version on the metadata. Connected CRM integrations should fire as normal.

Frequently asked questions

How do I add the FlexForm Android SDK to my Kotlin project?

Add the FlexForm Kotlin Multiplatform dependency to your app module under io.flexform:flexform-sdk, sync Gradle, and call FlexFormView(formId = "...") from any Composable. The SDK targets Android API 28 and above, requires Jetpack Compose, and ships with KotlinX Serialization plus Room baked in. No additional setup beyond the Gradle line is needed for a basic render.

Does the FlexForm Android SDK work offline?

Yes. The SDK persists draft answers to Room (the Kotlin Multiplatform variant) on every page navigation. When the device returns online, queued submissions sync to the FlexForm backend with exponential backoff. The same session_id is used across reconnects so a partially completed form resumes exactly where the user left it, including any file uploads that were captured but not yet transferred.

Can I update a form without releasing a new app version?

Yes. The FlexForm SDK uses server-driven UI: the form schema is fetched at runtime by formId. Adding a field, changing a label, reordering pages, or updating the theme is published from the FlexForm dashboard and reflects on the next form open. No Play Store release is required for any of these changes, and rollback is one click in the dashboard.

Which form element types does the Android SDK render natively?

All 44 element types from the FlexForm schema render natively in Compose, including short_answer, long_answer, email_input, phone_number, multiple_choice, dropdown, checkboxes, multiselect, date_picker, time_picker, star_rating, opinion_scale, file_uploader, signature, and the layout primitives (h1_heading, paragraph, divider). Each element ships as a Compose function so visual tweaks live in standard Modifier chains.

What is the minimum Android version supported by FlexForm SDK?

The FlexForm Android SDK requires minSdk 28 (Android 9 Pie) and Kotlin 2.0 or newer. Jetpack Compose is required because every element is a Composable. Compose Material 3 is used for default theming, but the SDK exposes hooks so apps that use Material You or a custom design system can override the rendered widgets.

Ship Android forms without writing UI code

Generate a form on the web, drop FlexFormView into your Android app, ship updates without releasing.

Start Free →
A

Atul Kumar

Co-Founder & CTO, FlexForm