Skip to main content

Feed Placement

Feed Placement (Content Recommendations)

Feed placements display content recommendation widgets, perfect for keeping users engaged with related content.

warning

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.adplacement.interfaces.core.TeadsAdPlacement
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(this@ContentActivity) {
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)
warning

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
}
}