メインコンテンツへスキップ

CapacitorでAdMobのGDPR同意を処理する方法

GoogleのUMP SDKを使用して、CapacitorアプリでAdMob GDPR同意を管理する方法:同意フォーム、プライバシー設定、ATTの順序、デバッグ用の地域設定、エラーハンドリング、そしてcodeについて。

CapacitorアプリにおけるAdMob GDPR同意のプライバシーと同意のイラスト

CapacitorアプリでAdMob GDPR同意を管理するには、GoogleのUser Messaging Platform (UMP) SDKを使用します。起動のたびに同意情報をリクエストし、必要に応じて同意フォームを表示し、 canRequestAds がtrueの場合にのみ広告を初期化します。また、ユーザーが選択を変更できるようにプライバシー設定への入り口を用意する必要があります。iOSでは、UMPのフローをApp Tracking Transparencyのプロンプトより前に実行する必要があります。このガイドでは、UMPをJavaScriptでラップするプラグインと、2つのセットアップを用いた完全なフローを紹介します。 @capgo/capacitor-admob 軽量なネイティブの同意ブリッジを使用して実現します。

UMPの機能とAdMobがそれを必要とする理由

2024年1月より、GoogleはEEA、英国、スイスのユーザーに広告を配信するパブリッシャーに対し、IAB Transparency and Consent Framework (TCF) と統合されたGoogle認定の同意管理プラットフォーム (CMP) の使用を義務付けています。UMP SDK はGoogle独自の認定CMPであり、AdMobコンソールに組み込まれた無料のツールです。

