Skip to main content

Kotlin SDK

See how easy it is to integrate high-performance ads into your Android app using our lightweight SDK.

Requirements

  • Min SDK version 26
  • Kotlin 1.9+
  • kotlinx-coroutines 1.8.1+

Migration Guide

Migrating from 1.x to 2.x

AdResult.Success has been removed. Replace it with AdResult.Filled (for available ads) and AdResult.NoFill (for unavailable ads).
// Before
is AdResult.Success -> updateMessagesWithAds(result.ads)

// After
is AdResult.Filled -> updateMessagesWithAds(result.ads)
is AdResult.NoFill -> { /* no ad available, reason: result.skipCode */ }

Getting started

1. Installation

To get started, you will need to set up a publisher account to get a publisherToken and code.
The SDK is available on mavenCentral(). Ensure mavenCentral() is listed in your project’s repository configuration.
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
Add the dependency to your app-level build.gradle.kts file or version catalog:
dependencies {
    implementation("so.kontext:ads:1.0.0") // Replace with the latest version
}

2. Initialize AdsProvider

First, create an instance of AdsProvider using the Builder. This object should be scoped to a single conversation and ideally tied to a lifecycle-aware component, like a ViewModel.
import so.kontext.ads.AdsProvider

val adsProvider = AdsProvider.Builder(
    context = applicationContext, 
    publisherToken = "<publisher-token>", 
    userId = "user-1234", 
    conversationId = "conv-5678",
    enabledPlacementCodes = listOf("inlineAd") 
)
    .regulatory(
        Regulatory(
            coppa = 0,
        )
    )
    .character( // The character object used in this conversation
        Character(
            id = "character-1234",
            name = "John Doe",
            avatarUrl = "https://example.com/avatar.png",
            greeting = "Hello, how can I help you today?"
        )
    )
    .build()

3. Set up IFA (Identifier for Advertisers)

The SDK automatically reads and forwards the advertising identifier (GAID) with each ad request. The AD_ID permission is automatically included via manifest merger from the SDK — no changes to your AndroidManifest.xml are required. No runtime prompt is required — this is an install-time permission.
If your app targets Android 13+ (API level 33+) and you have set tools:node="remove" on the AD_ID permission anywhere in your manifest configuration, remove that override or the advertising identifier will not be accessible.

4. Prepare messages

To provide context for ad targeting, your app’s chat messages must be represented in a way the SDK understands. You have two options: Option 1: Conform to MessageRepresentable You can make your existing message data class conform to the MessageRepresentable interface. This involves overriding the required properties to map to your class’s fields.
import so.kontext.ads.MessageRepresentable
import so.kontext.ads.domain.Role

// Your existing chat message class
data class MyChatMessage(
    val uniqueId: String,
    val text: String,
    val author: String, // "user" or "assistant"
    val creationDate: String // e.g., ISO 8601 format
) : MessageRepresentable {

    // Map your properties to the interface requirements
    override val id: String
        get() = uniqueId

    override val role: Role
        get() = if (author == "user") Role.User else Role.Assistant

    override val content: String
        get() = text

    override val createdAt: String
        get() = creationDate
}
Option 2: Use the AdsMessage Data Class If you cannot or prefer not to modify your existing data class, you can map your message objects to the AdsMessage type provided by the SDK. AdsMessage already conforms to MessageRepresentable.
import so.kontext.ads.AdsMessage
import so.kontext.ads.domain.Role

// When you update the SDK, map your list of messages
val messagesForSdk = myChatMessages.map { myMessage ->
    AdsMessage(
        id = myMessage.uniqueId,
        role = if (myMessage.author == "user") Role.User else Role.Assistant,
        content = myMessage.text,
        createdAt = myMessage.creationDate
    )
}

// Then pass this new list to the provider
adsProvider.setMessages(messagesForSdk)

Updating Messages and Collecting Ads

Whenever your list of messages changes, pass the new list to the AdsProvider. The adsProvider.ads property is a kotlinx.coroutines.flow.Flow that emits AdResult. Collect this flow from a CoroutineScope to receive and display ads.

5. Show your first ad

