> For the complete documentation index, see [llms.txt](https://cmp.docs.sirdata.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/setup.md).

# Android — Setup

This page takes an Android app from no consent management at all to a release build with Sirdata CMP, one step at a time: requirements, installation, initialization, the consent UI, reading consent, the settings entry point, US users, testing, the release build and the upgrade from 1.2.0.

## 1. Requirements

|                       | Minimum                                                                           |
| --------------------- | --------------------------------------------------------------------------------- |
| `minSdk`              | 23 (Android 6.0)                                                                  |
| `compileSdk`          | 34                                                                                |
| JDK running Gradle    | 17                                                                                |
| Android Gradle Plugin | 8.2.2                                                                             |
| Kotlin                | 1.9.22                                                                            |
| Host activity         | a `ComponentActivity` — `AppCompatActivity` and `FragmentActivity` are subclasses |

* **`compileSdk` 34 comes from the SDK's AndroidX dependencies.** `androidx.activity` 1.8.2, `androidx.core` 1.12.0 and Compose Material 3 1.2.0 declare 34 as their minimum compile SDK, and the Android Gradle Plugin stops the build of an app that compiles against less.
* **Build tools.** Version 2.0.0 was verified with Android Gradle Plugin 8.2.2 and Kotlin 1.9.22, in a debug build and in a minified release build; the Views snippets on this page also compile with Kotlin 2.2.0. The shared core (`shared-android`) is compiled to Java 21 bytecode, which your build's D8 and R8 must accept: AGP 8.2.2 does, and earlier versions were not tested. Your own module can keep targeting Java 17.
* **Your app does not need Jetpack Compose.** The consent UI is written in Compose, but the SDK brings its own Compose dependencies: an app built with Views works unchanged.

**Your app needs `androidx.activity` and kotlinx coroutines on its compile classpath.** The SDK's API takes a `ComponentActivity` and returns coroutine types (`StateFlow`, `suspend` functions), but it does not expose those libraries to your code. Without them, the call sites fail to compile with `Cannot access class 'androidx.activity.ComponentActivity'` or `Cannot access class 'kotlinx.coroutines.flow.StateFlow'`. An app that depends on AppCompat (`androidx.appcompat:appcompat`) or on `androidx.activity:activity-compose` already has both; otherwise add them:

```kotlin
// app/build.gradle.kts — only if your app has neither AppCompat nor activity-compose
dependencies {
    implementation("androidx.activity:activity-ktx:1.8.2")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
}
```

**A partner ID and a config ID** from the [Sirdata Console](https://cmp.sirdata.io). They select the publisher configuration the SDK downloads: texts, theme, purposes, partners, capping. The examples on this page use `1020` and `"hmDnl"`: replace them with your own IDs.

**A licence for production use.** The SDK is proprietary software, under the [Sirdata CMP Mobile SDK Proprietary Licence v1.1](https://sirdata.com/legal/sdk-licence-v1.1.txt). Using it in an app distributed to end users requires a valid, current subscription to the Sirdata Consent Management Platform. A subscription taken through the Sirdata Console is enough: no signed agreement is required (sections 25.1 and 25.2). Without a subscription, the licence is a 90-day evaluation grant: internal evaluation, development, testing and integration, with no distribution to third parties or to an app store (section 1.4). Subscriptions: [sirdata.com](https://sirdata.com); licence questions: <legal@sirdata.com>.

## 2. Add the SDK to your project

Declare both repositories in `settings.gradle.kts`. The SDK is on Maven Central, and its AndroidX and Compose dependencies are only on Google's Maven repository. Projects created by Android Studio already declare both.

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
```

In an older project that declares its repositories in the root `build.gradle`, under `allprojects { repositories { … } }`, add them there instead.

Then add the dependency to your app module.

Kotlin DSL:

```kotlin
// app/build.gradle.kts
dependencies {
    implementation("com.sirdata.cmp:android-sdk:2.0.0")
}
```

Groovy DSL:

```groovy
// app/build.gradle
dependencies {
    implementation 'com.sirdata.cmp:android-sdk:2.0.0'
}
```

Version catalog:

```toml
# gradle/libs.versions.toml
[versions]
sirdataCmp = "2.0.0"

[libraries]
sirdata-cmp-android = { module = "com.sirdata.cmp:android-sdk", version.ref = "sirdataCmp" }
```

```kotlin
// app/build.gradle.kts
dependencies {
    implementation(libs.sirdata.cmp.android)
}
```

You only declare `android-sdk`. Gradle adds the rest:

* `com.sirdata.cmp:shared` and its Android variant `com.sirdata.cmp:shared-android`, the core the Android and iOS SDKs share. Never declare them yourself.
* Jetpack Compose UI and Material 3 (Compose BOM 2024.02.00), `androidx.activity` 1.8.2 and `androidx.lifecycle` 2.7.0, for the consent UI.
* Ktor 2.3.9 over OkHttp, kotlinx.coroutines 1.8.0 and kotlinx.serialization 1.6.3, for the network.
* AndroidSVG 1.4, to draw the SVG logos a configuration can carry.

When your app already uses a newer version of one of them, Gradle keeps the newer one.

There is nothing to add to your `AndroidManifest.xml` for the SDK itself: through manifest merging it brings the `INTERNET` and `ACCESS_NETWORK_STATE` permissions and declares its own consent activity, which is not exported. There is nothing to add for R8 either (step 9).

## 3. Initialize the SDK

Create an `Application` subclass, or open the one your app already has, and initialize the SDK in `onCreate()`:

```kotlin
package com.example.myapp

import android.app.Application
import com.sirdata.cmp.android.SirDataCMPApi

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        // Before any ad or analytics SDK.
        SirDataCMPApi.initialize(
            context = this,
            partnerId = 1020, // replace with your partner ID
            configId = "hmDnl", // replace with your config ID
        )
    }
}
```

If the class is new, declare it on the `<application>` element of `AndroidManifest.xml`. Keep the attributes and the activities your manifest already has:

```xml
<!-- app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:name=".MyApp">
        <!-- your activities, unchanged -->
    </application>
</manifest>
```

Four rules:

* **Before any ad or analytics SDK.** A vendor SDK that reads storage before the CMP has identified itself behaves as though no CMP were present.
* **On the main thread.** `Application.onCreate()` runs there. Called from another thread, `initialize` logs the violation under the Logcat tag `SirDataCMP`, or throws when the configuration enables `debugMode`.
* **Once per process.** A second call throws `IllegalStateException` ("SirDataCMP is already initialized. Call shutdown() first."). Code that can run twice can test `SirDataCMPApi.isInitialized()` first.
* **Before any other SDK call.** Before `initialize`, the string and boolean getters return `""` or `false`, `awaitConsentDisplayDecision()` answers at once that nothing should be displayed, and `getConsentState()`, `getConsentUpdates()`, `getGoogleConsentState()`, `acceptAll()` and the other consent actions throw `IllegalStateException`. Initializing in `Application.onCreate()` rules all of this out.

During the call, before any network request, the SDK writes `IABTCF_CmpSdkID` = `92` to the app's default `SharedPreferences`, together with `IABTCF_CmpSdkVersion` and `IABTCF_PolicyVersion`: a vendor SDK reading storage from then on finds a CMP. Everything else happens asynchronously afterwards: the SDK restores the stored consent, downloads the publisher configuration, the vendor lists and the texts, writes `IABTCF_gdprApplies` and the other keys that do not wait for a choice (listed in step 5), and fires the `tcloaded` event to in-app TCF listeners.

### Options

For more control, build a `SirDataCMPConfig`:

```kotlin
package com.example.myapp

import android.app.Application
import com.sirdata.cmp.android.SirDataCMPApi
import com.sirdata.cmp.android.SirDataCMPConfig

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        val config = SirDataCMPConfig.Builder(partnerId = 1020, configId = "hmDnl")
            .gdprDefaultApplies(true) // assumption used until the configuration answers
            .debugMode(BuildConfig.DEBUG) // main-thread misuse throws, in debug builds only
            .build()
        SirDataCMPApi.initialize(context = this, config = config)
    }
}
```

| Builder option                                                      | Default                                                                                                                                                       | Effect                                                                                                                                                                                           |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `gdprDefaultApplies(Boolean)`                                       | `true`                                                                                                                                                        | Whether GDPR applies while the configuration has not loaded yet and the device gives no country (SIM, network, then locale). Once the configuration has loaded, the server's answer replaces it. |
| `debugMode(Boolean)`                                                | `false`                                                                                                                                                       | A call to `initialize`, `shutdown` or `setCcpaOptOut` made off the main thread throws instead of being logged. It changes nothing else, logging included.                                        |
| `apiRoot(String)`, `cacheApiRoot(String)`, `consentApiRoot(String)` | `https://api.consentframework.com/api/v1/public`, `https://choices.consentframework.com/api/v1/public`, `https://mychoice.consentframework.com/api/v1/public` | The three hosts the SDK talks to. Change them only to go through your own proxy.                                                                                                                 |
| `certificatePins(CertificatePins?)`                                 | none                                                                                                                                                          | Certificate pinning, per host name: `CertificatePins(mapOf("api.consentframework.com" to listOf("sha256/…")))`. A wrong pin blocks the SDK's requests.                                           |
| `language(String?)`, `theme(CmpTheme)`                              | —                                                                                                                                                             | Accepted, but **no effect in 2.0.0** — see below.                                                                                                                                                |

