Skip to content

Repository files navigation

AMDev.IT AdMob Wrapper

License: Apache-2.0.NET

A modern, lightweight AdMob wrapper for .NET 10 Android, .NET 10 iOS, and .NET MAUI, designed to solve the lack of working AdMob bindings in the current .NET ecosystem.

The library wraps the native Android and iOS SDKs in bindable Kotlin and Swift layers and exposes them to .NET with both callback and async/await APIs.


Highlights

  • Google Mobile Ads SDK Next-Gen on Android.
  • Native Swift XCFramework built with Google's official Mobile Ads Swift package on iOS.
  • Google User Messaging Platform (UMP) consent flow on Android and iOS, including privacy options, current status, test reset, under-age settings, debug geography, and test-device configuration.
  • Native and managed logging through IDroidLogger, IAppleLogger, and Microsoft.Extensions.Logging adapters in MAUI.
  • Banner, interstitial, rewarded, and app-open ads with callback and async/await APIs.
  • Adaptive and fixed banner sizes on Android and iOS.
  • MAUI dependency-injection services and XAML banner control.
  • Safe MAUI desktop behavior: configurable banner fallback and no-op consent service on Windows and Mac Catalyst.

Platform support

CapabilityAndroidiOSWindowsMac Catalyst
Native AdMob adsYesYesNoNo
UMP consentYesYesNo-opNo-op
IAdMobConsentService.IsSupportedtruetruefalsefalse
MAUI bannerNativeNativeFallbackTemplateFallbackTemplate
MAUI full-screen adsYesYesNot supportedNot supported
Native logging bridgeYesYesNot applicableNot applicable

The Windows and Mac Catalyst consent implementation never throws. It reports a neutral NotRequired state so a cross-platform application can skip UMP and continue its non-advertising or fallback UI. Full-screen advertising services remain mobile-only and throw PlatformNotSupportedException if called on a desktop target.

Documentation

The project wiki contains the complete getting-started guide, platform-specific setup, UMP privacy workflow, MAUI examples, logging, desktop fallbacks, and troubleshooting.


Packages

PackageDescriptionNuGetDownloads
AMDevIT.Admob.Wrapper.Droid.NET binding for the native Kotlin AARNuGetDownloads
AMDevIT.Admob.Wrapper.iOSNative.NET binding for the native Swift xcframeworkNuGetDownloads
AMDevIT.Admob.WrapperMulti-platform wrapper with async/await extensionsNuGetDownloads
AMDevIT.Admob.Wrapper.MAUICrossMAUI controls, handlers, and full-screen servicesNuGetDownloads

Requirements

  • .NET 10
  • Android API 33+ (Android 13)
  • iOS 15.0+
  • Mac Catalyst 15.0+ or Windows 10 version 1809+ for the MAUI fallback UI

The Android binding embeds Google Mobile Ads SDK Next-Gen 1.3.1 and brings Google UMP 4.0.0 through the official .NET Android bindings. Do not add the legacy Xamarin.GooglePlayServices.Ads package.

The iOS XCFramework is built from Google's official swift-package-manager-google-mobile-ads package (13.7.0). UMP is supplied transitively by that package; do not add a second UMP Swift package.


Installation

Android project

<PackageReferenceInclude="AMDevIT.Admob.Wrapper.Droid"Version="0.1.10" />

iOS project

<PackageReferenceInclude="AMDevIT.Admob.Wrapper.iOSNative"Version="0.1.10" />

Android or iOS project with async/await support

<PackageReferenceInclude="AMDevIT.Admob.Wrapper"Version="0.1.10" />

MAUI project with async/await support and XAML controls

<PackageReferenceInclude="AMDevIT.Admob.Wrapper.MAUICross"Version="0.1.10" />

Add AMDevIT.Admob.Wrapper as well only when the application uses the lower-level native async/await extension methods.

AndroidManifest.xml

Add your AdMob App ID inside the <application> tag:

