Teads Android SDK Integration Guide
This guide provides comprehensive instructions for integrating the Teads SDK into your Android application. Whether you're building a news app, content platform, or any other type of application, this guide will help you implement both video advertising and content recommendations.
Installation
For detailed installation instructions, see Installation.
SDK Initialization
Initialize the Teads SDK as early as possible in your app's lifecycle:
import tv.teads.sdk.TeadsSDK
// Initialize the SDK with your partner key
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
TeadsSDK.configure(applicationContext, "YOUR_PARTNER_KEY")
// Enable test mode for development
if (BuildConfig.DEBUG) {
TeadsSDK.testMode = true
TeadsSDK.testLocation = "us" // Optional: Test specific geolocations
}
}
}
Important: Calling the method the TeadsSDK.configure with your YOUR_PARTNER_KEY is mandatory when using the Feed or Recommendations placements. Otherwise, you can skip this part.
Implementation Samples
For complete working examples and sample applications, refer to our public GitHub repositories:
- Android Sample App - Complete Android implementation with all placement types
These repositories contain:
- Full working applications demonstrating all placement types
- Best practices and common integration patterns
Direct Integrations on This Page
| Placement | Description |
|---|---|
| Media Placement (Video Ads) | Premium video ads in-content |
| Feed Placement (Content Recommendations) | Content recommendation widgets |
| Media Native Placement | Custom-layout video ads |
| Recommendations API | Programmatic recommendations with custom UI |
| Banner Placement (Direct) | Anchored adaptive banner ad |
| Interstitial Placement (Direct) | Fullscreen ads at transition points |
Settings
Find the full settings list for each placement in the Settings Configuration.
Media Placement (Video Ads)
Media placements display premium video advertisements within your content.
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementMedia
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementMediaConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import android.net.Uri
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var mediaPlacement: TeadsAdPlacementMedia? = null
private lateinit var binding: ActivityArticleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityArticleBinding.inflate(layoutInflater)
setContentView(binding.root)
setupMediaPlacement()
}
private fun setupMediaPlacement() {
// Create configuration
val config = TeadsAdPlacementMediaConfig(
pid = 84242, // Your unique Placement ID
articleUrl = Uri.parse("https://yoursite.com/article")
)
// Create placement with delegate
mediaPlacement = TeadsAdPlacementMedia(
this, // Context
config, // Placement config
this // Event delegate
)
// Load the ad
val mediaAdView = mediaPlacement?.loadAd()
// Add to your view hierarchy
binding.myContainerAdView.addView(mediaAdView)
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// Listen the ad lifecycle events
}
override fun onDestroy() {
super.onDestroy()
teadsAdPlacementMedia?.clean()
}
}
Important: When using Media Placement, it's mandatory to set your ad container to dynamic sizing instead of fixed dimensions.
This is because ad creatives served for Media Placement have not a fixed size. The VPAID standard allows them to have dynamic dimensions and aspect ratios, meaning they can change size and shape on the fly.
By using dynamic sizing, you ensure the ad container can automatically resize to perfectly fit the creative's content, preventing issues like cropping or blank spaces. This dynamic resizing is crucial for delivering a great user experience and accurately measuring ad performance throughout the entire ad lifecycle, not just at initialization.
XML Implementation: Use android:layout_height="wrap_content" for your ad container.
Compose Implementation: Use Modifier.wrapContentHeight() or Modifier.height(IntrinsicSize.Min) for your ad container.
Feed Placement (Content Recommendations)
Feed placements display content recommendation widgets, perfect for keeping users engaged with related content.
Use an Activity context to create TeadsAdPlacementFeed (not the application context). The SDK uses it to open paid recommendation clicks and to size the creative correctly. Passing the application context can cause paid clicks to not open and the ad to be mis-sized.
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementFeed
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementFeedConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import android.net.Uri
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var feedPlacement: TeadsAdPlacementFeed? = null
private lateinit var binding: ActivityContentBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityContentBinding.inflate(layoutInflater)
setContentView(binding.root)
setupFeedPlacement()
}
private fun setupFeedPlacement() {
// Create configuration
val config = TeadsAdPlacementFeedConfig(
articleUrl = Uri.parse("https://yoursite.com/article"),
widgetId = "MB_1", // Your unique Placement ID
installationKey = "YOUR_INSTALLATION_KEY",
widgetIndex = 0,
userId = null, // Optional user ID for personalization
darkMode = false,
testDisplay = false,
extId = null, // External ID
extSecondaryId = null, // External Secondary ID
obPubImpl = null // OB Publisher Implementation
)
// Create placement
feedPlacement = TeadsAdPlacementFeed(
this, // Context — MUST be an Activity
config, // Placement config
this // Event delegate
)
// Load the feed
val feedView = feedPlacement?.loadAd()
// Add to your view hierarchy
binding.myContainerAdView.addView(feedView)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
feedPlacement?.onActivityConfigurationChanged()
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// Listen the ad lifecycle events
if (placement is TeadsAdPlacementFeed && event == TeadsAdPlacementEventName.CLICKED_ORGANIC) {
val url = data?.get("url") as? String
url?.let {
// Programmatically open browser with the url
}
}
}
}
Explore More Feature
The Feed placement supports an "Explore More" feature that shows additional content when users navigate away:
// Enable explore more when the user leaves the article
override fun onBackPressed() {
super.onBackPressed()
TeadsAdPlacementFeed.handleExploreMore(MyContentActivity@this) {
runOnUiThread { finish() }
}
}
Multiple Feed Widgets
You can display multiple feed widgets on the same page by using the widgetIndex parameter. Each widget is identified by its index, starting from 0. To create a second widget, use .copy(widgetIndex = 1) on an existing configuration:
// First widget (widgetIndex = 0, the default)
val config = TeadsAdPlacementFeedConfig(
widgetId = "MB_1",
articleUrl = Uri.parse("https://yoursite.com/article"),
installationKey = "YOUR_INSTALLATION_KEY",
widgetIndex = 0
)
// Second widget — reuse the same config, just change widgetIndex
val config2 = config.copy(widgetIndex = 1)
RecyclerView / LazyColumn require sequential loading. In recycling views, the second widget's loadAd() must be deferred until the first widget (widgetIndex = 0) fires the LOADED event. Because RecyclerView and LazyColumn recycle view holders, the second widget may fail to initialize if it loads before the first one completes.
In non-recycling views (ScrollView, Column) both widgets can call loadAd() simultaneously because every view stays attached to the hierarchy.
Below is a simplified RecyclerView example based on the demo app:
class Feed2WidgetsFragment : Fragment(), TeadsAdPlacementEventsDelegate {
private lateinit var feedAd: TeadsAdPlacementFeed
private lateinit var feedAd2: TeadsAdPlacementFeed
private lateinit var adapter: ArticleAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val config = TeadsAdPlacementFeedConfig(
widgetId = "MB_1",
articleUrl = Uri.parse("https://yoursite.com/article"),
installationKey = "YOUR_INSTALLATION_KEY",
widgetIndex = 0
)
val config2 = config.copy(widgetIndex = 1)
// requireContext() returns the Activity context — required (do not pass applicationContext)
feedAd = TeadsAdPlacementFeed(requireContext(), config, this)
feedAd2 = TeadsAdPlacementFeed(requireContext(), config2, this)
adapter = ArticleAdapter(feedAd, feedAd2)
view.findViewById<RecyclerView>(R.id.recycler_view).apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@Feed2WidgetsFragment.adapter
}
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// When the first widget is loaded, allow the second widget to load
if (placement === feedAd && event == TeadsAdPlacementEventName.LOADED) {
adapter.isFirstAdReady = true
view?.post {
adapter.notifyItemChanged(ArticleAdapter.SECOND_AD_POSITION)
}
}
if (event == TeadsAdPlacementEventName.CLICKED_ORGANIC) {
val url = data?.get("url") as? String
// Open URL in browser
}
}
}
class ArticleAdapter(
private val feedAd: TeadsAdPlacementFeed,
private val feedAd2: TeadsAdPlacementFeed
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var isFirstAdReady = false
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (position) {
FIRST_AD_POSITION -> {
val adHolder = holder as TeadsPlacementViewHolder
if (adHolder.container.isEmpty()) {
adHolder.container.addView(feedAd.loadAd())
}
}
SECOND_AD_POSITION -> {
val adHolder = holder as TeadsPlacementViewHolder
// Only load once the first widget has fired LOADED
if (isFirstAdReady && adHolder.container.isEmpty()) {
adHolder.container.addView(feedAd2.loadAd())
}
}
// ... other view types
}
}
companion object {
private const val FIRST_AD_POSITION = 6
const val SECOND_AD_POSITION = 11
}
}
Media Native Placement
Media Native placements allow you to create custom layouts for video ads that match your app's design.
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementMediaNative
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementMediaNativeConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.renderer.NativeAdView
import android.net.Uri
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var nativePlacement: TeadsAdPlacementMediaNative? = null
private var nativeAdView: NativeAdView? = null
private lateinit var binding: ActivityNativeAdBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNativeAdBinding.inflate(layoutInflater)
setContentView(binding.root)
setupNativeAd()
}
private fun setupNativeAd() {
// Create custom native ad view
nativeAdView = createCustomNativeAdView()
// Create configuration
val config = TeadsAdPlacementMediaNativeConfig(
124859, // Your unique Placement ID
Uri.parse("https://yoursite.com/article")
)
// Create placement
nativePlacement = TeadsAdPlacementMediaNative(
this, // Context
config, // Placement config
this // Event delegate
)
// Load and bind the ad
nativePlacement
?.loadAd()
?.let { binder ->
// Bind the ad native object to your native ad view
binder(binding.myContainerAdView.nativeAdView)
}
}
override fun onPlacementEvent(
placement: TeadsAdPlacement,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// Listen the ad lifecycle events
}
override fun onDestroy() {
super.onDestroy()
nativePlacement?.clean()
}
}
Recommendations API
For programmatic access to content recommendations with custom UI:
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementRecommendations
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsConfig
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsURLConfig
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementRecommendationsPlatformConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import com.outbrain.OBSDK.Entities.OBRecommendation
import com.outbrain.OBSDK.Entities.OBRecommendationsResponse
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var recommendationsPlacement: TeadsAdPlacementRecommendations? = null
private lateinit var binding: ActivityRecommendationsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRecommendationsBinding.inflate(layoutInflater)
setContentView(binding.root)
loadRecommendations()
}
private fun loadRecommendations() {
// Create configuration — pass a URL or platform request config (see below)
val config = TeadsAdPlacementRecommendationsConfig(
widgetId = "SDK_1", // Your widget ID
urlConfig = TeadsAdPlacementRecommendationsURLConfig(
articleUrl = Uri.parse("https://yoursite.com/article")
)
)
// Create placement
recommendationsPlacement = TeadsAdPlacementRecommendations(
config, // Placement config
this // Event delegate
)
// Fetch recommendations
lifecycleScope.launch {
try {
val recommendations = recommendationsPlacement?.loadAdSuspend()
recommendations?.let { displayRecommendations(it) }
} catch (e: Exception) {
Log.e("TeadsSDK", "Failed to load recommendations: ${e.message}")
}
}
}
private fun displayRecommendations(recommendations: OBRecommendationsResponse) {
binding.recommendationsContainer.removeAllViews()
if (recommendations.all.isNotEmpty()) {
recommendations.all.forEach { recommendations ->
val recommendationView = createRecommendationView(recommendations)
binding.recommendationsContainer.addView(recommendationView)
}
}
}
private fun createRecommendationView(recommendation: OBRecommendation): View {
val binding = RecommendationItemBinding.inflate(LayoutInflater.from(this))
binding.recommendationTitle.text = recommendation.content
binding.recommendationSource.text = recommendation.sourceName
// Load image (using your preferred image loading library)
recommendation.thumbnail?.url?.let { imageUrl ->
// E.g. Glide.with(this).load(imageUrl).into(binding.recommendationImage)
}
// Handle click
binding.root.setOnClickListener {
TeadsAdPlacementRecommendations.getUrl(recommendation)?.let { url ->
// Open URL in browser
}
}
// Configure viewability tracking — required for accurate impression reporting
TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
binding.root as ViewGroup,
recommendation
)
return binding.root
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// Listen the ad lifecycle events
}
}
Viewability tracking
Viewability tracking is required for accurate impression reporting. Publishers who skip this step will see incorrect viewability metrics in their dashboard.
Call TeadsAdPlacementRecommendations.configureViewabilityPerListingFor() for each recommendation view as you add it to the layout. Pass the ViewGroup container that wraps the recommendation content and the corresponding OBRecommendation object:
TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
recContainer, // ViewGroup holding the recommendation content
recommendation
)
The SDK monitors viewability automatically once configured and fires the TeadsAdPlacementEventName.VIEWED event when the IAB viewability threshold is met.
AdChoices compliance
AdChoices compliance is required. Omitting the widget icon click handler or the per-item disclosure icon violates Teads ad serving policies.
Widget AdChoices icon
Your recommendations widget header must include a clickable AdChoices icon (ImageView). The SDK ships the icon asset — use @drawable/teads_ic_adchoices directly in your layout:
<ImageView
android:id="@+id/rec_ad_choices_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/teads_ic_adchoices" />
When tapped, open the URL returned by TeadsAdPlacementRecommendations.getAdChoicesURL() in a browser or Custom Tab:
adChoicesIcon.setOnClickListener {
val url = TeadsAdPlacementRecommendations.getAdChoicesURL()
// Open url in browser or Custom Tab
}
RTB disclosure icon (paid recommendations)
Paid recommendations require a per-item disclosure icon overlaid on the thumbnail. In your XML layout, wrap the recommendation ImageView in a FrameLayout and add a second ImageView for the disclosure icon. The disclosure icon must be at least 25dp × 25dp:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- Your existing recommendation thumbnail ImageView -->
<ImageView
android:id="@+id/rec_image_view"
android:layout_width="..."
android:layout_height="..." />
<!-- Disclosure icon — minimum 25dp × 25dp -->
<ImageView
android:id="@+id/rec_disclosure_image_view"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="top|end"
android:visibility="gone" />
</FrameLayout>
In code, check isPaid() && shouldDisplayDisclosureIcon() — if true, load the icon and set a click handler; otherwise keep the view GONE:
if (recommendation.isPaid() && recommendation.shouldDisplayDisclosureIcon()) {
disclosureImageView.visibility = View.VISIBLE
// Load icon — e.g. Glide.with(this).load(recommendation.getDisclosure()?.iconUrl).into(disclosureImageView)
disclosureImageView.setOnClickListener {
// Open articleUrl in browser or Custom Tab
}
} else {
disclosureImageView.visibility = View.GONE
}
To verify your implementation, enable RTB test mode so paid recommendations always appear in responses:
TeadsAdPlacementRecommendations.setTestRTB(true)
Remember to remove this call before releasing to production.
Request configuration
TeadsAdPlacementRecommendationsConfig takes a widgetId, a request config (URL or platform), an optional widgetIndex (default 0), and an optional obPubImp. Choose one of the two request configs:
URL request — TeadsAdPlacementRecommendationsURLConfig:
| Parameter | Type | Description |
|---|---|---|
articleUrl | Uri | URL of the article for which recommendations are fetched |
externalId | String? | Optional external identifier |
Platform request — TeadsAdPlacementRecommendationsPlatformConfig:
| Parameter | Type | Description |
|---|---|---|
bundleUrl | String? | App bundle URL |
portalUrl | String? | Portal URL |
contentUrl | String? | Content URL |
lang | String? | Content language |
psub | String? | Publisher subdomain / section identifier |
// Platform request variant
val config = TeadsAdPlacementRecommendationsConfig(
widgetId = "SDK_1",
platformConfig = TeadsAdPlacementRecommendationsPlatformConfig(
bundleUrl = "https://yourapp.example",
contentUrl = "https://yoursite.com/article",
lang = "en"
)
)
The single-argument TeadsAdPlacementRecommendationsConfig(articleUrl, widgetId) constructor is deprecated. Use the widgetId + URL/platform request config constructors shown above.
Banner Placement (Direct)
Banner placements display an anchored banner ad inline in your layout. In the direct integration the JS engine drives the banner height — set the container height to wrap_content and it resizes as the creative loads. Use the direct integration when you don't use AdMob or GAM mediation.
Use an Activity context to create TeadsAdPlacementBanner (not the application context). The SDK uses it to size the creative correctly. Passing the application context can cause the ad to be mis-sized.
<!-- res/layout/activity_main.xml -->
<FrameLayout
android:id="@+id/banner_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
import android.os.Bundle
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementBanner
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementBannerConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.adplacement.interfaces.core.TeadsAdPlacement
class BannerActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var banner: TeadsAdPlacementBanner? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Build the placement configuration
val config = TeadsAdPlacementBannerConfig(
articleUrl = "https://yoursite.com/article".toUri(),
widgetId = "MB_10", // Provided by your Teads account manager
installationKey = "YOUR_INSTALLATION_KEY",
widgetIndex = 0
)
// 2. Create the placement and load the ad — context (this) MUST be an Activity
val placement = TeadsAdPlacementBanner(this, config, this)
banner = placement
// 3. Attach the returned view to your container
val adView = placement.loadAd()
findViewById<FrameLayout>(R.id.banner_container).addView(adView)
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// React to placement lifecycle events — see the Lifecycle Events table below.
}
}
For production, articleUrl, widgetId, and installationKey will be unique values provided by your product manager. Contact your local account manager to obtain them.
Keep the host container's height as wrap_content. A fixed height clips the ad because the JS engine resizes the view dynamically as the creative loads.
Lifecycle Events
The Banner placement uses the standard TeadsAdPlacementEventsDelegate. The key events are:
| Event | Meaning |
|---|---|
LOADED | Ad data was returned by the server |
READY | Ad is ready to be displayed |
RENDERED | Ad has been rendered on screen |
HEIGHT_UPDATED | Engine reports a new height — read it from data["height"] if you animate the slot |
VIEWED | Ad met viewability criteria |
CLICKED | User tapped the ad (Teads opens the landing URL in a Custom Tab automatically) |
CLICKED_ORGANIC | User tapped an organic recommendation; landing URL is in data["url"] |
FAILED | No ad to display — fall back to other content |
If you use AdMob or GAM mediation instead, see the Mediation — Banner guide.
Interstitial Placement (Direct)
Interstitial placements display fullscreen ads at natural transition points in your app. Use the direct integration when you don't use AdMob or GAM mediation.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import tv.teads.sdk.combinedsdk.TeadsAdPlacementEventName
import tv.teads.sdk.combinedsdk.adplacement.TeadsAdPlacementInterstitial
import tv.teads.sdk.combinedsdk.adplacement.config.TeadsAdPlacementInterstitialConfig
import tv.teads.sdk.combinedsdk.adplacement.interfaces.TeadsAdPlacementEventsDelegate
import tv.teads.sdk.combinedsdk.adplacement.interfaces.core.TeadsAdPlacement
class InterstitialActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
private var interstitial: TeadsAdPlacementInterstitial? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. Build the placement configuration
val config = TeadsAdPlacementInterstitialConfig(
articleUrl = "https://yoursite.com/article",
widgetId = "INT_MW_1", // Provided by your Teads account manager
installationKey = "YOUR_INSTALLATION_KEY"
)
// 2. Create the placement — context MUST be an Activity
interstitial = TeadsAdPlacementInterstitial(this, config, this)
// 3. Request the ad
interstitial?.loadAd()
}
override fun onPlacementEvent(
placement: TeadsAdPlacement<*, *>,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
// 4. Show the ad as soon as it's ready
if (event == TeadsAdPlacementEventName.READY) {
interstitial?.show(this)
}
}
override fun onDestroy() {
super.onDestroy()
interstitial?.clean()
}
}
For production, articleUrl, widgetId, and installationKey will be unique values provided by your product manager. Contact your local account manager to obtain them.
Lifecycle Events
The interstitial uses the standard TeadsAdPlacementEventsDelegate. The key events are:
| Event | Meaning |
|---|---|
READY | Ad is preloaded — safe to call show(context) |
FAILED | Ad failed to load — fall back to unlocking content or a different flow |
WILL_PRESENT | Fired right before the fullscreen interstitial is presented |
PRESENTED | Fired when the fullscreen interstitial has been presented |
RENDERED | Ad is on screen |
VIEWED | Ad met viewability criteria |
CLICKED | User tapped the ad (SDK opens the landing URL automatically) |
WILL_DISMISS | Fired right before the fullscreen interstitial is dismissed |
DISMISSED | User closed the fullscreen ad — resume your flow |
If you use AdMob or GAM mediation instead, see the Mediation — Interstitial guide.
Event Handling
Comprehensive Event Handling
class ComprehensiveEventHandler : TeadsAdPlacementEventsDelegate {
override fun onPlacementEvent(
placement: TeadsAdPlacement,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
Log.d("TeadsSDK", "Event: $event from $placement with data $data")
when (event) {
TeadsAdPlacementEventName.LOADED -> {
// Placement is requested to load the ad
Log.d("TeadsSDK", "Content fully loaded")
}
TeadsAdPlacementEventName.READY -> {
// Ad is loaded and ready to display
Log.d("TeadsSDK", "Ad is ready to be displayed")
}
TeadsAdPlacementEventName.RENDERED -> {
// Ad has been rendered on screen
Log.d("TeadsSDK", "Ad has been rendered")
}
// Interstitial events
TeadsAdPlacementEventName.WILL_PRESENT -> {
// Fired right before the fullscreen interstitial is presented
Log.d("TeadsSDK", "Interstitial will present")
}
TeadsAdPlacementEventName.PRESENTED -> {
// Fired when the fullscreen interstitial has been presented
Log.d("TeadsSDK", "Interstitial presented")
}
TeadsAdPlacementEventName.WILL_DISMISS -> {
// Fired right before the fullscreen interstitial is dismissed
Log.d("TeadsSDK", "Interstitial will dismiss")
}
TeadsAdPlacementEventName.DISMISSED -> {
// Fired when the fullscreen interstitial has been dismissed by the user
Log.d("TeadsSDK", "Interstitial dismissed")
}
TeadsAdPlacementEventName.VIEWED -> {
// Ad has met IAB viewability standards
Log.d("TeadsSDK", "Ad has met viewability criteria")
}
TeadsAdPlacementEventName.CLICKED -> {
// User clicked on the ad
// SDK handles URL opening automatically
Log.d("TeadsSDK", "User clicked on ad")
}
TeadsAdPlacementEventName.CLICKED_ORGANIC -> {
// User clicked on organic content (Feed placement)
val url = data?.get("url") as? String
Log.d("TeadsSDK", "User clicked on organic content: $url")
url?.let {
// Open in browser or in-app browser
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))
startActivity(intent)
}
}
TeadsAdPlacementEventName.FAILED -> {
// Ad failed to load
// Show fallback content or hide ad container
val reason = data?.get("reason") as? String
Log.e("TeadsSDK", "Ad failed to load: $reason")
}
TeadsAdPlacementEventName.PLAY -> {
// Video started playing
Log.d("TeadsSDK", "Video started playing")
}
TeadsAdPlacementEventName.PAUSE -> {
// Video paused
Log.d("TeadsSDK", "Video paused")
}
TeadsAdPlacementEventName.COMPLETE -> {
// Video completed
Log.d("TeadsSDK", "Video completed")
}
TeadsAdPlacementEventName.START_PLAY_AUDIO -> {
// Ad started playing audio
Log.d("TeadsSDK", "Ad started playing audio")
}
TeadsAdPlacementEventName.STOP_PLAY_AUDIO -> {
// Ad stopped playing audio
Log.d("TeadsSDK", "Ad stopped playing audio")
}
TeadsAdPlacementEventName.HEIGHT_UPDATED -> {
// Ad height changed (useful for dynamic layouts)
// This is the height your ad container must match
val height = data?.get("height") as? Int
Log.d("TeadsSDK", "Ad height updated to: $height")
}
else -> {
Log.d("TeadsSDK", "Other event: $event")
}
}
}
}
The SDK automatically handles opening the click URL in the CLICKED event. Do not implement URL navigation in this event handler — doing so will result in the browser opening twice.
Use this event only for tracking/analytics purposes.
Best Practices
1. Placement Lifecycle Management
Media, Media Native and Interstitial placements
Call clean() method for Media, Media Native and Interstitial placements when they're no longer needed to prevent memory leaks:
class ContentActivity : AppCompatActivity(), TeadsAdPlacementEventsDelegate {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... setup your placements
}
override fun onDestroy() {
super.onDestroy()
when (placement) {
is TeadsAdPlacementMedia -> placement.clean()
is TeadsAdPlacementMediaNative -> placement.clean()
is TeadsAdPlacementInterstitial -> placement.clean()
}
}
}
Feed and Recommendations placements
For Feed and Recommendation placements, ensure you load only one ad instance per screen.
To prevent redundant ad requests during view recycling (RecyclerView) or recomposition (LazyList), cache the loaded ad by holding a strong lifecycle aware reference to it.
2. Error Handling
Always implement proper error handling:
// Handle errors through the delegate
override fun onPlacementEvent(
placement: TeadsAdPlacement,
event: TeadsAdPlacementEventName,
data: Map<String, Any>?
) {
if (event == TeadsAdPlacementEventName.FAILED) {
// Ad failed to load
// Show fallback content or hide ad container
val reason = data?.get("reason") as? String
Log.e("TeadsSDK", "Ad failed to load: $reason")
}
}
3. Testing
Use test mode during development:
// Enable test mode for development
if (BuildConfig.DEBUG) {
TeadsSDK.testMode = true
TeadsSDK.testLocation = "us" // Optional: Test specific geolocations
}
Troubleshooting
Common Issues and Solutions
-
Ad not displaying
- Verify your placement ID is correct
- Check network connectivity
- Ensure SDK is properly initialized
- Check delegate for error events
-
Layout issues
- Let the SDK manage ad view height
- Do not add invisible overlays on your ad container
-
Memory leaks
- Release resources and listeners in lifecycle methods (
onDestroy). - Avoid context leaks from inner classes; use
WeakReferencefor long-lived operations. - Cancel coroutines or background tasks when the
ViewModelor UI component is cleared.
- Release resources and listeners in lifecycle methods (
-
Performance issues
- Preload ads when appropriate
- Limit number of concurrent placements
- Use lazy loading for off-screen content
Next Steps
- Review the Migration Guide for Teads Users
- Review the Migration Guide for Outbrain Users
- Learn about Privacy & Compliance
For additional support, see Support