**`language(...)` and `theme(...)` do nothing in 2.0.0.** The builder accepts them and nothing reads them, so do not rely on them to force a language or a dark theme. What actually decides:

* **The language of the consent UI** is the language the configuration sets, when it sets one; otherwise the device language (`Locale.getDefault()`); otherwise the configuration's default language; otherwise English.
* **Its look** is the configuration's theme from the Sirdata Console — colours, position, logo, text size. When the device is in dark mode and the configuration defines a dark theme, the dark one is used; otherwise the light one.

**`BuildConfig.DEBUG` needs `buildConfig` enabled.** Since Android Gradle Plugin 8.0, the `BuildConfig` class is generated only for modules that ask for it, so the snippet above does not compile in a new project until you add:

```kotlin
// app/build.gradle.kts
android {
    buildFeatures {
        buildConfig = true
    }
}
```

`BuildConfig` is generated in your module's `namespace`: the snippet needs no import because its package is that namespace. Elsewhere, import `<your namespace>.BuildConfig`.

## 4. Present the consent UI

Let the SDK decide whether to present the consent UI, from your launcher activity:

```kotlin
package com.example.myapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.sirdata.cmp.android.SirDataCMPApi
import kotlinx.coroutines.launch

// Any ComponentActivity: if yours extends AppCompatActivity or FragmentActivity, keep it.
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(...) or setContent { ... }, as usual

        lifecycleScope.launch {
            val decision = SirDataCMPApi.awaitConsentDisplayDecision()
            if (decision.shouldDisplay) {
                SirDataCMPApi.showConsentBanner(this@MainActivity, decision.workflow)
            }
        }
    }
}
```

