> ## 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.

# Android In-App Messages

> Trigger-based banners, modals, and fullscreen messages

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

<Tip>
  You can A/B test this in-app message with no extra code — create an experiment on it in the Console and the SDK serves the assigned variant automatically. See [Servable Surface Experiments](/sdks/android/experiments#servable-surface-experiments).
</Tip>

In-app messages are configured in the AppDNA Console and displayed automatically by the SDK when trigger conditions are met. **No code is required** to show messages — the SDK evaluates triggers on every tracked event and presents the message if conditions match.

## How It Works

1. You create an in-app message in the Console with content, layout, and trigger rules.
2. The message definition is synced to the SDK via the config bundle.
3. On every `track()` call (including auto-tracked events), the SDK evaluates all active message triggers.
4. If conditions match, the message is presented automatically.

<Info>
  In-app messages are fully server-driven. You can change the message content, trigger rules, and audience without an app update.
</Info>

## Message Types

| Type         | Description                                  |
| ------------ | -------------------------------------------- |
| `banner`     | Small bar at the top or bottom of the screen |
| `modal`      | Centered overlay with a dimmed background    |
| `fullscreen` | Full-screen takeover                         |
| `tooltip`    | Small popup anchored to a UI element         |

## Triggers

Messages trigger based on events and optional conditions, configured in the Console:

* **Event match** — e.g., trigger on `workout_completed`
* **Property conditions** — e.g., `duration >= 30`
* **Frequency** — `once`, `once_per_session`, or `every_time`
* **Delay** — wait N seconds after the trigger event before showing

## Module Access

```kotlin theme={null}
val messages = AppDNA.inAppMessages
```

### Module Methods

| Method            | Signature                                            | Description                          |
| ----------------- | ---------------------------------------------------- | ------------------------------------ |
| `suppressDisplay` | `suppressDisplay(suppress: Boolean)`                 | Suppress or resume in-app messages   |
| `setDelegate`     | `setDelegate(delegate: AppDNAInAppMessageDelegate?)` | Set a delegate for message callbacks |

## Suppressing Messages

Suppress messages during critical flows like checkout or onboarding to avoid interrupting the user:

```kotlin theme={null}
// Suppress during purchase flow
AppDNA.inAppMessages.suppressDisplay(true)
startPurchaseFlow()

// Resume after purchase completes
fun purchaseDidComplete() {
    AppDNA.inAppMessages.suppressDisplay(false)
}
```

## AppDNAInAppMessageDelegate

All 4 methods on this delegate fire from `MessageManager` for every in-app message lifecycle event. Register your delegate via `AppDNA.inAppMessages.setDelegate(...)`.

**`shouldShowMessage` is a true veto.** The SDK calls it BEFORE constructing the view or tracking the `in_app_message_shown` analytics event. If you return `false`, the message is suppressed entirely — no view, no analytics. The interface's default implementation returns `true`, so hosts that don't implement this method continue to see all messages.

Implement the delegate to respond to message lifecycle events:

```kotlin theme={null}
interface AppDNAInAppMessageDelegate {
    fun onMessageShown(messageId: String, trigger: String)
    fun onMessageAction(messageId: String, action: String, data: Map<String, Any>?)
    fun onMessageDismissed(messageId: String)
    fun shouldShowMessage(messageId: String): Boolean = true
}
```

### Example Implementation

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

class MessageHandler : AppDNAInAppMessageDelegate {

    override fun onMessageShown(messageId: String, trigger: String) {
        Log.d("Message", "Message shown: $messageId (trigger: $trigger)")
    }

    override fun onMessageAction(messageId: String, action: String, data: Map<String, Any>?) {
        val url = data?.get("url") as? String
        if (url != null) {
            // Handle deep link or URL action
            navigate(url)
        }
    }

    override fun onMessageDismissed(messageId: String) {
        Log.d("Message", "Message dismissed: $messageId")
    }

    override fun shouldShowMessage(messageId: String): Boolean {
        // Return false to prevent the message from being shown
        return true
    }

    private fun navigate(url: String) { /* ... */ }
}

AppDNA.inAppMessages.setDelegate(MessageHandler())
```

## Rich Media

In-app messages support rich media content configured in the Console:

* **Lottie animations** — animated hero images or backgrounds
* **Video** — inline video with optional autoplay and looping
* **Icon buttons** — CTA buttons with icon references (Lucide, Material, or emoji)
* **Haptic feedback** — triggered on message display or button taps
* **Particle effects** — confetti, sparkles, or other effects on message actions
* **Blur backdrop** — glassmorphism-style blurred background for modals

See the [Rich Media](/sdks/android/rich-media) guide for details on supported formats and configuration.

## Auto-Tracked Events

| Event                      | Trigger                                                               |
| -------------------------- | --------------------------------------------------------------------- |
| `in_app_message_shown`     | An in-app message is displayed                                        |
| `in_app_message_clicked`   | User taps an action in the message                                    |
| `in_app_message_dismissed` | Message is closed                                                     |
| `in_app_message_received`  | (Pending-message channel) A message is received before display gating |

## Full Example

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

class AppCoordinator : AppDNAInAppMessageDelegate {

    fun setup() {
        AppDNA.inAppMessages.setDelegate(this)
    }

    fun startOnboarding() {
        // Suppress messages during onboarding
        AppDNA.inAppMessages.suppressDisplay(true)
        presentOnboardingFlow()
    }

    fun onboardingDidFinish() {
        // Resume messages after onboarding
        AppDNA.inAppMessages.suppressDisplay(false)
    }

    // MARK: AppDNAInAppMessageDelegate

    override fun onMessageShown(messageId: String, trigger: String) {
        // Optionally track in your own analytics
    }

    override fun onMessageAction(messageId: String, action: String, data: Map<String, Any>?) {
        when (action) {
            "deep_link" -> (data?.get("url") as? String)?.let { navigate(it) }
            "dismiss" -> { }
            else -> { }
        }
    }

    override fun onMessageDismissed(messageId: String) {
        // Message closed
    }

    override fun shouldShowMessage(messageId: String): Boolean = true

    private fun navigate(url: String) { /* ... */ }
    private fun presentOnboardingFlow() { /* ... */ }
}
```

<Note>
  In-app messages are created in the Console under **Engagement > In-App Messages**. Design the message, set trigger rules, and publish. The SDK handles everything else.
</Note>