Once you collected the ads Map in your ViewModel, you can use it in your Composable UI to display the ads. The InlineAd composable is provided for this purpose. It takes an AdConfig object and handles the ad rendering. For View support use InlineAdView.

API documentation

AdsProvider.Builder

Constructor Parameters

context
Context
required
Application context
publisherToken
string
required
Your unique publisher token.
userId
string
required
Unique identifier that remains the same for the user’s lifetime (used for retargeting and rewarded ads).
conversationId
string
required
Unique ID of the conversation.
enabledPlacementCodes
array
Placement codes enabled for the conversation. Example: ['inlineAd'].

Builder Methods

character
object
required
Character object used in this conversation.
variantId
string
Publisher-provided identifier for the user cohort (for A/B testing).
userEmail
string
Email address of the user.
disabled
boolean
If true, the ads generation will be disabled initially. Can be later enabled by calling enable().
theme
String
Your application theme
regulatory
object
Regulatory object used in this conversation.TCF (Transparency and Consent Framework) signals — gdpr and gdprConsent — are handled automatically by the SDK if you have a TCF-compliant CMP (Consent Management Platform) integrated in your app. You do not need to set these manually.

AdResult types

Filled

Ad is available.
ads
Map<String, List<AdConfig>>
required
Map of message IDs to ad configurations.

NoFill

Ad is not available.
skipCode
string
required
The code indicating the reason why the ad was skipped.

Error

An error occurred.
error
KontextError
required
The error that occurred.

InlineAdView view & InlineAd composable properties

config
AdConfig
required
AdConfig object you will receive from the AdsProvider when observing the ads flow.
onEvent
AdEvent
Callback with ad events. All possible types are described below.

AdEvent types

viewed

The user has viewed the ad.
bidId
string
required
Bid id.
content
string
required
Generated ad content.
messageId
string
required
Message ID.
format
string
Format.

clicked

The user has clicked the ad.
bidId
string
required
Bid id.
content
string
required
Generated ad content.
messageId
string
required
Message id.
url
string
required
Click URL.
format
string
Format.

renderStarted

Triggered before the first token is received.
bidId
string
required
Bid id.

renderCompleted

Triggered after the last token is received.
bidId
string
required
Bid id.

error

Triggered when an error occurs.
message
string
required
Error message.
errCode
string
required
Error code.

videoStarted

Triggered when the video playback starts.
bidId
string
required
Bid id.

videoCompleted

Triggered when the video playback finishes.
bidId
string
required
Bid id.

rewardGranted

Triggered when the user receives a reward.
bidId
string
required
Bid id.

Generic

Any other event.
payload
Map<String, Any>
required
Key-value pairs of any other possible event

Guides

Lifecycle Management

It is crucial to release the resources used by the SDK. Call the close() method when the AdsProvider is no longer needed.

Example ViewModel Setup

Here is a simplified example of how to integrate the AdsProvider into an Android ViewModel. For a complete, working implementation, please see the example module in SDK repository.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import so.kontext.ads.AdsProvider
import so.kontext.ads.MessageRepresentable

class ChatViewModel(application: Application) : ViewModel() {

    private val adsProvider: AdsProvider
    private val _messages = MutableStateFlow<List<MyChatMessage>>(emptyList())

    init {
        adsProvider = AdsProvider.Builder(
            context = application,
            publisherToken = "...",
            userId = "...",
            conversationId = "...",
            enabledPlacementCodes = listOf("inlineAd")
        ).build()

        // Collect the flow of ads
        viewModelScope.launch {
            adsProvider.ads.collect { result ->
                when (result) {
                  is AdResult.Filled -> {
                      updateMessagesWithAds(result.ads)
                  }
                  is AdResult.NoFill -> {
                      // No ad available, skipCode: result.skipCode
                  }
                  is AdResult.Error -> {
                      // handle error
                  }
                }
            }
        }
    }

    fun onNewMessage(message: MyChatMessage) {
        // Update your local message list
        val updatedMessages = _messages.value + message
        _messages.value = updatedMessages

        // Pass the updated list to the SDK
        viewModelScope.launch {
            adsProvider.setMessages(updatedMessages)
        }
    }

    override fun onCleared() {
        // IMPORTANT: Clean up SDK resources
        adsProvider.close()
        super.onCleared()
    }
}