Do **not** test the TC string yourself: a partially answered consent must re-open the banner, an active capping window must not, and neither is visible from the string.

`awaitConsentDisplayDecision()` waits for the publisher configuration, then applies the same cascade as the web CMP: GDPR applicability, stored consent, cookie wall, capping windows, and whether the stored choice still covers every active purpose, partner and publisher purpose. It returns a `ConsentDisplayDecision`:

* `shouldDisplay` — whether to present the consent UI;
* `workflow` — how: `MAIN` for the standard banner; `APPLY_CHOICES` when only partners are missing from the stored choice, to ask only about what is left to decide; `COOKIEWALL` for the cookie wall below. Pass it to `showConsentBanner` as it is;
* `reason` — why, which is what to log when no banner appears (see [Logging](/en/mobile-apps-native-sdk/mobile-sdk/logging.md)).

`showConsentBanner` starts the SDK's consent activity on top of yours — the banner, then the purposes and partners screens, or the US screen for a US user (step 7) — fires the `cmpuishown` TCF event, and returns at once. The consent activity closes itself when the user records a choice or closes it, and your activity resumes.

**The cookie wall.** When the configuration enables it, a stored choice that does not grant purpose 1 (storing or accessing information on a device) brings the consent UI back at every launch, in the `COOKIEWALL` workflow, whatever the capping windows. Passing `decision.workflow` is what opens it as a cookie wall.