UMPは主に以下の3つの役割を担います:

  1. ユーザーが同意が必要な地域にいるかどうかを検出します。
  2. AdMobで設定した同意メッセージを、ユーザーの言語で表示します。
  3. ユーザーの選択をTC文字列としてデバイスに保存します(標準的な IABTCF_ キーを使用して、 UserDefaults iOSでは、 SharedPreferences (Androidの場合)、Google Mobile Ads SDK およびメディエーションパートナーが広告リクエスト時に読み取るものです。

同じコンソールから、AdMobが「US states」メッセージと呼んでいる米国の州のプライバシー規制向けにUMPを使用することも可能です。

ステップ 1: AdMobでメッセージを作成する

AdMobコンソールで、 プライバシーとメッセージ:

  • から、 欧州の規制に基づく メッセージ(GDPR)を作成します。適用するアプリ、言語、広告パートナーのリストを選択してください。
  • 必要に応じて、 米国の州別の メッセージも作成できます。
  • iOSでは、 IDFAの説明 メッセージを作成します(App Tracking Transparencyをリクエストする予定がある場合)。UMPは、システムのATTプロンプトが表示される前にこのメッセージを表示します。
  • メッセージを公開してください。未公開のメッセージは表示されません。

アプリでフォームが全く表示されない場合は、まずメッセージが未公開または未割り当てになっていないか確認してください。

ステップ 2: 実行順序の決定

ほとんどのアプリで推奨される順序:

  1. アプリの起動
  2. UMPを使用して同意情報の更新をリクエスト(起動ごと)
  3. 必要な場合は、同意フォームをロードして表示
  4. もし canRequestAds が true の場合、Mobile Ads SDK を開始して広告をロード
  5. UMP がプライバシー設定の選択が必要だと判断した際、設定画面に「プライバシー設定」のエントリを表示します。

また、Google では同意リクエストと並行して Mobile Ads SDK を開始することも可能です。これは、 canRequestAds が前回のセッションからすでに true だった場合に有効です。これにより、リピーターの初回広告表示までの時間が短縮されます。

オプション A: UMP を JavaScript でラップするプラグイン

@capacitor-community/admob (バージョン8はCapacitor 8をサポートしています) UMPメソッドを直接公開しています: requestConsentInfo, showConsentForm, showPrivacyOptionsForm と resetConsentInfo.

bun add @capacitor-community/admob
bunx cap sync
import {
  AdMob,
  AdmobConsentStatus,
  AdmobConsentDebugGeography,
} from '@capacitor-community/admob';

export async function initAdsWithConsent(isDebug: boolean) {
  await AdMob.initialize();

  let info = await AdMob.requestConsentInfo(
    isDebug
      ? {
          debugGeography: AdmobConsentDebugGeography.EEA,
          testDeviceIdentifiers: ['YOUR_TEST_DEVICE_ID'],
        }
      : undefined,
  );

  if (info.isConsentFormAvailable && info.status === AdmobConsentStatus.REQUIRED) {
    info = await AdMob.showConsentForm();
  }

  // The enum value is the string 'REQUIRED'
  const showPrivacyEntry = String(info.privacyOptionsRequirementStatus) === 'REQUIRED';

  return { canRequestAds: info.canRequestAds, showPrivacyEntry };
}

export async function openPrivacyOptions() {
  await AdMob.showPrivacyOptionsForm();
}

このプラグインは上記の順序における唯一の例外です。READMEには以下のように記載されています: AdMob.initialize() の前に requestConsentInfo(), またiOSでは、以下の処理が完了するまで同意フォームを表示できません: initialize() が実行されるまで。 initialize() はSDKを開始しますが広告のリクエストは行いません。そのため、重要なルールは「以下がtrueになるまで広告をロードしないこと」です: canRequestAds がtrueであること。ユーザーが回答するまでSDKを停止させておきたい場合は、オプションBを使用してください。オプションBでは、起動時だけでなく、 AdMob.start().

確認 canRequestAds すべての広告ロード前に同意プロセスを実行します。これは、ユーザーがプライバシー設定フォームから同意を撤回できるためです。

オプションB: ネイティブUMPブリッジを使用した@capgo/capacitor-admob

@capgo/capacitor-admob により、以下が可能になります: AdMob.start(), リクエスト設定、バナー、インタースティシャル、リワード広告、リワードインタースティシャル広告、およびiOSのトラッキング許可を扱います。UMPをラップしていないため、広告を呼び出す前に同意フローを実行する小さなアプリローカルプラグインを追加します。 AdMob.start().

bun add @capgo/capacitor-admob
bunx cap sync

UMP SDK を追加

  • iOS: Googleの GoogleUserMessagingPlatform パッケージをSwift Package Managerで追加するか (Capacitor 8のプロジェクトはデフォルトでSPMを使用)、プロジェクトがまだCocoaPodsを使用している場合は同名のCocoaPods podを追加してください。
  • Android: における android/app/build.gradle:
dependencies {
    implementation "com.google.android.ump:user-messaging-platform:<latest version>"
}

GoogleのMavenリポジトリから最新版を使用してください。

作成します ios/App/App/ConsentPlugin.swift:

import Capacitor
import UserMessagingPlatform

@objc(ConsentPlugin)
public class ConsentPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "ConsentPlugin"
    public let jsName = "Consent"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "gather", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "showPrivacyOptions", returnType: CAPPluginReturnPromise),
    ]

    @objc func gather(_ call: CAPPluginCall) {
        let parameters = RequestParameters()
        DispatchQueue.main.async {
            ConsentInformation.shared.requestConsentInfoUpdate(with: parameters) { error in
                if let error = error {
                    call.reject(error.localizedDescription)
                    return
                }
                guard let vc = self.bridge?.viewController else {
                    call.reject("No view controller")
                    return
                }
                ConsentForm.loadAndPresentIfRequired(from: vc) { formError in
                    if let formError = formError {
                        call.reject(formError.localizedDescription)
                        return
                    }
                    call.resolve(self.state())
                }
            }
        }
    }

    @objc func showPrivacyOptions(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            guard let vc = self.bridge?.viewController else {
                call.reject("No view controller")
                return
            }
            ConsentForm.presentPrivacyOptionsForm(from: vc) { formError in
                if let formError = formError {
                    call.reject(formError.localizedDescription)
                    return
                }
                call.resolve(self.state())
            }
        }
    }

    private func state() -> [String: Any] {
        return [
            "canRequestAds": ConsentInformation.shared.canRequestAds,
            "privacyOptionsRequired":
                ConsentInformation.shared.privacyOptionsRequirementStatus == .required,
        ]
    }
}

