Installation & Initialization
The minSdkVersion version number in your build.gradle should not be less than 24.
To add the SDK to your app, please follow these steps:
-
Get the latest version from https://github.com/UtiqTech/android-sdk/releases
-
Add the following dependency to your project’s
build.gradleorsettings.gradlefile under theallprojectsblock
Groovy
allprojects {
repositories {
maven {
url 'https://maven.pkg.github.com/UtiqTech/android-sdk'
credentials {
username = GITHUB_USERNAME
password = GITHUB_TOKEN
}
}
}
}
Kotlin DSL
allprojects {
repositories {
maven {
url = uri("https://maven.pkg.github.com/UtiqTech/android-sdk")
credentials {
username = GITHUB_USERNAME
password = GITHUB_TOKEN
}
}
}
}
-
Add this dependency
com.utiq:utiq-android:{VERSION}to thebuild.graldeorbuild.gradle.ktsfile of your App or module.
Please make sure that you have the latest version from https://github.com/UtiqTech/android-sdk/releases
-
Also, add the following permissions to your manifest file if you didn't' already.
<uses-permission android:name="android.permission.INTERNET"/>
-
Initialize the SDK
Basic initialization
Assuming you have an Application class registered in AndroidManifest.xml, call the initialize function inside onCreate(), passing the application context and the SDK token provided by us.
Kotlin
Utiq.initialize(this, SDK_TOKEN)
Java
Utiq.initialize(this, SDK_TOKEN);
Please contact onboard@utiq.com to request a new SDK Token for your mobile App.
Initializing with custom options
UtiqOptions() is an optional parameter for SDK initialization. You can initialize the SDK without it, but if you want to enable or disable specific options, configure them in UtiqOptions and pass it to the initializer.
Currently, the only available options are enabling debugging.
Kotlin
val options = UtiqOptions().enableLogging()
Utiq.initialize(this, SDK_TOKEN, options)
Java
UtiqOptions options = new UtiqOptions().enableLogging();
Utiq.initialize(this, SDK_TOKEN, options);
If your app uses OneTrust (or any CMP that internally uses WorkManager), release builds with R8 enabled may crash at startup with an error like:
java.lang.RuntimeException: Unable to get provider androidx.startup.InitializationProvider
Caused by: java.lang.RuntimeException: Failed to create an instance of class androidx.work.impl.WorkDatabase
Fix: Add the following to your app module’s proguard-rules.pro
-keep class androidx.work.** { *; }
Basic usage
Once the SDK is initialized, all the functions can be called by calling Utiq. to access all the SDK functions.
Fetch Utiq data
Kotlin
Utiq.fetchIdConnectData(dataCallback = {
println("marTechPass: " + it.marTechPass + "adTechPass: " + it.adTechPass)
}, errorCallback = {
error.printStackTrace()
})
Java
Utiq.fetchIdConnectData(null, idcData -> {
System.out.println("adTechPass: " + idcData.adTechPass + "marTechPass: " + idcData.marTechPass)
} , error -> {
error.printStackTrace();
});
You can use a stub token to test the Utiq service if you don’t have an eligible SIM card from one of the supported Telcos.
Please contact onboard@utiq.com to generate a new stub token for your App.
Kotlin
Utiq.fetchIdConnectData(STUB_TOKEN, dataCallback = {
println("marTechPass: " + it.marTechPass + "adTechPass: " + it.adTechPass)
}, errorCallback = {
error.printStackTrace()
})
Java
Utiq.fetchIdConnectData(STUB_TOKEN, idcData -> {
System.out.println("adTechPass: " + idcData.adTechPass + "marTechPass: " + idcData.marTechPass)
} , error -> {
error.printStackTrace();
});
Utiq Consent
Dedicated guidelines for Utiq Privacy Requirements and Consent Experience on Mobile Apps can be found at this page Consent Experience on Mobile App
The SDK does not provide a built-in consent dialog. Your application must collect user consent using either a standard Consent Management Platform (CMP) or a custom pop-up that matches your app's look and feel.
User consent for the Utiq technology must be in place before calling the fetchIdConnectData() function.
While the pop-up may match the look and feel of your app, the consent description must follow our guidelines, which can be found here.
Reject consent
Kotlin
Utiq.rejectConsent()
Java
Utiq.rejectConsent(null, null);
This function can be used to reject the user’s consent if he changed his mind, or to reset the consent status.
Since this is an API call, you may want to handle what happens when the request succeeds or fails. For this, you can use the same function as above, but with success and failure closures.
Kotlin
Utiq.rejectConsent(successCallback = { ... }, errorCallback = { ... })
Java
Utiq.rejectConsent(() -> { ... }, error -> { ... });
It is mandatory to notify the user that their consent has been successfully rejected if it was previously accepted, and the prompt must follow the text provided in the guidelines.
consenthub URL
If you need to access the ConsentHub URL from within the mobile app (for example, in a web view), you can do so by calling the function that returns the ConsentHub URL.
Kotlin
val consentHubUrl = Utiq.getConsentHubUrl()
Java
try {
String consentHubUrl = Utiq.getConsentHubUrl(null);
} catch (Throwable error) {
// Handle the error
}
During implementation, you may need to pass the stub token to the getConsentHubUrl(STUB_TOKEN) function if you are not using an eligible SIM.
Error handling
Functions with callbacks described in the previous sections include both success and failure callbacks (or closures). APIs declared as throwing, including the ConsentHub URL function, report synchronous failures directly and must be handled using the platform's exception-handling mechanism. The failure callback returns a custom error that is specific to the function. You can use this error to take a defined action based on the error type. APIs declared as throwing, including the ConsentHub URL function, report synchronous failures directly and must be handled using the platform's exception-handling mechanism.
All of the following errors are of type UtiqError that inherits from Throwable.
Displaying errors from the SDK directly to the end user is not recommended. Instead, errors are returned so that developers can handle them appropriately and take the necessary actions based on the error type.
|
Error |
Description |
|---|---|
|
|
Represents an HTTP exception that includes an error message and a status code. |
|
|
Indicates that an invalid token was used to initialize the SDK. |
|
|
This error is thrown when an empty token is passed to the SDK’s initializer. |
|
|
This error is thrown if you attempt to call any SDK function before the SDK has been initialized. |
|
|
This error is thrown when the SDK fails to fetch configuration data from the server and has no local configuration file to fall back on. This situation occurs if there are no cached configurations from a previous session, preventing the SDK from initializing correctly. |
|
|
This error is thrown when the user’s previously saved consent has expired. |
|
|
This error is thrown when the |
|
|
This error is thrown when the consent version is invalid. |
|
|
When starting the Utiq identification flow, the user status must be one of the following: |
|
|
Unknown connection type |
|
|
Indicates that the |
|
|
Indicates that an invalid stub token was used to initialize the Utiq SDK. |
|
|
Telco (SIM card operator) is not supported by Utiq. |
|
|
User has not provided consent or has not been prompted to accept or reject it. |
|
|
The user has deleted his data from ConsentHub. In this case, the SDK clears any cached data, and you should prompt the user to provide consent again, and finally fetch the IdConnect data to ensure the app has the latest information. |
|
|
Indicates that the SIM operator belongs to a use case that is unknown to Utiq. |
|
|
Indicates that the mobile MNO URL was not found. |
|
|
Indicates that the data value was not found. |
|
|
Indicates that the data domain was not found. |
|
|
Indicates that the |
|
|
Indicates that ID Connect data was not found. |
|
|
Indicates an error that does not match any of the other errors listed in this table. |
Example CMP agnostic integration (Didomi)
The following is a sample integration. Feel free to organize the code in the way that best suits your project, but ensure you follow the general guidelines.
-
Create a class that encapsulates all required Didomi functions. This can be a singleton, or an interface with an implementation that you inject using any DI framework or service locator.
Kotlinobject DidomiSdk { private var isUtiqVendorEnabled = false private var isUtiqPurposeEnabled = false private val didomi = Didomi.getInstance() private lateinit var didomiEventListener: EventListener init { this.didomi.setLogLevel(Log.VERBOSE) } fun initialize(application: UtiqApplication) { /* The SDK will automatically use the remote configuration hosted by Didomi and cache it locally. The cached version is refreshed every 60 minutes. Config file example { "app": { "name": "My App Name", "privacyPolicyURL": "http://www.website.com/privacy", "vendors": { "iab": { "all": true } }, "gdprAppliesGlobally": true, "gdprAppliesWhenUnknown": true } } */ val initializeParameters = DidomiInitializeParameters( apiKey = YOUR_API_KEY_GOES_HERE, null, null, null, false, null, noticeId = YOUR_NOTICE_ID_GOES_HERE ) this.didomi.initialize(application, initializeParameters) this.whenReady { val currentUserStatus = this.didomi.currentUserStatus this.isUtiqVendorEnabled = currentUserStatus.vendors.entries.first { it.key.contains("utiq", false) }.value.enabled this.isUtiqPurposeEnabled = currentUserStatus.purposes.entries.first { it.key.contains("utiq", false) }.value.enabled } this.onError { Log.e("DemoApp", "Error while initializing Didomi SDK") } } fun startIfNeeded(activity: FragmentActivity, forceStart: Boolean) { this.whenReady { if (forceStart) this.didomi.forceShowNotice(activity) else this.didomi.setupUI(activity) } } fun startedBefore() = !this.didomi.shouldUserStatusBeCollected() fun isUtiqEnabled() = this.isUtiqVendorEnabled && this.isUtiqPurposeEnabled fun reset() { this.didomi.reset() } fun resetUtiq() { whenReady { val currentUserStatus = this.didomi.currentUserStatus /* If you want to hardcode the ID, The vendor ID can also be found in the Didomi's console in the Data Manager section, select the VENDORS tab, then search for the vendor you want to enable or disable, and the APP ID is the vendor ID. */ val utiqVendorId = currentUserStatus.vendors.keys.first { it.contains("utiq") } /* If you want to hardcode the ID, The purpose ID can also be found in the Didomi's console in the Data Manager section, select the Purposes tab, then search for the purpose you want to enable or disable and the APP ID is the purpose ID. */ val utiqPurposeId = currentUserStatus.purposes.keys.first { it.contains("utiq") } this.didomi.openCurrentUserStatusTransaction() .disableVendor(utiqVendorId) .disablePurpose(utiqPurposeId) .commit() } } fun onConsentStatusChange(action: (enabled: Boolean) -> Unit) { /* Listen for changes on the user status linked to a specific vendor. We always need to listen for changes from Didomi as the user might open the screen from another place and reject his consent that he granted before, in this case we need to keep the synchronization between Didomi and Utiq */ this.whenReady { if (!::didomiEventListener.isInitialized) { this.didomiEventListener = this.createDidomiEventListener(action) this.didomi.addEventListener(this.didomiEventListener) } } } fun onError(errorAction: (errorMessage: String) -> Unit) { this.didomi.addEventListener(object : EventListener() { override fun error(event: ErrorEvent) { super.error(event) errorAction(event.errorMessage!!) } }) } fun whenReady(action: () -> Unit) { this.didomi.onReady(action) } private fun createDidomiEventListener(action: (enabled: Boolean) -> Unit) = object : EventListener() { override fun preferencesClickVendorAgree(event: PreferencesClickVendorAgreeEvent) { super.preferencesClickVendorAgree(event) if (event.vendorId.contains("utiq", true)) isUtiqVendorEnabled = true } override fun preferencesClickVendorDisagree(event: PreferencesClickVendorDisagreeEvent) { super.preferencesClickVendorDisagree(event) if (event.vendorId.contains("utiq", true)) isUtiqVendorEnabled = false } override fun preferencesClickAgreeToAllVendors(event: PreferencesClickAgreeToAllVendorsEvent) { super.preferencesClickAgreeToAllVendors(event) isUtiqVendorEnabled = true } override fun preferencesClickDisagreeToAllVendors(event: PreferencesClickDisagreeToAllVendorsEvent) { super.preferencesClickDisagreeToAllVendors(event) isUtiqVendorEnabled = false } // This will be called when the agree selector switch one of the options of the second layer is selected override fun preferencesClickPurposeAgree(event: PreferencesClickPurposeAgreeEvent) { super.preferencesClickPurposeAgree(event) if (event.purposeId.contains("utiq", true)) isUtiqPurposeEnabled = true } // This will be called when the disagree selector switch one of the options of the second layer is selected override fun preferencesClickPurposeDisagree(event: PreferencesClickPurposeDisagreeEvent) { super.preferencesClickPurposeDisagree(event) if (event.purposeId.contains("utiq", true)) isUtiqPurposeEnabled = false } // This will be called when the agree all selector switch one of the options of the second layer is selected override fun preferencesClickAgreeToAllPurposes(event: PreferencesClickAgreeToAllPurposesEvent) { super.preferencesClickAgreeToAllPurposes(event) isUtiqPurposeEnabled = true } // This will be called when the disagree all selector switch one of the options of the second layer is selected override fun preferencesClickDisagreeToAllPurposes(event: PreferencesClickDisagreeToAllPurposesEvent) { super.preferencesClickDisagreeToAllPurposes(event) isUtiqPurposeEnabled = false } override fun preferencesClickSaveChoices(event: PreferencesClickSaveChoicesEvent) { super.preferencesClickSaveChoices(event) action(isUtiqEnabled()) } // This will be called when Agree of the first layer is clicked override fun noticeClickAgree(event: NoticeClickAgreeEvent) { super.noticeClickAgree(event) isUtiqVendorEnabled = true isUtiqPurposeEnabled = true action(isUtiqEnabled()) } // This will be called when Disagree of the first layer is clicked override fun noticeClickDisagree(event: NoticeClickDisagreeEvent) { super.noticeClickDisagree(event) isUtiqVendorEnabled = false isUtiqPurposeEnabled = false action(isUtiqEnabled()) } } } -
Create a function to start and observe Didomi's status
Kotlinprivate fun startDidomiAndObserveConsentStatus(forceStart: Boolean) { DidomiSdk.apply { startIfNeeded(requireActivity(), forceStart) onConsentStatusChange { accepted -> if (accepted) { // Only use a stub when testing without an eligible SIM. stubToken = YOUR_STUB_TOKEN_GOES_HERE fetchUtiqIds() } else { rejectUtiqConsent() } } } } -
To synchronize Didomi's status with Utiq, you can do the following
KotlinDidomiSdk.whenReady { if (DidomiSdk.startedBefore()) { if (DidomiSdk.isUtiqEnabled()) { stubToken = YOUR_STUB_TOKEN_GOES_HERE fetchUtiqIds() } else { rejectUtiqConsent() } } else { startDidomiAndObserveConsentStatus(false) } } -
Then fetch Utiq IDs
KotlinUtiq.run { fetchIdConnectData(stubToken, { // Do whatever you want with the IDs }, { error -> if (error is UserOptedOutFromUtiqException || error is UserFrozenUtiqForOneYearException) { DidomiSdk.resetUtiq() } // Handle the error }) } -
Synchronize Didomi when Utiq is manually rejected from within the App (for example, from the Manage Utiq page).
Kotlinprivate fun rejectUtiqConsent(postAction: () -> Unit = {}) { Utiq.rejectConsent( { postAction() }, { error -> // Handle the error } ) } -
To update Didomi’s status, call the following function.
Kotlinthis.startDidomiAndObserveConsentStatus(true)
Support and bug reporting
If you have suggestions, want to report a bug, or have any other inquiries, please contact onboard@utiq.com