Recommendations API
Recommendations API
For programmatic access to content recommendations with custom UI:
- Kotlin
- Java
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
}
}
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.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.view.ViewGroup;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Map;
public class ContentActivity extends AppCompatActivity implements TeadsAdPlacementEventsDelegate {
private TeadsAdPlacementRecommendations recommendationsPlacement;
private ActivityRecommendationsBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityRecommendationsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
loadRecommendations();
}
private void loadRecommendations() {
// Create configuration — pass a URL or platform request config (see below).
// This 2-arg constructor is a real Java-visible overload; externalId on the
// URL config has no such overload, so it's passed explicitly as null below.
TeadsAdPlacementRecommendationsConfig config = new TeadsAdPlacementRecommendationsConfig(
"SDK_1", // Your widget ID
new TeadsAdPlacementRecommendationsURLConfig(
Uri.parse("https://yoursite.com/article"),
null // externalId
)
);
// Create placement — no Context argument, unlike the other placements
recommendationsPlacement = new TeadsAdPlacementRecommendations(
config, // Placement config
this // Event delegate
);
// Fetch recommendations — loadAd() returns a TeadsAdPlacementObservable, subscribe
// from Java (the suspend loadAdSuspend() variant doesn't bridge cleanly to Java)
recommendationsPlacement.loadAd().subscribe(recommendations -> {
if (recommendations != null) {
displayRecommendations(recommendations);
}
});
}
private void displayRecommendations(OBRecommendationsResponse recommendations) {
binding.recommendationsContainer.removeAllViews();
if (!recommendations.getAll().isEmpty()) {
for (OBRecommendation recommendation : recommendations.getAll()) {
View recommendationView = createRecommendationView(recommendation);
binding.recommendationsContainer.addView(recommendationView);
}
}
}
private View createRecommendationView(OBRecommendation recommendation) {
RecommendationItemBinding binding = RecommendationItemBinding.inflate(LayoutInflater.from(this));
binding.recommendationTitle.setText(recommendation.getContent());
binding.recommendationSource.setText(recommendation.getSourceName());
// Load image (using your preferred image loading library)
if (recommendation.getThumbnail() != null) {
String imageUrl = recommendation.getThumbnail().getUrl();
// E.g. Glide.with(this).load(imageUrl).into(binding.recommendationImage);
}
// Handle click
binding.getRoot().setOnClickListener(v -> {
String url = TeadsAdPlacementRecommendations.getUrl(recommendation);
if (url != null) {
// Open URL in browser
}
});
// Configure viewability tracking — required for accurate impression reporting
TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
(ViewGroup) binding.getRoot(),
recommendation
);
return binding.getRoot();
}
@Override
public void onPlacementEvent(TeadsAdPlacement<?, ?> placement, TeadsAdPlacementEventName event, Map<String, ?> data) {
// 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:
- Kotlin
- Java
TeadsAdPlacementRecommendations.configureViewabilityPerListingFor(
recContainer, // ViewGroup holding the recommendation content
recommendation
)
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:
- Kotlin
- Java
adChoicesIcon.setOnClickListener {
val url = TeadsAdPlacementRecommendations.getAdChoicesURL()
// Open url in browser or Custom Tab
}
adChoicesIcon.setOnClickListener(v -> {
String 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:
- Kotlin
- Java
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
}
if (recommendation.isPaid() && recommendation.shouldDisplayDisclosureIcon()) {
disclosureImageView.setVisibility(View.VISIBLE);
// Load icon — e.g.
// Glide.with(this).load(recommendation.getDisclosure() != null ? recommendation.getDisclosure().getIconUrl() : null).into(disclosureImageView);
disclosureImageView.setOnClickListener(v -> {
// Open articleUrl in browser or Custom Tab
});
} else {
disclosureImageView.setVisibility(View.GONE);
}
To verify your implementation, enable RTB test mode so paid recommendations always appear in responses:
- Kotlin
- Java
TeadsAdPlacementRecommendations.setTestRTB(true)
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 |
- Kotlin
- Java
// Platform request variant
val config = TeadsAdPlacementRecommendationsConfig(
widgetId = "SDK_1",
platformConfig = TeadsAdPlacementRecommendationsPlatformConfig(
bundleUrl = "https://yourapp.example",
contentUrl = "https://yoursite.com/article",
lang = "en"
)
)
// Platform request variant — 2-arg TeadsAdPlacementRecommendationsConfig(widgetId, platformConfig)
// overload is real; TeadsAdPlacementRecommendationsPlatformConfig has no overload skipping
// portalUrl/psub, so its 5-arg canonical constructor is used with explicit nulls
TeadsAdPlacementRecommendationsConfig config = new TeadsAdPlacementRecommendationsConfig(
"SDK_1",
new TeadsAdPlacementRecommendationsPlatformConfig(
"https://yourapp.example", // bundleUrl
null, // portalUrl
"https://yoursite.com/article", // contentUrl
"en", // lang
null // psub
)
);
The single-argument TeadsAdPlacementRecommendationsConfig(articleUrl, widgetId) constructor is deprecated. Use the widgetId + URL/platform request config constructors shown above.