Skip to main content
The AppDNA SDK supports server-driven onboarding flows that are configured remotely and rendered natively using Jetpack Compose. Onboarding flows guide users through welcome screens, questions, and value propositions without requiring app updates.

Present an Onboarding Flow

Present an onboarding flow by passing the current Activity and an optional flow ID:
import ai.appdna.sdk.AppDNA

val presented = AppDNA.presentOnboarding(
    activity = this,
    flowId = "main_flow",
    listener = onboardingListener
)

if (!presented) {
    Log.w("Onboarding", "No onboarding flow available")
}
The method returns a Boolean indicating whether the flow was successfully presented. If flowId is null, the SDK uses the active flow from the remote configuration.
// Use the default active flow
val presented = AppDNA.presentOnboarding(
    activity = this,
    flowId = null,
    listener = onboardingListener
)

Onboarding Module

The AppDNA.onboarding module provides direct access to onboarding functionality:
MethodReturnDescription
present(activity, flowId?, context?)BooleanPresents the onboarding flow
setDelegate(delegate: AppDNAOnboardingDelegate?)UnitSets the onboarding delegate

Onboarding Context

Pass additional context when presenting an onboarding flow:
import ai.appdna.sdk.onboarding.OnboardingContext

val context = OnboardingContext(
    source = "deep_link",
    campaign = "summer_promo",
    referrer = "friend_invite",
    userProperties = mapOf("tier" to "free"),
    experimentOverrides = mapOf("welcome_variant" to "b")
)

AppDNA.onboarding.present(
    activity = this,
    flowId = "main_flow",
    context = context
)

OnboardingContext Properties

PropertyTypeDescription
sourceString?Where the user came from
campaignString?Marketing campaign identifier
referrerString?Referral source
userPropertiesMap<String, Any>?Additional user properties for targeting
experimentOverridesMap<String, String>?Override experiment variants for testing

Onboarding Delegate

Implement AppDNAOnboardingDelegate to receive onboarding lifecycle callbacks:
import ai.appdna.sdk.onboarding.AppDNAOnboardingDelegate

class MyOnboardingDelegate : AppDNAOnboardingDelegate {

    override fun onOnboardingStarted(flowId: String) {
        Log.d("Onboarding", "Flow started: $flowId")
    }

    override fun onOnboardingStepChanged(
        flowId: String,
        stepId: String,
        stepIndex: Int,
        totalSteps: Int
    ) {
        Log.d("Onboarding", "Step $stepIndex/$totalSteps: $stepId")
    }

    override fun onOnboardingCompleted(flowId: String, responses: Map<String, Any>) {
        Log.d("Onboarding", "Flow completed: $flowId")
        Log.d("Onboarding", "Responses: $responses")
        // Navigate to main app
    }

    override fun onOnboardingDismissed(flowId: String, atStep: Int) {
        Log.d("Onboarding", "Flow dismissed at step $atStep")
    }

    // Async hooks (optional — default implementations provided)
    override suspend fun onBeforeStepAdvance(
        flowId: String, fromStepId: String, stepIndex: Int,
        stepType: String, responses: Map<String, Any>,
        stepData: Map<String, Any>?
    ): StepAdvanceResult = StepAdvanceResult.Proceed

    override suspend fun onBeforeStepRender(
        flowId: String, stepId: String, stepIndex: Int,
        stepType: String, responses: Map<String, Any>
    ): StepConfigOverride? = null
}

// Set the delegate
AppDNA.onboarding.setDelegate(MyOnboardingDelegate())

Step Types

Onboarding flows are composed of steps, each with a specific type:
Step TypeDescription
WELCOMEWelcome or splash screen
QUESTIONUser input step with single or multi-select options
VALUE_PROPValue proposition or feature highlight screen
FORMStructured form with multiple native input fields
PERMISSIONPush notification or tracking permission request
CUSTOMCustom step rendered from server-defined templates

Selection Modes

Question steps support two selection modes:
ModeDescription
SINGLEUser selects exactly one option
MULTIUser can select multiple options
Onboarding flows are rendered using Jetpack Compose. The SDK handles the full UI lifecycle, including step transitions, animations, and response collection. Your app only needs to handle the delegate callbacks.

Content Blocks

