For the complete documentation index, see llms.txt.

Track screenviews

Last updated View as MarkdownSet up with AI

The SDK supports two screenview autocapture modes:

Enhanced screenview autocapture

Section titled Enhanced screenview autocapture

Enhanced screenview autocapture reports each Fragment, Jetpack Compose destination, or custom screen as its own screenview, and attributes each interaction to the screen where it occurred. Some screens are captured with no code change, and others need an annotation or a wrapper depending on how your app navigates.

Enhanced screenview autocapture is the default mode. Autocapture itself is off until you set enableViewAutocapture = true:

MyApplication.kt
CSQ.start(
context = this,
StartConfig.withEnvironmentId(
id = "YOUR_ENVIRONMENT_ID",
options = AnalyticsOptions(
enableViewAutocapture = true
// disablePageviewAutocapture = false by default, so screenviews are captured
)
)
)

Two AnalyticsOptions settings control automatic screenviews:

OptionDefaultEffect
enableViewAutocapturefalseRegisters interaction autocapture for Views and Jetpack Compose. Set it to true to collect interactions.
disablePageviewAutocapturefalseSet it to true to turn off automatic screenviews. Interaction autocapture stays on. In enhanced mode, the default, interactions attach to screenviews tracked manually with CSQ.trackScreenview().

Set up screens for your navigation

Section titled Set up screens for your navigation

Pick the setup that matches each part of your app. A mixed app can use more than one approach.

Your app usesSetup
An Activity for each screenAnnotate each screen Activity with @CsqScreenView.
Fragments inside a screen containerImplement CsqScreenViewContainerOwner on the host Activity.
Navigation Compose (Nav2) destinationsAutomatic. Name typed routes with @CsqScreenName, or configure string routes at startup.
Navigation 3 (Nav3) entriesAutomatic. Optionally name a key class with @CsqScreenName.
Custom Compose navigationWrap each screen's content with CsqScreenView.

If your app uses a single Activity with Nav2 or Nav3, leave the host Activity unannotated so it doesn't create an extra Activity screenview.

When an Activity itself represents a screen, annotate it:

CheckoutActivity.kt
import androidx.appcompat.app.AppCompatActivity
import com.contentsquare.autocapture.pageview.CsqScreenView
@CsqScreenView(screenName = "Checkout")
class CheckoutActivity : AppCompatActivity()

If you omit screenName, the screenview uses the Activity class name.

An unannotated Activity with no eligible child screen doesn't produce an automatic screenview. Annotate it to collect it.

When Fragments represent screens, identify the container that directly hosts them:

CheckoutFlowActivity.kt
import androidx.fragment.app.FragmentActivity
import com.contentsquare.autocapture.pageview.CsqScreenViewContainerOwner
class CheckoutFlowActivity :
FragmentActivity(),
CsqScreenViewContainerOwner {
override val csqScreenViewContainerId: Int
get() = R.id.fragment_container
}

Visible Fragments in that container are captured automatically, and the host Activity needs no annotation. To report a readable name instead of the class name, annotate the Fragment:

PaymentFragment.kt
import androidx.fragment.app.Fragment
import com.contentsquare.autocapture.pageview.CsqScreenName
@CsqScreenName(name = "Payment")
class PaymentFragment : Fragment()

The naming annotation doesn't enable capture for Fragments outside the configured container.

Supported Nav2 destinations are captured automatically. For a typed destination, add an optional name to its route class:

Product.kt
import com.contentsquare.autocapture.pageview.CsqScreenName
import kotlinx.serialization.Serializable
@Serializable
@CsqScreenName(name = "Product Details")
data class Product(val id: String)

The screenview name is the annotation value, "Product Details". Route arguments such as the product ID don't create separate screens, and your navigation graph stays unchanged.

String routes such as composable("product/{id}") are also captured automatically. By default, they use the stable route pattern, not the argument value. To name or exclude them, configure their declared patterns in Application.onCreate, before any NavHost is composed:

MyApplication.kt
import com.contentsquare.autocapture.AutocaptureSDK
AutocaptureSDK.configureNav2StringRoutes(
screenNames = mapOf("product/{id}" to "Product Details"),
ignoredRoutes = setOf("checkout")
)

Keep these rules in mind:

  • Use exact declared patterns, such as product/{id}, rather than paths with argument values.
  • Matching is case-sensitive, surrounding whitespace is trimmed, and wildcards aren't supported.
  • The configuration applies across your app, so identical patterns in different graphs share the same settings.
  • Supply all settings in one startup call. Each call replaces the entire configuration, and calling with no arguments clears it.
  • If a route appears in both collections, it's excluded.
  • Updating the configuration after navigation starts isn't supported.

Supported Nav3 entries are captured automatically. To name one, annotate its key class:

ProductDetailsKey.kt
import androidx.navigation3.runtime.NavKey
import com.contentsquare.autocapture.pageview.CsqScreenName
import kotlinx.serialization.Serializable
@Serializable
@CsqScreenName(name = "Product Details")
data class ProductDetailsKey(val productId: String) : NavKey

Without the annotation, the screen name comes from the key class. Argument values such as productId don't create separate screens.

When a screen isn't represented by a supported navigation API, wrap its content:

PaymentContent.kt
import androidx.compose.runtime.Composable
import com.contentsquare.autocapture.compose.CsqScreenView
@Composable
fun PaymentContent() {
CsqScreenView(screenName = "Payment") {
PaymentScreen()
}
}

The wrapper defines a screen while it's active. Use a stable name without user identifiers, order numbers, or other values that change. Don't add the wrapper to a destination that is already captured automatically unless it should introduce a separate screen boundary.

When a screen closes, remove the wrapper from the composition. An empty wrapper that stays in the composition keeps its screen active.

To stop a typed Nav2 route or Nav3 key from generating automatic screenviews, annotate its class with @CsqIgnoreScreenView:

Checkout.kt
import com.contentsquare.autocapture.pageview.CsqIgnoreScreenView
import kotlinx.serialization.Serializable
@Serializable
@CsqIgnoreScreenView
object Checkout

For string routes, use ignoredRoutes as shown in Navigation Compose (Nav2). An excluded screen keeps its interaction capture, but the following interactions stay attributed to the last screenview that was collected.

Open Live Data Feed, then navigate through your app, for example from Product to Checkout and Payment. Check the collected screenviews and confirm that interactions are attributed to the screen where they occurred.

ActionExpected result
Open an eligible screenA screenview appears with its configured name, or its class or route name.
Tap a button on a screenThe interaction references that screen.
Open an excluded screenNo screenview appears. Later interactions can stay attributed to the previous screen.
Trigger ordinary recompositionNo duplicate screenview for the unchanged screen.
Rotate the device on the same screenNo duplicate screenview solely for the configuration change.
Background and reopen the appEligible screens can generate a new screenview.

Each intended transition should generate one screenview. If your app also calls CSQ.trackScreenview() from an earlier integration, check for duplicates.

SymptomCheck
No automatic screenviewsConfirm that the SDK is started, that capture is active, and that enableViewAutocapture is true and disablePageviewAutocapture is false.
An Activity or Fragment is missingConfirm the Activity annotation, or the Fragment's host container.
A route's name or exclusion has no effectConfirm that it's a supported typed Nav2 route or Nav3 key, and that you annotated its class.
A string route's name or exclusion has no effectCall configureNav2StringRoutes before the first NavHost is composed, use the exact declared pattern, and check that no later call replaced it.
Autocapture imports don't resolveConfirm that your SDK version includes enhanced screenview autocapture.
Extra screenviews appearCheck for an annotated host Activity in a single-Activity app, unnecessary wrappers, or duplicate manual calls.
A closed screen stays activeRemove the whole CsqScreenView wrapper from the composition, and check for another visible wrapper with the same name.

Activity-only autocapture generates a screenview each time an Activity enters the foreground, with no per-screen setup. With enhanced screenview autocapture, an unannotated Activity entering the foreground no longer generates an automatic screenview just because it became visible.

To keep Activity-only screenviews, call AutocaptureSDK.useLegacyActivityPageviewAutocapture() before CSQ.start(). Autocapture must still be enabled with enableViewAutocapture = true, and disablePageviewAutocapture must be false.

MyApplication.kt
import android.app.Application
import com.contentsquare.CSQ
import com.contentsquare.StartConfig
import com.contentsquare.api.model.AnalyticsOptions
import com.contentsquare.autocapture.AutocaptureSDK
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AutocaptureSDK.useLegacyActivityPageviewAutocapture()
CSQ.start(
context = this,
StartConfig.withEnvironmentId(
id = "YOUR_ENVIRONMENT_ID",
options = AnalyticsOptions(
enableViewAutocapture = true,
disablePageviewAutocapture = false
)
)
)
}
}