# Track sessions with the native mobile trackers
> Track user sessions with configurable timeouts, session callbacks, and cross-navigation support in mobile applications.
> Source: https://docs.snowplow.io/docs/sources/mobile-trackers/tracking-events/session-tracking/
> Documentation index: https://docs.snowplow.io/llms.txt

Session tracking captures the session which helps to keep track of the user activity in the app.

Client session tracking is enabled by default. It can be set through the `TrackerConfiguration` as explained below.

**iOS:**

```swift
let trackerConfig = TrackerConfiguration()
    .sessionContext(true)
```

**Android (Kotlin):**

```kotlin
val trackerConfig = TrackerConfiguration("appId")
    .sessionContext(true)
```

**Android (Java):**

```java
TrackerConfiguration trackerConfig = new TrackerConfiguration("appId")
    .sessionContext(true);
```

***

When enabled, the tracker appends a [`client_session` entity](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2) to each event it sends and it maintains this session information as long as the application is installed on the device.

Sessions correspond to tracked user activity. User information is set via the [session entity](/docs/events/identifiers/#session-entity).

A session expires when no tracking events have occurred for the amount of time defined in a timeout (by default 30 minutes). The session timeout check is executed for each event tracked. If the gap between two consecutive events is longer than the timeout the session is renewed. There are two timeouts since a session can timeout in the foreground (while the app is visible) or in the background (when the app has been suspended, but not closed).

The timeouts for the session can be configured in the `SessionConfiguration` like in the example below:

**iOS:**

```swift
let sessionConfig = SessionConfiguration(
    foregroundTimeout: Measurement(value: 360, unit: .seconds),
    backgroundTimeout: Measurement(value: 360, unit: .seconds)
)
Snowplow.createTracker(
    namespace: "appTracker",
    network: networkConfig,
    configurations: [trackerConfig, sessionConfig]
)
```

**Android (Kotlin):**

```kotlin
val sessionConfig = SessionConfiguration(
    TimeMeasure(6, TimeUnit.SECONDS),
    TimeMeasure(30, TimeUnit.SECONDS)
)
Snowplow.createTracker(applicationContext, namespace, networkConfig, sessionConfig)
```

**Android (Java):**

```java
SessionConfiguration sessionConfig = new SessionConfiguration(
    new TimeMeasure(6, TimeUnit.SECONDS),
    new TimeMeasure(30, TimeUnit.SECONDS)
);
Snowplow.createTracker(getApplicationContext(), namespace, networkConfig, sessionConfig);
```

***

The lifecycle events (`Foreground` and `Background` events) have a role in the session expiration. The lifecycle events can be enabled as explained in [App Lifecycle Tracking](/docs/sources/mobile-trackers/tracking-events/lifecycle-tracking/). Once enabled they will be fired automatically when the app moves from foreground state to background state and vice versa.

When the app moves from foreground to background, the `Background` event is fired. If session tracking is enabled, the session entity will be attached to the event checking the session expiration using the foreground timeout. When the app moves from background to foreground, the `Foreground` event is fired. If session tracking is enabled, the session entity will be attached to the event checking the session expiration using the background timeout.

For instance, with this configuration:

**iOS:**

```swift
SessionConfiguration(
    foregroundTimeout: Measurement(value: 360, unit: .seconds),
    backgroundTimeout: Measurement(value: 15, unit: .seconds)
)
```

**Android (Kotlin):**

```kotlin
val sessionConfig = SessionConfiguration(
    TimeMeasure(360, TimeUnit.SECONDS),
    TimeMeasure(15, TimeUnit.SECONDS)
)
```

**Android (Java):**

```java
SessionConfiguration sessionConfig = new SessionConfiguration(
    new TimeMeasure(360, TimeUnit.SECONDS),
    new TimeMeasure(15, TimeUnit.SECONDS)
);
```

***

the session would expire if the app is in background for more than 15 seconds, like in this example:

```text
time: 0s - ScreenView event - foreground timeout session check - session 1
time: 3s - Background event - foreground timeout session check (3 < 360) - session 1
time: 30s - Foreground event - background timeout session check (30 > 15) - session 2
```

In the above example, the `Foreground` event triggers a new session because the time spent in background (without tracked events) is bigger than the background timeout for the session.

## Session callback

> **Info:** This feature is available since v3.1.

The tracker allows the configuration of a callback to inform the app every time a new session is created (in correspondence of a session timeout check). This can be configured in the `SessionConfiguration` and it provides the `SessionState` where all the info already tracked in the session can be accessed.

Below is an example of where the session callback is used to print out the values of session every time a new session is generated by the tracker:

**iOS:**

```swift
...
let sessionConfig = SessionConfiguration()
    .onSessionStateUpdate { session in
        print("SessionState: id: \(session.sessionId) - index: \(session.sessionIndex) - userID: \(session.userId) - firstEventID: \(session.firstEventId)")
    }
...
let tracker = Snowplow.createTracker(namespace: kNamespace, network: networkConfig, configurations: [sessionConfig])
```

**Android (Kotlin):**

```kotlin
val sessionConfig = SessionConfiguration(
    TimeMeasure(6, TimeUnit.SECONDS),
    TimeMeasure(30, TimeUnit.SECONDS)
)
    .onSessionUpdate { state ->
        log(
            "Session: " + state.getSessionId()
                .toString() + "\r\nprevious: " + state.getPreviousSessionId()
                .toString() + "\r\neventId: " + state.getFirstEventId()
                .toString() + "\r\nindex: " + state.getSessionIndex()
                .toString() + "\r\nuserId: " + state.getUserId()
        )
    }

Snowplow.createTracker(applicationContext, namespace, networkConfig, sessionConfig)
```

**Android (Java):**

```java
...
SessionConfiguration sessionConfig = new SessionConfiguration(
    new TimeMeasure(6, TimeUnit.SECONDS),
    new TimeMeasure(30, TimeUnit.SECONDS)
)
    .onSessionUpdate(state -> log(
        "Session: " + state.getSessionId()
                + "\r\nprevious: " + state.getPreviousSessionId()
                + "\r\neventId: " + state.getFirstEventId()
                + "\r\nindex: " + state.getSessionIndex()
                + "\r\nuserId: " + state.getUserId()
    ));

...
Snowplow.createTracker(getApplicationContext(), namespace, networkConfig, sessionConfig);
```

***

## Decorating outgoing links using cross-navigation tracking

> **Note:** This feature was introduced in version 6.0.0 of the iOS and Android trackers.

The `decorateLink` function is part of Snowplow's [cross-navigation solution](/docs/events/cross-navigation/) for tracking the movement of users across different apps and platforms.

Choose which parameters to include using a `CrossDeviceParameterConfiguration` object. The `domainUserId` and `timestamp` are always included automatically. Use booleans to select which properties to include in the link decoration. The tracker will derive the required values from its configuration.

This table shows the options:

| Property         | Description                      | Type    | Included by default? | Value used                         |
| ---------------- | -------------------------------- | ------- | -------------------- | ---------------------------------- |
| `sessionId`      | Current session UUID identifier  | Boolean | ✅                    | `SessionController.sessionId`      |
| `subjectUserId`  | Custom business user identifier  | Boolean | ❌                    | `SubjectController.userId`         |
| `sourceId`       | Application identifier           | Boolean | ✅                    | `TrackerConfiguration.appId`       |
| `sourcePlatform` | Platform of the current device   | Boolean | ❌                    | `TrackerController.devicePlatform` |
| `reason`         | Custom information or identifier | String  | ❌                    | Custom string provided by you      |

The `subjectUserId` property is not included by default, in case it contains personal data.

**iOS:**

```swift
let link = URL(string: "https://example.com")!
let decoratedLink = Snowplow.defaultTracker()?.decorateLink(
  link,
  // optional configuration for which information to be added to the link
  extendedParameters: CrossDeviceParameterConfiguration(sessionId: false, subjectUserId: true)
)
```

**Android (Kotlin):**

```kotlin
val link = Uri.parse("http://example.com")
val decoratedLink = Snowplow.defaultTracker.decorateLink(
  link,
  // optional configuration for which information to be added to the link
  CrossDeviceParameterConfiguration(sessionId = false, subjectUserId = true)
)
```

**Android (Java):**

```java
Uri link = Uri.parse("http://example.com");
Uri decoratedLink = Snowplow.getDefaultTracker().decorateLink(link, new CrossDeviceParameterConfiguration(
    false, // sessionId
    true, // subjectUserId
    false, // sourceId
    false, // sourcePlatform
    null // reason
));
```

***

## Session behavior on app restarts

By default, each time the mobile app restarts (or when a new tracker instance is created), a new session is started regardless of whether the previous session timed out or not.

Since version 6.2 of the iOS and Android trackers, there is an option to continue the previously persisted session when the app restarts. The previous session is only continued in case the app is restarted within the configured session timeout interval. The option can be configured using `SessionConfiguration`:

**iOS:**

```swift
let sessionConfig = SessionConfiguration()
    .continueSessionOnRestart(true)
```

**Android (Kotlin):**

```kotlin
val sessionConfig = SessionConfiguration()
    .continueSessionOnRestart(true)
```

**Android (Java):**

```java
SessionConfiguration trackerConfig = new SessionConfiguration()
    .continueSessionOnRestart(true);
```

***

With the option enabled, every session update is persisted to UserDefaults or shared preferences storage. In case it's disabled, only session changes are persisted to UserDefaults. This means that there is more overhead when the option is enabled.
