Langsung ke konten utama

Cara Menangani Persetujuan GDPR AdMob di Capacitor

Kelola persetujuan GDPR AdMob di aplikasi Capacitor dengan SDK UMP dari Google: formulir persetujuan, opsi privasi, urutan ATT, debug geography, dan penanganan error, dengan code.

Ilustrasi privasi dan persetujuan untuk consent GDPR AdMob di aplikasi Capacitor

Untuk mengelola persetujuan GDPR AdMob di aplikasi Capacitor, gunakan SDK User Messaging Platform (UMP) dari Google: minta informasi persetujuan setiap kali aplikasi dijalankan, tampilkan formulir persetujuan jika diperlukan, dan hanya inisialisasi iklan saat canRequestAds bernilai true. Anda juga perlu entry point opsi privasi agar pengguna bisa mengubah pilihannya, dan di iOS alur UMP harus muncul sebelum prompt App Tracking Transparency. Panduan ini menunjukkan alur lengkap dengan dua setup: plugin yang membungkus UMP dalam JavaScript, dan @capgo/capacitor-admob dengan bridge consent native yang ringkas.

Apa fungsi UMP dan mengapa AdMob mewajibkannya

Sejak Januari 2024, Google mewajibkan publisher yang menampilkan iklan ke pengguna di EEA, Inggris, dan Swiss untuk menggunakan consent management platform (CMP) bersertifikat Google yang terintegrasi dengan IAB Transparency and Consent Framework (TCF). SDK UMP adalah CMP bersertifikat milik Google sendiri, gratis, dan sudah terintegrasi di konsol AdMob.

UMP melakukan tiga hal untuk Anda:

  1. Mendeteksi apakah pengguna berada di wilayah yang memerlukan consent.
  2. Menampilkan pesan consent yang Anda konfigurasi di AdMob, dalam bahasa pengguna.
  3. Menyimpan pilihan pengguna sebagai TC string di perangkat (standar IABTCF_ key di UserDefaults pada iOS dan SharedPreferences pada Android), yang dibaca oleh Google Mobile Ads SDK dan mitra mediasi saat meminta iklan.

Anda juga bisa menggunakan UMP untuk regulasi privasi negara bagian AS, yang oleh AdMob disebut pesan “US states”, dari konsol yang sama.

Langkah 1: buat pesan di AdMob

Di konsol AdMob, buka Privasi & pesan:

  • Buat file Regulasi Eropa pesan (GDPR). Pilih aplikasi mana yang akan menerapkannya, bahasa, dan daftar mitra iklan Anda.
  • Opsional, buat Negara bagian AS pesan.
  • Di iOS, buat penjelasan IDFA pesan jika Anda berencana meminta App Tracking Transparency. UMP akan menampilkannya sebelum prompt sistem ATT.
  • Publikasikan pesan. Pesan yang belum dipublikasikan tidak akan pernah ditampilkan.

Jika formulir tidak pernah muncul di aplikasi Anda nanti, hal pertama yang harus diperiksa adalah pesan yang belum dipublikasikan atau belum ditugaskan.

Langkah 2: tentukan urutan operasi

Urutan yang cocok untuk kebanyakan aplikasi:

  1. Aplikasi dimulai.
  2. Minta pembaruan info consent dengan UMP (setiap launch).
  3. Jika diperlukan, muat dan tampilkan form consent.
  4. Jika canRequestAds Jika true, mulai Mobile Ads SDK dan muat iklan.
  5. Tampilkan entri “Pengaturan privasi” di layar pengaturan Anda ketika UMP menyatakan opsi privasi diperlukan.

Google juga mengizinkan memulai Mobile Ads SDK secara paralel dengan permintaan consent ketika canRequestAds sudah true dari sesi sebelumnya. Ini mempersingkat waktu hingga iklan pertama untuk pengguna yang kembali.

Opsi A: plugin yang membungkus UMP di JavaScript

@capacitor-community/admob (versi 8 mendukung Capacitor 8) mengekspos metode UMP secara langsung: requestConsentInfo, showConsentForm, showPrivacyOptionsForm dan 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();
}

Plugin ini adalah satu-satunya pengecualian dari urutan di atas: README-nya mewajibkan AdMob.initialize() sebelum requestConsentInfo(), dan di iOS form persetujuan tidak bisa ditampilkan sampai initialize() sudah berjalan. initialize() memulai SDK tapi tidak meminta iklan, jadi aturan pentingnya adalah jangan pernah memuat iklan sampai canRequestAds bernilai true. Jika Anda ingin SDK tetap berhenti sampai pengguna menjawab, gunakan Opsi B, di mana persetujuan berjalan sebelum AdMob.start().

Periksa canRequestAds sebelum setiap pemuatan iklan, bukan hanya saat startup, karena pengguna bisa menarik persetujuan dari form opsi privasi.

Opsi B: @capgo/capacitor-admob dengan bridge UMP native

@capgo/capacitor-admob memberikan Anda AdMob.start(), konfigurasi request, banner, interstitial, rewarded, dan rewarded interstitial ads, serta otorisasi tracking iOS. Ini tidak membungkus UMP, jadi Anda menambahkan plugin kecil lokal aplikasi yang menjalankan alur consent sebelum memanggil AdMob.start().

bun add @capgo/capacitor-admob
bunx cap sync