<application ...>
<meta-dataandroid:name="com.google.android.gms.ads.APPLICATION_ID"android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />
</application>

Info.plist (iOS)

Add your AdMob App ID:

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>

For testing, use the official Google test App ID: ca-app-pub-3940256099942544~3347511713


Ad formats supported

FormatAndroid classiOS class
BannerBannerAdWrapperBannerAdWrapper
InterstitialInterstitialAdWrapperInterstitialAdWrapper
RewardedRewardedAdWrapperRewardedAdWrapper
App OpenAppOpenAdWrapperAppOpenAdWrapper

Usage — Android

Consent and initialization

Request or refresh consent before initializing and loading ads. The async API is available from AMDevIT.Admob.Wrapper:

conststringappId="ca-app-pub-3940256099942544~3347511713";AdMobManagermanager=AdMobManager.Instance;try{ConsentGatheringResultconsent=awaitmanager.GatherConsentAsync(this);if(!consent.CanRequestAds)return;}catch(ConsentExceptionexception)when(exception.CanRequestAds==true){// UMP failed, but a previous consent state still allows ad requests.}awaitmanager.InitializeAsync(this.ApplicationContext!,appId);

Use ConsentRequestOptions to set the under-age flag. Debug geography and test device IDs are also available through ConsentDebugParameters; never ship debug consent settings in production.

varoptions=newConsentRequestOptions(TagForUnderAgeOfConsent:false,DebugParameters:newConsentDebugParameters(ConsentDebugGeography.Eea,"TEST-DEVICE-HASH"));ConsentGatheringResultconsent=awaitmanager.GatherConsentAsync(this,options);

Other UMP operations are exposed as UpdateCurrentConsentInformationAsync, ShowPrivacyOptionsFormAsync, LoadAndShowConsentFormIfRequiredAsync, GetCurrentConsentInformation, CanRequestAds, and ResetConsentForTesting.

Callback style

AdMobManager.Instance.Initialize(this.ApplicationContext!,"ca-app-pub-3940256099942544~3347511713",newMyInitListener());privateclassMyInitListener:Java.Lang.Object,IOnInitializedListener{publicvoidOnInitialized(){// SDK ready, load ads}publicvoidOnInitializationFailed(stringerror){Console.WriteLine($"AdMob init failed: {error}");}}

Banner Ad

Callback style

varbannerWrapper=newBannerAdWrapper(this,logger:null);varadView=bannerWrapper.Load(adUnitId:"ca-app-pub-3940256099942544/6300978111",loadListener:newMyBannerLoadListener());bannerContainer.AddView(adView);privateclassMyBannerLoadListener:Java.Lang.Object,IOnAdLoadedListener{publicvoidOnAdLoaded()=>Console.WriteLine("Banner loaded");publicvoidOnAdFailedToLoad(interrorCode,stringerrorMessage)=>Console.WriteLine($"Banner failed: [{errorCode}] {errorMessage}");}

Async style

varbannerWrapper=newBannerAdWrapper(this,logger:null);varadView=awaitbannerWrapper.LoadAsync("ca-app-pub-3940256099942544/6300978111");bannerContainer.AddView(adView);

Interstitial Ad

Callback style

varinterstitialWrapper=newInterstitialAdWrapper();interstitialWrapper.Load(adUnitId:"ca-app-pub-3940256099942544/1033173712",loadListener:newMyLoadListener(),eventListener:newMyEventListener());if(interstitialWrapper.IsLoaded)interstitialWrapper.Show(this,loadListener:null);privateclassMyLoadListener:Java.Lang.Object,IOnAdLoadedListener{publicvoidOnAdLoaded()=>Console.WriteLine("Interstitial loaded");publicvoidOnAdFailedToLoad(interrorCode,stringerrorMessage)=>Console.WriteLine($"Interstitial failed: [{errorCode}] {errorMessage}");}privateclassMyEventListener:Java.Lang.Object,IOnAdEventListener{publicvoidOnAdShown()=>Console.WriteLine("Interstitial shown");publicvoidOnAdDismissed()=>Console.WriteLine("Interstitial dismissed");publicvoidOnAdClicked()=>Console.WriteLine("Interstitial clicked");publicvoidOnAdImpression()=>Console.WriteLine("Interstitial impression");publicvoidOnAdFailedToShow(interrorCode,stringerrorMessage)=>Console.WriteLine($"Interstitial show failed: [{errorCode}] {errorMessage}");}

Note: Interstitial ads are one-shot. Once dismissed, you need to call Load again before showing. This is by design — it gives you full control over which Ad Unit ID to use on the next load.

Rewarded Ad

Callback style

varrewardedWrapper=newRewardedAdWrapper();rewardedWrapper.Load(adUnitId:"ca-app-pub-3940256099942544/5224354917",loadListener:newMyLoadListener());if(rewardedWrapper.IsLoaded)rewardedWrapper.Show(this,newMyRewardListener());privateclassMyRewardListener:Java.Lang.Object,IOnRewardEarnedListener{publicvoidOnRewardEarned(stringtype,intamount)=>Console.WriteLine($"Reward earned: {amount}{type}");}

App Open Ad

Callback style

varappOpenWrapper=newAppOpenAdWrapper();appOpenWrapper.Load(adUnitId:"ca-app-pub-3940256099942544/9257395921",loadListener:newMyLoadListener(),eventListener:newMyEventListener());if(appOpenWrapper.IsLoaded&&!appOpenWrapper.IsShowing)appOpenWrapper.Show(this,loadListener:null);

Usage — iOS

Consent and initialization

Gather consent before initializing the SDK and loading ads:

AdMobManagermanager=AdMobManager.Instance;try{ConsentGatheringResultconsent=awaitmanager.GatherConsentAsync(this);if(!consent.CanRequestAds)return;}catch(ConsentExceptionexception)when(exception.CanRequestAds==true){// UMP failed, but a previous consent state still allows ad requests.}awaitmanager.InitializeAsync(this);

The iOS async API also exposes UpdateCurrentConsentInformationAsync, ShowPrivacyOptionsFormAsync, LoadAndShowConsentFormIfRequiredAsync, and GetCurrentConsentInformation. The native manager exposes CanRequestAds() and the test-only ResetConsentForTesting() operation.

Initialization

Callback style

AdMobManager.Instance.InitializeWithViewController(this,newMyInitListener());privateclassMyInitListener:NSObject,IOnInitializedListener{publicvoidOnInitialized(){// SDK ready, load ads}publicvoidOnInitializationFailedWithError(stringerror){Console.WriteLine($"AdMob init failed: {error}");}}

Async style

awaitAdMobManager.Instance.InitializeAsync(this);

Banner Ad

Callback style

varbannerWrapper=newBannerAdWrapper();varadView=bannerWrapper.LoadWithAdUnitId(adUnitId:"ca-app-pub-3940256099942544/6300978111",viewController:this,loadListener:newMyBannerLoadListener(),eventListener:null);bannerContainer.AddSubview(adView);privateclassMyBannerLoadListener:NSObject,IOnAdLoadedListener{publicvoidOnAdLoaded()=>Console.WriteLine("Banner loaded");publicvoidOnAdFailedToLoadWithErrorCode(ninterrorCode,stringerrorMessage)=>Console.WriteLine($"Banner failed: [{errorCode}] {errorMessage}");}

The low-level iOS ad wrappers use listener callbacks. MAUI consumers can use the XAML banner events/commands and asynchronous full-screen services.

Interstitial Ad

Callback style

varinterstitialWrapper=newInterstitialAdWrapper();interstitialWrapper.LoadWithAdUnitId(adUnitId:"ca-app-pub-3940256099942544/1033173712",loadListener:newMyLoadListener(),eventListener:newMyEventListener());if(interstitialWrapper.IsLoaded)interstitialWrapper.ShowWithViewController(this);

Rewarded Ad

Callback style

varrewardedWrapper=newRewardedAdWrapper();rewardedWrapper.LoadWithAdUnitId(adUnitId:"ca-app-pub-3940256099942544/5224354917",loadListener:newMyLoadListener(),eventListener:null);if(rewardedWrapper.IsLoaded)rewardedWrapper.ShowWithViewController(this,newMyRewardListener());

App Open Ad

Callback style

varappOpenWrapper=newAppOpenAdWrapper();appOpenWrapper.LoadWithAdUnitId(adUnitId:"ca-app-pub-3940256099942544/9257395921",loadListener:newMyLoadListener(),eventListener:null);if(appOpenWrapper.IsLoaded&&!appOpenWrapper.IsShowing)appOpenWrapper.ShowWithViewController(this);

Usage — MAUI (AMDevIT.Admob.Wrapper.MAUICross)

Setup

Register the handler in MauiProgram.cs:

builder.UseAMDevITAdMobWrapper();

Inject IAdMobConsentService, check whether the platform supports UMP, and complete consent before initializing or loading ads:

publicsealedclassAdMobStartup(IAdMobConsentServiceconsentService){publicasyncTaskInitializeAsync(CancellationTokencancellationToken=default){if(!consentService.IsSupported)return;boolcanRequestAds;try{ConsentGatheringResultconsent=awaitconsentService.GatherConsentAsync(cancellationToken:cancellationToken);canRequestAds=consent.CanRequestAds;}catch(ConsentExceptionexception)when(exception.CanRequestAds==true){// UMP failed, but a previous consent state still allows ad requests.canRequestAds=true;}if(!canRequestAds)return;stringapplicationId=OperatingSystem.IsAndroid()?"ca-app-pub-3940256099942544~3347511713":string.Empty;awaitconsentService.InitializeAsync(applicationId,cancellationToken);}}

IAdMobConsentService also exposes the current consent snapshot, privacy options form, required consent form, CanRequestAds, and the test-only reset operation. It is registered on every MAUI target. Android and iOS report IsSupported == true; Windows and Mac Catalyst receive a safe no-op service that reports IsSupported == false, logs skipped operations, and returns a neutral not-required consent state without throwing. The application ID argument is required on Android; iOS ignores it and reads GADApplicationIdentifier from Info.plist.

Run this workflow before making a banner visible or calling a full-screen load method. Request updated consent information on every app launch, initialize AdMob only when ads may be requested, and expose a persistent privacy-options entry point whenever PrivacyOptionsRequirementStatus.Required is reported.

Call the privacy-options form from the app's privacy settings when required:

awaitconsentService.ShowPrivacyOptionsFormAsync(cancellationToken);

Banner Ad in XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPagexmlns="http://schemas.microsoft.com/dotnet/2021/maui"xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"xmlns:admob="clr-namespace:AMDevIT.Admob.Wrapper.MAUICross;assembly=AMDevIT.Admob.Wrapper.MAUICross"x:Class="YourApp.MainPage">
<GridRowDefinitions="*, Auto">
<Label Grid.Row="0"Text="Hello MAUI!" />
<admob:BannerAd Grid.Row="1"AdUnitId="ca-app-pub-3940256099942544/6300978111"AdSize="Adaptive"AdLoaded="OnBannerLoaded"AdFailed="OnBannerFailed">
<admob:BannerAd.FallbackTemplate>
<DataTemplate>
<BorderPadding="12">
<LabelText="AdMob banner ads aren't supported on this platform." />
</Border>
</DataTemplate>
</admob:BannerAd.FallbackTemplate>
</admob:BannerAd>
</Grid>
</ContentPage>
privatevoidOnBannerLoaded(objectsender,EventArgse){Console.WriteLine("Banner loaded");}privatevoidOnBannerFailed(objectsender,AdFailedEventArgse){Console.WriteLine($"Banner failed: [{e.ErrorCode}] {e.ErrorMessage}");}

FallbackTemplate is rendered on Windows and Mac Catalyst, where AdMob isn't supported. Its content is created lazily by the platform handler. If the property isn't set, the default template creates an empty ContentView. Android and iOS continue to render the native AdMob banner view and don't instantiate the fallback template.

Full-screen ad services are available for dependency injection on every supported MAUI target. Calling them on Windows or Mac Catalyst throws PlatformNotSupportedException.

Full-screen ads

Inject IInterstitialAdService, IAppOpenAdService, or IShowableRewardedAdService, then await loading before showing the ad:

publicsealedclassAdCoordinator(IInterstitialAdServiceinterstitialAdService,IShowableRewardedAdServicerewardedAdService){publicTaskShowInterstitialAsync(CancellationTokencancellationToken=default){returninterstitialAdService.LoadAndShowAsync("ca-app-pub-3940256099942544/1033173712",cancellationToken);}publicasyncTaskShowRewardedAsync(CancellationTokencancellationToken=default){rewardedAdService.AdRewardEarned+=OnAdRewardEarned;awaitrewardedAdService.LoadAndShowAsync("ca-app-pub-3940256099942544/5224354917",cancellationToken);}privatestaticvoidOnAdRewardEarned(object?sender,AdRewardreward){Console.WriteLine($"Reward: {reward.Amount}{reward.Type}");}}

Each registered service supports one native load operation at a time. A second overlapping call throws InvalidOperationException. Cancelling the token cancels the caller's wait, but it cannot cancel the native SDK operation; wait for its load callback before starting another load on the same service.

Banner Ad sizes

ValueDescription
AdaptiveAdapts to the container width (default)
BannerStandard 320x50
LargeBanner320x100
MediumRectangle300x250
FullBanner468x60
Leaderboard728x90

Logging and diagnostics

MAUICross automatically bridges native Android and iOS wrapper messages to the configured Microsoft.Extensions.Logging providers. Register the wrapper and the desired providers normally:

builder.Logging.AddDebug();builder.UseAMDevITAdMobWrapper();

Low-level native consumers can implement IDroidLogger or IAppleLogger and pass the logger to AdMobManager and the individual ad-wrapper constructors. Trace, debug, information, warning, error, and critical levels are supported. Ad dismissal is raised only from the native didDismiss callback; the earlier willDismiss callback is diagnostic-only.


Error handling

The lower-level async extensions throw AdException on failure. MAUI full-screen loading throws AdLoadException; both exceptions expose the native error code:

try{ConsentGatheringResultconsent=awaitAdMobManager.Instance.GatherConsentAsync(this);if(!consent.CanRequestAds)return;awaitAdMobManager.Instance.InitializeAsync(this.ApplicationContext!,"ca-app-pub-3940256099942544~3347511713");varadView=awaitbannerWrapper.LoadAsync(adUnitId);bannerContainer.AddView(adView);}catch(AdExceptionex){Console.WriteLine($"AdMob error [{ex.ErrorCode}]: {ex.Message}");}catch(ConsentExceptionex){Console.WriteLine($"Consent error [{ex.ErrorCode}]: {ex.Message}");}

Test Ad Unit IDs

Use these IDs during development. Never use real Ad Unit IDs on a device you own.

FormatTest Ad Unit ID
App Openca-app-pub-3940256099942544/9257395921
Bannerca-app-pub-3940256099942544/6300978111
Interstitialca-app-pub-3940256099942544/1033173712
Rewardedca-app-pub-3940256099942544/5224354917
Rewarded Interstitialca-app-pub-3940256099942544/5354046379
Nativeca-app-pub-3940256099942544/2247696110

Test applications

  • AMDevIT.Admob.Wrapper.DroidTestApp exercises Android UMP, initialization, banners, interstitial, rewarded, and app-open ads.
  • AMDevIT.Admob.Wrapper.AppleTestApp exercises iOS UMP, privacy options, native logging, adaptive banners, and every supported full-screen format.
  • AMDevIT.Admob.Wrapper.MAUITestApp targets Android, iOS, Windows, and Mac Catalyst. It delays mobile ad materialization until consent succeeds and demonstrates the desktop no-op consent and banner fallback behavior.

Always use Google's test IDs while developing and perform final consent/ad-flow checks on physical Android and iOS devices before publishing.


Project structure

AMDevITAdMobWrapper/
├── sources/
│ ├── droid/ # Kotlin source (Android Studio)
│ │ └── admob-wrapper/
│ │ ├── AdMobManager.kt
│ │ └── ads/
│ │ ├── BannerAdWrapper.kt
│ │ ├── InterstitialAdWrapper.kt
│ │ ├── RewardedAdWrapper.kt
│ │ └── AppOpenAdWrapper.kt
│ ├── apple/ios/ # Swift source (Xcode)
│ │ ├── build_xcframework.sh
│ │ └── AdMobWrapper/
│ │ ├── AdMobManager.swift
│ │ └── Ads/
│ │ ├── BannerAdWrapper.swift
│ │ ├── InterstitialAdWrapper.swift
│ │ ├── RewardedAdWrapper.swift
│ │ └── AppOpenAdWrapper.swift
│ └── dotnet/AMDevIT.Admob.Wrapper/
│ ├── AMDevIT.Admob.Wrapper.Droid/ # .NET binding project (Android)
│ ├── AMDevIT.Admob.Wrapper.iOSNative/ # .NET binding project (iOS)
│ ├── AMDevIT.Admob.Wrapper/ # Multi-platform wrapper + async extensions
│ ├── AMDevIT.Admob.Wrapper.MAUICross/ # MAUI controls and services
│ ├── AMDevIT.Admob.Wrapper.DroidTestApp/ # Android test app
│ ├── AMDevIT.Admob.Wrapper.AppleTestApp/ # iOS test app
│ ├── AMDevIT.Admob.Wrapper.MAUITestApp/ # Android/iOS/desktop MAUI test app
│ └── AMDevIT.Admob.Wrapper.MAUICross.Tests/ # async lifecycle tests

Notes about building

Android

The native Android SDK is built as an AAR using Gradle. When making changes to the native code, rebuild the release AAR and replace AMDevIT.Admob.Wrapper.Droid/Jars/admob-wrapper-release.aar.

iOS

The native iOS SDK is built as an xcframework using Xcode. A build script is provided at sources/apple/ios/build_xcframework.sh. Run it from that directory:

./build_xcframework.sh

Then replace sources/dotnet/AMDevIT.Admob.Wrapper/AMDevIT.Admob.Wrapper.iOSNative/libs/AdMobWrapper.xcframework.


Contributing

Contributions are welcome. Please open an issue before submitting a pull request for significant changes.

When updating the native Android SDK version:

  1. Update adsMobileSdkVersion in libs.versions.toml
  2. Recompile the AAR from Android Studio
  3. Replace the release AAR in AMDevIT.Admob.Wrapper.Droid/Jars/
  4. Update the AndroidMavenLibrary Next-Gen version and its explicit .NET Android dependency bindings in AMDevIT.Admob.Wrapper.Droid.csproj
  5. Verify the generated NuGet embeds the expected Next-Gen AAR and does not depend on the legacy Xamarin.GooglePlayServices.Ads package
  6. Bump the package version and publish

When updating the native iOS SDK version:

  1. Update the SPM dependency version in Xcode
  2. Run ./build_xcframework.sh from sources/apple/ios/
  3. Replace the xcframework in AMDevIT.Admob.Wrapper.iOSNative/libs/
  4. Bump the package version and publish

License

Apache 2.0 License — see LICENSE for details.

This library is not affiliated with or endorsed by Google. AdMob is a trademark of Google LLC.

About

Ad Mob wrapper for simplify AdMob usage in cross platform systems like .NET Maui or .NET 10.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages