> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Driven Screens

> Display any screen designed in the console, rendered natively

<Info>
  **Supported on:** Android SDK `1.0.33+`
</Info>

## Overview

Server-driven screens let growth teams design and deploy native screens from the AppDNA console -- no app release needed. The SDK renders screens from JSON config fetched via Firestore.

Screens combine content blocks from any SDK module (onboarding, paywalls, surveys, messages) into a single composable surface. You can build feature announcements, upgrade prompts, referral screens, guided tutorials, and more -- all without writing native UI code.

## Show a Screen

```kotlin theme={null}
AppDNA.showScreen("upgrade_prompt") { result ->
    Log.d("Screens", "Screen dismissed: ${result.dismissed}")
    Log.d("Screens", "Responses: ${result.responses}")
}
```

## Show a Multi-Screen Flow

```kotlin theme={null}
AppDNA.showFlow("onboarding_v2") { result ->
    Log.d("Screens", "Flow completed: ${result.completed}")
    Log.d("Screens", "Screens viewed: ${result.screensViewed}")
}
```

## Dismiss

```kotlin theme={null}
AppDNA.dismissScreen()
```

## Screen Slots (Inline Content)

Place named slots in your Compose views. The console assigns screens to slots.

```kotlin theme={null}
@Composable
fun HomeScreen() {
    Column {
        AppDNAScreenSlot("home_hero")
        // ... your app content ...
        AppDNAScreenSlot("home_bottom")
    }
}
```

* Empty slots render nothing (no visual impact)
* Content updates on next config refresh
* Supports audience targeting per slot

## Navigation Interception

Automatically inject screens between app navigations:

```kotlin theme={null}
// Enable (one-time setup)
AppDNA.enableNavigationInterception()

// Or for specific screens only
AppDNA.enableNavigationInterception(forScreens = listOf("SettingsActivity", "Premium*"))

// Disable
AppDNA.disableNavigationInterception()
```

## Debug Preview

Test screens from raw JSON without publishing to Firestore:

```kotlin theme={null}
if (BuildConfig.DEBUG) {
    val json = """
    {"id":"test","name":"Test","presentation":"modal",
     "layout":{"type":"scroll"},"sections":[...]}
    """.trimIndent()
    AppDNA.previewScreen(json = json)
}
```

## Screen Delegate

The delegate fires for **every** screen lifecycle event. Result and action payloads are delivered as `Map<String, Any?>` for forward-compat with new fields the SDK adds (e.g., `last_action`, `duration_ms`):

```kotlin theme={null}
import ai.appdna.sdk.screens.AppDNAScreenDelegate

class MyDelegate : AppDNAScreenDelegate {
    override fun onScreenPresented(screenId: String) { }

    override fun onScreenDismissed(screenId: String, result: Map<String, Any?>) {
        val dismissed = result["dismissed"] as? Boolean ?: false
        val responses = result["responses"] as? Map<String, Any?>
        val lastAction = result["last_action"] as? String
    }

    override fun onFlowCompleted(flowId: String, result: Map<String, Any?>) {
        val completed = result["completed"] as? Boolean ?: false
        val screensViewed = result["screens_viewed"] as? List<String> ?: emptyList()
        val responses = result["responses"] as? Map<String, Any?>
    }

    override fun onScreenAction(screenId: String, action: Map<String, Any?>): Boolean {
        val type = action["type"] as? String
        // Return true to let the SDK apply default handling, false to suppress it
        return true
    }
}

AppDNA.screenDelegate = MyDelegate()
```

The top-level `AppDNA.showScreen(...)` / `AppDNA.showFlow(...)` callbacks DO use the typed `ScreenResult` / `FlowResult` data classes (see [API Reference](/sdks/android/api-reference)) — only the delegate uses untyped maps.

## Presentation Modes

Screens support the following presentation modes configured in the Console:

| Mode           | Description                                                               |
| -------------- | ------------------------------------------------------------------------- |
| `fullscreen`   | Full-screen modal covering the entire screen (default)                    |
| `modal`        | Sheet that doesn't cover the full height (iOS pageSheet-style on Android) |
| `page_sheet`   | Alias of `modal` on Android — same rendering                              |
| `bottom_sheet` | Draggable bottom sheet                                                    |

Unrecognized values fall through to `fullscreen`.

## Section Types

Screens are composed of ordered sections. The unified section registry includes content from all SDK modules:

| Category   | Section Types                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| Generic    | `content_blocks`, `hero`, `spacer`, `divider`, `cta_footer`, `sticky_footer`                            |
| Onboarding | `onboarding_step`, `progress_indicator`, `navigation_controls`                                          |
| Paywall    | `paywall_header`, `paywall_plans`, `paywall_cta`, `paywall_features`, and more                          |
| Survey     | `survey_question`, `survey_nps`, `survey_csat`, `survey_rating`, `survey_free_text`, `survey_thank_you` |
| Message    | `message_banner`, `message_modal`, `message_content`                                                    |
| Media      | `image_section`, `video_section`, `lottie_section`, `rive_section`                                      |

## Auto-Tracked Events

| Event                    | When                   |
| ------------------------ | ---------------------- |
| `screen_presented`       | Screen appears         |
| `screen_dismissed`       | Screen disappears      |
| `screen_action`          | User taps a CTA        |
| `flow_started`           | Flow begins            |
| `flow_completed`         | Flow finishes          |
| `flow_abandoned`         | Flow dismissed early   |
| `slot_rendered`          | Slot displays content  |
| `slot_registered`        | Slot first renders     |
| `interception_triggered` | Nav interception fires |

## Full Example

```kotlin theme={null}
import ai.appdna.sdk.AppDNA
import ai.appdna.sdk.screens.AppDNAScreenDelegate

class ScreenCoordinator : AppDNAScreenDelegate {

    init {
        AppDNA.screenDelegate = this
    }

    fun showUpgradePrompt() {
        AppDNA.showScreen("upgrade_prompt") { result ->
            // top-level callback uses the typed ScreenResult data class
            if (result.responses["purchased"] == true) {
                unlockPremium()
            }
        }
    }

    fun startFeatureTour() {
        AppDNA.showFlow("feature_tour_v2") { result ->
            Log.d("Screens", "Tour completed: ${result.completed}")
        }
    }

    // AppDNAScreenDelegate (maps)

    override fun onScreenPresented(screenId: String) {
        Log.d("Screens", "Screen shown: $screenId")
    }

    override fun onScreenDismissed(screenId: String, result: Map<String, Any?>) {
        val dismissed = result["dismissed"] as? Boolean
        Log.d("Screens", "Screen $screenId dismissed=$dismissed")
    }

    override fun onFlowCompleted(flowId: String, result: Map<String, Any?>) {
        val screens = result["screens_viewed"] as? List<String> ?: emptyList()
        Log.d("Screens", "Flow $flowId completed, screens=$screens")
    }

    override fun onScreenAction(screenId: String, action: Map<String, Any?>): Boolean {
        // Return false to prevent default action handling
        return true
    }

    private fun unlockPremium() { /* ... */ }
}
```

<Note>
  Screens are rendered using Jetpack Compose. The SDK handles the full UI lifecycle. Screens are delivered via the same Firestore config bundle as other SDK modules.
</Note>