Each onboarding step is composed of content blocks that control the visual layout. Blocks are configured in the Console and rendered natively using Jetpack Compose. The following block types are available:
Block TypeDescription
TITLEPrimary heading text
SUBTITLESecondary descriptive text
IMAGEStatic image with sizing and corner radius
LOTTIELottie animation (JSON or dotLottie)
RIVERive state-machine animation
VIDEOInline video (MP4, HLS) with autoplay and loop options
BUTTONTappable button with configurable action
OPTION_LISTList of selectable options (single or multi-select)
FORMForm input group (see Form Steps below)
PAGE_INDICATORDot or bar indicator showing current step progress
WHEEL_PICKERScrollable wheel-style picker for value selection
PULSING_AVATARAnimated avatar with a pulsing ring effect
SOCIAL_LOGINSocial sign-in buttons (Google, etc.)
TIMELINEVertical timeline with labeled milestones
ANIMATED_LOADINGSkeleton or spinner loading animation between steps
COUNTDOWN_TIMERCountdown timer with configurable duration and expiry action
RATINGStar or emoji rating selector
RICH_TEXTMarkdown-style rich text with inline formatting
PROGRESS_BARHorizontal progress bar with percentage or label
CIRCULAR_GAUGECircular progress indicator with value label
DATE_WHEEL_PICKERNative date wheel picker (day/month/year columns)
STACKVertical container that groups child blocks
ROWHorizontal container that arranges child blocks side by side
CUSTOM_VIEWHost-app-provided Composable view (see Custom View Registration)
STAR_BACKGROUNDAnimated starfield or particle background effect
PRICING_CARDProduct pricing card with plan details and CTA
Blocks are configured entirely in the Console. The SDK renders them automatically — no code is needed unless you register custom views.

Custom View Registration

Register your own Composable views to be rendered inside onboarding steps wherever a CUSTOM_VIEW block appears:
AppDNA.registerCustomView("my_view") {
    MyComposableView()
}

// Register before presenting the onboarding flow
AppDNA.registerCustomView("terms_acceptance") {
    TermsAcceptanceView(onAccept = { accepted ->
        // Handle acceptance
    })
}
The id must match the custom view identifier configured in the Console for the CUSTOM_VIEW block.

Dynamic Bindings

When you return data from the onBeforeStepRender hook, that data flows into block properties via a bindings map. This allows you to inject dynamic content into any block at render time.
override suspend fun onBeforeStepRender(
    flowId: String, stepId: String, stepIndex: Int,
    stepType: String, responses: Map<String, Any>
): StepConfigOverride? {
    return StepConfigOverride(
        bindings = mapOf(
            "hero_title" to "Welcome, $userName!",
            "plan_price" to fetchedPrice,
            "avatar_url" to userAvatarUrl
        )
    )
}
In the Console, reference bindings in block properties using {{binding_key}} syntax. The SDK resolves these before rendering.

Block Styling

Every content block supports a block_style design token that controls appearance properties such as padding, margin, background color, corner radius, border, shadow, and opacity. Block styles are configured in the Console and applied automatically by the SDK.

Visibility Conditions

Blocks can be shown or hidden based on user responses, bindings, or device attributes. Visibility conditions are configured per-block in the Console using rules like answer_equals, binding_not_empty, platform_is, and locale_matches. The SDK evaluates conditions client-side before rendering each block.

Entrance Animations

Each block supports an entrance animation that plays when the block first appears. Animations are configured per-block in the Console. Supported animation types include FADE, SLIDE_UP, SLIDE_DOWN, SLIDE_LEFT, SLIDE_RIGHT, SCALE, FLIP, and BOUNCE. You can configure duration, delay, and easing curve.

Form Steps

The FORM step type provides native input fields for collecting structured user data. Each form can contain multiple fields with validation, conditional visibility, and custom configuration.

Supported Input Types

TypeDescriptionExample Use Case
TEXTSingle-line text inputName, username
TEXTAREAMulti-line text inputBio, notes
NUMBERNumeric input with stepperAge, quantity
EMAILEmail input with validationEmail address
PHONEPhone number inputContact number
DATEDate pickerBirthday, start date
TIMETime pickerPreferred time
DATETIMECombined date and time pickerAppointment scheduling
SELECTDropdown or scrollable pickerCountry, category
SLIDERNumeric slider with min/maxBudget, intensity level
TOGGLEOn/off switchOpt-in preferences
STEPPERIncrement/decrement counterNumber of items
SEGMENTEDSegmented control for few optionsGender, frequency
PASSWORDSecure text input with visibility togglePassword, PIN
RATINGStar rating inputSatisfaction, preference
RANGE_SLIDERDual-handle range sliderPrice range, age range
IMAGE_PICKERPhoto picker from gallery or cameraProfile photo, document
COLORColor picker or preset swatchesTheme preference, branding
URLURL input with validationWebsite, portfolio link
CHIPSTag-style multi-select chipsInterests, skills
SIGNATUREFreehand signature drawing padAgreement, consent
LOCATIONAutocomplete location searchCity, address, country

