Skip to main content
Skip table of contents

Android SDK

Installation

The minSdkVersion version number in your build.gradle should not be less than 21.

To add the SDK to your app, please follow these steps:

  1. Add the following dependency to your project’s build.gradle or settings.gradle file under the allprojects block

Groovy

GROOVY
allprojects {
   repositories {
      maven {
         url 'https://jitpack.io'
      }
   }
}

Kotlin DSL

KOTLIN
allprojects {
   repositories {
       maven (url = uri("https://jitpack.io"))
   }
} 
  1. Add this dependency com.github.Utiq-tech.UTIQ-Mobile-SDK:utiq:{VERSION} to the .gralde file of your App or module.

You can get the latest version from here.

  1. Also, add the following permissions to your manifest file

XML
<uses-permission android:name="android.permission.INTERNET"/>
  1. Initialize the SDK

Initializing the SDK

Basic initializing

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

KOTLIN
Utiq.initialize(this, SDK_TOKEN)

Java

JAVA
Utiq.initialize(this, SDK_TOKEN);

Please email us to request a new token and configuration file 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 and using the fallback configuration.

Kotlin

KOTLIN
val fallbackConfigJson : String = "{ /* fallbackConfigJson */ }"
val options = UtiqOptions().enableLogging().setFallBackConfigJson(fallbackConfigJson)
Utiq.initialize(this, SDK_TOKEN, options)

Java

JAVA
String fallbackConfigJson = "{ /* fallbackConfigJson */ }";
UtiqOptions options = new UtiqOptions().enableLogging().setFallBackConfigJson(fallbackConfigJson);
Utiq.initialize(this, SDK_TOKEN, options);

Check if the SDK is initialized

At some point, you will need to check if the SDK is initialized before doing anything else.

Kotlin

KOTLIN
 UTIQ.isInitialized()

Java

JAVA
 UTIQ.isInitialized();

onInitialize listener

You should use this if you need to interact with the SDK very early in the App (for example, on the splash screen) and want to ensure the SDK is initialized before calling any of its functions. However, if you are using SDK functions in a flow that naturally involves multiple steps—such as a cart page in an e-commerce app—you likely won’t need this check, since the SDK will already be initialized by the time the user reaches that page or class. It is up to you to decide when this check is necessary and when it can be skipped.

Kotlin

KOTLIN
Utiq.onInitialize({
   // Success Action
}) {
   // Failure Action
}

Java

JAVA
Utiq.onInitialize(() -> {
       // Success Action
}, error -> {
       // Failure Action
}); 

Basic usage

Once the SDK is initialized, all the functions can be called by calling Utiq. to access all the SDK functions.

Integration

Fetch Utiq data

Kotlin

KOTLIN
Utiq.fetchIdConnectData(dataCallback = {
    println("marTechPass: " + it.marTechPass + "adTechPass: " + it.adTechPass)
}, errorCallback = {
    error.printStackTrace()
})

Java

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 email us to generate a new stub token for your App.

Kotlin

KOTLIN
Utiq.fetchIdConnectData(STUB_TOKEN, dataCallback = {
    println("marTechPass: " + it.marTechPass + "adTechPass: " + it.adTechPass)
}, errorCallback = {
    error.printStackTrace()
})

Java

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

Utiq services require explicit user consent before data can be fetched. Therefore, it is essential to display a consent prompt to the user. Without the user’s acceptance, Utiq data will not be retrieved.

The SDK does not provide a built-in consent dialog. You must implement a custom pop-up that aligns with the look and feel of your app.

This consent pop-up should be displayed before calling the fetchIdConnectData() function (and only if the consent prompt has not already been shown). The user’s choice must then be passed to the SDK.

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.

Is consent accepted

This function returns a Boolean value that indicates the user’s consent status (accepted or rejected).

Kotlin

KOTLIN
Utiq.isConsentAccepted()

Java

JAVA
Utiq.isConsentAccepted();

You can use this function to verify the user’s consent status before displaying a consent dialog.

Accept consent

Kotlin

KOTLIN
Utiq.acceptConsent()

Java

JAVA
Utiq.acceptConsent();

Reject consent

Kotlin

KOTLIN
Utiq.rejectConsent()

Java

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

KOTLIN
Utiq.rejectConsent(successCallback = { ... }, errorCallback = { ... })

Java

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

KOTLIN
Utiq.getConsentHubUrl()

Java

JAVA
Utiq.getConsentHubUrl();

During implementation, you may need to pass the stub token to the consentHubUrl(stubToken: STUB_TOKEN) function if you are not using an eligible SIM.

User Eligibility

Before requesting user consent and fetching IdConnect data, it is recommended to first check whether the user is on a supported Telco by using the following function. This helps avoid unnecessary API calls and consent popups. In all cases, Utiq will not return any data if the Telco is not supported.

Kotlin

KOTLIN
Utiq.checkMNOEligibility({
    // Success       
}, {
    error.printStackTrace()
})

Java

JAVA
Utiq.checkMNOEligibility(() -> {
      // Success
      return null;
}, error -> {
     error.printStackTrace();
});

The eligibility check function also accepts a stub token so you can test freely without a supported SIM.

Clear cached data

Locally cached data and cookies can be cleared easily by using the following functions.

Kotlin

KOTLIN
Utiq.clearData()

Java

JAVA
Utiq.clearData();

This does not delete data and cookies from our back-end, but only from the mobile device’s local storage. To delete the user’s data, you should use the function that rejects the user’s consent.