**Jetpack Compose apps.** Keep the same code in your activity's `onCreate()`, next to `setContent { … }`. From inside a composable, get the activity from `LocalContext`, as in step 6.

### Bounding the wait on a splash screen

The unbounded wait lasts as long as initialization: on a dead network, that is the sum of the HTTP timeouts. Bound it with `timeoutMillis`:

```kotlin
// Fragment: replaces the lifecycleScope block in MainActivity.onCreate() above
import androidx.lifecycle.lifecycleScope
import com.sirdata.cmp.android.SirDataCMPApi
import kotlinx.coroutines.launch

lifecycleScope.launch {
    val decision = SirDataCMPApi.awaitConsentDisplayDecision(timeoutMillis = 3_000)
    if (decision.shouldDisplay) {
        // No workflow argument: when the configuration arrives after the deadline, the
        // consent UI adopts the workflow of the full decision.
        SirDataCMPApi.showConsentBanner(this@MainActivity)
    }
}
```

Past the deadline the decision is provisional: it only knows whether GDPR applies, as estimated from the device, and whether a choice is stored. Its reason is `GDPR_NOT_APPLIES` when that estimate says GDPR does not apply, and `CONFIG_NOT_LOADED` otherwise. A stored but incomplete choice is therefore not asked again on that launch.

### Re-opening from a settings screen

See step 6 below.

## 5. Read consent in your own code

```kotlin
// Fragment: in any function, once the SDK is initialized
import com.sirdata.cmp.android.SirDataCMPApi

val tcString = SirDataCMPApi.getTcString() // "" until a choice is recorded
val gdprApplies = SirDataCMPApi.isGdprApplies()
val deviceStorageOK = SirDataCMPApi.hasPurposeConsent(1) // purpose 1: store/access on a device
val googleOK = SirDataCMPApi.hasVendorConsent(755) // 755: Google in the Global Vendor List
```

* **The getters read the recorded choice**, from storage, never the toggles the user is moving on screen. `getTcString()`, `getAddtlConsent()`, `getGppString()`, `getUspString()`, `getPurposeConsents()` and `getVendorConsents()` return `""`, and `hasPurposeConsent()` and `hasVendorConsent()` return `false`, until a choice is recorded.
* **Purpose and vendor IDs** are those of the IAB Global Vendor List, starting at 1.
* **`isGdprApplies()`** is the server's answer once the configuration has loaded — based on the country of the device's IP address, unless the configuration forces GDPR. Before that, it is an estimate from the device, with `gdprDefaultApplies` when the device gives no country.

**Where the signals live.** The SDK writes every signal under the standard IAB keys, in the app's default `SharedPreferences` file, `<applicationId>_preferences` — the one `PreferenceManager.getDefaultSharedPreferences()` opens. Compliant vendor SDKs read them on their own; the full list is in [Sharing consent with vendor SDKs](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md#the-keys-the-sdk-maintains). The keys appear in this order:

