Skip to main content

Recommendations API

For programmatic access to content recommendations with custom UI:


Sample App

The official Teads SDK sample application includes working examples of this format. Use it for implementation examples and as a reference while you set up this placement.

Teads Android Sample App on GitHub

Example​

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.adplacement.interfaces.core.TeadsAdPlacement
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​

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​

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
}

Paid recommendations always appear in responses when test mode is enabled:

TeadsAdPlacementRecommendations.setTestRTB(true)

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:

ParameterTypeDescription
articleUrlUriURL of the article for which recommendations are fetched
externalIdString?Optional external identifier

Platform request — TeadsAdPlacementRecommendationsPlatformConfig:

ParameterTypeDescription
bundleUrlString?App bundle URL
portalUrlString?Portal URL
contentUrlString?Content URL
langString?Content language
psubString?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"
)
)

Best Practices​

Viewability

Viewability tracking is required for accurate impression reporting.

AdChoices

The widget AdChoices icon click handler and the per-item disclosure icon are required to ship.

RTB test mode

Remember to remove the setTestRTB(true) call before releasing to production.

Deprecated constructor

The single-argument TeadsAdPlacementRecommendationsConfig(articleUrl, widgetId) constructor is deprecated. Use the widgetId + URL/platform request config constructors shown above.

Ad Lifecycle Listener​

Implement TeadsAdPlacementEventsDelegate to observe playback, clicks, and viewability. See Placement Events Listener for the full event reference and error handling guide.

Checklist​