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

# Jetpack Compose

> Show video ads from Jetpack Compose with so.kontext:video-compose: rememberVideoAds and VideoAdHost.

The SDK draws ads with Android Views. `so.kontext:video-compose` adds two Compose functions, so a screen does not write the host view, the effects and the listener wiring itself.

## Add the module

```kotlin theme={null}
dependencies {
    implementation("so.kontext:video:0.1.0")
    implementation("so.kontext:video-compose:0.1.0")
}
```

It works with Compose 1.6 or later and uses your app's own Compose version.

## A player screen

```kotlin theme={null}
@Composable
fun EpisodeScreen(content: KontextVideo.Content, player: ExoPlayer) {
    val ads = rememberVideoAds(content) { it.track(player) }
    var adOnScreen by remember { mutableStateOf<VideoAd?>(null) }
    val scope = rememberCoroutineScope()

    Box(Modifier.fillMaxSize()) {
        AndroidView(factory = { PlayerView(it).apply { this.player = player } }, modifier = Modifier.fillMaxSize())
        VideoAdHost(
            ad = adOnScreen,
            modifier = Modifier.fillMaxSize(),
            onEnded = { _, reason ->
                adOnScreen = null
                if (reason == VideoAd.EndReason.RESUMED) player.play()
            },
        )
    }

    // The viewer pauses: show a pause ad
    val onPause = {
        player.pause()
        scope.launch {
            adOnScreen = try { ads.format(AdFormat.PAUSE).apply { load() } } catch (e: VideoAdException) { null }
        }
    }
}
```

Loading the ad early makes `show` instant: keep a loaded ad in state and set it as `adOnScreen` at the moment.

## `rememberVideoAds`

```kotlin theme={null}
val ads = rememberVideoAds(content, labels = mapOf("stage" to "free")) { ads ->
    ads.track(player)           // runs once for each VideoAds created
}
```

* Creates the episode's `VideoAds`, and a new one when `content.id` changes.
* Destroys it when the composition leaves, or when the episode changes. Destroying ends the ad on screen, a linear ad too: move to the next episode after a post-roll's `onEnded`.
* A rotation recreates the composition, and with it the `VideoAds`. Declare `android:configChanges` for rotation on the Activity, or keep the `VideoAds` in a `ViewModel` (below).

## `VideoAdHost`

| Parameter | |
| - | - |
| `ad` | The ad to show. A loaded ad is shown once the host is laid out; `null`, or another ad, dismisses the one shown. |
| `modifier` | Where the ad goes: usually `Modifier.fillMaxSize()` in a `Box` over the player. |
| `insets` | `PaddingValues` that keep an overlay or pause ad clear of your controls. |
| `onShowFailed` | The ad could not be shown, for the reasons `show` throws (`AnotherAdShowing`, `ContentPaused`, `ContainerTooSmall`, …). Nothing was shown, and `onEnded` does not follow. |
| `onImpression`, `onClicked` | The impression was counted; the viewer tapped the ad. |
| `onEnded` | The ad's presentation is over, with its reason. Set your state back to `null` here. |

* **Every format.** An overlay or pause ad shows where the host is placed. A linear ad is shown full screen from the Activity hosting the composition; it is never dismissed by the host, and ends by itself or by the viewer.
* **The host owns the ad's listener.** Hear the ad through the host's callbacks, not `ad.listener`.
* **Leaving the composition** dismisses an overlay or pause ad on screen.

## Ads that survive a rotation

Keep the `VideoAds` and the ad in a `ViewModel`; the host shows whatever the state holds:

```kotlin theme={null}
class EpisodeViewModel : ViewModel() {
    val ads = VideoAds(content)
    var adOnScreen by mutableStateOf<VideoAd?>(null)
    override fun onCleared() = ads.destroy()
}

@Composable
fun EpisodeScreen(viewModel: EpisodeViewModel, player: ExoPlayer) {
    DisposableEffect(player) {
        viewModel.ads.track(player)       // the new player after a recreation
        onDispose { }
    }
    Box(Modifier.fillMaxSize()) {
        // the player
        VideoAdHost(ad = viewModel.adOnScreen, modifier = Modifier.fillMaxSize(), onEnded = { _, _ -> viewModel.adOnScreen = null })
    }
}
```

## A vertical pager

Give each page its own `VideoAds` and host; show an ad on the current page only:

```kotlin theme={null}
VerticalPager(state = pagerState) { page ->
    val episode = episodes[page]
    val ads = rememberVideoAds(episode.content)
    var adOnScreen by remember { mutableStateOf<VideoAd?>(null) }
    Box(Modifier.fillMaxSize()) {
        // the page's player
        VideoAdHost(ad = adOnScreen.takeIf { pagerState.currentPage == page }, modifier = Modifier.fillMaxSize(),
            onEnded = { _, _ -> adOnScreen = null })
    }
}
```

An ad on a page swiped away ends within about a second (`INTERRUPTED`), freeing the screen for the next page's ad.

## Without the module

The same, by hand: host a remembered `FrameLayout` with `AndroidView`, call `show(container)` on it, set the ad's listener in a `DisposableEffect` and clear it in `onDispose`, and destroy the `VideoAds` in `onDispose`. For a linear ad, pass the Activity hosting the composition to `show(activity)`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.