* during `initialize`: `IABTCF_CmpSdkID`, `IABTCF_CmpSdkVersion`, `IABTCF_PolicyVersion`;
* once the configuration has loaded, before any choice: `IABTCF_gdprApplies`, `IABTCF_PolicyVersion` again (from the loaded vendor list), `IABTCF_PublisherCC`, `IABTCF_PurposeOneTreatment`, `IABTCF_UseNonStandardTexts`, the publisher bit strings (`IABTCF_PublisherConsent`, `IABTCF_PublisherLegitimateInterests` and the custom-purpose strings) and, for a US user, the GPP keys with the US National section; a stored choice is also rewritten to match the current configuration;
* when a choice is recorded: the TC string, the purpose and vendor bit strings, `IABTCF_AddtlConsent` and the GPP keys (`IABGPP_HDR_GppString`, `IABGPP_GppSID`);
* `IABUSPrivacy_String` only after a US choice (step 7).

The publisher bit strings are therefore present on a first launch, holding the SDK's defaults: their presence does not mean a choice was recorded. `IABTCF_TCString` does.

**React to changes** rather than reading once:

```kotlin
// Fragment: in an activity's onCreate()
import androidx.lifecycle.lifecycleScope
import com.sirdata.cmp.android.SirDataCMPApi
import kotlinx.coroutines.launch

lifecycleScope.launch {
    SirDataCMPApi.getConsentUpdates().collect {
        // Read the recorded values here and re-evaluate whatever you gate on consent.
        val deviceStorageOK = SirDataCMPApi.hasPurposeConsent(1)
    }
}
```

`getConsentUpdates()` is a counter. It emits its current value as soon as you collect it, then a new value on every change of the SDK's selections: several while the SDK initializes and while a choice is saved, and one for each toggle the user flips in the consent UI before saving. Reading the recorded values in the collector, as above, gives a result that only changes once a choice is recorded.

A US choice — saved on the US screen, or recorded with `setCcpaOptOut` (step 7) — does not change the counter. Re-read `getUspString()` and `getGppString()` when the consent UI closes: your activity's `onResume()` runs then.

**In-app TCF listeners cannot tell events apart in 2.0.0.** A listener registered with `tcfApi().execute("addEventListener", 2, …)` is called on `tcloaded`, `cmpuishown`, `useractioncomplete` and `cmpuiclosed`, but the data it receives carries no `eventStatus`. Treat each call as "something may have changed" and read the recorded values.

**Google Consent Mode.** The SDK derives the Google consent signals from the recorded TCF choice:

```kotlin
// Fragment: once the SDK is initialized
import com.sirdata.cmp.android.SirDataCMPApi

val google = SirDataCMPApi.getGoogleConsentState()
val adStorage = google.adStorageGranted
val analyticsStorage = google.analyticsStorageGranted
val adUserData = google.adUserDataGranted
val adPersonalization = google.adPersonalizationGranted
```

That is what the consent adapters would forward; `getLastForwardedConsentState()` is what they last forwarded, `null` until a choice is recorded in the session. How each vendor SDK — Google Mobile Ads, Firebase, AppsFlyer, Adjust, Branch, Meta — receives consent, and what you have to do for it, is in [Sharing consent with vendor SDKs](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md).

**Your own consent screen.** `acceptAll()`, `rejectAll()`, `saveConsent()` and `submitConsent(userAction)` record a choice without the SDK's UI. They are `suspend` functions returning a `ConsentResult` — `ConsentResult.Success` or `ConsentResult.Error(message)` — and store the choice, fire `useractioncomplete` and forward it to the consent adapters, like the SDK's UI. `recordAskLater()` hides the banner for one hour, a fixed window, and records no choice.

## 6. Reopen from a settings screen

Users must be able to change or withdraw their choice at any time. Give them an entry point — a "Privacy settings" item in your settings screen, for example — that opens the consent UI in the `MANUAL_DISPLAY` workflow:

