# Events & screen tracking

> Track custom events and screen views in your Android app with the Grovs SDK.

Source: https://docs.grovs.io/docs/sdk/android/analytics

Requires Android SDK **3.0** or later. Screen views and custom events can also trigger [in-app messages](https://docs.grovs.io/docs/sdk/android/in-app-messages) (3.1).

## Tracking custom events

`track` records an event with optional properties and tags:

```kotlin
Grovs.track("checkout_completed", properties = mapOf("sku" to "abc", "total" to 42.0), tags = listOf("shop"))
Grovs.track("level_complete", properties = mapOf("level" to 5, "score" to 1200))
Grovs.track("button_tap")
```

How events are sent:

- Written to disk and sent in one batch every 30 seconds. A failed batch stays on disk and is retried on the next tick.
- Up to 1,000 events are kept on the device. Events older than seven days are dropped.
- Before authentication, events are stored and sent once it completes.
- While install attribution resolves, events are held so they credit the right link. See [Install attribution timing](https://docs.grovs.io/docs/sdk/android/deep-linking#install-attribution-timing).

### Event name rules

Names can't be blank or a reserved system name:

`view`, `open`, `install`, `reinstall`, `app_open`, `time_spent`, `reactivation`, `user_referred`, `custom`, `screen_view`

Push, in-app and journey event names are refused too: `in_app_viewed`, `in_app_clicked`, `in_app_dismissed`, `in_app_answered`, `in_app_commented`, `in_app_held`, `push_open`, `push_delivered`, `push_held`, `journey_entered`, `journey_held`, `journey_path`. `in_app_held`, the `push_*` and the `journey_*` names are refused since 3.1.

Names are compared after trimming, ignoring case. Rejected events are logged and dropped. Use [`trackScreenView`](#tracking-screen-views) for screen views, not `track("screen_view")`.

### Property values

Supported: strings, booleans, standard numbers, string-keyed maps, lists and arrays. The SDK sanitizes each value, for `track` and `trackScreenView` alike:

- `Date`, `URL` and `UUID` become strings, including when nested.
- Whole numbers are sent as whole numbers (`4`, not `4.0`).
- A value JSON can't represent is dropped **on its own**: a list element from its list, a map entry from its map, a top-level property from the event. Everything else is kept. Dropped top-level properties are logged.
- That covers `NaN`, infinity, other types (enums, sets, arbitrary objects), maps with any non-string key (the whole map is dropped), references back to an enclosing collection, and nesting deeper than 16 levels.
- `BigInteger` and `BigDecimal` beyond the `Double` range are dropped like infinity.
- Over **8 KB** of UTF-8, or more than 8,192 values traversed, omits the whole properties map. The event is still recorded.

### Tags

Up to 20 tags per event, each up to 255 characters. Global tags attach to every later event:

```kotlin
Grovs.setGlobalTags(listOf("android", "production"))

Grovs.setGlobalTags(null)  // clear
```

Global and per-event tags are merged, up to 20 combined.

## Tracking screen views

```kotlin
Grovs.trackScreenView("Checkout", properties = mapOf("step" to 2))
```

The same screen reported twice within one second is dropped, so a brief pause/resume or a re-attached Fragment doesn't flood the dashboard. A genuine later return to the screen still counts.

## Automatic screen tracking

`autoTrackScreenViews` defaults to `true`: the SDK reports Activities and Fragments as screens. When an Activity hosts Fragments, only the **Fragment** is reported, as iOS does for container view controllers.

- **Modals** (`DialogFragment`, `BottomSheetDialogFragment`) are overlays, never reported under their own name. Opening one re-resolves the visible screen, so the screen underneath may be reported again unless it was reported in the last second. To report the modal, call `trackScreenView` when it opens.
- **Tabs** using the Navigation component, `ViewPager2`, `setMaxLifecycle` or `replace()` are tracked, because they resume the destination Fragment. Legacy `hide()`/`show()` doesn't change Fragment lifecycle, so it isn't tracked. Use [`trackNavigation`](#jetpack-navigation), `setMaxLifecycle` or `trackScreenView` in your tab handler.
- **Jetpack Compose** destinations aren't tracked automatically; the SDK has no Compose dependency. Use [`trackNavigation`](#jetpack-navigation) or `trackScreenView`.

To turn it off, pass `autoTrackScreenViews = false`:

```kotlin
Grovs.configure(this, "your-api-key", useTestEnvironment = false, baseURL = null, autoTrackScreenViews = false)
```

`Grovs.setSDK(false)` stops automatic screen tracking along with all other collection.

### Jetpack Navigation

With Jetpack Navigation (Navigation Compose or route-based graphs), pass your `NavController` to track every destination change. That includes bottom-navigation tabs, navigation rails and Compose destinations the lifecycle tracker can't see:

```kotlin
Grovs.trackNavigation(navController)
```

The name comes from the destination's route, then `android:label`, then its display name. Call it once per `NavController`, for example after setting the graph. Repeat calls are ignored. The SDK holds a weak reference, so it doesn't leak the host Activity or Fragment.

> **Warning:** With **Fragment-based** navigation, `trackNavigation` overlaps the automatic tracker and can report duplicates under different names. Either set `autoTrackScreenViews = false` and rely on `trackNavigation`, or use `trackNavigation` only for Compose and route-based graphs.

## Screen name aliases

Map Activity and Fragment class names to friendlier dashboard names:

```kotlin
Grovs.setScreenAliases(mapOf("MainActivity" to "Home", "CartFragment" to "Shopping Cart"))
```

Aliases sync after authentication. While the SDK is disabled, the latest map is held until it's enabled.

## Sessions

Custom events and screen views carry an analytics session. Brief trips to the background keep the session; returning after more than 30 minutes starts a new one. A campaign resolved for a session stays with that session's events and isn't inherited by the next. Queued events keep their original session and attribution.

## Next steps

- [Track revenue and purchases →](https://docs.grovs.io/docs/sdk/android/revenue)
- [Full API reference →](https://docs.grovs.io/docs/sdk/android/api-reference)