Tambahkan UMP SDK

  • iOS: tambahkan GoogleUserMessagingPlatform package Google dengan Swift Package Manager (proyek Capacitor 8 menggunakan SPM secara default), atau pod CocoaPods dengan nama yang sama jika proyek Anda masih menggunakan CocoaPods.
  • Android: di android/app/build.gradle:
dependencies {
    implementation "com.google.android.ump:user-messaging-platform:<latest version>"
}

Gunakan rilis terbaru dari repositori Maven Google.

Buat 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,
        ]
    }
}

Daftarkan dari sebuah CAPBridgeViewController subclass, lalu atur class tersebut pada view controller di Main.storyboard:

import Capacitor

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

Buat android/app/src/main/java/com/example/app/ConsentPlugin.java (gunakan nama package Anda):

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;
    }
}

Daftarkan di MainActivity.java sebelum super.onCreate:

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

Panggil dari 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();

Perubahan code native membutuhkan build store baru. Setelah itu, teks consent di layar pengaturan dan logika penempatan iklan dapat dikirim dengan Live updates Capgo.

Langkah 3: tambahkan titik masuk opsi privasi

Ketika UMP melaporkan bahwa opsi privasi diperlukan, Google mengharapkan cara yang terlihat untuk membuka kembali pilihan consent. Tambahkan baris “Pengaturan privasi” ke halaman pengaturan Anda dan sembunyikan jika tidak diperlukan:

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

Setelah pengguna mengubah pilihan mereka, baca canRequestAds lagi sebelum permintaan iklan berikutnya.

Langkah 4: App Tracking Transparency di iOS

ATT terpisah dari GDPR. ATT mengontrol akses ke IDFA di iOS, sedangkan persetujuan GDPR mengontrol apakah dan bagaimana data pribadi diproses. Anda biasanya butuh keduanya untuk pengguna EU di iOS.

  • Tambah NSUserTrackingUsageDescription ke Info.plist.
  • Biarkan UMP menampilkan pesan penjelasan IDFA dan memicu ATT jika Anda sudah mengonfigurasi pesan tersebut di AdMob. Jika tidak, minta izin ATT sendiri setelah alur persetujuan selesai.

Dengan @capgo/capacitor-admob Anda bisa membaca dan meminta status ATT secara langsung:

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

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

Capgo juga memiliki plugin App Tracking Transparency mandiri jika Anda butuh ATT untuk SDK lain. Jangan lupa deklarasikan tracking di privacy manifest dan label privasi App Store Anda, lihat panduan privacy manifest kami.

Langkah 5: tes dengan debug geography

UMP hanya menampilkan formulir GDPR kepada pengguna yang terdeteksi di EEA, UK, atau Swiss. Untuk mengujinya di lokasi lain:

  • Jalankan aplikasi sekali dan temukan baris log dari UMP dengan hashed ID perangkat Anda.
  • Tambahkan sebagai test device dan atur debug geography ke EEA. Dengan plugin komunitas, teruskan debugGeography dan testDeviceIdentifiers ke requestConsentInfo. Pada native bridge, atur DebugSettings pada iOS dan ConsentDebugSettings pada Android hanya di debug builds.
  • Reset consent di antara run (resetConsentInfo() di plugin komunitas, ConsentInformation.reset() secara native) agar formulir muncul lagi.

Jangan pernah menyertakan debug settings di release builds. Batasi dengan build flag.

Langkah 6: tangani error tanpa memblokir aplikasi

Permintaan persetujuan bisa gagal: tidak ada jaringan, App ID salah konfigurasi, atau pesan belum dipublikasikan. Anggap kegagalan sebagai "tidak ada iklan untuk saat ini", bukan sebagai crash:

  • Jika requestConsentInfoUpdate gagal tetapi canRequestAds bernilai true dari sesi sebelumnya, Anda tetap bisa meminta iklan.
  • Jika form gagal dimuat, catat di log dan coba lagi saat aplikasi dibuka berikutnya.
  • Jangan pernah menampilkan iklan saat form consent sedang muncul di layar.

Pemecahan Masalah

Form tidak pernah muncul. Pesan belum dipublikasikan, tidak ditugaskan ke App ID ini, atau Anda berada di luar EEA tanpa debug geography. Periksa AdMob App ID di Info.plist dan AndroidManifest.xml cocok dengan yang ada di console.

Formulir tampil di setiap peluncuran. User menutupnya tanpa memilih, atau Anda memanggil reset di produksi code.

canRequestAds false untuk user di AS. Anda memublikasikan pesan negara bagian AS dan user memilih untuk tidak ikut penjualan atau pembagian data. Tayangkan iklan terbatas hanya melalui SDK, jangan melewatinya.

Partner mediasi tidak mendapatkan consent. Pastikan setiap adapter partner sudah up to date dan tercantum di daftar partner iklan pada pesan GDPR Anda.

Prompt iOS ATT tidak pernah muncul. NSUserTrackingUsageDescription hilang, atau user menonaktifkan “Allow Apps to Request to Track” di Pengaturan.

Kepatuhan di luar formulir

UMP menangani sisi iklan. App Anda tetap butuh privacy policy yang mencantumkan AdMob, aturan retensi data, dan cara menangani permintaan data. Kebijakan privasi kami checklist kepatuhan GDPR menangani sisa bagian aplikasi.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.