```kotlin
package com.example.myapp

import androidx.activity.ComponentActivity
import com.sirdata.cmp.android.SirDataCMPApi
import com.sirdata.cmp.model.Workflow

// Any ComponentActivity: if yours extends AppCompatActivity or FragmentActivity, keep it.
class SettingsActivity : ComponentActivity() {
    // Call it from your "Privacy settings" item's click listener.
    fun openPrivacySettings() {
        SirDataCMPApi.showConsentBanner(this, Workflow.MANUAL_DISPLAY)
    }
}
```

From a composable, get the activity from `LocalContext`:

```kotlin
package com.example.myapp

import android.content.Context
import android.content.ContextWrapper
import androidx.activity.ComponentActivity
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.sirdata.cmp.android.SirDataCMPApi
import com.sirdata.cmp.model.Workflow

@Composable
fun PrivacySettingsButton() {
    val activity = LocalContext.current.findComponentActivity()
    TextButton(onClick = { SirDataCMPApi.showConsentBanner(activity, Workflow.MANUAL_DISPLAY) }) {
        Text("Privacy settings")
    }
}

tailrec fun Context.findComponentActivity(): ComponentActivity =
    when (this) {
        is ComponentActivity -> this
        is ContextWrapper -> baseContext.findComponentActivity()
        else -> error("No ComponentActivity in this context")
    }
```

What `MANUAL_DISPLAY` changes, compared with the banner shown at launch:

* the banner shows a close cross at the top, and a "Reject all" button whatever refusal button and style the configuration sets — even none — so that the user can always withdraw;
* the close cross closes without recording anything, except under the CNIL variant, where closing records the choice;
* opening it restarts the configuration's capping window, during which the banner is not shown automatically;
* it is counted in your Sirdata statistics as a revisit (`load:revisit`, or `ccpa_load:revisit` for a user in the United States).

Keep the entry point available to every user, including when `isGdprApplies()` is `false`: for a US user it opens the US screen.

## 7. US users

* **US users never get the banner automatically.** The display decision follows GDPR: for a user the server places outside its scope, `awaitConsentDisplayDecision()` returns `shouldDisplay = false` with the reason `GDPR_NOT_APPLIES`, and that includes US users.
* **The US screen opens when your app opens the consent UI.** When the US regime applies, `showConsentBanner` shows the US screen in place of the TCF banner — titled "Your Privacy Choices" in English by default — with one opt-out control covering what the configuration offers (sale, sharing, targeted advertising, or a combination) and a "Save My Choices" button; when the configuration offers no opt-out, the screen shows neither. Your app therefore needs an entry point US users can reach: the settings entry of step 6 is one. Which label and placement your app needs is a question for your legal team.
* **Saving the US screen records the choice** through `setCcpaOptOut`: it writes `IABUSPrivacy_String`, with its opt-out flag set to `Y` or `N`, and re-encodes the GPP string (`IABGPP_HDR_GppString`, `IABGPP_GppSID`) with its US sections. Closing the screen without saving records nothing. Until a US choice is recorded, `IABUSPrivacy_String` is absent and `getUspString()` returns `""`. A US choice does not change the `getConsentUpdates()` counter (step 5).
* **`isCcpaApplies()`** is the server's answer, from the user's country and US state, once the configuration has loaded; before that, an estimate from the device's country.
* **Call `setCcpaOptOut(optedOut)` yourself only from a control the user operates**, such as your own opt-out switch. It records that the notice was given and the user's answer, so calling it on your own — `if (isCcpaApplies()) setCcpaOptOut(true)`, for example — records an opt-out the user never made. Call it on the main thread; `isCcpaOptedOut()` reads the answer back.

## 8. Test and verify the integration

Use a debug build on a device or an emulator, with Logcat filtered on the tags `SirDataCMP` and `SirDataCMPManager`.