それを次から登録します CAPBridgeViewController サブクラスで、そのクラスを view controller に設定します Main.storyboard:

import Capacitor

class MainViewController: CAPBridgeViewController {
    override open func capacitorDidLoad() {
        bridge?.registerPluginInstance(ConsentPlugin())
    }
}

作成します android/app/src/main/java/com/example/app/ConsentPlugin.java (パッケージ名を使用):

package com.example.app;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.UserMessagingPlatform;

@CapacitorPlugin(name = "Consent")
public class ConsentPlugin extends Plugin {

    @PluginMethod
    public void gather(PluginCall call) {
        ConsentInformation info = UserMessagingPlatform.getConsentInformation(getContext());
        ConsentRequestParameters params = new ConsentRequestParameters.Builder().build();

        getActivity().runOnUiThread(() ->
            info.requestConsentInfoUpdate(
                getActivity(),
                params,
                () -> UserMessagingPlatform.loadAndShowConsentFormIfRequired(getActivity(), formError -> {
                    if (formError != null) {
                        call.reject(formError.getMessage());
                        return;
                    }
                    call.resolve(state(info));
                }),
                requestError -> call.reject(requestError.getMessage())
            )
        );
    }

    @PluginMethod
    public void showPrivacyOptions(PluginCall call) {
        getActivity().runOnUiThread(() ->
            UserMessagingPlatform.showPrivacyOptionsForm(getActivity(), formError -> {
                if (formError != null) {
                    call.reject(formError.getMessage());
                    return;
                }
                call.resolve(state(UserMessagingPlatform.getConsentInformation(getContext())));
            })
        );
    }

    private JSObject state(ConsentInformation info) {
        JSObject ret = new JSObject();
        ret.put("canRequestAds", info.canRequestAds());
        ret.put(
            "privacyOptionsRequired",
            info.getPrivacyOptionsRequirementStatus() ==
                ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
        );
        return ret;
    }
}

以下に登録します: MainActivity.java の前に super.onCreate:

public class MainActivity extends BridgeActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        registerPlugin(ConsentPlugin.class);
        super.onCreate(savedInstanceState);
    }
}

TypeScriptから呼び出す

import { registerPlugin } from '@capacitor/core';
import { AdMob, BannerAd } from '@capgo/capacitor-admob';

interface ConsentState {
  canRequestAds: boolean;
  privacyOptionsRequired: boolean;
}

interface ConsentPlugin {
  gather(): Promise<ConsentState>;
  showPrivacyOptions(): Promise<ConsentState>;
}

const Consent = registerPlugin<ConsentPlugin>('Consent');

export async function startAds() {
  let state: ConsentState;
  try {
    state = await Consent.gather();
  } catch (err) {
    console.warn('Consent flow failed', err);
    return { showPrivacyEntry: false };
  }

  if (state.canRequestAds) {
    await AdMob.start();
    const banner = new BannerAd({
      adUnitId: 'ca-app-pub-3940256099942544/2934735716', // Google test banner (iOS)
      position: 'bottom',
    });
    await banner.show();
  }

  return { showPrivacyEntry: state.privacyOptionsRequired };
}

export const openPrivacyOptions = () => Consent.showPrivacyOptions();

Native code の変更には新しいストアビルドが必要です。その後は、設定画面の同意文言や広告配置ロジックも一緒に配信できます: Capgo Live Updates.

ステップ 3: プライバシーオプションのエントリポイントを追加する

UMPがプライバシーオプションの表示が必要だと示した場合、Googleは同意の選択肢を再度開ける分かりやすい導線を求めています。設定ページに「プライバシー設定」の行を追加し、不要なときは非表示にしてください:

const { showPrivacyEntry } = await startAds();
document.querySelector('#privacy-row')?.toggleAttribute('hidden', !showPrivacyEntry);
document.querySelector('#privacy-row')?.addEventListener('click', () => openPrivacyOptions());

ユーザーが選択を変更した後、以下を読み取ります: canRequestAds 次の広告リクエストの前に再度読み取ります。