Error handling

All functions with callbacks described in the previous sections include both success and failure callbacks (or closures). 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.

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

HttpException

Represents an HTTP exception that includes an error message and a status code.

InvalidSdkTokenException

Indicates that an invalid token was used to initialize the SDK.

SdkAlreadyInitializedException

This error is thrown when attempting to initialize the SDK after it has already been initialized.

ConfigFileNotFoundDeprecated

In favor of CachedConfigsNotFound

Indicates that the SDK config was not found.

SdkTokenCanNotBeEmptyException

This error is thrown when an empty token is passed to the SDK’s initializer.

SdkNotInitializedException

This error is thrown if you attempt to call any SDK function before the SDK has been initialized.

FailedToFetchConfigsException

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 no fallback config file was provided during initialization or if there are no cached configurations from a previous session, preventing the SDK from initializing correctly.

UtiqConsentExpiredException

This error is thrown when the user’s previously saved consent has expired.

TemplateDataUrlNotFoundException

This error is thrown when the templateDataUrl is missing from the config API response or the fallback configuration file.

InvalidConsentVersionsException

This error is thrown when the consent version is invalid.

UnKnowUserStatusException

When starting the Utiq identification flow, the user status must be one of the following: NEW, OK, or NotCreated. If the status is any value other than the expected one, this error will be thrown.

UnKnownConnectionTypeException

Unknown connection type

EmptySetCookieHeaderException

Indicates that the Set-Cookie header is missing from the response header.

InvalidStubTokenException

Indicates that an invalid stub token was used to initialize the Utiq SDK.

MnoIneligibleException

SIM card operator is not supported by Utiq.

UtiqConsentNotSetException

User has not provided consent or has not been prompted to accept or reject it.

UserOptedOutFromUtiqException

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.

UnknownTelcoUseCaseException

Indicates that the SIM operator belongs to a use case that is unknown to Utiq.

MnoUrlNotFoundException

Indicates that the mobile MNO URL was not found.

DataValueNotFoundException

Indicates that the data value was not found.

DataDomainNotFoundException

Indicates that the data domain was not found.

UndefinedTelcoException

Indicates a Telco with a token use case that is not Utiq-enabled.

InvalidSamlSessionIdException

Indicates a SAML use-case Telco with an invalid session ID.

InvalidSamlLocationURLException

Indicates a SAML use-case Telco with an invalid location URL.

InvalidSamlAuthenticationURLException

Indicates a SAML use-case Telco with an invalid authentication URL.

NetworkIdentificationException

Indicates that the fetchIdConnectData function was not called.

IdConnectApiHostNotFoundException

Indicates that the ID Connect API host was not found.

IdConnectDataNotFoundException

Indicates that ID Connect data was not found.

GenericException

Indicates an error that does not match any of the other errors listed in this table.

UnknownException

Indicates an unknown error.

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.

  1. 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.

    KOTLIN
    object 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_HER
            )
            this.didomi.initialize(application, initializeParameters)
            this.onInitialized {
                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.onInitialized {
                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() {
            onInitialized {
                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.onInitialized {
                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 onInitialized(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())
            }
        }
    }
  2. Create a function to start and observe Didomi's status

    KOTLIN
    private fun startDidomiAndObserveConsentStatus(forceStart: Boolean) {
            DidomiSdk.apply {
                startIfNeeded(requireActivity(), forceStart)
                onConsentStatusChange { accepted ->
                    if (accepted) {
                        Utiq.clearData()
                        // Only use stub if you are testing and do not have an eligible SIM card
                        stubToken = YOUR_STUB_TOKEN_GOES_HERE
                        Utiq.acceptConsent()
                        fetchUtiqIds()
                    } else {
                        rejectUtiqConsent()
                        // Do any further actions if needed
                    }
                }
            }
        }
  3. To synchronize Didomi's status with Utiq, you can do the following

    KOTLIN
     DidomiSdk.onInitialized {
                    if (DidomiSdk.startedBefore()) {
                        if (DidomiSdk.isUtiqEnabled()) {
                            // Only use stub if you are testing and do not have an eligible SIM card
                            stubToken = YOUR_STUB_TOKEN_GOES_HERE
                            Utiq.acceptConsent()
                            fetchUtiqIds()
                        } else
                           // Didomi Consent was rejected before, Utiq will not start!
                    } else
                        startDidomiAndObserveConsentStatus(false)
                }
  4. Then fetch Utiq IDs

    KOTLIN
    Utiq.run {
                fetchIdConnectData(stubToken, {
                    // Do Whatever you want with the IDs
                }, {
                    if (it is UserOptedOutFromUtiqException || it is UserFrozenUtiqForOneYearException)
                        DidomiSdk.resetUtiq()
                    // Handle the error
                })
            }
  5. Synchronize Didomi when Utiq is manually rejected from within the App (for example, from the Manage Utiq page).

    KOTLIN
    private fun rejectUtiqConsent(postAction: () -> Unit = {}) {
            Utiq.rejectConsent({
                Utiq.apply {
                    clearData()
                    postAction()
                }
            }, {
                // Handle the error
            })
        }
  6. To update Didomi’s status, call the following function.

    KOTLIN
    this.startDidomiAndObserveConsentStatus(true)

Support and bug reporting

If you have suggestions, want to report a bug, or have any other inquiries, you can contact the Utiq Customer Success team at csm@utiq.com

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.