A comprehensive Android ads SDK that provides a unified interface for multiple ad networks (AdMob and AppLovin MAX) with Firebase Remote Config integration, GDPR/CCPA consent management, and coroutines support.
- Multi-Network Support: Unified interface for AdMob and AppLovin MAX
- Firebase Remote Config: Dynamic ad configuration without app updates
- GDPR/CCPA Compliance: Built-in consent management using Google UMP
- Coroutines Integration: Full async/await support with Kotlin coroutines
- Ad Types: Banner, Interstitial, Rewarded Video, Native, and App Open ads
- Simple API: Easy-to-use Common API for all ad types
- Installation
- SDK Initialization
- Splash Screen Integration
- Ad Implementation
- XML Layouts
- Best Practices
Create or edit your gradle.properties file (in project root or ~/.gradle/gradle.properties):
# Nexus Repository Credentials
NEXUS_USERNAME=your_nexus_username
NEXUS_PASSWORD=your_nexus_passwordImportant: Shouldn't commit gradle.properties with real credentials to version control. Add it to .gitignore.
Add the Nexus repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
// Add Dream Monet SDK repository
maven {
url = uri("https://nexus.synthraai.tech/repository/maven-releases/")
credentials {
username = settings.providers.gradleProperty("NEXUS_USERNAME").orNull
password = settings.providers.gradleProperty("NEXUS_PASSWORD").orNull
}
}
maven {
url = uri("https://nexus.synthraai.tech/repository/maven-snapshots/")
credentials {
username = settings.providers.gradleProperty("NEXUS_USERNAME").orNull
password = settings.providers.gradleProperty("NEXUS_PASSWORD").orNull
}
}
maven{ url = uri("https://maven.singular.net/") }
}
}Add the SDK dependency to your app's build.gradle.kts:
dependencies {
// Dream Monet Ads SDK
implementation("com.dream.monet:ads:1.0.0-alpha33-SNAPSHOT")
// Required: Firebase (if not already added)
implementation(platform("com.google.firebase:firebase-bom:34.2.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-config")
}Also add the Google Services plugin to your app's build.gradle.kts:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services") // Add this
}And in your project-level build.gradle.kts:
plugins {
id("com.google.gms.google-services") version "4.4.3" apply false
}Don't forget to add your google-services.json file to the app/ directory.
Before using any ads functionality, you MUST follow this initialization sequence:
-
At Application Level: After fetching Firebase Remote Config, always invoke:
SDKManager.updateRemoteConfig(remoteConfig) -
At Splash Screen: Register a listener and wait for the
onUpdatedRemoteConfig()callback:override fun onUpdatedFirebaseRemoteConfig() { // Remote Config is now synced with the SDK // NOW you can proceed with consent gathering and loading ads }
-
All ads actions (loading, showing, etc.) should ONLY happen after the
onUpdatedRemoteConfig()callback is triggered.
Why this matters: The SDK needs the Remote Config data to properly configure ad units, networks, and behavior. Loading ads before this callback completes may result in incorrect configuration or ads not loading properly.
Create an Application class to initialize the SDK. This should be done once when your app starts.
class MyApplication : Application() {
private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private lateinit var firebaseRemoteConfig: FirebaseRemoteConfig
override fun onCreate() {
super.onCreate()
// 1. Initialize Firebase Remote Config
initializeFirebaseRemoteConfig()
// 2. Initialize the Ads SDK
initializeAdSDK()
}
private fun initializeFirebaseRemoteConfig() {
firebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
// Configure Remote Config settings
val configSettings = FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(3600) // 1 hour for production
.build()
firebaseRemoteConfig.setConfigSettingsAsync(configSettings)
Log.d("MyApp", "Firebase Remote Config initialized")
}
private fun initializeAdSDK() {
applicationScope.launch {
try {
// Initialize SDK with AdMob and MAX
val initResult = SDKManager.initialize(
this@DreamMonetApplication,
config = InitializeConfig.builder()
.enableAdMob(true)
.setMaxSdkKey(
"YOUR-MAX-SDK-KEY"
)
.setSingularKeys(
SingularKeys(
sdkKey = "your-singular-sdk-key",
sdkSecret = "your-singular-secret-key",
)
)
.setTestDeviceIds(
setOf(
"Your-device-id"
)
)
.build()
)
if (initResult.isSuccess) {
Log.d("MyApp", "SDK initialized successfully")
} else {
Log.e("MyApp", "SDK initialization failed: ${initResult.exceptionOrNull()?.message}")
}
// Fetch and update remote config
fetchAndUpdateRemoteConfig()
} catch (e: Exception) {
Log.e("MyApp", "Error initializing SDK: ${e.message}", e)
}
}
}
private suspend fun fetchAndUpdateRemoteConfig() {
try {
Log.d("MyApp", "Fetching Firebase Remote Config...")
val fetchResult = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { continuation ->
firebaseRemoteConfig.fetchAndActivate()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("MyApp", "Remote config fetch successful")
continuation.resume(true)
} else {
Log.e("MyApp", "Remote config fetch failed")
continuation.resume(false)
}
}
}
}
// Update SDK with new remote config
// ⚠️ CRITICAL: This must be called after fetching Remote Config
// The SDK will notify listeners via onUpdatedFirebaseRemoteConfig()
SDKManager.updateRemoteConfig(firebaseRemoteConfig)
} catch (e: Exception) {
Log.e("MyApp", "Error fetching remote config: ${e.message}", e)
}
}
}The SDK automatically initializes both AdMob and AppLovin MAX when you call SDKManager.initialize():
- AdMob: Initialized automatically (no app ID required in code, it's read from AndroidManifest.xml)
- AppLovin MAX: Pass your SDK key to the
maxSdkKeyparameter, or passnullif not using MAX
Add AdMob App ID to AndroidManifest.xml:
<manifest>
<application>
...
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY"/>
</application>
</manifest>Use the Splash screen to handle consent gathering and wait for Remote Config updates before showing the main app.
class SplashActivity : AppCompatActivity(), SDKInitializationListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
// Register for SDK initialization events
SDKManager.addInitializationListener(this)
}
override fun onInitializationComplete(success: Boolean, errorMessage: String?) {
if (success) {
Log.d("Splash", "SDK initialized successfully")
} else {
Log.e("Splash", "SDK initialization failed: $errorMessage")
}
}
override fun onUpdatedFirebaseRemoteConfig() {
// ⚠️ CRITICAL: This callback is triggered after SDKManager.updateRemoteConfig() is called
// Remote Config is now synced with the SDK
Log.d("Splash", "Remote Config updated - SDK is ready")
// NOW it's safe to gather consent and proceed with ads
startConsentGathering()
}
private fun startConsentGathering() {
lifecycleScope.launch {
val consentResult = SDKManager.gatherConsent(
activity = this@SplashActivity,
testDeviceHashedId = "YOUR_TEST_DEVICE_ID", // Optional, for testing
debugGeography = ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA // Optional
)
if (consentResult.isSuccess) {
Log.d("Splash", "Consent gathered successfully")
} else {
Log.e("Splash", "Consent gathering failed")
}
// Navigate to main activity
startMainActivity()
}
}
private fun startMainActivity() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun onDestroy() {
super.onDestroy()
SDKManager.removeInitializationListener(this)
}
}The SDKInitializationListener interface provides two callbacks:
onInitializationComplete(success: Boolean, errorMessage: String?): Called when SDK initialization completesonUpdatedFirebaseRemoteConfig(): Called when Firebase Remote Config is fetched and updated viaSDKManager.updateRemoteConfig()
- At Application level: Fetch Remote Config → Call
SDKManager.updateRemoteConfig(remoteConfig) - At Splash Screen: Wait for
onUpdatedFirebaseRemoteConfig()callback - After callback: Proceed with consent gathering and loading ads
DO NOT load or show any ads before onUpdatedFirebaseRemoteConfig() is triggered. The SDK needs the Remote Config data to properly configure ad units, networks, and behavior.
The SDK provides a simple Common API for all ad types. Each ad type has a corresponding class that handles loading and showing ads.
Banner ads are displayed inline within your app's layout.
class MyActivity : AppCompatActivity() {
private lateinit var bannerAd: CommonBannerAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create banner ad
bannerAd = CommonBannerAd(
key = "my_banner_key", // Unique key for Remote Config
defaultUnitId = "ca-app-pub-3940256099942544/6300978111" // Test ad unit
)
// Optional: Set listener
bannerAd.setListener(object : BannerAdListener() {
override fun onAdLoaded() {
Log.d("Banner", "Ad loaded")
}
override fun onAdFailedToLoad(error: String) {
Log.e("Banner", "Ad failed to load: $error")
}
override fun onAdClicked() {
Log.d("Banner", "Ad clicked")
}
})
// Load and show banner ad
lifecycleScope.launch {
val container = findViewById<ViewGroup>(R.id.bannerAdContainer)
bannerAd.show(this@MyActivity, container)
}
}
}<FrameLayout
android:id="@+id/bannerAdContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#F5F5F5"
android:minHeight="50dp" />Native ads match the look and feel of your app's content.
class MyActivity : AppCompatActivity() {
private lateinit var nativeAd: CommonNativeAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create native ad
nativeAd = CommonNativeAd(
key = "my_native_key", // Unique key for Remote Config
defaultUnitId = "ca-app-pub-3940256099942544/2247696110", // Test ad unit
layoutRes = R.layout.ad_unified // Your custom native ad layout
)
// Optional: Set listener
nativeAd.setListener(object : NativeAdListener() {
override fun onAdLoaded() {
Log.d("Native", "Ad loaded")
}
override fun onAdFailedToLoad(error: String) {
Log.e("Native", "Ad failed to load: $error")
}
override fun onAdClicked() {
Log.d("Native", "Ad clicked")
}
})
// Load native ad
lifecycleScope.launch {
nativeAd.load(this@MyActivity)
}
}
private fun showNativeAd() {
lifecycleScope.launch {
if (nativeAd.isReady()) {
val container = findViewById<ViewGroup>(R.id.nativeAdContainer)
nativeAd.show(this@MyActivity, container)
}
}
}
override fun onDestroy() {
super.onDestroy()
nativeAd.destroy()
}
}<FrameLayout
android:id="@+id/nativeAdContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />Note: You need to create a custom native ad layout. See the XML Layouts section below for a complete example.
Interstitial ads are full-screen ads that cover the interface of their host app. They're typically displayed at natural transition points in the flow of an app, such as between activities or during the pause between levels in a game.
class MyActivity : AppCompatActivity() {
private lateinit var interstitialAd: CommonInterstitialAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create interstitial ad
interstitialAd = CommonInterstitialAd(
key = "my_interstitial_key", // Unique key for Remote Config
defaultUnitId = "ca-app-pub-3940256099942544/1033173712" // Test ad unit
)
// Load interstitial ad
lifecycleScope.launch {
interstitialAd.load(this@MyActivity)
}
// Show button
findViewById<Button>(R.id.showInterstitialButton).setOnClickListener {
showInterstitialAd()
}
}
private fun showInterstitialAd() {
if (interstitialAd.isReady()) {
interstitialAd.show(
activity = this,
callback = object : InterstitialAdShowCallback {
override fun onAdShowed() {
Log.d("Interstitial", "Ad showed")
}
override fun onAdDismissed() {
Log.d("Interstitial", "Ad dismissed")
// Pre-load next ad
lifecycleScope.launch {
interstitialAd.load(this@MyActivity)
}
}
override fun onShowFailed(error: String) {
Log.e("Interstitial", "Ad show failed: $error")
}
}
)
} else {
Log.d("Interstitial", "Ad not ready yet")
}
}
}Rewarded ads give users rewards (coins, extra lives, etc.) for watching video ads.
class MyActivity : AppCompatActivity() {
private lateinit var rewardedAd: CommonRewardedAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create rewarded ad
rewardedAd = CommonRewardedAd(
key = "my_rewarded_key", // Unique key for Remote Config
defaultUnitId = "ca-app-pub-3940256099942544/5224354917" // Test ad unit
)
// Load rewarded ad
lifecycleScope.launch {
rewardedAd.load(this@MyActivity)
}
// Show button
findViewById<Button>(R.id.showRewardedButton).setOnClickListener {
showRewardedAd()
}
}
private fun showRewardedAd() {
if (rewardedAd.isReady()) {
rewardedAd.show(
activity = this,
timeoutMs = 0L, // Optional timeout
callback = object : RewardedAdShowCallback {
override fun onAdShowed() {
Log.d("Rewarded", "Ad showed")
}
override fun onAdRewarded(isRewarded: Boolean) {
if (isRewarded) {
Log.d("Rewarded", "User earned reward!")
// Give reward to user
giveRewardToUser()
}
}
override fun onAdDismissed() {
Log.d("Rewarded", "Ad dismissed")
// Pre-load next ad
lifecycleScope.launch {
rewardedAd.load(this@MyActivity)
}
}
override fun onShowFailed(error: String) {
Log.e("Rewarded", "Ad show failed: $error")
}
}
)
} else {
Log.d("Rewarded", "Ad not ready yet")
}
}
private fun giveRewardToUser() {
// Your reward logic here
Toast.makeText(this, "You earned 100 coins!", Toast.LENGTH_SHORT).show()
}
}App open ads are shown when users open or switch back to your app.
class AppOpenAdManager(private val application: Application) : LifecycleObserver {
companion object {
private const val AD_UNIT_ID = "ca-app-pub-3940256099942544/9257395921" // Test ad unit
}
private var appOpenAd: CommonAppOpenAd? = null
private var isLoadingAd = false
private var isShowingAd = false
private var currentActivity: Activity? = null
init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {
currentActivity = activity
}
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
showAdIfAvailable()
}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {
if (currentActivity == activity) {
currentActivity = null
}
}
})
}
fun loadAd() {
if (isLoadingAd || isAdAvailable()) {
return
}
isLoadingAd = true
appOpenAd = CommonAppOpenAd("my_app_open_key", AD_UNIT_ID).apply {
load(
application,
object : AdLoadListener {
override fun onAdLoaded(adId: String, adNetworkType: String) {
isLoadingAd = false
}
override fun onAdLoadFailed(adId: String, error: String, code: Int?, adNetworkType: String?) {
isLoadingAd = false
appOpenAd = null
}
}
)
}
}
private fun isAdAvailable(): Boolean {
return appOpenAd?.isReady() == true
}
private fun showAdIfAvailable() {
if (isShowingAd || !isAdAvailable()) {
loadAd() // Pre-load for next time
return
}
val activity = currentActivity ?: return
isShowingAd = true
appOpenAd?.show(
activity,
object : FullScreenAdListener() {
override fun onAdDismissed(adId: String, adNetworkType: String) {
isShowingAd = false
appOpenAd = null
loadAd() // Pre-load the next ad
}
override fun onAdFailedToShow(adId: String, error: String, adNetworkType: String?) {
isShowingAd = false
appOpenAd = null
loadAd()
}
}
)
}
}Initialize in Application class:
class MyApplication : Application() {
private lateinit var appOpenAdManager: AppOpenAdManager
override fun onCreate() {
super.onCreate()
// ... SDK initialization code ...
// Initialize App Open Ad Manager
appOpenAdManager = AppOpenAdManager(this)
appOpenAdManager.loadAd()
}
}Create a custom native ad layout file (e.g., res/layout/ad_unified.xml):
<com.google.android.gms.ads.nativead.NativeAdView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ad_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:orientation="vertical"
android:padding="8dp">
<!-- Ad Badge and Options -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFCC66"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:text="Ad"
android:textColor="#FFFFFF"
android:textSize="12sp" />
<FrameLayout
android:id="@+id/options_view"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="4dp" />
</LinearLayout>
<!-- Ad Content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<!-- Ad Icon -->
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp" />
<!-- Ad Headline and Advertiser -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<!-- Ad Body -->
<TextView
android:id="@+id/ad_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#333333"
android:textSize="14sp" />
<!-- Ad Media -->
<com.google.android.gms.ads.nativead.MediaView
android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="8dp" />
<!-- Ad Call to Action -->
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#FFFFFF" />
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>Important Native Ad View IDs:
The SDK automatically binds these view IDs to the native ad components:
ad_view: Root NativeAdView (required)ad_headline: Ad headline textad_body: Ad body textad_call_to_action: Call to action buttonad_app_icon: App icon imagead_advertiser: Advertiser namead_media: Media view for images/videosad_stars: Star rating (RatingBar for AdMob)max_ad_stars: Star rating (FrameLayout for MAX)options_view: Ad choices icon
Initialize the SDK in your Application class's onCreate() method to ensure it's ready before any activities start.
Load ads before you need to show them to provide a better user experience:
// Load in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
rewardedAd.load(this@MyActivity)
}
}
// Show later when needed
button.setOnClickListener {
if (rewardedAd.isReady()) {
rewardedAd.show(this, callback)
}
}Always handle activity lifecycle events for banner ads:
override fun onPause() {
super.onPause()
bannerAd.pause()
}
override fun onResume() {
super.onResume()
bannerAd.resume()
}
override fun onDestroy() {
super.onDestroy()
bannerAd.destroy()
}The PO/BA is responsible for operating this part.
Always use test ad units during development to avoid policy violations:
AdMob Test Ad Units:
- Banner:
ca-app-pub-3940256099942544/6300978111 - Interstitial:
ca-app-pub-3940256099942544/1033173712 - Rewarded:
ca-app-pub-3940256099942544/5224354917 - Native:
ca-app-pub-3940256099942544/2247696110 - App Open:
ca-app-pub-3940256099942544/9257395921
Always gather user consent before showing ads, especially for users in GDPR regions:
lifecycleScope.launch {
val consentResult = SDKManager.gatherConsent(
activity = this@SplashActivity,
debugGeography = ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA
)
if (consentResult.isSuccess) {
// Proceed with loading ads
}
}Always check if an ad is ready before showing it:
if (rewardedAd.isReady()) {
rewardedAd.show(this, callback)
} else {
// Load the ad first
lifecycleScope.launch {
rewardedAd.load(this@MyActivity)
}
}Always implement error callbacks to handle ad failures gracefully:
bannerAd.setListener(object : BannerAdListener() {
override fun onAdFailedToLoad(error: String) {
Log.e("Banner", "Failed to load: $error")
// Hide ad container or show alternative content
}
})The SDK includes AppsFlyer, which has backup rules in its AndroidManifest.xml to opt out of backing up Shared Preferences data. This prevents retaining the same counters and AppsFlyer ID during app reinstallation, ensuring accurate detection of new installs or re-installs.
You may encounter manifest merger conflicts when integrating the SDK. Here's how to resolve them:
If you add android:fullBackupContent="true" in your AndroidManifest.xml, you might get this error:
Manifest merger failed : Attribute application@fullBackupContent value=(true)
Solution: Add tools:replace="android:fullBackupContent" in the <application> tag:
<application
android:fullBackupContent="true"
tools:replace="android:fullBackupContent"
...>If you add android:dataExtractionRules="true" in your AndroidManifest.xml, you might get this error:
Manifest merger failed : Attribute application@dataExtractionRules value=(true)
Solution: Add tools:replace="android:dataExtractionRules" in the <application> tag:
<application
android:dataExtractionRules="true"
tools:replace="android:dataExtractionRules"
...>If you add android:allowBackup="false" in your AndroidManifest.xml, you might get this error:
Error:
Attribute application@allowBackup value=(false) from AndroidManifest.xml:
is also present at [com.appsflyer:af-android-sdk:6.14.0] AndroidManifest.xml: value=(true).
Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml to override.
Solution: Add tools:replace="android:allowBackup" in the <application> tag:
<application
android:allowBackup="false"
tools:replace="android:allowBackup"
...>If you're targeting Android 12+ and have your own backup rules specified (android:dataExtractionRules="@xml/my_rules"), you need to manually merge your backup rules with AppsFlyer's rules.
Step 1: Add the tools:replace attribute as shown above.
Step 2: Create or update your data extraction rules file (e.g., res/xml/my_rules.xml):
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<!-- Your custom rules here -->
<!-- AppsFlyer exclusion rule -->
<exclude domain="sharedpref" path="appsflyer-data"/>
</cloud-backup>
<device-transfer>
<!-- Your custom rules here -->
<!-- AppsFlyer exclusion rule -->
<exclude domain="sharedpref" path="appsflyer-data"/>
</device-transfer>
</data-extraction-rules>Step 3: Reference it in your AndroidManifest.xml:
<application
android:dataExtractionRules="@xml/my_rules"
tools:replace="android:dataExtractionRules"
...>If you're also targeting Android 11 and lower with your own backup rules (android:fullBackupContent="@xml/my_backup_rules"), merge them with AppsFlyer's rules.
Step 1: Add the tools:replace attribute as shown above.
Step 2: Create or update your full backup content file (e.g., res/xml/my_backup_rules.xml):
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- Your custom rules here -->
<!-- AppsFlyer exclusion rule -->
<exclude domain="sharedpref" path="appsflyer-data"/>
</full-backup-content>Step 3: Reference it in your AndroidManifest.xml:
<application
android:fullBackupContent="@xml/my_backup_rules"
tools:replace="android:fullBackupContent"
...>Here's a complete example with all backup rules properly configured:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
tools:replace="android:allowBackup,android:dataExtractionRules,android:fullBackupContent"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- AdMob App ID -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY"/>
<!-- Your activities here -->
</application>
</manifest>Note: Make sure to add the tools namespace at the top of your manifest file:
xmlns:tools="http://schemas.android.com/tools"Error: Ads not loading or crashes when calling ad methods
Solution: Ensure SDKManager.initialize() is called in your Application class before loading any ads.
Possible causes:
- Invalid ad unit IDs
- Network connectivity issues
- Ad inventory not available
- Consent not gathered
Solution:
- Verify ad unit IDs are correct
- Check internet connection
- Use test ad units during development
- Ensure consent is gathered before loading ads
Error: Ad configuration not changing
Solution:
- Check Firebase Remote Config fetch interval (default 1 hour)
- Call
fetchAndActivate()to force update - Verify Remote Config keys match your code
Possible causes:
- Missing required view IDs in layout
- Layout not properly configured
Solution:
- Ensure your native ad layout includes all required view IDs
- Use
NativeAdViewas the root element - Check logs for binding errors
Possible causes:
- Ad not pre-loaded
- Showing too frequently
- Activity lifecycle issues
Solution:
- Pre-load app open ads in Application class
- Implement proper timing between ad shows
- Check activity lifecycle callbacks
To test consent forms and ads, you need your test device ID:
// Add this temporarily to get your device ID
Log.d("TEST_DEVICE", "Device ID: ${Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)}")Or check logcat for UMP SDK messages that include your hashed device ID.
Main entry point for SDK operations.
-
suspend fun initialize(context: Context, maxSdkKey: String?): Result<Unit>- Initialize the SDK with AdMob and optionally MAX
-
fun updateRemoteConfig(firebaseRemoteConfig: FirebaseRemoteConfig)- Update SDK with new Remote Config values
-
fun addInitializationListener(listener: SDKInitializationListener)- Register a listener for initialization events
-
fun removeInitializationListener(listener: SDKInitializationListener)- Unregister an initialization listener
-
suspend fun gatherConsent(activity: Activity, testDeviceHashedId: String?, debugGeography: Int): Result<Unit>- Gather user consent for ads
-
fun canRequestAds(): Boolean- Check if ads can be requested (consent obtained)
-
var enable: Boolean- Enable or disable all ads globally
CommonBannerAd(key: String, defaultUnitId: String)fun setListener(listener: BannerAdListener)suspend fun show(context: Context, container: ViewGroup)fun destroy()
CommonNativeAd(key: String, defaultUnitId: String, @LayoutRes layoutRes: Int)fun setListener(listener: NativeAdListener)suspend fun load(context: Context)suspend fun show(context: Context, container: ViewGroup)fun isReady(): Boolean
CommonInterstitialAd(key: String, defaultUnitId: String)suspend fun load(context: Context)fun show(activity: Activity, callback: InterstitialAdShowCallback)fun isReady(): Boolean
CommonRewardedAd(key: String, defaultUnitId: String)suspend fun load(context: Context)fun show(activity: Activity, timeoutMs: Long, callback: RewardedAdShowCallback)fun isReady(): Boolean
CommonAppOpenAd(key: String, defaultUnitId: String)fun load(context: Context, listener: AdLoadListener)fun show(activity: Activity, listener: FullScreenAdListener)fun isReady(): Booleanfun destroy()
This SDK is proprietary software. Please contact the development team for licensing information.
For support and questions, please contact:
- Email: thuando.work@gmail.com
- Sample code: Will be updated soon!
- Initial release
- Support for AdMob and AppLovin MAX
- Banner, Native, Interstitial, Rewarded, and App Open ads
- Firebase Remote Config integration
- GDPR/CCPA consent management
- Common API for simplified ad implementation
- Support Singular, Appsflyer for MMP