.NET 10 + Plugin.MauiMTAdmob v2.4.0 の実装例です。 バナー広告とリワード広告の両方をカバーします。
目次
はじめに
.NET MAUI アプリに Google AdMob 広告を組み込むのは、一見シンプルに見えて意外とハマりどころが多いです。 本記事では、実際の開発で遭遇したトラブルとその解決策を含め、バナー広告・リワード広告の実装方法を解説します。
環境・前提条件
| 項目 | バージョン |
|---|---|
| .NET | 10 |
| .NET MAUI | 最新 |
| Plugin.MauiMTAdmob | 2.4.0 |
| CommunityToolkit.Mvvm | 8.4.2 |
| ターゲット | Android(iOS も対応可) |
セットアップ手順
Step 1: NuGet パッケージのインストール
<PackageReference Include="Plugin.MauiMTAdmob" Version="2.4.0" />
Step 2: AndroidManifest.xml に AdMob App ID を追加
Platforms/Android/AndroidManifest.xml に以下を追加します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true"
android:icon="@mipmap/appicon"
android:roundIcon="@mipmap/appicon_round"
android:supportsRtl="true">
<!-- ★ AdMob App ID -->
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" />
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
⚠️
android:valueには AdMob コンソール で取得した App ID(広告ユニットIDではない)を設定します。
Step 3: MauiProgram.cs でプラグインを登録
using Plugin.MauiMtAdmob;
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiMTAdmob() // ★ これを追加
.ConfigureFonts(fonts => { /* ... */ });
// AdService を DI 登録
builder.Services.AddSingleton<IAdService, AdService>();
return builder.Build();
}
Step 4: MainActivity.cs で SDK を初期化
ここが最も重要なポイントです。 AdMob SDK の初期化は MainActivity.OnCreate で行います。
using Android.App;
using Android.Content.PM;
using Android.OS;
using Plugin.MauiMtAdmob;
using Plugin.MauiMtAdmob.Extra;
namespace HabitTracker;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true,
LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation
| ConfigChanges.UiMode | ConfigChanges.ScreenLayout
| ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
// AndroidManifest.xml から App ID を取得
var appInfo = PackageManager?.GetApplicationInfo(
PackageName!, PackageInfoFlags.MetaData);
var appId = appInfo?.MetaData?.GetString(
"com.google.android.gms.ads.APPLICATION_ID") ?? string.Empty;
CrossMauiMTAdmob.Current.Init(
activity: this,
appId: appId,
license: string.Empty,
openAdsId: string.Empty,
nativeAdsId: string.Empty,
enableOpenAds: false,
tagForUnderAgeOfConsent: false,
testDeviceId: string.Empty,
#if DEBUG
forceTesting: true, // デバッグ時はテストモード
#else
forceTesting: false, // リリース時は本番モード
#endif
geography: DebugGeography.DEBUG_GEOGRAPHY_DISABLED,
initialiseConsentAtStartup: false,
debugMode: false);
}
}
Step 5: テスト用 / 本番用の広告ユニットID を切り替え
開発中は Google 公式テスト広告 ID を使いましょう。 本番 ID ではエミュレータに広告が配信されません(NO_FILL エラー)。
namespace HabitTracker.Services;
public static class AdUnitIds
{
#if DEBUG
// Google 公式テスト広告ユニットID(常にテスト広告が配信される)
public const string Banner = "ca-app-pub-3940256099942544/6300978111";
public const string Rewarded = "ca-app-pub-3940256099942544/5224354917";
#else
// 本番広告ユニットID(AdMob コンソールで取得)
public const string Banner = "ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX";
public const string Rewarded = "ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX";
#endif
}
📝 Google 公式テスト広告ID 一覧
広告タイプ テストID バナー ca-app-pub-3940256099942544/6300978111インタースティシャル ca-app-pub-3940256099942544/1033173712リワード ca-app-pub-3940256099942544/5224354917
バナー広告の実装
バナー広告は XAML に MTAdView コントロールを配置するだけで表示できます。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:admob="clr-namespace:Plugin.MauiMtAdmob.Controls;assembly=Plugin.MauiMtAdmob"
xmlns:services="clr-namespace:HabitTracker.Services"
x:Class="HabitTracker.Views.MainPageView">
<Grid RowDefinitions="*, Auto">
<!-- メインコンテンツ -->
<ScrollView Grid.Row="0">
<!-- ... -->
</ScrollView>
<!-- ★ バナー広告 -->
<admob:MTAdView Grid.Row="1"
AdsId="{x:Static services:AdUnitIds.Banner}"
HorizontalOptions="Fill" />
</Grid>
</ContentPage>
ポイント:
x:StaticでAdUnitIds.Bannerを参照することで、DEBUG/RELEASE で自動的にIDが切り替わりますGrid.Rowの最下部に配置するのが一般的ですHorizontalOptions="Fill"で画面幅いっぱいに広告を表示します
リワード広告の実装
リワード広告はバナーと違い、ロード → 表示 → 報酬受け取り → クローズ のライフサイクル管理が必要です。
インターフェース定義
namespace HabitTracker.Services;
public interface IAdService
{
void LoadRewardedAd();
Task<bool> ShowRewardedAdAsync();
}
AdService 実装
using Plugin.MauiMtAdmob;
using Plugin.MauiMtAdmob.Extra;
namespace HabitTracker.Services;
public class AdService : IAdService
{
private const string RewardedAdUnitId = AdUnitIds.Rewarded;
private static readonly TimeSpan LoadTimeout = TimeSpan.FromSeconds(15);
private static readonly TimeSpan ShowTimeout = TimeSpan.FromSeconds(30);
#if ANDROID || IOS
private TaskCompletionSource<bool>? _tcs;
private TaskCompletionSource<bool>? _loadTcs;
private bool _rewardEarned;
#endif
public AdService()
{
// ★ コンストラクタでは LoadRewardedAd() を呼ばない!
// SDK 初期化(MainActivity.Init())の後で呼ぶこと
}
public void LoadRewardedAd()
{
#if ANDROID || IOS
CrossMauiMTAdmob.Current.LoadRewarded(RewardedAdUnitId);
#endif
}
public async Task<bool> ShowRewardedAdAsync()
{
#if ANDROID || IOS
// 広告がロードされていなければロードして待つ
if (!CrossMauiMTAdmob.Current.IsRewardedLoaded())
{
_loadTcs = new TaskCompletionSource<bool>();
CrossMauiMTAdmob.Current.OnRewardedLoaded += OnRewardedAdLoaded;
CrossMauiMTAdmob.Current.OnRewardedFailedToLoad += OnRewardedAdFailedToLoad;
LoadRewardedAd();
var loadCompleted = await Task.WhenAny(
_loadTcs.Task, Task.Delay(LoadTimeout));
CrossMauiMTAdmob.Current.OnRewardedLoaded -= OnRewardedAdLoaded;
CrossMauiMTAdmob.Current.OnRewardedFailedToLoad -= OnRewardedAdFailedToLoad;
if (loadCompleted != _loadTcs.Task || !_loadTcs.Task.Result)
{
return false; // ロード失敗 or タイムアウト
}
}
_rewardEarned = false;
_tcs = new TaskCompletionSource<bool>();
CrossMauiMTAdmob.Current.OnUserEarnedReward += OnUserEarnedReward;
CrossMauiMTAdmob.Current.OnRewardedClosed += OnRewardedClosed;
CrossMauiMTAdmob.Current.OnRewardedFailedToShow += OnRewardedFailedToShow;
CrossMauiMTAdmob.Current.ShowRewarded();
// タイムアウト付きで広告表示完了を待つ
var showCompleted = await Task.WhenAny(
_tcs.Task, Task.Delay(ShowTimeout));
if (showCompleted != _tcs.Task)
{
CleanupShowHandlers();
return false; // タイムアウト
}
return _tcs.Task.Result;
#else
return false;
#endif
}
#if ANDROID || IOS
private void OnRewardedAdLoaded(object? sender, EventArgs e)
=> _loadTcs?.TrySetResult(true);
private void OnRewardedAdFailedToLoad(object? sender, MTEventArgs e)
=> _loadTcs?.TrySetResult(false);
private void OnUserEarnedReward(object? sender, MTEventArgs e)
=> _rewardEarned = true;
private void OnRewardedClosed(object? sender, EventArgs e)
{
CleanupShowHandlers();
_tcs?.TrySetResult(_rewardEarned);
LoadRewardedAd(); // 次回用にプリロード
}
private void OnRewardedFailedToShow(object? sender, MTEventArgs e)
{
CleanupShowHandlers();
_tcs?.TrySetResult(false);
}
private void CleanupShowHandlers()
{
CrossMauiMTAdmob.Current.OnUserEarnedReward -= OnUserEarnedReward;
CrossMauiMTAdmob.Current.OnRewardedClosed -= OnRewardedClosed;
CrossMauiMTAdmob.Current.OnRewardedFailedToShow -= OnRewardedFailedToShow;
}
#endif
}
リワード広告のライフサイクル図
┌──────────────┐
│ LoadRewarded │ ← SDK初期化後にプリロード
└──────┬───────┘
▼
┌──────────────────┐ 失敗 ┌──────────┐
│ OnRewardedLoaded │ ───────→ │ 再ロード │
└──────┬───────────┘ └──────────┘
▼
┌──────────────┐
│ ShowRewarded │ ← ユーザーアクションで呼び出し
└──────┬───────┘
▼
┌────────────────────┐
│ OnUserEarnedReward │ ← ユーザーが広告を最後まで視聴
└──────┬─────────────┘
▼
┌────────────────┐
│ OnRewardedClosed│ ← 広告が閉じられた
└──────┬─────────┘
▼
┌──────────────┐
│ LoadRewarded │ ← 次回用にプリロード
└──────────────┘
ViewModel での使い方
MVVM パターンに従い、ViewModel から IAdService を DI で注入して使用します。
public partial class MainPageViewModel : ObservableObject
{
private readonly IAdService _adService;
public MainPageViewModel(IAdService adService)
{
_adService = adService;
}
[RelayCommand]
private async Task LoadHabitsAsync()
{
// ... データ読み込み ...
// SDK 初期化後にリワード広告をプリロード
_adService.LoadRewardedAd();
}
[RelayCommand]
private async Task NavigateToAddAsync()
{
if (IsAtHabitLimit)
{
bool confirmed = await Shell.Current.DisplayAlertAsync(
"追加上限に達しました",
$"習慣は現在最大 {MaxHabitSlots} 個までです。\n"
+ "広告を見ると1枠追加できます。",
"広告を見る",
"キャンセル");
if (!confirmed) return;
// ★ リワード広告を表示し、結果を受け取る
bool rewarded = await _adService.ShowRewardedAdAsync();
if (rewarded)
{
// 報酬付与の処理
MaxHabitSlots++;
Preferences.Default.Set(MaxSlotsPrefKey, MaxHabitSlots);
await Shell.Current.GoToAsync("HabitAdd");
}
else
{
await Shell.Current.DisplayAlertAsync(
"広告エラー",
"広告を表示できませんでした。"
+ "しばらくしてからもう一度お試しください。",
"OK");
}
}
else
{
await Shell.Current.GoToAsync("HabitAdd");
}
}
}
ハマりポイントと解決策
実際の開発で遭遇した問題とその解決策をまとめます。
❌ 1. コンストラクタで広告をロードしてはいけない
問題: AdService のコンストラクタで LoadRewardedAd() を呼ぶと、SDK 初期化前にロードが走り、必ず失敗する。
// ❌ NG: DI でインスタンス生成される = base.OnCreate() の中 = Init() より前
public AdService()
{
LoadRewardedAd(); // SDK 未初期化でクラッシュ or 無視される
}
解決策: コンストラクタは空にし、SDK 初期化後(画面表示時など)に明示的に LoadRewardedAd() を呼ぶ。
// ✅ OK: ページ表示時にプリロード
[RelayCommand]
private async Task LoadHabitsAsync()
{
// ... データ読み込み ...
_adService.LoadRewardedAd(); // SDK 初期化済みのタイミングで呼ぶ
}
❌ 2. 本番 ID ではエミュレータに広告が配信されない
問題: 本番用の広告ユニットIDを使うと、エミュレータでは エラーコード 3(NO_FILL) が返され、広告が一切表示されない。forceTesting: true を設定しても改善しない。
I/Ads: This request is sent from a test device.
I/Ads: Ad failed to load : 3 ← NO_FILL
解決策: #if DEBUG で Google 公式テスト広告ID に切り替える(前述の AdUnitIds クラス)。
❌ 3. ShowRewarded() に広告ユニットIDを渡してはいけない
問題: Plugin.MauiMTAdmob v2.4.0 では、リワード広告は queueId ベースのキューシステム を使っている。API のパラメータ名を見ないと間違えやすい。
// API シグネチャ
void LoadRewarded(string adUnit, MTRewardedAdOptions? options = null, string queueId = "default");
void ShowRewarded(string queueId = "default");
bool IsRewardedLoaded(string queueId = "default");
// ❌ NG: ShowRewarded のパラメータは queueId であり、広告ユニットIDではない!
CrossMauiMTAdmob.Current.ShowRewarded("ca-app-pub-xxx/yyy");
// ❌ NG: LoadRewarded の第3引数に空文字を渡すと queueId が "default" と不一致
CrossMauiMTAdmob.Current.LoadRewarded(adUnitId, null, string.Empty);
// ✅ OK: すべてデフォルトの queueId = "default" で統一
CrossMauiMTAdmob.Current.LoadRewarded(adUnitId); // queueId = "default"
CrossMauiMTAdmob.Current.IsRewardedLoaded(); // queueId = "default"
CrossMauiMTAdmob.Current.ShowRewarded(); // queueId = "default"
❌ 4. TaskCompletionSource にタイムアウトがないと UI がフリーズする
問題: ShowRewardedAdAsync() で TaskCompletionSource を使って非同期に結果を待つが、イベントが発火しないケースがあると await が永遠に完了しない → UIフリーズ。
解決策: Task.WhenAny + Task.Delay でタイムアウトを設ける。
// ✅ OK: 30秒でタイムアウト
var showCompleted = await Task.WhenAny(_tcs.Task, Task.Delay(TimeSpan.FromSeconds(30)));
if (showCompleted != _tcs.Task)
{
CleanupShowHandlers();
return false; // タイムアウト
}
❌ 5. イベントハンドラのクリーンアップ忘れ
問題: OnRewardedClosed 等のイベントを解除しないと、2回目以降の広告表示でハンドラが多重登録され、予期しない動作になる。
解決策: 広告のクローズ・失敗時に必ずハンドラを解除する CleanupShowHandlers() を用意する。
まとめ
最終的なファイル構成
HabitTracker/
├── Services/
│ ├── AdUnitIds.cs ← DEBUG/RELEASE 切り替え
│ ├── IAdService.cs ← インターフェース
│ └── AdService.cs ← リワード広告のロード・表示
├── ViewModels/
│ └── MainPageViewModel.cs ← AdService を DI で利用
├── Views/
│ └── MainPageView.xaml ← MTAdView でバナー広告表示
├── Platforms/Android/
│ ├── MainActivity.cs ← SDK 初期化
│ └── AndroidManifest.xml ← App ID 設定
└── MauiProgram.cs ← .UseMauiMTAdmob() + DI 登録
チェックリスト
- [ ]
AndroidManifest.xmlに AdMob App ID を設定した - [ ]
MauiProgram.csで.UseMauiMTAdmob()を呼んだ - [ ]
MainActivity.OnCreateでCrossMauiMTAdmob.Current.Init()を呼んだ - [ ] DEBUG ビルドでは Google 公式テスト広告 ID を使っている
- [ ]
LoadRewarded/IsRewardedLoaded/ShowRewardedの queueId が統一されている - [ ]
ShowRewardedAdAsync()にタイムアウトを設けている - [ ] イベントハンドラを適切にクリーンアップしている
- [ ] コンストラクタではなく SDK 初期化後 に広告をプリロードしている
この記事が .NET MAUI での AdMob 実装の参考になれば幸いです!

コメント