ステップ 4: iOS の App Tracking Transparency

ATT は GDPR とは別物です。iOS において ATT は IDFA へのアクセスを制御し、GDPR の同意は個人データの処理の可否と方法を制御します。iOS を使用する EU ユーザーに対しては、通常その両方が必要になります。

  • 追加 NSUserTrackingUsageDescription まで Info.plist.
  • AdMob でメッセージを設定している場合は、UMP に IDFA の説明メッセージを表示させ、ATT をトリガーさせてください。そうでない場合は、同意フローの後に自身で ATT をリクエストしてください。

を使用すると、 @capgo/capacitor-admob ATT のステータスを直接読み取ったり、リクエストしたりするには以下を使用します:

import { AdMob, TrackingAuthorizationStatus } from '@capgo/capacitor-admob';

const { status } = await AdMob.trackingAuthorizationStatus();
if (status === TrackingAuthorizationStatus.notDetermined) {
  await AdMob.requestTrackingAuthorization();
}

Capgo には、単体での App Tracking Transparency プラグインも用意されています 他の SDK で ATT が必要な場合に利用できます。プライバシーマニフェストと App Store のプライバシーラベルへのトラッキングの宣言を忘れないでください。詳細は当社の privacy manifest ガイドを.

ステップ 5: デバッグ用地理情報のテスト

UMPは、EEA、英国、またはスイスにいるユーザーにのみGDPRフォームを表示します。それ以外の地域でテストするには:

  • アプリを一度実行し、デバイスのハッシュ化されたIDを含むUMPのログラインを探します。
  • それをテストデバイスとして追加し、debug geographyをEEAに設定します。コミュニティプラグインを使用する場合は、 debugGeography と testDeviceIdentifiers まで requestConsentInfoを渡します。ネイティブブリッジの場合は、 DebugSettings をiOSで、 ConsentDebugSettings をAndroidで、デバッグビルド時のみ設定してください。
  • フォームを再度表示させるために、実行ごとに同意をリセット(resetConsentInfo() はコミュニティプラグイン、 ConsentInformation.reset() はネイティブでの操作)します。

デバッグ設定をリリースビルドに含めないでください。必ずビルドフラグで制御してください。

ステップ 6: アプリを停止させずにエラーを処理する

同意リクエストは、ネットワークがない、App ID の設定ミス、公開済みメッセージがない、といった理由で失敗することがあります。失敗はクラッシュではなく「今は広告を出さない」として扱ってください:

  • もし requestConsentInfoUpdate 失敗しても canRequestAds 前のセッションで true だった場合は、引き続き広告をリクエストできます。
  • フォームの読み込みに失敗した場合は、ログに記録し、次回の起動時にリトライしてください。
  • 同意フォームが表示されている間は、絶対に広告を表示しないでください。

トラブルシューティング

フォームが全く表示されません。 メッセージが未公開、この App ID に割り当てられていない、またはデバッグ用の地域設定なしで EEA 域外にいる可能性があります。次の場所で AdMob App ID を確認してください: Info.plist と AndroidManifest.xml コンソールと一致しているか確認してください。

フォームは起動のたびに表示されます。 ユーザーが選択せずに閉じた場合、または本番環境の code で reset を呼び出した場合です。

canRequestAds 米国ユーザーの場合は false になります。 米国の州法に基づくメッセージを表示し、ユーザーがデータの販売・共有を拒否した場合、SDK を通じて制限付き広告のみを配信してください。バイパスしないでください。

メディエーションパートナーには同意が得られません。 各パートナーのアダプターが最新であり、GDPRメッセージ内の広告パートナーリストに記載されていることを確認してください。

iOSのATT promptが表示されません。 NSUserTrackingUsageDescription が不足しているか、ユーザーが設定で「Appにトラッキングを要求することを許可」を無効にしています。

フォームを超えたコンプライアンス

UMPは広告側を処理します。アプリ側には、AdMobの記載、データ保持ルール、データリクエストへの対応方法を明記したプライバシーポリシーが引き続き必要です。当社の GDPRコンプライアンス・チェックリスト はアプリの残りの部分をカバーします。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。