Field Validation

Form fields support built-in and custom validation:
  • Required fields — marked in the Console, the SDK prevents advancing until filled
  • Regex patterns — custom validation (e.g., ^[A-Z]{2}\\d{4}$ for a code format)
  • Min/max values — for NUMBER, SLIDER, and STEPPER fields
  • Max length — for TEXT and TEXTAREA fields

Conditional Fields

Fields can depend on other fields using depends_on rules. For example, a “Company name” field can appear only when the user selects “Employed” in a previous field. Supported operators: equals, not_equals, contains, not_empty, empty, gt, lt, is_set.

Form Responses

Form field values are included in the responses map passed to onOnboardingCompleted, keyed by step ID:
override fun onOnboardingCompleted(flowId: String, responses: Map<String, Any>) {
    val formData = responses["profile_step"] as? Map<String, Any>
    val name = formData?.get("full_name") as? String
    val age = formData?.get("age") as? Int
    val email = formData?.get("email") as? String
    // Use collected data to personalize the experience
}

Location Fields

The LOCATION field type provides an autocomplete search input that returns structured location data including city, state, country, coordinates, and timezone. When the user types a location name, the SDK calls the AppDNA geocoding proxy and displays matching suggestions. The selected result is stored as structured data.

Location Data Structure

Each location selection contains:
FieldTypeExample
formatted_addressString"New York, New York, United States"
cityString"New York"
stateString"New York"
state_codeString"NY"
countryString"United States"
country_codeString"US"
latitudeDouble40.7128
longitudeDouble-74.0060
timezoneString"America/New_York"
timezone_offsetInt-300 (minutes from UTC)
postal_codeString?null

Accessing Location Data

Location data is included in the form responses:
override fun onOnboardingCompleted(flowId: String, responses: Map<String, Any>) {
    val locationStep = responses["location_step"] as? Map<String, Any>
    val location = locationStep?.get("user_location") as? Map<String, Any>
    if (location != null) {
        val city = location["city"] as? String         // "New York"
        val country = location["country_code"] as? String // "US"
        val timezone = location["timezone"] as? String  // "America/New_York"
        val lat = location["latitude"] as? Double       // 40.7128
        val lng = location["longitude"] as? Double      // -74.0060
    }
}

Template Engine

Location data is accessible in dynamic content templates:
Welcome from {{onboarding.location_step.user_location.city}}!
Your timezone: {{onboarding.location_step.user_location.timezone}}

Configuration in Console

In the onboarding flow editor, add a LOCATION field to a form step and configure:
OptionDescriptionDefault
Location TypeFilter results: city, address, region, countrycity
Bias CountryISO country code to prioritize results (e.g., US)None
LanguageLanguage for results (e.g., en, fr)en
Min CharactersCharacters required before search triggers2
Location autocomplete uses a server-side proxy — no third-party SDK is added to your app binary. The geocoding provider (Mapbox by default) can be configured in Settings > Geocoding.

Async Step Hooks

The onboarding delegate supports two suspend functions that let you intercept step transitions for server-side validation, dynamic content loading, or custom routing logic.

onBeforeStepAdvance

Called before the SDK advances to the next step. Return a StepAdvanceResult to control what happens next:
override suspend fun onBeforeStepAdvance(
    flowId: String,
    fromStepId: String,
    stepIndex: Int,
    stepType: String,
    responses: Map<String, Any>,
    stepData: Map<String, Any>?
): StepAdvanceResult {
    // Example: validate a referral code with your backend
    if (stepType == "form") {
        val formData = responses[fromStepId] as? Map<String, Any>
        val code = formData?.get("referral_code") as? String
        if (code != null) {
            val isValid = validateReferralCode(code) // your suspend function
            if (!isValid) {
                return StepAdvanceResult.Block("Invalid referral code. Please try again.")
            }
            return StepAdvanceResult.ProceedWithData(mapOf("referral_validated" to true))
        }
    }
    return StepAdvanceResult.Proceed
}

