[Unity] 1. consentmanager SDK Integration
[Unity] 1. consentmanager SDK Integration
On this document, you'll find general information on how to integrate our SDK to your project. For further details, please refer to our API Reference documentation.
1. Installation
The consentmanager SDK for Android apps implements and provides functionality to inform the user about data protection and ask and collect consent from the user. It enables app-developers to easily integrate the consentmanager service into their app. As main features, we provide:
- Support both iOS and Android platforms.
- A bridge between Unity and native platform-specific CMP functionalities.
- An easy way to initialize, manage user consent, and handle privacy-related data.
In terms of compatibility, we have:
- Unity 20XX.X.X or later
- iOS (via DllImport)
- Android (via JNI)
Steps - High Level
-
-
Integration and Configuration:
- Integrate the SDK into your app.
- Configure the SDK settings according to your needs.
-
Creating an Instance:
- On app startup, create an instance of the
CMPConsentTool
class. This instance will handle the consent process.
- On app startup, create an instance of the
-
SDK Initialization:
- Once the instance is ready, the SDK automatically retrieves necessary information from the consentmanager servers to prepare for its operation.
-
Displaying the Consent Screen:
- The SDK will automatically display the consent screen if needed when the
CMPConsentTool
instance is created.
- The SDK will automatically display the consent screen if needed when the
-
Processing Personal Data:
- Once consents are collected, info is stored and is available for querying through different properties and methods exposed by our SDK. You'll have information about rejected or accepted consents, vendors, purposes, etc.
By following these steps, you ensure that your app is compliant with consent requirements and that user consent is properly managed and stored.
-
Consent Manager Provider SDK Sequence Diagram
To illustrate the steps above, let's check in the diagram below three possible SDK sequence flows.
1. When creating an instance using the Initialize()
function, there are two possible outcomes. The first is when the consentmanger API informs the SDK that the CMP will not open, which triggers the OnCmpNotOpenedCallback. The second outcome is when the consent layer opens, allowing the user to interact with it, and this triggers the OnOpenCallback. Once the user gives consent and the consent is processed, the OnCmpCloseCallback is called.
Please note that the OnErrorCallback is represented by the red dashed arrow lines to provide examples of when errors may occur during the process.
2. Creating an instance and calling the OpenConsentLayerOnCheck
functions will lead to a similar process. The difference is that by decoupling the creation of the instance and the check for the consentmanger API, you gain the ability to add business logic and interact with the libraries API.
3. Creating an instance and calling the OpenConsentLayer
function will open the layer without checking the consentmanager, if it's necessary. If there is already given consent, the options and settings will be shown to the user. The process flow will look like this:
For further information about our SDK Version Overview & Changelog, please refer to this link.
Importing the SDK package
In just two steps you can have all set up.
-
- Download the latest release of the plugin.
- Import the package into your Unity project using Assets > Import Package > Custom Package.
Config JSON
In the CmpSdkConfig.json you can set the native SDK Version for iOS and Android which will be used for the build process:
Find the compatible native SDK Versions here:
{
"displayName": "Consentmanager SDK",
"name": "CmpSdk",
"androidBasePath": "net.consentmanager.sdk",
"version": "1.0.0",
"androidLibraryVersion": "x.xx.x",
"iosLibraryVersion": "x.xx.x",
"description": "Unity plugin helps you to use native Consentmanager functionality on Android and iOS."
}
Build Settings
To change the build settings go to Window
-> CmpSdk
iOS Build Settings
- Enable iOS Build Script: Toggle this to enable or disable the build script responsible for integrating the iOS SDK into the Unity project.
- xcFramework Path: Specify the path to the xcFramework directory. This path can be edited directly or browsed to using the accompanying button.
- Include Version Tag: When enabled, appends the iOS SDK version to the xcFramework path, allowing for version-specific configurations.
- Resulting Framework Path: Displays the fully resolved path to the xcFramework, including the SDK version if the version tag is included.
- Enable App Tracking Transparency: Toggle this to enable the App Tracking Transparency feature for iOS, which is necessary for user consent under iOS privacy guidelines.
- App Tracking Transparency Consent Message: A text field to input the custom message displayed to users when requesting consent for tracking. This message should be clear and concise, explaining why the consent is needed.
Android Build Settings
- Enable Android Build Script: Toggle to enable or disable the build script for integrating the Android SDK into the Unity project.
-
Integrate Custom Layout: When enabled, this allows the use of a custom layout for the consent layer for the fragment UIView.
If you are willing to use a custom layout with fragments, make sure that your Unity Project adds the
appcompat
dependency. Add a custom main Template:
Assets/Plugins/Android/mainTemplate.gradle
and add the dependency:
implementation 'androidx.appcompat:appcompat:1.x.x'
2. Initializing the SDK
Follow these steps to start using the plugin.
Initializing the ConsentTool - Automatically
Within the app-start (usually the regular override onCreate()
function) , you must create an instance of class CMPConsentTool
. The initialize()
function will automatically fetch the necessary data from our server and determine if the consent screen needs to be shown or not. If so, the SDK will automatically show the consent screen at this point, collect the data and provide the data to the app. The instance can then be used in order to get consent details from the SDK in order to use it in the app.
Example of initialization, using the automatic behaviour of the Initialize()
method.
public string codeId = "<YOUR-CONSENTMANAGER-APP-ID>", // example: b238acdf1a
public string domain = "<YOUR-CONSENTMANAGER-APP-DOMAIN>", // example: delivery.consentmanager.net
public string appName = "<YOUR-CONSENTMANAGER-APP-NAME>", // example: testApp
public string language = "<YOUR-CONSENTMANAGER-APP-LANGUAGE>" // example: DE
CmpManager.Instance.Initialize(domain, codeId, appName, language);
For domain
use the Server-Domain found in your consentmanager account under Menu > CMPs > Get Codes for Apps. For codeID
use the Code-ID found on the same page in your consentmanger account. The appName
can be used in order to distinguish different apps in the consentmanager reporting. For the language
, you can either use an empty string ("") for auto-detection or a 2-letter language code ("EN", "DE", "FR" and so on).
Initializing the ConsentTool - Manually
The SDK offers, for the sake of flexibility, a way to manually display the consent layer, as demonstrated below:
bool isConsentRequired = await _cmpManager.CheckConsentIsRequired();
if (isConsentRequired)
{
_cmpManager.OpenConsentLayer();
}
3. Using the SDK
Checking for consent
Check Consent: Check if the user has given consent:
bool hasConsent = CmpManager.Instance.HasConsent();
Callbacks: Set callback listeners for various events:
CmpManager.Instance.AddEventListeners(OnOpen, OnClose, OnNotOpened, OnCmpButtonClicked, OnError);
Purpose and Vendor Checks: Check for consent related to specific purposes and vendors:
bool hasPurpose = CmpManager.Instance.HasPurpose(id);
bool hasVendor = CmpManager.Instance.HasVendor(id);
Export Data: Export CMP data:
string cmpString = CmpManager.Instance.ExportCmpString();
Event Listener
Callback Event | Description | Parameters Passed |
---|---|---|
OnOpen | Triggered when the CMP consent tool is opened. | None |
OnClose | Triggered when the CMP consent tool is closed. | None |
OnNotOpened | Triggered if the CMP consent tool fails to open. | None |
OnCmpButtonClicked | Triggered when a button within the CMP consent tool is clicked. | CmpButtonEvent buttonEvent |
OnError | Triggered when an error occurs within the CMP consent tool. |
CmpErrorType errorType , string message
|
OnGoogleConsentUpdated | Triggered when the Google consent mode status is updated. | CmpGoogleConsentModeStatus status |
OnCmpATTrackingStatusChanged (iOS only) | Triggered when the App Tracking Transparency status changes. |
ATTrackingManagerAuthorizationStatus oldStatus , ATTrackingManagerAuthorizationStatus newStatus , double lastUpdated
|
Custom Layout
Unity supports different custom layouts:
public enum ScreenConfig
{
FullScreen,
HalfScreenBottom,
HalfScreenTop,
CenterScreen,
SmallCenterScreen,
LargeTopScreen,
LargeBottomScreen,
}
Example usage:
_cmpConfig = new CmpConfig(CodeId, Domain, AppName, Language)
{
Debug = true,
Timeout = 8000
};
_cmpConfig.UIConfig.screenConfig = (CmpUIConfig.ScreenConfig) Enum.Parse(typeof(CmpUIConfig.ScreenConfig), s);
_cmpManager.SetUIConfig(_cmpConfig.UIConfig);
Google Consent Mode
Reference documentation : Get started with Google Firebase for Unity
Integration : Unity Setup
To use the google consent mode the CMP Unity SDK supports an interface to set the google consent status:
// public class CmpSampleScript : MonoBehaviour, IOnCmpGoogleConsentUpdatedCallback { ...
// Make sure to implement the Interface IOnCmpGoogleConsentUpdatedCallback
public void OnGoogleConsentUpdated(CmpGoogleConsentModeStatus status)
{
// Convert CmpGoogleConsentModeStatus to Firebase compatible dictionary
var firebaseConsentDict = new Dictionary<ConsentType, ConsentStatus>();
foreach (var consent in status.ConsentDictionary)
{
// Convert GoogleConsentType to Firebase ConsentType
var firebaseConsentType = ConvertToFirebaseConsentType(consent.Key);
// Convert GoogleConsentStatus to Firebase ConsentStatus
var firebaseConsentStatus = ConvertToFirebaseConsentStatus(consent.Value);
firebaseConsentDict[firebaseConsentType] = firebaseConsentStatus;
}
// Apply the consent settings to Firebase Analytics
FirebaseAnalytics.SetConsent(firebaseConsentDict);
AppendLog($"Google Consent Mode: {firebaseConsentDict}");
}
private static ConsentType ConvertToFirebaseConsentType(GoogleConsentType googleConsentType)
{
return googleConsentType switch
{
GoogleConsentType.AnalyticsStorage => ConsentType.AnalyticsStorage,
GoogleConsentType.AdStorage => ConsentType.AdStorage,
GoogleConsentType.AdUserData => ConsentType.AdUserData,
GoogleConsentType.AdPersonalization => ConsentType.AdPersonalization,
_ => throw new InvalidEnumArgumentException($"Unknown GoogleConsentType: {googleConsentType}")
};
}
private static ConsentStatus ConvertToFirebaseConsentStatus(GoogleConsentStatus googleConsentStatus)
{
return googleConsentStatus switch
{
GoogleConsentStatus.Granted => ConsentStatus.Granted,
GoogleConsentStatus.Denied => ConsentStatus.Denied,
_ => throw new InvalidEnumArgumentException($"Unknown GoogleConsentStatus: {googleConsentStatus}")
};
}
in this example the OnGoogleConsentUpdate callback is called when the user gives a consent.
FirebaseAnalytics.SetConsent(firebaseConsentDict);
In this line the Consent Status is set to Firebase Analytics. The other two functions are mapping the CMP Consent Status to the Google Consent Types and Status.
Example Script
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using CmpSdk.Callbacks;
#if UNITY_IOS
using CmpSdk.Delegates;
#endif
using CmpSdk.Models;
using Firebase.Analytics;
using Firebase.Extensions;
using UnityEngine;
using FirebaseApp = Firebase.FirebaseApp;
namespace CmpSdk.Samples.Scripts
{
// V prefix for Vendor
public static class Vendors
{
public const string GoogleAnalytics = "S26";
public const string AmazonAD = "793";
public const string Facebook = "S7";
public const string S1 = "S1";
}
// P prefix for Purpose
public static class Purposes
{
public const string P1 = "1";
public const string Marketing = "C2";
public const string Technical = "S2";
public const string Security = "S1";
}
public class CmpSampleScript : MonoBehaviour, IOnOpenCallback, IOnCloseCallback, IOnCmpNotOpenedCallback,
IOnCmpButtonClickedCallback, IOnErrorCallback, IOnCmpGoogleConsentUpdatedCallback
{
readonly List<string> _purposeList = new() { Purposes.P1, Purposes.Marketing, Purposes.Technical, Purposes.Security };
readonly List<string> _vendorList = new() { Vendors.S1, Vendors.GoogleAnalytics, Vendors.AmazonAD, Vendors.Facebook };
// UI elements
private string _idPurposeOrVendorInputField;
private string _importStringInputField;
[SerializeField] private CmpUIManager uiManager;
// CmpManager Instance
private CmpConfig _cmpConfig;
private CmpManager _cmpManager;
private Thread _mainThread;
// Configuration constants
private const string CodeId = "TOOD Your CMP Code ID";
private const string Domain = "delivery.consentmanager.net";
private const string AppName = "UnityExample";
private const string Language = "DE";
private FirebaseApp _app;
private void Awake()
{
if (!Application.isPlaying)
{
Debug.Log("Application is not playing.");
return;
}
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
var dependencyStatus = task.Result;
if (dependencyStatus == Firebase.DependencyStatus.Available) {
// Create and hold a reference to your FirebaseApp,
// where app is a Firebase.FirebaseApp property of your application class.
_app = FirebaseApp.DefaultInstance;
// Set a flag here to indicate whether Firebase is ready to use by your app.
} else {
Debug.LogError($"Could not resolve all Firebase dependencies: {dependencyStatus}");
// Firebase Unity SDK is not safe to use here.
}
});
_cmpManager = CmpManager.Instance;
}
private void Start()
{
_cmpConfig = new CmpConfig(CodeId, Domain, AppName, Language)
{
Debug = true,
Timeout = 8000
};
#if UNITY_ANDROID
_cmpConfig.UIConfig.isOutsideTouchable = true;
_cmpConfig.UIConfig.SetAndroidUiType(AndroidUiType.Dialog);
#endif
// Initialize Consent Manager
InitializeCmpManager();
// Initialize UI buttons
InitializeUIButtons();
// Launch Consent Manager
_cmpManager.Launch();
}
private void InitializeUIButtons()
{
uiManager.CreateButtons(
new ButtonData("Open", OnClickOpenConsentLayer),
new ButtonData("Check", OnClickOpenConsentLayerOnCheck),
new ButtonData("Check?", OnclickCheckConsentRequired),
new ButtonData("ATT?", OnClickRequestATTrackingStatus),
new ButtonData("Get Status", OnClickDebugConsentStatus),
new ButtonData("Initialize", OnClickInitialize),
new ButtonData("Accept All", OnClickAcceptAll),
new ButtonData("Reject All", OnClickRejectAll),
new ButtonData("Reset", OnClickResetConsentData),
new ButtonData("Import", OnClickImportCmpString),
new ButtonData("Export", OnClickExportCmpString)
);
uiManager.CreateDropdown("Screen Config", GetScreenConfigOptions(), s =>
{
AppendLog($"Set Screen ${s}");
_cmpConfig.UIConfig.screenConfig = (CmpUIConfig.ScreenConfig) Enum.Parse(typeof(CmpUIConfig.ScreenConfig), s);
_cmpManager.SetUIConfig(_cmpConfig.UIConfig);
});
uiManager.CreateDropdown("Purposes", _purposeList, s =>
{
_idPurposeOrVendorInputField = s;
CheckHasPurpose(s);
});
uiManager.CreateDropdown("Vendors", _vendorList, s =>
{
_idPurposeOrVendorInputField = s;
CheckHasVendor(s);
});
}
private void InitializeCmpManager()
{
_mainThread = Thread.CurrentThread;
AppendLog("Consentmanager SampleScene started");
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
_cmpManager.Initialize(_cmpConfig);
_cmpManager.AddGoogleConsentModeListener(OnGoogleConsentUpdated);
_cmpManager.AddEventListeners(OnOpen, OnClose, OnNotOpened, OnCmpButtonClicked, OnError);
#endif
#if UNITY_IOS
CmpATTrackingManager.EnableAutomaticATTracking();
CmpATTrackingManager.Instance.RegisterOnATTrackingStatusChangedListener(OnCmpATTrackingStatusChanged);
#endif
}
#region Button Events
private void OnClickInitialize()
{
AppendLog("Initialize");
_cmpManager.Launch();
}
private async void OnClickRejectAll()
{
AppendLog("Calling Reject All");
await _cmpManager.RejectAll();
AppendLog("Rejected All");
}
private async void OnClickAcceptAll()
{
AppendLog("Calling Accept All");
await _cmpManager.AcceptAll();
AppendLog("Accepted All");
}
private void OnClickRequestATTrackingStatus()
{
AppendLog("Request AT Tracking Status");
_ = _cmpManager.RequestATTrackingPermission();
}
private async void OnclickCheckConsentRequired()
{
AppendLog("Calling Check Consent Required");
var isRequired = await _cmpManager.CheckConsentIsRequired();
AppendLog($"Is consent required: {isRequired}");
}
private void OnClickOpenConsentLayer()
{
AppendLog("Open Consent Layer");
_cmpManager.OpenConsentLayer();
}
private void OnClickOpenConsentLayerOnCheck()
{
AppendLog("Open Consent Layer on Check");
_cmpManager.OpenConsentLayerOnCheck();
}
private void OnClickImportCmpString()
{
AppendLog("Click Import");
ImportCmpString();
}
private void OnClickResetConsentData()
{
_cmpManager.Reset();
AppendLog("Reset");
}
private void OnClickDebugConsentStatus()
{
DebugConsentStatus();
}
private void OnClickExportCmpString()
{
var cmpString = _cmpManager.ExportCmpString();
AppendLog($"Exported CMP String: {cmpString}");
}
private void CheckHasPurpose(string purposeId)
{
var hasPurpose = _cmpManager.HasPurpose(purposeId);
AppendLog($"Has Purpose ({purposeId}): {hasPurpose}");
}
private void CheckHasVendor(string vendorId)
{
var hasVendor = _cmpManager.HasVendor(vendorId);
AppendLog($"Has Vendor ({vendorId}): {hasVendor}");
}
private async void ImportCmpString()
{
var cmpString = _importStringInputField;
CmpImportResult result;
if (!string.IsNullOrEmpty(cmpString))
{
AppendLog($"Importing CMP String from input field: {cmpString}");
result = await _cmpManager.ImportCmpString(cmpString);
}
else
{
AppendLog($"Importing CMP String from sample string: {cmpString}");
result = await _cmpManager.ImportCmpString(cmpString);
}
AppendLog($"Unity import result: {result.IsSuccess} with message: {result.Message}");
}
#endregion
#region Callbacks
public void OnGoogleConsentUpdated(CmpGoogleConsentModeStatus status)
{
// Convert CmpGoogleConsentModeStatus to Firebase compatible dictionary
var firebaseConsentDict = new Dictionary<ConsentType, ConsentStatus>();
foreach (var consent in status.ConsentDictionary)
{
// Convert GoogleConsentType to Firebase ConsentType
var firebaseConsentType = ConvertToFirebaseConsentType(consent.Key);
// Convert GoogleConsentStatus to Firebase ConsentStatus
var firebaseConsentStatus = ConvertToFirebaseConsentStatus(consent.Value);
firebaseConsentDict[firebaseConsentType] = firebaseConsentStatus;
}
// Apply the consent settings to Firebase Analytics
FirebaseAnalytics.SetConsent(firebaseConsentDict);
AppendLog($"Google Consent Mode: {firebaseConsentDict}");
}
private static ConsentType ConvertToFirebaseConsentType(GoogleConsentType googleConsentType)
{
return googleConsentType switch
{
GoogleConsentType.AnalyticsStorage => ConsentType.AnalyticsStorage,
GoogleConsentType.AdStorage => ConsentType.AdStorage,
GoogleConsentType.AdUserData => ConsentType.AdUserData,
GoogleConsentType.AdPersonalization => ConsentType.AdPersonalization,
_ => throw new InvalidEnumArgumentException($"Unknown GoogleConsentType: {googleConsentType}")
};
}
private static ConsentStatus ConvertToFirebaseConsentStatus(GoogleConsentStatus googleConsentStatus)
{
return googleConsentStatus switch
{
GoogleConsentStatus.Granted => ConsentStatus.Granted,
GoogleConsentStatus.Denied => ConsentStatus.Denied,
_ => throw new InvalidEnumArgumentException($"Unknown GoogleConsentStatus: {googleConsentStatus}")
};
}
public void OnClose()
{
LogThreadContext("OnClose");
AppendLog("CMPConsentTool closed");
}
public void OnCmpButtonClicked(CmpButtonEvent buttonEvent)
{
LogThreadContext("OnCmpButtonClicked");
AppendLog($"CMPButton clicked. Event: {buttonEvent}");
}
public void OnNotOpened()
{
LogThreadContext("OnNotOpened");
AppendLog("CMPConsentTool not opened");
}
public void OnError(CmpErrorType errorType, string message)
{
LogThreadContext("OnError");
AppendLog($"Error: {errorType}, {message}");
}
public void OnOpen()
{
LogThreadContext("OnOpen");
AppendLog("CMPConsentTool opened");
}
#if UNITY_IOS
private void OnCmpATTrackingStatusChanged(ATTrackingManagerAuthorizationStatus oldStatus,
ATTrackingManagerAuthorizationStatus newStatus, double lastUpdated)
{
var unixTime = lastUpdated;
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds(unixTime).ToLocalTime();
AppendLog("OnCmpATTrackingStatusChanged: " + newStatus + " lastUpdated: " + dtDateTime);
}
#endif
#endregion
private void DebugConsentStatus()
{
var hasConsent = _cmpManager.HasConsent();
var allPurposes = _cmpManager.GetAllPurposes();
var disabledPurposes = _cmpManager.GetAllPurposes();
var enabledPurposes = _cmpManager.GetEnabledPurposes();
var allVendors = _cmpManager.GetAllVendors();
var disabledVendors = _cmpManager.GetDisabledVendors();
var enabledVendors = _cmpManager.GetEnabledVendors();
var exportCmp = _cmpManager.ExportCmpString();
AppendLog("-----------------");
AppendLog($"Unity All Purposes: {string.Join(", ", allPurposes)}");
AppendLog($"Unity Disabled Purposes: {string.Join(", ", disabledPurposes)}");
AppendLog($"Unity Enabled Purposes: {string.Join(", ", enabledPurposes)}");
AppendLog($"Unity All Vendors: {string.Join(", ", allVendors)}");
AppendLog($"Unity Disabled Vendors: {string.Join(", ", disabledVendors)}");
AppendLog($"Unity Enabled Vendors: {string.Join(", ", enabledVendors)}");
AppendLog($"Unity Exported CMP String: {exportCmp}");
AppendLog($"Unity Has Consent: {hasConsent}");
AppendLog($"Unity US Privacy String: {_cmpManager.GetUsPrivacyString()}");
AppendLog($"Unity Google Ac String: {_cmpManager.GetGoogleAcString()}");
AppendLog($"Unity Has Purpose C1: {_cmpManager.HasPurpose("c1")}");
AppendLog($"Unity Has Vendor 10: {_cmpManager.HasVendor("628")}");
AppendLog($"Unity Google Consent Mode Status: {_cmpManager.GetGoogleConsentModeStatus()}");
AppendLog("-----------------");
}
#region Helper
private void LogThreadContext(string callbackName)
{
var onMainThread = IsMainThread();
var threadId = Thread.CurrentThread.ManagedThreadId;
AppendLog($"{callbackName} called. Is main thread: {onMainThread} ID: {threadId}");
}
private bool IsMainThread()
{
return _mainThread.Equals(Thread.CurrentThread);
}
private void AppendLog(string message)
{
Debug.Log(message);
if (uiManager != null)
{
uiManager.AddLogText(message);
}
}
private List<string> GetScreenConfigOptions()
{
var options = new List<string>();
foreach (var config in Enum.GetValues(typeof(CmpUIConfig.ScreenConfig)))
{
options.Add(config.ToString());
}
return options;
}
#endregion
}
}