* **GDPR applicability is decided by the server,** from the country of the device's IP address, unless the configuration forces GDPR. Testing from a country where GDPR does not apply, you get no banner, with the reason `GDPR_NOT_APPLIES`.
* **To replay a first launch,** clear the app's data (`adb shell pm clear <applicationId>`), or call `SirDataCMPApi.clearConsent()` and restart the app. `clearConsent()` deletes the stored choice and, until the next launch or the next choice, `IABTCF_CmpSdkID` as well.
* **When you switch partner ID or config ID, clear the stored consent as well.** A TC string belongs to the publisher configuration that collected it.

Three tools help. Read the stored keys of a debug build with `run-as`, replacing `com.example.myapp` with your `applicationId`, suffix included:

```bash
adb shell run-as com.example.myapp cat shared_prefs/com.example.myapp_preferences.xml
```

Surface a configuration that failed to load — the same reason Logcat shows after `Config load failed:` under `SirDataCMPManager`:

```kotlin
// Fragment: in an activity's onCreate()
import android.util.Log
import androidx.lifecycle.lifecycleScope
import com.sirdata.cmp.android.SirDataCMPApi
import kotlinx.coroutines.launch

lifecycleScope.launch {
    SirDataCMPApi.getInitError().collect { error ->
        if (error != null) Log.w("CMP", "Configuration failed to load: $error")
    }
}
```

Call the in-app TCF API:

```kotlin
// Fragment: anywhere after initialize(); the callback runs on the main thread
import android.util.Log
import com.sirdata.cmp.android.SirDataCMPApi

SirDataCMPApi.tcfApi().execute("ping", 2, { data, _, success ->
    Log.d("CMP", "ping success=$success data=$data")
})
```

Then check, in this order:

1. **Right after launch**, `IABTCF_CmpSdkID` is `92` — before the network has answered.
2. **Logcat** shows `Config loaded successfully` under `SirDataCMPManager`. `Config load failed: …` means the configuration did not load: check the IDs and the network.
3. **`ping`** answers `success=true` and `cmpId=92`, with `cmpStatus=stub` until the configuration has loaded, then `cmpStatus=loaded`, `cmpLoaded=true` and `gdprApplies`. The switch can come a moment after `awaitConsentDisplayDecision()` returns.
4. **On a first launch**, the decision has `shouldDisplay = true` and the reason `CONSENT_REQUIRED`, and the banner appears. [Logging](/en/mobile-apps-native-sdk/mobile-sdk/logging.md) shows how to log the decision.
5. **After "Accept all"**, storage holds `IABTCF_TCString`, `IABTCF_gdprApplies` = `1`, `IABTCF_PurposeConsents`, `IABTCF_VendorConsents` and `IABGPP_HDR_GppString`. `IABUSPrivacy_String` is absent: it only appears after a US choice.
6. **Relaunch the app** (force-stop it, then open it): no banner. The reason is `CONSENT_OK` after "Accept all", `CAPPING_ACTIVE` after "Reject all".
7. **Your settings entry** reopens the banner with the close cross and a "Reject all" button.
8. **A minified release build** (step 9) shows the banner and records a choice too. Release builds are not debuggable, so `run-as` does not work there: check through the getters.
9. **If your app embeds Firebase Analytics, AppsFlyer, Adjust or Branch**, `getRegisteredAdapters()` lists their adapters and, after a choice, `getLastForwardedConsentState()` shows what was forwarded.

## 9. Release build

