The Google User Messaging Platform (UMP) SDK is a privacy and messaging tool to help you manage privacy choices. For more information, see About Privacy & messaging.
Prerequisites
- Android API level 21 or higher (for Android)
Create a message type
Create user messages with one of the Available user message types under the Privacy & messaging tab of your AdMob account. The UMP SDK attempts to display a privacy message created from the AdMob Application ID set in your project.
For more details, see About privacy and messaging.
Install the SDK
Follow the steps to install the Google Mobile Ads (GMA) C++ SDK. The UMP C++ SDK is included in the GMA C++ SDK.
Ensure that you configure your app's AdMob app ID in the project before continuing.
In your code, initialize the UMP SDK by calling
ConsentInfo::GetInstance()
.- On Android, you need to pass in the
JNIEnv
andActivity
provided by the NDK. You only need to do this the first time you callGetInstance()
. - Alternatively, if you're already using the Firebase C++
SDK in your app, you can pass
in a
firebase::App
the first time you callGetInstance()
.
#include "firebase/gma/ump.h" namespace ump = ::firebase::gma::ump; // Initialize using a firebase::App void InitializeUserMessagingPlatform(const firebase::App& app) { ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance(app); } // Initialize without a firebase::App #ifdef ANDROID void InitializeUserMessagingPlatform(JNIEnv* jni_env, jobject activity) { ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance(jni_env, activity); } #else // non-Android void InitializeUserMessagingPlatform() { ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance(); } #endif
- On Android, you need to pass in the
Subsequent calls to ConsentInfo::GetInstance()
all return the same instance.
If you're finished using the UMP SDK, you can shut down the SDK by deleting the
ConsentInfo
instance:
void ShutdownUserMessagingPlatform() {
ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance();
delete consent_info;
}
Use a Future
to monitor asynchronous operations
A
firebase::Future
provides you a way to determine the completion status of asynchronous method
calls.
All UMP C++ functions and method calls that operate asynchronously return a
Future
, and also provide a "last result" function to retrieve the Future
from the most recent operation.
There are two ways to obtain a result from a Future
:
- Call
OnCompletion()
, passing in your own callback function, which is called when the operation completes. - Periodically check the
Future
'sstatus()
. When the status changes fromkFutureStatusPending
tokFutureStatusCompleted
, the operation has been completed.
After the asynchronous operation is completed, you should check the
Future
's error()
to obtain the operation's error
code. If the error code is 0
(kConsentRequestSuccess
or kConsentFormSuccess
),
the operation completed successfully; otherwise, check the error code and
error_message()
to determine what went wrong.
Completion callback
Here is an example of how to use OnCompletion
to set a completion callback,
which is called when the asynchronous operation completes.
void MyApplicationStart() {
// [... other app initialization code ...]
ump::ConsentInfo *consent_info = ump::ConsentInfo::GetInstance();
// See the section below for more information about RequestConsentInfoUpdate.
firebase::Future<void> result = consent_info->RequestConsentInfoUpdate(...);
result.OnCompletion([](const firebase::Future<void>& req_result) {
if (req_result.error() == ump::kConsentRequestSuccess) {
// Operation succeeded. You can now call LoadAndShowConsentFormIfRequired().
} else {
// Operation failed. Check req_result.error_message() for more information.
}
});
}
Update loop polling
In this example, after an asynchronous operation is started at app launch, the results are checked elsewhere, in the game's update loop function (which runs once per frame).
ump::ConsentInfo *g_consent_info = nullptr;
bool g_waiting_for_request = false;
void MyApplicationStart() {
// [... other app initialization code ...]
g_consent_info = ump::ConsentInfo::GetInstance();
// See the section below for more information about RequestConsentInfoUpdate.
g_consent_info->RequestConsentInfoUpdate(...);
g_waiting_for_request = true;
}
// Elsewhere, in the game's update loop, which runs once per frame:
void MyGameUpdateLoop() {
// [... other game logic here ...]
if (g_waiting_for_request) {
// Check whether RequestConsentInfoUpdate() has finished.
// Calling "LastResult" returns the Future for the most recent operation.
firebase::Future<void> result =
g_consent_info->RequestConsentInfoUpdateLastResult();
if (result.status() == firebase::kFutureStatusComplete) {
g_waiting_for_request = false;
if (result.error() == ump::kConsentRequestSuccess) {
// Operation succeeded. You can call LoadAndShowConsentFormIfRequired().
} else {
// Operation failed. Check result.error_message() for more information.
}
}
}
}
For more information about firebase::Future
, see the Firebase C++ SDK
documentation
and the GMA C++ SDK documentation.
Add the application ID
You can find your application ID in the AdMob UI. Add the ID to your with the following code snippet:
Request for consent information
You should request an update of the user's consent information at every app
launch, using
RequestConsentInfoUpdate()
. This request checks
the following:
- Whether consent is required. For example, consent is required for the first time, or the previous consent decision expired.
- Whether a privacy options entry point is required. Some privacy messages require apps to allow users to modify their privacy options at any time.
#include "firebase/gma/ump.h"
namespace ump = ::firebase::gma::ump;
void MyApplicationStart() {
ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance();
// Create a ConsentRequestParameters struct.
ump::ConsentRequestParameters params;
// Set tag for under age of consent. False means users are NOT under age
// of consent.
params.tag_for_under_age_of_consent = false;
consent_info->RequestConsentInfoUpdate(params).OnCompletion(
[](const Future<void>& result) {
if (result.error() != ump::kConsentRequestSuccess) {
LogMessage("Error requesting consent update: %s", result.error_message());
} else {
// Consent status is now available.
}
});
}
See above for an example of checking for completion using update loop polling rather than a completion callback.
Load and present a privacy message form if required
After you have received the most up-to-date consent status, call
LoadAndShowConsentFormIfRequired()
to load any forms required to
collect user consent. After loading, the forms present immediately.
void MyApplicationStart(ump::FormParent parent) {
ump::ConsentInfo* consent_info = ump::ConsentInfo::GetInstance();
// Create a ConsentRequestParameters struct..
ump::ConsentRequestParameters params;
// Set tag for under age of consent. False means users are NOT under age of consent.
params.tag_for_under_age_of_consent = false;
consent_info->RequestConsentInfoUpdate(params).OnCompletion(
[*](const Future<void>& req_result) {
if (req_result.error() != ump::kConsentRequestSuccess) {
// req_result.error() is a kConsentRequestError enum.
LogMessage("Error requesting consent update: %s", req_result.error_message());
} else {
consent_info->LoadAndShowConsentFormIfRequired(parent).OnCompletion(
[*](const Future<void>& form_result) {
if (form_result.error() != ump::kConsentFormSuccess) {
// form_result.error() is a kConsentFormError enum.
LogMessage("Error showing privacy message form: %s", form_result.error_message());
} else {
// Either the form was shown and completed by the user, or consent was not required.
}
});
}
});
}
If you need to perform any actions after the user has made a choice or dismissed
the form, place that logic in the code that handles the Future
returned by LoadAndShowConsentFormIfRequired()
.
Privacy options
Some privacy message forms are presented from a publisher-rendered privacy options entry point, letting users manage their privacy options at any time. To learn more about which message your users see at the privacy options entry point, see Available user message types.
To implement a privacy options entry point, complete the following steps:
- Check
getPrivacyOptionsRequirementStatus()
. - If a privacy options entry point is required, add a visible and interactable UI element to your app.
- Trigger the privacy options form using
showPrivacyOptionsForm()
.
The following code example demonstrates these steps:
Request ads
Before requesting ads in your app, check if you have obtained consent
from the user using
ConsentInfo::GetInstance()‑>
CanRequestAds()
. There are two
places to check while gathering consent:
- After consent has been gathered in the current session.
- Immediately after you have called
RequestConsentInfoUpdate()
. It is possible consent has been obtained in the previous session. As a latency best practice, we recommend not waiting for the callback to complete so you can start loading ads as soon as possible after your app launches.
If an error occurs during the consent gathering process, you should still attempt to request ads. The UMP SDK uses the consent status from the previous session.
The following complete example uses update loop polling, but you can also use
OnCompletion
callbacks to monitor asynchronous operations. Use
whichever technique fits your code structure better.
#include "firebase/future.h"
#include "firebase/gma/gma.h"
#include "firebase/gma/ump.h"
namespace gma = ::firebase::gma;
namespace ump = ::firebase::gma::ump;
using firebase::Future;
ump::ConsentInfo* g_consent_info = nullptr;
// State variable for tracking the UMP consent flow.
enum { kStart, kRequest, kLoadAndShow, kInitGma, kFinished, kErrorState } g_state = kStart;
bool g_ads_allowed = false;
void MyApplicationStart() {
g_consent_info = ump::ConsentInfo::GetInstance(...);
// Create a ConsentRequestParameters struct..
ump::ConsentRequestParameters params;
// Set tag for under age of consent. False means users are NOT under age of consent.
params.tag_for_under_age_of_consent = false;
g_consent_info->RequestConsentInfoUpdate(params);
// CanRequestAds() can return a cached value from a previous run immediately.
g_ads_allowed = g_consent_info->CanRequestAds();
g_state = kRequest;
}
// This function runs once per frame.
void MyGameUpdateLoop() {
// [... other game logic here ...]
if (g_state == kRequest) {
Future<void> req_result = g_consent_info->RequestConsentInfoUpdateLastResult();
if (req_result.status() == firebase::kFutureStatusComplete) {
g_ads_allowed = g_consent_info->CanRequestAds();
if (req_result.error() == ump::kConsentRequestSuccess) {
// You must provide the FormParent (Android Activity or iOS UIViewController).
ump::FormParent parent = GetMyFormParent();
g_consent_info->LoadAndShowConsentFormIfRequired(parent);
g_state = kLoadAndShow;
} else {
LogMessage("Error requesting consent status: %s", req_result.error_message());
g_state = kErrorState;
}
}
}
if (g_state == kLoadAndShow) {
Future<void> form_result = g_consent_info->LoadAndShowConsentFormIfRequiredLastResult();
if (form_result.status() == firebase::kFutureStatusComplete) {
g_ads_allowed = g_consent_info->CanRequestAds();
if (form_result.error() == ump::kConsentRequestSuccess) {
if (g_ads_allowed) {
// Initialize GMA. This is another asynchronous operation.
firebase::gma::Initialize();
g_state = kInitGma;
} else {
g_state = kFinished;
}
// Optional: shut down the UMP SDK to save memory.
delete g_consent_info;
g_consent_info = nullptr;
} else {
LogMessage("Error displaying privacy message form: %s", form_result.error_message());
g_state = kErrorState;
}
}
}
if (g_state == kInitGma && g_ads_allowed) {
Future<gma::AdapterInitializationStatus> gma_future = gma::InitializeLastResult();
if (gma_future.status() == firebase::kFutureStatusComplete) {
if (gma_future.error() == gma::kAdErrorCodeNone) {
g_state = kFinished;
// TODO: Request an ad.
} else {
LogMessage("Error initializing GMA: %s", gma_future.error_message());
g_state = kErrorState;
}
}
}
}
Testing
If you want to test the integration in your app as you're developing, follow these steps to programmatically register your test device. Be sure to remove the code that sets these test device IDs before you release your app.
- Call
RequestConsentInfoUpdate()
. Check the log output for a message similar to the following example, which shows your device ID and how to add it as a test device:
Android
Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("33BE2250B43518CCDA7DE426D04EE231") to set this as a debug device.
iOS
<UMP SDK>To enable debug mode for this device, set: UMPDebugSettings.testDeviceIdentifiers = @[2077ef9a63d2b398840261c8221a0c9b]
Copy your test device ID to your clipboard.
Modify your code to set
ConsentRequestParameters.debug_settings.debug_device_ids
to a list of your test device IDs.void MyApplicationStart() { ump::ConsentInfo consent_info = ump::ConsentInfo::GetInstance(...); ump::ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_device_ids = {"TEST-DEVICE-HASHED-ID"}; consent_info->RequestConsentInfoUpdate(params); }
Force a geography
The UMP SDK provides a way to test your app's behavior as though the device was
located in the EEA or UK using
debug_settings.debug_geography
. Note that
debug settings only work on test devices.
void MyApplicationStart() {
ump::ConsentInfo consent_info = ump::ConsentInfo::GetInstance(...);
ump::ConsentRequestParameters params;
params.tag_for_under_age_of_consent = false;
params.debug_settings.debug_device_ids = {"TEST-DEVICE-HASHED-ID"};
// Geography appears as EEA for debug devices.
params.debug_settings.debug_geography = ump::kConsentDebugGeographyEEA
consent_info->RequestConsentInfoUpdate(params);
}
Reset consent state
In testing your app with the UMP SDK, you might find it helpful to reset the
state of the SDK so that you can simulate a user's first install experience.
The SDK provides the Reset()
method to do this.
ConsentInfo::GetInstance()->Reset();