StepAdvanceResult

CaseDescription
ProceedContinue to the next step normally
ProceedWithData(data)Continue and merge additional data into the session
Block(message)Block advancement and show an error message to the user
SkipTo(stepId, data?)Skip to a specific step by ID, optionally with data

onBeforeStepRender

Called before a step is rendered. Return a StepConfigOverride to dynamically modify the step’s content:
override suspend fun onBeforeStepRender(
    flowId: String,
    stepId: String,
    stepIndex: Int,
    stepType: String,
    responses: Map<String, Any>
): StepConfigOverride? {
    // Pre-fill form fields based on user data
    if (stepId == "profile_step") {
        return StepConfigOverride(
            fieldDefaults = mapOf(
                "email" to currentUser.email,
                "name" to currentUser.displayName
            ),
            title = "Welcome back, ${currentUser.firstName}!"
        )
    }
    return null
}
Both hooks are suspend functions — the SDK shows a loading indicator while waiting for your response. If either hook throws an exception or times out, the SDK proceeds normally.

Row Direction and Distribution

The ROW content block supports configurable direction and distribution, set in the Console:
PropertyOptionsDescription
directionHORIZONTAL, VERTICALAxis along which child blocks are arranged
distributionEQUAL, FILL, START, CENTER, END, SPACE_BETWEEN, SPACE_AROUNDHow child blocks are distributed within the row
// Example: Two buttons side by side, equally spaced
Row (direction: HORIZONTAL, distribution: EQUAL)
  ├── Button "Skip"
  └── Button "Continue"

Button Gradients

Buttons support gradient backgrounds configured in the Console:
PropertyTypeDescription
gradient_colorsList<String>Array of hex color stops (e.g., ["#FF6B6B", "#4ECDC4"])
gradient_directionStringhorizontal, vertical, diagonal_tl_br, diagonal_tr_bl
Gradients override the solid background_color when set.

Select Display Styles

Question steps with selectable options support three display styles:
StyleDescription
DROPDOWNNative dropdown picker. Best for long option lists (5+ items).
STACKEDVertically stacked option buttons. Default style.
GRIDGrid layout with 2 or 3 columns. Good for visual options with icons.
The display style is configured per question step in the Console.

Progress Bar Custom Colors

The PROGRESS_BAR content block supports custom color configuration:
PropertyTypeDescription
fill_colorStringHex color for the filled portion
track_colorStringHex color for the unfilled track
fill_gradientList<String>Gradient color stops for the fill (overrides fill_color)
corner_radiusNumberCorner radius of the progress bar
heightNumberHeight of the progress bar in dp
Colors are configured per block in the Console. When not set, the SDK uses the app’s primary theme color.

Per-Step Progress Visibility

The progress indicator can be hidden on specific steps while still counting them in the total progress. This is useful for splash screens, permission prompts, or transition steps where the progress bar would be distracting. Configure hide_progress per step in the Console under Step Design > Logic. When enabled, the progress bar is hidden on that step but the step still contributes to the overall progress calculation (e.g., step 3 of 5 still advances progress to 60%).
PropertyTypeDefaultDescription
hide_progressBooleanfalseHides the progress indicator on this step only
This is a per-step override of the flow-level show_progress setting. If show_progress is false at the flow level, the progress bar is hidden on all steps regardless of hide_progress.

Conditional Branching

Onboarding flows support conditional branching — the next step changes based on the user’s answer. Branching is configured entirely in the Console:
  1. Open your flow in the Console (Onboarding > Flows)
  2. On a question step, click Add branching rule
  3. Map each answer option to a target step
The SDK handles routing automatically — no code needed. All answers are returned in the responses map in onOnboardingCompleted.

Auto-Tracked Events

The onboarding module automatically tracks the following events:
EventTriggered When
onboarding_flow_startedUser begins an onboarding flow
onboarding_step_viewedA step is displayed to the user
onboarding_step_completedUser completes a step
onboarding_step_skippedUser skips a step
onboarding_flow_completedUser reaches the end of the flow
onboarding_flow_dismissedUser dismisses the flow before completing
The responses map passed to onOnboardingCompleted contains all user answers keyed by step ID. Use these responses to personalize the user experience or send them to your backend for analysis.

Next Steps

  • Present Paywalls at the end of onboarding flows
  • Set up Billing to handle purchases triggered from onboarding
  • Learn about Offline Support for onboarding config caching