* **R8 and ProGuard: nothing to add.** The SDK's artifacts carry their own R8 rules, including the ones its consent adapters need. Rules you added for earlier versions — `-keep class com.sirdata.cmp.** { *; }`, or a `-dontwarn` for slf4j — can go: the SDK ships the `-dontwarn` itself, and a `-keep` on the whole SDK only stops R8 from shrinking it.
* **`debugMode` in debug builds only**, through `.debugMode(BuildConfig.DEBUG)` as in step 3.
* **Your own partner ID and config ID**, not the example values.
* **A production licence**: a current Sirdata CMP subscription, which the Sirdata Console provides (step 1).
* **Attribution notices.** The SDK embeds open-source components under the Apache License 2.0, and slf4j under the MIT licence. An app that embeds it must make available to its users the `NOTICE-android` file and the full texts of those two licences, in a place they can readily reach — your app's open-source licences screen, for example (section 26.1(b) of the licence). In 2.0.0 these files are not inside the Maven Central artifacts. Take them from the npm package `@sirdata/react-native-cmp@2.0.0`: `npm pack @sirdata/react-native-cmp@2.0.0` downloads it as a `.tgz` archive holding `NOTICE-android` and, under `THIRD-PARTY-LICENSES/`, `Apache-2.0.txt` and `MIT-slf4j.txt` (`BSL-1.0.txt` is for iOS). Or ask <legal@sirdata.com>.
* **Google Play Data safety.** The SDK exchanges data with Sirdata's servers: it downloads the configuration, the vendor lists and the texts, and it sends each recorded choice — the consent strings — and usage statistics about the consent UI (displays, clicks, answers). Your Data safety form must cover what third-party code in your app collects or shares; see Google's [Provide information for Google Play's Data safety section](https://support.google.com/googleplay/android-developer/answer/10787469).
* **Network allowlists, proxies and certificate pinning** must let three hosts through: `api.consentframework.com`, `choices.consentframework.com` and `mychoice.consentframework.com`. When the user opens a partner's storage details in the partners screen, the SDK also fetches that partner's device-storage disclosure from the URL the partner publishes.

## 10. Upgrading from 1.2.0

1. **Bump the version** to `2.0.0` and rebuild. Four public data classes gained a field (`GoogleConsentState`, `UserAction`, `Settings`, `Info`); their previous constructors and `copy` signatures are kept as overloads, so source code compiles unchanged, but a library compiled against 1.2.0 that calls them with default arguments must be recompiled.
2. **Remove the R8 rules** you added for the SDK (step 9). 1.2.0 failed a minified build on `org.slf4j.impl.StaticLoggerBinder`; 2.0.0 ships its own rules.
3. **If your code uses the SDK's consent UI classes**: `ConsentButtonStyle.CLOSE` is removed, and `ConsentUiState.scopeReminderOpensHostnames` is replaced by `scopeReminderSegments` and `purposesText2Segments`, with no overload keeping the previous constructor.
4. **AppsFlyer**: call `enableTCFDataCollection(true)` before `start()` — see [AppsFlyer](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md#appsflyer).
5. **Firebase Analytics**: the adapter now forwards each choice, minified release builds included. If your app calls Firebase's `setConsent` itself for the signals the adapter sends — to work around 1.2.0, for example — remove those calls: see [Firebase Analytics](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md#firebase-analytics).
6. **Expect the settings entry to look different**: in `MANUAL_DISPLAY` the banner now shows a "Reject all" button with the close cross at the top (step 6); 1.2.0 showed no refusal.
7. **Run the checks of step 8 again.**

Every change is listed in [Versions](/en/mobile-apps-native-sdk/mobile-sdk/versions.md).

## Next

* [Reference](/en/mobile-apps-native-sdk/mobile-sdk/reference.md) — every method and event
* [Sharing consent with vendor SDKs](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md) — the part that makes ads work
* [Logging](/en/mobile-apps-native-sdk/mobile-sdk/logging.md) — what the SDK logs, and reading the display decision
* [Troubleshooting](/en/mobile-apps-native-sdk/mobile-sdk/troubleshooting.md) — symptoms and fixes
* [Versions](/en/mobile-apps-native-sdk/mobile-sdk/versions.md) — what changed in each release


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/setup.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
