How to properly track screen view on Android with Jetpack Compose UI?

Viewed 1626

I want to track screen views in a compose-only app, so no activities, fragments, only composable functions.

When and how I should call the screen tracking e.g. for firebase?

Because the body of the composable function is executed on every recomposition, side effects can be also called repeatedly, and none of these means a real "view", composable can be invisible or just measured.

3 Answers

You can use LaunchedEffect or DisposableEffect with a constant key, for example Unit, to execute side effects exactly once a composable enters composition (and in the case of DisposableEffect also on exit), ignoring recompositions.

// I don't think PascalCase would suit this method, as it suggests an on-screen element
@SuppressLint("ComposableNaming") 
@Composable
fun trackScreenView(name: String) {
    DisposableEffect(Unit){
        Log.d("SCREENTRACKING", "screen enter : $name")
        onDispose { Log.d("SCREENTRACKING", "screen exit : $name") }
    }
}

@Composable
fun Screen1() {
    trackScreenView("Screen 1")
    // your screen content here
}

@Composable
fun Screen2() {
    trackScreenView("Screen 2")
    // your screen content here
}

@Adrian K DisposableEffect, as in your answer will work pretty well even if you use Lazy Rows in a Lazy Column.

DisposableEffect(Unit){
    Log.d("SCREENTRACKING", "screen enter : $name")
    onDispose { Log.d("SCREENTRACKING", "screen exit : $name")
}

Only the on screen items have DisposableEffect called initially, and the scrolling down or side ways in the list the new items are called. The one problem that needs improvement for fine grained impression data is that scrolling back over the same items that have already been rendered before do not necessarily call DisposableEffect again.

(would have left a comment but need more reputation)

If you want to send analytics events based on Lifecycle events you can use a LifecycleObserver. To listen for those events in Compose, use a DisposableEffect to register and unregister the observer when needed.

@Composable
fun TrackedScreen(
    name: String,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
) {
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                Firebase.analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
                    param(FirebaseAnalytics.Param.SCREEN_NAME, name)
                }
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

@Composable
fun HomeScreen() {
    TrackedScreen("Home")
   
    /* Home screen content */
}
Related