This guide covers how to add the MSP Android SDK to your app, including how to initialize the SDK, load ads, and display ads.
The guide also provides some best practices which help you avoid common pitfalls and achieve best performance.
System requirements
| Requirement | Version |
|---|---|
| Android minimum SDK | 24 (Android 7.0) |
| Android target SDK | 36 |
| Kotlin | 1.9+ |
| Gradle | 7.0+ |
The MSP SDK is distributed via Maven Central. Add the core dependency and any adapter dependencies for the ad networks you want to monetize with.
settings.gradle
Ensure Maven Central is listed in your repository configuration:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}build.gradle (app module)
dependencies {
// Core SDK (required)
implementation 'ai.themsp:prebid-adapter:$LATEST_VERSION'
implementation 'ai.themsp:msp-core:$LATEST_VERSION'
// Ad network adapters (add only those you need)
implementation 'ai.themsp:google-adapter:$LATEST_VERSION'
implementation 'ai.themsp:facebook-adapter:$LATEST_VERSION'
implementation 'ai.themsp:nova-adapter:$LATEST_VERSION'
implementation 'ai.themsp:moloco-adapter:$LATEST_VERSION'
implementation 'ai.themsp:applovin-adapter:$LATEST_VERSION'
}Add the required permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Required for ad personalization -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />Network-specific manifest entries (Google App ID, etc.) are covered per-network in the Mediation Networks Guide.
Initialize the MSP SDK as early as possible — ideally in your Application.onCreate(). The SDK uses the initialization window to prefetch ad configurations and warm up adapter networks.
MyApplication.kt
import com.particles.msp.api.MSPInitListener
import com.particles.msp.api.MSPInitStatus
import com.particles.msp.api.MSPInitializationParameters
import com.particles.msp.util.Logger
import com.particles.prebidadapter.MSP
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initializeMSP()
}
private fun initializeMSP() {
// Optional: enable verbose logging during development
Logger.setLogLevel(Logger.VERBOSE)
// Optional: pass a publisher-provided user identifier for frequency capping
MSP.setPpid("your-user-id")
// Build initialization parameters
val initParams = object : MSPInitializationParameters {
override fun getPrebidAPIKey(): String = "YOUR_PREBID_API_KEY"
override fun getOrgId(): Int = 12345 // Your org ID from the MSP dashboard
override fun getAppId(): Int = 67890 // Your app ID from the MSP dashboard
override fun isAgeRestrictedUser(): Boolean = false
override fun isInTestMode(): Boolean = false
// Additional network-specific init parameters
override fun getParameters(): Map<String, Any> = mapOf(
MSPConstants.INIT_PARAM_KEY_GOOGLE_APP_ID to "ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX",
MSPConstants.INIT_PARAM_KEY_MOLOCO_APP_KEY to "xxxxx-v9C8uNYV5riG7Vwu",
MSPConstants.INIT_PARAM_KEY_APPLOVIN_SDK_KEY to "6KrA5SQHFTBpGDUU4FeLIZG..."
)
}
// Initialize
MSP.init(
context = applicationContext,
initParams = initParams,
sdkInitListener = object : MSPInitListener {
override fun onComplete(status: MSPInitStatus, message: String) {
Logger.info("[MSP] Initialization complete. Status: $status, message: $message")
}
}
)
}
}Register your Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
...>Key initialization parameters
| Parameter | Type | Description |
|---|---|---|
getPrebidAPIKey() |
String |
API key issued by Particle Inc. for your app |
getOrgId() |
Int |
Organization ID provisioned from Particle Inc. |
getAppId() |
Int |
App ID provisioned from Particle Inc. |
getParameters() |
Map<String, Any> |
Additional network-specific initialization parameters |
Optional MSP properties
| Method | Description |
|---|---|
MSP.setPpid(ppid) |
Publisher-provided user ID for frequency capping |
The pattern for every ad format is the same:
- Create an
AdLoaderand anAdRequest. - Call
loader.loadAd(placementId, adListener, adRequest). - In
onAdLoaded, callloader.getAd(placementId)to retrieve the ad object. - Present the ad (for interstitial/rewarded) or attach the ad view to your layout.
Call MSP.notifyLoss when:
- The MSP Ad loses the auction, or
- The MSP SDK does not fill while another bidder wins
fun notifyLoss(winnerBidder: String, winnerPrice: Float, requestId: String, ad: MSPAd?)
winnerBidder: Name of the winning bidder other than MSPwinnerPrice: Ad price of the winning bid other than MSPrequestId: Provided byonAdLoadedandonErrorcallback parameterloadInfo[MSPConstants.LOAD_INFO_KEY_REQUEST_ID]ofLoadAdAPIad: MSP Ad that loses the auction. Passnullfor the no-fill case.
import android.view.View
import com.particles.msp.api.AdFormat
import com.particles.msp.api.AdListener
import com.particles.msp.api.AdLoader
import com.particles.msp.api.AdRequest
import com.particles.msp.api.AdSize
import com.particles.msp.api.BannerAdView
import com.particles.msp.api.MSPAd
class BannerActivity : AppCompatActivity() {
private val loader = AdLoader()
private fun loadBanner() {
val request = AdRequest.Builder(AdFormat.BANNER)
.setContext(this)
.setPlacement("YOUR_BANNER_PLACEMENT_ID")
.setAdSize(AdSize(width = 320, height = 50, isInlineAdaptiveBanner = false, isAnchorAdaptiveBanner = false))
.build()
loader.loadAd("YOUR_BANNER_PLACEMENT_ID", adListener, request)
}
private val adListener = object : AdListener {
override fun onAdLoaded(placementId: String, loadInfo: Map<String, Any>) {
val bannerAd = loader.getAd(placementId) as? BannerAdView ?: return
val adView = bannerAd.adView as View
runOnUiThread {
// Attach the ad view to your layout
val container = findViewById<FrameLayout>(R.id.banner_container)
container.removeAllViews()
container.addView(adView)
}
}
override fun onError(msg: String, loadInfo: Map<String, Any>) {
Log.e("MSP", "Banner error: $msg")
}
override fun onAdImpression(ad: MSPAd) {}
override fun onAdClicked(ad: MSPAd) {}
override fun onAdDismissed(ad: MSPAd) {}
}
}Standard banner sizes
| Size | Width | Height |
|---|---|---|
| Banner | 320 | 50 |
| Medium rectangle | 300 | 250 |
| Leaderboard | 728 | 90 |
Load the interstitial before you need to show it. Display it at a natural transition point — level completion, article end, etc.
import android.app.Activity
import com.particles.msp.api.AdFormat
import com.particles.msp.api.AdListener
import com.particles.msp.api.AdLoader
import com.particles.msp.api.AdRequest
import com.particles.msp.api.InterstitialAd
import com.particles.msp.api.MSPAd
class InterstitialActivity : AppCompatActivity() {
private val loader = AdLoader()
private fun loadInterstitial() {
val request = AdRequest.Builder(AdFormat.INTERSTITIAL)
.setContext(this)
.setPlacement("YOUR_INTERSTITIAL_PLACEMENT_ID")
.build()
loader.loadAd("YOUR_INTERSTITIAL_PLACEMENT_ID", adListener, request)
}
private fun showInterstitialIfReady() {
val interstitialAd = loader.getAd("YOUR_INTERSTITIAL_PLACEMENT_ID") as? InterstitialAd
if (interstitialAd == null) {
loadInterstitial() // not ready yet — start loading
return
}
interstitialAd.show(this)
}
private val adListener = object : AdListener {
override fun onAdLoaded(placementId: String, loadInfo: Map<String, Any>) {
Log.d("MSP", "Interstitial ready")
showInterstitialIfReady()
}
override fun onError(msg: String, loadInfo: Map<String, Any>) {
Log.e("MSP", "Interstitial error: $msg")
}
override fun onAdDismissed(ad: MSPAd) {
Log.d("MSP", "Interstitial dismissed")
}
override fun onAdImpression(ad: MSPAd) {}
override fun onAdClicked(ad: MSPAd) {}
}
}Rewarded ads are full-screen placements that grant a reward — coins, lives, premium content access — after the user completes the ad experience. The reward is delivered via onAdRewardReceived.
import com.particles.msp.api.AdFormat
import com.particles.msp.api.AdListener
import com.particles.msp.api.AdLoader
import com.particles.msp.api.AdRequest
import com.particles.msp.api.MSPAd
import com.particles.msp.api.RewardedAd
class RewardedActivity : AppCompatActivity() {
private val loader = AdLoader()
private fun loadRewardedAd() {
val request = AdRequest.Builder(AdFormat.REWARDED)
.setContext(this)
.setPlacement("YOUR_REWARDED_PLACEMENT_ID")
.build()
loader.loadAd("YOUR_REWARDED_PLACEMENT_ID", adListener, request)
}
fun onUserTappedWatchAd() {
val rewardedAd = loader.getAd("YOUR_REWARDED_PLACEMENT_ID") as? RewardedAd
if (rewardedAd == null) {
Log.d("MSP", "Rewarded ad not ready yet")
return
}
rewardedAd.show(this)
}
private val adListener = object : AdListener {
// Override this to grant the reward to the user
override fun onAdRewardReceived(ad: MSPAd) {
val rewardType = ad.adInfo["ad_reward_type"]
val rewardAmount = ad.adInfo["ad_reward_amount"]
Log.d("MSP", "Reward: $rewardAmount $rewardType")
// Grant the reward to the user here
}
override fun onAdLoaded(placementId: String, loadInfo: Map<String, Any>) {}
override fun onError(msg: String, loadInfo: Map<String, Any>) {}
override fun onAdDismissed(ad: MSPAd) {}
override fun onAdImpression(ad: MSPAd) {}
override fun onAdClicked(ad: MSPAd) {}
}
}Native ads return a NativeAd object you render with your own layout. Use NativeAdViewBinder to map your XML layout view IDs to the SDK, then pass it to NativeAdView.
1. Define your native ad layout (res/layout/native_ad.xml):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/icon_image_view"
android:layout_width="40dp"
android:layout_height="40dp" />
<TextView
android:id="@+id/ad_title_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />
<TextView
android:id="@+id/advertiser_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/ad_body_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/ad_media_view_container"
android:layout_width="match_parent"
android:layout_height="200dp" />
<FrameLayout
android:id="@+id/options_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/cta_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>2. Load and render the native ad:
import com.particles.msp.api.AdFormat
import com.particles.msp.api.AdListener
import com.particles.msp.api.AdLoader
import com.particles.msp.api.AdRequest
import com.particles.msp.api.MSPAd
import com.particles.msp.api.NativeAd
import com.particles.msp.api.NativeAdView
import com.particles.msp.api.NativeAdViewBinder
class NativeActivity : AppCompatActivity() {
private val loader = AdLoader()
private fun loadNativeAd() {
val request = AdRequest.Builder(AdFormat.NATIVE)
.setContext(this)
.setPlacement("YOUR_NATIVE_PLACEMENT_ID")
.build()
loader.loadAd("YOUR_NATIVE_PLACEMENT_ID", adListener, request)
}
private val adListener = object : AdListener {
override fun onAdLoaded(placementId: String, loadInfo: Map<String, Any>) {
val nativeAd = loader.getAd(placementId) as? NativeAd ?: return
val binder = NativeAdViewBinder.Builder()
.layoutResourceId(R.layout.native_ad)
.titleTextViewId(R.id.ad_title_text_view)
.advertiserTextViewId(R.id.advertiser_text_view)
.bodyTextViewId(R.id.ad_body_text_view)
.callToActionViewId(R.id.cta_button)
.mediaViewId(R.id.ad_media_view_container)
.optionsViewId(R.id.options_view)
.iconViewId(R.id.icon_image_view)
.build()
runOnUiThread {
val nativeAdView = NativeAdView(nativeAd, binder, this@NativeActivity)
val container = findViewById<FrameLayout>(R.id.ad_container)
container.removeAllViews()
container.addView(nativeAdView)
}
}
override fun onError(msg: String, loadInfo: Map<String, Any>) {}
override fun onAdImpression(ad: MSPAd) {}
override fun onAdClicked(ad: MSPAd) {}
override fun onAdDismissed(ad: MSPAd) {}
}
}AdFormat.MULTI_FORMAT allows both native and banner ads to fill the same placement. In onAdLoaded, check which type was returned and render accordingly.
import android.view.View
import com.particles.msp.api.AdFormat
import com.particles.msp.api.AdListener
import com.particles.msp.api.AdLoader
import com.particles.msp.api.AdRequest
import com.particles.msp.api.AdSize
import com.particles.msp.api.BannerAdView
import com.particles.msp.api.MSPAd
import com.particles.msp.api.NativeAd
import com.particles.msp.api.NativeAdView
import com.particles.msp.api.NativeAdViewBinder
class MultiFormatActivity : AppCompatActivity() {
private val loader = AdLoader()
private fun loadAd() {
val request = AdRequest.Builder(AdFormat.MULTI_FORMAT)
.setContext(this)
.setPlacement("YOUR_MULTI_FORMAT_PLACEMENT_ID")
.setAdSize(AdSize(300, 250, false, false))
.build()
loader.loadAd("YOUR_MULTI_FORMAT_PLACEMENT_ID", adListener, request)
}
private val adListener = object : AdListener {
override fun onAdLoaded(placementId: String, loadInfo: Map<String, Any>) {
val ad = loader.getAd(placementId)
runOnUiThread {
val container = findViewById<FrameLayout>(R.id.ad_container)
container.removeAllViews()
when (ad) {
is NativeAd -> {
val binder = NativeAdViewBinder.Builder()
.layoutResourceId(R.layout.native_ad)
.titleTextViewId(R.id.ad_title_text_view)
.advertiserTextViewId(R.id.advertiser_text_view)
.bodyTextViewId(R.id.ad_body_text_view)
.callToActionViewId(R.id.cta_button)
.mediaViewId(R.id.ad_media_view_container)
.optionsViewId(R.id.options_view)
.iconViewId(R.id.icon_image_view)
.build()
container.addView(NativeAdView(ad, binder, this@MultiFormatActivity))
}
is BannerAdView -> {
container.addView(ad.adView as View)
}
}
}
}
override fun onError(msg: String, loadInfo: Map<String, Any>) {}
override fun onAdImpression(ad: MSPAd) {}
override fun onAdClicked(ad: MSPAd) {}
override fun onAdDismissed(ad: MSPAd) {}
}
}| Dependency | Ad Network | Formats |
|---|---|---|
ai.themsp:google-adapter |
Google Ad Manager / AdMob | Banner, Interstitial, Rewarded, Native |
ai.themsp:facebook-adapter |
Meta Audience Network | Banner, Interstitial, Rewarded, Native |
ai.themsp:nova-adapter |
Nova | Banner, Interstitial, Native, Rewarded |
ai.themsp:moloco-adapter |
Moloco | Banner, Native, Interstitial, Rewarded |
ai.themsp:applovin-adapter |
AppLovin/Max | Banner, Native, Interstitial, Rewarded |
Pass your AdMob App ID in the getParameters() map during initialization:
override fun getParameters(): Map<String, Any> = mapOf(
MSPConstants.INIT_PARAM_KEY_GOOGLE_APP_ID to "ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX"
)Add the AdMob App ID to AndroidManifest.xml as well (required by the Google Mobile Ads SDK):
<application>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" />
</application>Pass your Moloco App Key in the getParameters() map during initialization:
override fun getParameters(): Map<String, Any> = mapOf(
MSPConstants.INIT_PARAM_KEY_MOLOCO_APP_KEY to "YOUR_MOLOCO_APP_KEY"
)Pass your AppLovin SDK Key in the getParameters() map during initialization:
override fun getParameters(): Map<String, Any> = mapOf(
MSPConstants.INIT_PARAM_KEY_APPLOVIN_SDK_KEY to "6KrA5SQHFTBpGDUU4FeLIZG..."
)The auction takes time. Call loadAd before the moment you need to show the ad:
- Banners: Load when the fragment or activity starts (
onCreate/onStart). - Interstitials: Load at app launch or immediately after the previous interstitial is dismissed.
- Rewarded: Load at app launch and again in
onAdDismissed.
When an ad is no longer needed, call destroy() to release adapter resources and prevent memory leaks:
override fun onDestroy() {
super.onDestroy()
currentAd?.destroy()
currentAd = null
}No ads filling
- Verify the placement IDs match what is provisioned by Particle Inc.
- Confirm
MSP.init()completes beforeloadAdis called. - Enable verbose logging during development:
Logger.setLogLevel(Logger.VERBOSE).
onAdLoaded fires but getAd returns null
getAd removes the ad from cache. Call it exactly once per load cycle and hold the returned ad strongly until you are done with it.
Interstitial or rewarded ad not showing
show(activity) must be called with a non-null, non-finishing Activity. Ensure the activity is in the foreground when you call show.
Rewarded ad shows but reward is never granted
onAdRewardReceived fires only if the user watches the ad to completion. Do not grant rewards based on onAdDismissed alone.
Prebid SDK which is introduced by by MSP SDK will read UserDefaults (iOS) or SharedPreferences (Android) key IABUSPrivacy_String for US Privacy signal. Prebid SDK CCPA doc
Prebid SDK which is introduced by by MSP SDK will read UserDefaults (iOS) or SharedPreferences (Android) keys IABTCF_gdprApplies and IABTCF_TCString for GDPR privacy signals. Predbid SDK GDPR doc
Set your COPPA signal through init parameter isAgeRestrictedUser