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
AppDNA.showScreen("upgrade_prompt") { result ->
Log.d("Screens", "Screen dismissed: ${result.dismissed}")
Log.d("Screens", "Responses: ${result.responses}")
}
Show a Multi-Screen Flow
AppDNA.showFlow("onboarding_v2") { result ->
Log.d("Screens", "Flow completed: ${result.completed}")
Log.d("Screens", "Screens viewed: ${result.screensViewed}")
}
Dismiss
Screen Slots (Inline Content)
Place named slots in your Compose views. The console assigns screens to slots.
@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:
// 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:
if (BuildConfig.DEBUG) {
val json = """
{"id":"test","name":"Test","presentation":"modal",
"layout":{"type":"scroll"},"sections":[...]}
""".trimIndent()
AppDNA.previewScreen(json = json)
}
Screen Delegate
class MyDelegate : AppDNAScreenDelegate {
override fun onScreenPresented(screenId: String) { }
override fun onScreenDismissed(screenId: String, result: ScreenResult) { }
override fun onFlowCompleted(flowId: String, result: FlowResult) { }
override fun onScreenAction(screenId: String, action: SectionAction): Boolean = true
}
AppDNA.screenDelegate = MyDelegate()
Presentation Modes
Screens support four presentation modes configured in the Console:
| Mode | Description |
|---|
fullscreen | Full-screen modal covering the entire screen |
modal | Standard modal sheet presentation |
bottom_sheet | Draggable bottom sheet |
push | Navigation push (within a navigation stack) |
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 |
| 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
import ai.appdna.sdk.AppDNA
import ai.appdna.sdk.screens.AppDNAScreenDelegate
import ai.appdna.sdk.screens.ScreenResult
import ai.appdna.sdk.screens.FlowResult
import ai.appdna.sdk.screens.SectionAction
class ScreenCoordinator : AppDNAScreenDelegate {
init {
AppDNA.screenDelegate = this
}
fun showUpgradePrompt() {
AppDNA.showScreen("upgrade_prompt") { result ->
if (result.responses["purchased"] == true) {
unlockPremium()
}
}
}
fun startFeatureTour() {
AppDNA.showFlow("feature_tour_v2") { result ->
Log.d("Screens", "Tour completed: ${result.completed}")
}
}
// AppDNAScreenDelegate
override fun onScreenPresented(screenId: String) {
Log.d("Screens", "Screen shown: $screenId")
}
override fun onScreenDismissed(screenId: String, result: ScreenResult) {
Log.d("Screens", "Screen dismissed: $screenId")
}
override fun onFlowCompleted(flowId: String, result: FlowResult) {
Log.d("Screens", "Flow completed: $flowId, screens: ${result.screensViewed}")
}
override fun onScreenAction(screenId: String, action: SectionAction): Boolean {
// Return false to prevent default action handling
return true
}
}
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.