Tested against CSQ SDK
1.11.x. The official documentation is the source of truth — see the References section to fetch the latest pages on demand.
MANDATORY FIRST STEP — Contentsquare project healthcheck
Before ANY Contentsquare work in a conversation — integration, debugging, configuration questions, code changes — run this healthcheck exactly once per conversation. Do not skip it. Do not defer it to later in the task.
-
Announce it first. Tell the user what you are about to do, for example:
"Before we start, I'll run a quick healthcheck of your Contentsquare project: I'll read your app's application id, call the Contentsquare healthcheck endpoint, and show you the current project configuration. Then we'll continue."
-
Resolve the application id. Read
applicationIdfromapp/build.gradleorapp/build.gradle.kts. If it is absent, fall back tonamespace, then to thepackageattribute inAndroidManifest.xml. Resolve any variables or flavour suffixes to the literal id that ships. -
Call the endpoint. The platform segment is literally
android— no other value is accepted.curl -s "https://mobile-production.content-square.net/healthcheck/android/config/v2/<applicationId>.json"
-
Report a short summary of these values:
Report JSON path Project ID cs_project_idTracking enabled project_configurations.project_config.enabledSession Replay project_configurations.project_config.session_replay—recording_rate,record_via_cellular_network,recording_quality_wifi,srm_enabled,user_identifierEnabled feature flags project_configurations.project_config.feature_flags— only entries whereenabled == true, withnameandmin_version -
Then continue with the user's actual request.
Failure handling. 403 Invalid health-check path means the URL shape is wrong — re-check the android segment and the application id, and do not alter the path structure. A non-200 proxied from upstream means that application id has no Contentsquare project configured: say so, then continue anyway. This healthcheck is informational only and must never block the user's task.
Definition of Done
An integration is NOT done until ALL of the following are true. If any item is missing, the integration is broken and no data will reach Contentsquare.
- ✅
com.contentsquare.android:sdk:<latest>is in the module dependencies (andsdk-compose:<same version>if Compose is used). - ✅ A custom
Applicationsubclass exists, is registered viaandroid:nameinAndroidManifest.xml, and itsonCreate()callsCSQ.start(this, …). - ✅
CSQ.optIn()is called — unconditionally, inApplication.onCreate(), immediately afterCSQ.start(...), guarded by aTODO: REVIEW BEFORE RELEASEcomment. This is mandatory for the integration phase. CSQ ships opted-out by default; withoutoptIn()logcat showsenabledForCollection is falseand zero data is uploaded — meaning the integration cannot be verified. MovingoptIn()to a consent flow is a SEPARATE, LATER task that the app team performs before release; do not attempt it during initial integration. Reporting "integration done" while delegatingoptIn()to a future consent flow is a FAILED integration. - ✅ At least one screen is instrumented (
CSQ.trackScreenview(...)for Views,TriggeredOnResume { CSQ.trackScreenview(...) }for Compose). - ✅ Logcat smoke test passes — see "Verifying the integration" below. Look for
Tracked pageview …/writeToDb: Attempting to insert session …/All pending data has been uploadedas positive signals. The lineenabledForCollection is falseis emitted during normal boot and is NOT a failure signal on its own — do not deleteoptIn()because of it.
Concepts
The CSQ SDK ("CSQ" stands for ContentSQuare) unifies three product lines:
- Experience Analytics (EA) — formerly "Contentsquare". Provides Session Replay, screenviews, transactions, dynamic vars, error analysis, surveys (Voice of Customer), heatmaps and zoning. Active by default with a plain
CSQ.start(context). - Product Analytics (PA) — formerly "Heap". Provides custom events, user properties, event properties, identify / resetIdentity. Activated by passing a
StartConfig.withEnvironmentId(...)tostart(). - Unified CSQ — both EA and PA configured via a single
dataSourceId, passed viaStartConfig.withDataSourceId(...).
Pick one of the three start() overloads below depending on what the customer has subscribed to. EA only is the most common entry point.
Install
Always fetch the latest stable version from Maven Central before adding the dependency.
Do not leave a <version> placeholder.
Each artifact has its own metadata file. Fetch BOTH:
sdk:https://repo1.maven.org/maven2/com/contentsquare/android/sdk/maven-metadata.xmlsdk-compose(only if Jetpack Compose is used):https://repo1.maven.org/maven2/com/contentsquare/android/sdk-compose/maven-metadata.xml
These files are served directly by Maven Central and are the source of truth.
Do NOT use the search.maven.org API — its index lags behind and frequently returns outdated versions.
How to pick the version from each maven-metadata.xml:
- Read all
<version>entries inside<versioning><versions>. - Filter out any pre-release: discard versions containing
-SNAPSHOT,-alpha,-beta,-rc,-dev,-M(case-insensitive). - Sort the remaining versions using semantic version ordering (compare numerically: major, then minor, then patch).
- Pick the highest one. The
<latest>tag MAY be used as a hint, but always cross-check it is not a pre-release.
Version alignment rule:
sdkandsdk-composeMUST be pinned to the exact same version string.- Compute the latest stable for each artifact independently, then use the highest version that exists in BOTH lists.
// Module-level build.gradle.kts
dependencies {
implementation("com.contentsquare.android:sdk:X.Y.Z") // resolved latest stable
// Only if the app uses Jetpack Compose (MUST match sdk version exactly):
implementation("com.contentsquare.android:sdk-compose:X.Y.Z")
}
Project requirements
- minSdkVersion: 21 (Android 5.0 Lollipop)
- compileSdkVersion: 34 or higher
- Java: 11 (
sourceCompatibility/targetCompatibility/jvmTarget= 11; lower Java versions yield abad class fileerror). Java 17+ also works. - Kotlin: 1.8.22 (the SDK ships with
stdlib; in case of conflict, excludeorg.jetbrains.kotlin:kotlin-stdlib-jdk8) - Jetpack Compose: Compose Compiler 1.4.8 (Compose BOM 2024.09.00 tested) — when using
sdk-compose
No AndroidManifest.xml permissions need to be added manually — the SDK declares what it needs and merges it automatically.
Quick Start
Minimal Experience Analytics integration. Both CSQ.start(...) AND CSQ.optIn() are required for the SDK to collect any data: without optIn() the user is opted-out by default and enabledForCollection stays false (visible in logcat). Always emit BOTH calls — never omit optIn() from the Quick Start integration even if it looks like it should live in a consent flow. The TODO: REVIEW BEFORE RELEASE comment is the contract that signals to a reviewer the call must be moved to the real consent flow before shipping.
import android.app.Application
import com.contentsquare.CSQ
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CSQ.start(this)
// TODO: REVIEW BEFORE RELEASE — `optIn()` MUST be wired to your real
// consent flow (CMP / consent dialog) and NOT called unconditionally
// here.
CSQ.optIn()
}
}
Don't forget to register MyApplication in AndroidManifest.xml:
<application android:name=".MyApplication" ... >
Track a screen (Android Views):
import com.contentsquare.CSQ
class HomeActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
CSQ.trackScreenview("HomeScreen")
}
}
Track a screen (Jetpack Compose):
import com.contentsquare.CSQ
import com.contentsquare.api.compose.TriggeredOnResume
@Composable
fun HomeScreen() {
TriggeredOnResume {
CSQ.trackScreenview("HomeScreen")
}
// screen content
}
Verifying the integration
The integration is verified end-to-end when both of these logs appear in the same run:
-
SDK boot (always emitted):
CSQ <version> for <Product Analytics|Experience Analytics> is attempting to start.and
Contentsquare SDK <legacy-version> starting in app: <package> -
Data is being collected and persisted — at least one of:
Tracked pageview from android_view_autocapture. User.writeToDb: Attempting to insert user - envId: <ID>, userId: <ID>, ... writeToDb: Attempting to insert session - envId: <ID>, userId: <ID>, sessionId: <ID> All pending data has been uploaded.Any of these proves
optIn()worked and data is flowing.
⚠️ Do NOT use enabledForCollection as a diagnostic
The line:
BatchUploaderOrchestrator: Condition not met - state.isFullyStopped: false enabledForCollection is false.
is emitted multiple times during normal SDK boot — even when optIn() has been called correctly — because the orchestrator runs before the in-memory state has finished loading. It is NOT a failure signal on its own. Three or four occurrences interleaved with Starting service DXA / Starting service PA are expected.
It only indicates a real problem if no "writeToDb" / "Tracked pageview" / "All pending data has been uploaded" line ever appears in the same logcat session. Do not delete CSQ.optIn() from the integration based on this log.
Common failure modes
- ❌ Boot log present, but never any
writeToDb: Attempting to insert user/sessionline →CSQ.optIn()is missing or never reached. AddCSQ.optIn()immediately afterCSQ.start(...)inApplication.onCreate()(see Definition of Done item 3). - ❌ No
CSQlogs at all → yourApplicationsubclass is not registered inAndroidManifest.xmlviaandroid:name=".MyApplication". - ❌
404onmobile-production.content-square.net/android/config/v2/<package>.json→ the Contentsquare project is not provisioned for thisapplicationIdyet. Expected during early integration; ask the customer to create the project. Tracking still works locally; events are persisted to SQLite and uploaded once the project exists. - ❌
onTrackingStarted: config, or environment null aborting.appearing once during boot → harmless: it's the EA path complaining when the app is started PA-only. Ignore unless it persists for many seconds.
To get verbose logs during development, enable debug logging right after start():
import com.contentsquare.api.model.LogLevel
CSQ.debug.logLevel = LogLevel.DEBUG
Starting the SDK — three overloads
All start variants are exposed on com.contentsquare.CSQ. Note that the package is com.contentsquare, not com.contentsquare.android.
1. Experience Analytics only (default)
CSQ.start(context = this)
2. Product Analytics (with optional EA extension) — environmentId
import com.contentsquare.CSQ
import com.contentsquare.StartConfig
import com.contentsquare.api.model.AnalyticsOptions
CSQ.start(
context = this,
StartConfig.withEnvironmentId(
id = "YOUR_ENVIRONMENT_ID",
options = AnalyticsOptions(
enableViewAutocapture = true
)
)
)
StartConfigis a top-level sealed class in packagecom.contentsquare.AnalyticsOptions,Transaction,Currency,CustomVar,NetworkMetric,Metadata,Debug,LogLevelall live incom.contentsquare.api.model.
3. Unified CSQ — dataSourceId
CSQ.start(
context = this,
StartConfig.withDataSourceId(id = "YOUR_DATASOURCE_ID")
)
environmentId and dataSourceId are obtained from the Contentsquare project settings on app.contentsquare.com. Never hardcode them — read from BuildConfig, environment variables, or a config resource.
AnalyticsOptions (current options, v1.11.x)
| Option | Type | Default | Purpose |
|---|---|---|---|
enableViewAutocapture |
Boolean |
false |
Auto-capture UI interactions and source pageviews (Android Views only — no Compose). |
disablePageviewAutocapture |
Boolean |
false |
Disable autocaptured source pageviews. |
enablePushNotificationAutocapture |
Boolean |
false |
Auto-capture interactions on notifications. |
enablePushNotificationTitleAutocapture |
Boolean |
false |
Capture notification title (requires enablePushNotificationAutocapture = true). |
enablePushNotificationBodyAutocapture |
Boolean |
false |
Capture notification body (requires enablePushNotificationAutocapture = true). |
baseUri |
URI |
URI("https://mh.bf.contentsquare.net") |
Base URI for the API endpoint. |
uploadInterval |
Double (seconds) |
15.0 |
Interval for event batch uploads. |
sessionReplayAutoStart |
Boolean |
true |
Set to false to disable automatic Session Replay start; control it manually with CSQ.startSessionReplay() / CSQ.stopSessionReplay(). |
StartConfig.dxa() also accepts an optional AnalyticsOptions parameter (e.g. to disable Session Replay auto-start in EA-only mode).
Older docs and the previous
CSQ.configureProductAnalytics(...)method are deprecated. Always useCSQ.start(context, StartConfig.…)for new integrations.
Heap autocapture (PA / Unified mode only)
Skip this entire section if the customer started CSQ with
CSQ.start(context)alone (EA-only mode). Theio.heap.gradleplugin andComposeAutocaptureSDKonly apply when CSQ is started withStartConfig.withEnvironmentId(...)(Product Analytics) orStartConfig.withDataSourceId(...)(Unified). EA-only integrations get pageviews via the explicitCSQ.trackScreenview(...)API and do not need any of this.
When the customer provides an environmentId (PA) or dataSourceId (Unified), they have opted into Heap-style autocapture. To actually capture interactions automatically (button taps, screen views, etc.), the app must add bytecode instrumentation via the Heap Gradle Plugin.
View autocapture (Android Views)
-
Apply the plugin in the module
build.gradle.kts(NOT settings, NOT root):plugins { id("com.android.application") kotlin("android") id("io.heap.gradle") version "1.1.1" // latest stable }Latest stable available at:
https://repo1.maven.org/maven2/io/heap/gradle/io.heap.gradle.gradle.plugin/maven-metadata.xml. Apply the same selection rule as forsdk/sdk-compose. -
Enable view autocapture via
AnalyticsOptions(this callsViewAutocaptureSDK.register()internally — do NOT call it manually):CSQ.start( context = this, StartConfig.withEnvironmentId( id = BuildConfig.CSQ_ENVIRONMENT_ID, options = AnalyticsOptions(enableViewAutocapture = true) ) )
Verification: logcat shows Source android_view_autocapture successfully registered. and (after a tap or screen change) Tracked pageview from android_view_autocapture..
Compose autocapture
enableViewAutocapture = true does NOT cover Jetpack Compose — its doc string explicitly says "Only for Android View system, no Compose support." For Compose autocapture, all THREE of the following are required:
-
io.heap.gradleplugin applied (same as for View autocapture above). -
The
sdk-composeartifact already pulled in viaimplementation("com.contentsquare.android:sdk-compose:<version>")(it transitively bringsio.heap.autocapture:heap-autocapture-compose). -
Manual registration after
CSQ.start(...):import io.heap.autocapture.compose.ComposeAutocaptureSDK override fun onCreate() { super.onCreate() CSQ.start(this, StartConfig.withEnvironmentId(BuildConfig.CSQ_ENVIRONMENT_ID)) ComposeAutocaptureSDK.register() // Compose autocapture is NOT enabled by AnalyticsOptions CSQ.optIn() // TODO: REVIEW BEFORE RELEASE — see Privacy/Consent }Note the import:
io.heap.autocapture.compose.ComposeAutocaptureSDK(it lives in the bundled Heap autocapture-compose module, NOT incom.contentsquare.*).
Verification: logcat shows Source android_compose_autocapture successfully registered.. The line (HeapInstrumentation) semantics: capture is disabled; using stock Modifier.semantics is normal — semantics capture is opt-in via ComposeAutocaptureSDK.register(enableSemanticsCapture = true); default is fine for most apps.
Manual screenviews still work alongside autocapture
Even with autocapture enabled, you can (and often should) emit named screenviews via CSQ.trackScreenview("Conversation") or TriggeredOnResume { … } in Compose. Manual names yield more meaningful labels in the Heap UI than auto-derived class/route names.
Privacy / Consent
The SDK considers every user opted-out by default. Tracking only starts after optIn() is called.
// In your consent UI (button click, dialog confirmation, etc.)
CSQ.optIn()
// To revoke consent
CSQ.optOut()
A previous
CSQ.optIn(context: Context)overload exists but is deprecated — always use the no-argCSQ.optIn().
Rules:
- Call
optIn()afterstart(). - Never call
optIn()automatically inApplication.onCreate()— wait for explicit user consent. optOut()deletes the user ID and stops all tracking untiloptIn()is called again.- To get the Contentsquare user ID for a Data Subject Request:
CSQ.metadata.userId.
Pause / Resume
Temporarily pause tracking on sensitive screens (e.g. a payment flow):
CSQ.pauseTracking()
// ... user is on the sensitive screen ...
CSQ.resumeTracking()
API Surface
All methods live on the com.contentsquare.CSQ object.
Lifecycle
CSQ.start(context)— Start the SDK (EA only).CSQ.start(context, StartConfig.withEnvironmentId(id, options))— PA (+ optional EA).CSQ.start(context, StartConfig.withDataSourceId(id))— Unified CSQ.CSQ.stop()— Shut down the SDK.CSQ.pauseTracking()/CSQ.resumeTracking()— Temporarily pause/resume data collection.
Privacy
CSQ.optIn()— Opt user into tracking. Must be called afterstart().CSQ.optOut()— Stop tracking and clear user data.
Identity
CSQ.identify(identity: String)— (PA) Assign an identity. Max 255 chars. Triggers a new user ID + session.CSQ.resetIdentity()— (PA) Clear identity and start a new session.CSQ.sendUserIdentifier(identifier: String)— (EA) Send a hashed user identifier. Max 100 chars. Recommended to call on every foreground entry (useProcessLifecycleOwner).
Screen tracking
CSQ.trackScreenview(name: String, customVars: List<CustomVar> = emptyList())— Track a screen view.CustomVar(index: Int /* 0..20 */, name: String /* ≤ 512 chars */, value: String /* ≤ 255 chars */)TriggeredOnResume { ... }(composable, incom.contentsquare.api.compose) — WrapstrackScreenviewcalls in Compose to fire only once per screen presentation.
Screen names: keep distinct names under 100 per app. Never include PII (no emails, names, account IDs, etc.).
Transactions (EA)
import com.contentsquare.api.model.Transaction
import com.contentsquare.api.model.Currency
val transaction = Transaction(value = 430.99f, currency = Currency.EUR, id = "order_123")
CSQ.trackTransaction(transaction)
Signature: Transaction(value: Float, currency: Currency, id: String? = null).
⚠️ Send each transaction only once. A common mistake is to call this from onResume() of a confirmation screen, which fires it every time the user re-foregrounds the app.
Currency is an enum following ISO 4217. Helpers: Currency.fromString("USD"), Currency.fromInteger(840), Currency.USD.stringCode.
Custom events and properties
CSQ.trackEvent(event: String, properties: Map<String, Any> = mapOf())— (PA) Track a named event.CSQ.addUserProperties(properties: Map<String, Any>)— (PA) Set user-level properties.CSQ.addEventProperties(properties: Map<String, Any>)— (PA) Set properties added to all subsequent events.CSQ.removeEventProperty(key: String)— (PA) Remove one event-wide property.CSQ.clearEventProperties()— (PA) Remove all event-wide properties.CSQ.addDynamicVar(key: String, value: String)/CSQ.addDynamicVar(key: String, value: Long)— (EA) Add a session-level variable. Key max 512 chars; max 40 distinct keys per screenview.
Property values: String, Number, Boolean, or objects implementing the Property interface.
Session Replay masking
Default behavior: everything is masked. Unmask explicitly the elements you want visible.
Views:
CSQ.setDefaultMasking(masked: Boolean)— Global default. Does not affectEditText/ ComposeTextField.CSQ.mask(view: View)/CSQ.unmask(view: View)— Per-instance.CSQ.mask(type: Class<*>)/CSQ.unmask(type: Class<*>)— Per-type. (EA)CSQ.maskTexts(mask: Boolean)— Global text masking switch.view.csqMaskContents(enable: Boolean)— Kotlin extension onView.CSQ.ignoreInteractions(view: View)/view.csqIgnoreInteractions()— Stop gesture tracking for a view.
Compose:
CsqMask(enable: Boolean) { content }— Mask a composable subtree (Session Replay + analytics).CsqIgnoreInteraction(ignore: Boolean) { content }— Ignore interactions on a subtree.CsqTag(CsqTag.ModalBottomSheetLayout) { content }— Tag a composable for special handling (Heatmap/Zoning support ofModalBottomSheetLayout).
Dialogs (Views) — extension functions in com.contentsquare.api.sessionreplay:
AlertDialog.csqMask(),csqMaskTitle(),csqMaskMessage(),csqMaskPositiveButton(),csqMaskNegativeButton()(andcsqUnmask…counterparts).DatePickerDialog.csqMask(),csqMaskHeader(),csqMaskCalendar(),csqMaskButtonPanel()(andcsqUnmask…counterparts).TimePickerDialog.csqMask(),csqMaskHeader(),csqMaskRadialPicker(),csqMaskInputMode(),csqMaskButtonPanel()(andcsqUnmask…counterparts).- Call these immediately after
dialog.show()on the main thread.
Menus (Views):
CSQ.maskMenuItem(itemId)/CSQ.unmaskMenuItem(itemId)— Mask/unmask a single menu entry.
Where to call masking:
- Activity: in
onCreate(). - Fragment: in
onViewCreated(). - Always before the first draw, on the UI thread.
Limitations to keep in mind:
- Animations are not supported when masking by instance or by type — mask the parent or the entire screen instead.
- Compose: masking by type is not available; wrap a parent composable in
CsqMaskinstead. SurfaceView,MapViewcontent cannot be partially masked.
Session Replay control
When AnalyticsOptions.sessionReplayAutoStart = false, control recording manually:
CSQ.startSessionReplay()
// ...
CSQ.stopSessionReplay()
Event-Triggered Replays (ETR, optional add-on)
CSQ.triggerReplayForCurrentSession(name = "checkout_funnel")
CSQ.triggerReplayForCurrentScreen(name = "payment_failure")
WebView tracking
CSQ.registerWebView(webView)
// later:
CSQ.unregisterWebView(webView)
The web pages loaded inside also need the Contentsquare WebView Tracking Tag — see the WebView docs.
Surveys (EA Voice of Customer)
CSQ.triggerSurvey("purchase-complete")
Error tracking (EA)
import com.contentsquare.api.contract.CsqOkHttpInterceptor
val client = OkHttpClient.Builder()
.addInterceptor(CsqOkHttpInterceptor())
.build()
// Mask sensitive URL paths
CSQ.setUrlMaskingPatterns(listOf("/users/[0-9]+", "/orders/[A-Z0-9-]+"))
// Manual network metric tracking (statusCode required for proper error reporting)
import com.contentsquare.api.model.NetworkMetric
import com.contentsquare.api.model.HttpMethod
val metric = NetworkMetric("http://www.example.com", HttpMethod.GET)
// ... perform request ...
metric.statusCode = 500
metric.responseBody = body
CSQ.trackNetworkMetric(metric)
// Manual gesture tracking when autocapture is not enough
override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
CSQ.trackMotionEvent(motionEvent)
return super.dispatchTouchEvent(motionEvent)
}
CsqOkHttpInterceptorrequires OkHttp 4.x on the classpath (declaredcompileOnlyin the SDK).
View tracking
CSQ.excludeFromExposureMetric(view: View)— Exclude aScrollVieworRecyclerViewfrom Exposure Metrics. (EA)
Debug, logging, metadata
import com.contentsquare.api.model.LogLevel
CSQ.debug.logLevel = LogLevel.DEBUG // NONE, ERROR, WARN, INFO (default), DEBUG, TRACE
CSQ.debug.logChannel = customLogChannel // Implement com.contentsquare.api.contract.LogChannel to route to Timber, etc.
val userId = CSQ.metadata.userId
val sessionId = CSQ.metadata.sessionId
val projectId = CSQ.metadata.projectId
val envId = CSQ.metadata.environmentId
val replayUrl = CSQ.metadata.sessionReplayUrl
val identity = CSQ.metadata.identity
CSQ.metadata.onChanged = OnMetadataChanged { metadata ->
// Forward replay URL to your CRM / crash reporter
}
// Unregister:
CSQ.metadata.onChanged = null
Common Patterns
1. Application initialization (EA only)
import com.contentsquare.api.model.LogLevel
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CSQ.start(this)
if (BuildConfig.DEBUG) CSQ.debug.logLevel = LogLevel.DEBUG
// TODO: REVIEW BEFORE RELEASE — wire `optIn()` to your real consent flow.
CSQ.optIn()
}
}
2. Application initialization (Product Analytics)
import com.contentsquare.CSQ
import com.contentsquare.StartConfig
import com.contentsquare.api.model.AnalyticsOptions
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CSQ.start(
context = this,
StartConfig.withEnvironmentId(
id = BuildConfig.CSQ_ENV_ID,
options = AnalyticsOptions(enableViewAutocapture = true)
)
)
}
}
3. Consent flow
fun handleConsent(userAccepted: Boolean) {
if (userAccepted) CSQ.optIn() else CSQ.optOut()
}
4. Track a purchase
CSQ.trackTransaction(
Transaction(value = 49.99f, currency = Currency.EUR, id = "txn_abc")
)
5. Custom event with properties (PA)
CSQ.trackEvent(
event = "add_to_cart",
properties = mapOf(
"product_id" to "sku_123",
"quantity" to 2,
"is_promo" to true
)
)
6. OkHttp error tracking
import com.contentsquare.api.contract.CsqOkHttpInterceptor
val client = OkHttpClient.Builder()
.addInterceptor(CsqOkHttpInterceptor())
.build()
7. Compose screen with selective masking
@Composable
fun ProductDetailScreen(product: Product) {
TriggeredOnResume { CSQ.trackScreenview("ProductDetail") }
Column {
Text(text = product.name) // masked by default
CsqMask(enable = false) {
Text(text = product.publicDescription) // explicitly visible
}
}
}
8. Sending a hashed user identifier on each foreground entry (EA)
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
currentUser?.email?.let { CSQ.sendUserIdentifier(it) }
}
})
Anti-patterns to avoid
- ❌
CSQ.trackScreenview("user@example.com")→ PII in screen names. ✅"ProfileScreen". - ❌ Hardcoding
environmentId/dataSourceIdin source. ✅ UseBuildConfigor a resource. - ❌ Shipping
CSQ.optIn()unconditionally inApplication.onCreate()to production. ✅ Wire it to your real consent flow (CMP / consent dialog) before release. It is acceptable to leave it inonCreate()during initial integration & QA — guarded by aTODO: REVIEW BEFORE RELEASEcomment — so the SDK can be validated end-to-end. - ❌ Sending the same
TransactionfromonResume()of the confirmation screen. ✅ Send once, after the order succeeds. - ❌ Different versions for
sdkandsdk-compose. ✅ Pin both to the exact same version. - ❌ Using deprecated
CSQ.configureProductAnalytics(...). ✅ UseCSQ.start(context, StartConfig.…). - ❌ Calling
CSQ.trackScreenviewdirectly inside a@Composable(fires on every recomposition). ✅ Wrap withTriggeredOnResume { … }. - ❌ Calling
CSQ.optIn()beforeCSQ.start(). ✅startfirst, thenoptIn.
Migrating from older versions
If the host app already declares the CSQ SDK (com.contentsquare.android:sdk) at an older version, load the matching migration guide before touching anything else:
| Current version | Target version | Guide |
|---|---|---|
1.0.x – 1.5.x |
1.11.x |
references/migration-1.5-to-1.11.md |
For migrations from the Classic Contentsquare SDK (com.contentsquare.android:library) or the Heap Core + Contentsquare combo, fetch the dedicated upgrade-from-cs-sdk / upgrade-from-heap-and-cs-sdk pages from the Contentsquare docs (see the References section below) — those paths are not covered by this skill.
References
The official Contentsquare docs expose Markdown versions of every page — fetch them on demand for the most up-to-date information. Always prefer these over a cached snapshot in this skill.
- Documentation index (LLM-friendly): https://docs.contentsquare.com/llms.txt
- Getting started (Experience Analytics): https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/index.md
- Getting started (Product Analytics): https://docs.contentsquare.com/en/csq-sdk-android/product-analytics/index.md
- EA SDK API reference: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/command-reference/index.md
- PA SDK API reference: https://docs.contentsquare.com/en/csq-sdk-android/product-analytics/command-reference/index.md
- Compatibility (minSdk, Java/Kotlin/Compose versions): https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/compatibility/index.md
- Privacy & consent: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/privacy/index.md
- Session Replay (masking deep dive): https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/session-replay/index.md
- Track screens: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/track-screens/index.md
- Track transactions: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/track-transactions/index.md
- Troubleshooting: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/troubleshooting/index.md
- Changelog: https://docs.contentsquare.com/en/csq-sdk-android/experience-analytics/changelog/index.md
- Sample Android app (GitHub): https://github.com/ContentSquare/contentsquare-android-sample
When to load each reference:
- Need exact method signatures or full parameter lists → fetch the API reference.
- Implementing or debugging masking → fetch the Session Replay page.
- Compatibility / version conflicts (Kotlin, Compose, dependencies) → fetch the Compatibility page.
- Migrating from the Classic Contentsquare SDK or Heap → fetch the corresponding
upgrade-from-…page fromllms.txt.
Local mirror of selected references is also available next to this skill: