Sessions

In Health Connect a session is a time interval during which a user performs an activity. SleepSessionRecord and ExerciseSessionRecord data types are both sessions.

Sessions allow users to measure time-based performance over a period of time, such as continuous heart rate or location data.

ExerciseSessionRecord sessions include a variety of activities, from running to badminton.

SleepSessionRecord sessions contain data that records sleep stages, such as AWAKE, SLEEPING and DEEP.

Subtype data is data that "belongs" to a session and is only meaningful when it's read with a parent session, e.g. sleep stage, exercise segment.

Associated data, is Record data can be read separately or alongside other sessions, e.g. steps, heart rate.

General guidance

Here are some best practice guidelines on how to work with sessions in Health Connect.

  • Sessions should be used to add data from a specific workout or activity, or for sleep:
suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = Instant.parse("2022-05-10T10:00:00.000Z"),
                startZoneOffset = ZoneOffset.of("-08:00"),
                endTime = Instant.parse("2022-05-10T11:00:00.000Z"),
                endZoneOffset = ZoneOffset.of("-08:00"),
                exerciseType = ExerciseSessionRecord.ExerciseType.WALKING,
                title = "My Walk"
            ),
            StepsRecord(
                startTime = Instant.parse("2022-05-10T10:00:00.000Z"),
                startZoneOffset = ZoneOffset.of("-08:00"),
                endTime = Instant.parse("2022-05-10T10:30:00.000Z"),
                endZoneOffset = ZoneOffset.of("-08:00"),
                count = 2800
            ),
StepsRecord(
                startTime = Instant.parse("2022-05-10T10:30:00.000Z"),
                startZoneOffset = ZoneOffset.of("-08:00"),
                endTime = Instant.parse("2022-05-10T11:00:00.000Z"),
                endZoneOffset = ZoneOffset.of("-08:00"),
                count = 3200
            ),  
        )
    )
}
  • Sessions should not be used for general measurements, such as daily step counts.
  • Subtype data doesn't contain a UID, but associated data has distinct UIDs.
  • Subtype data needs to be aligned in a session with sequential timestamps that don't overlap. Gaps are allowed, however.
  • Sessions are useful if the user wants data to be associated with (and tracked as part of) a session, rather than recorded continuously.

Sleep sessions

You can read or write sleep data in Health Connect. Sleep data is displayed as a session, and can be divided into 8 distinct sleep stages:

  • UNKNOWN: Unspecified or unknown if the user is sleeping.
  • AWAKE: The user is awake within a sleep cycle, not during the day.
  • SLEEPING: Generic or non-granular sleep description.
  • OUT_OF_BED: The user gets out of bed in the middle of a sleep session.
  • AWAKE_IN_BED: The user is awake in bed.
  • LIGHT: The user is in a light sleep cycle.
  • DEEP: The user is in a deep sleep cycle.
  • REM: The user is in a REM sleep cycle.

These values represent the type of sleep a user experiences within a time range. Writing sleep stages is optional, but recommended if available.

Write sleep sessions with or without sleep stages

The SleepSessionRecord data type has two parts:

  1. The overall session, spanning the entire duration of sleep.
  2. Individual stages during the sleep session such as light sleep or deep sleep.

Here's how you insert a sleep session without stages:

      SleepSessionRecord(
        title = "weekend sleep",
        startTime = startTime,
        endTime = endTime,
        startZoneOffset = ZoneOffset.UTC,
        endZoneOffset = ZoneOffset.UTC,
      )

Here's how to add stages that cover the entire period of a sleep session:

val stages = listOf(
    SleepSessionRecord.Stage(
        startTime = START_TIME
        endTime = END_TIME,
        stage = SleepSessionRecord.STAGE_TYPE_SLEEPING,
    )
)

SleepSessionRecord(
        title = "weekend sleep",
        startTime = START_TIME,
        endTime = END_TIME,
        startZoneOffset = START_ZONE_OFFSET,
        endZoneOffset = END_ZONE_OFFSET,
        stages = stages,
      )
  }

Read a sleep session

For every sleep session returned, you should check whether sleep stage data is also present:

suspend fun readSleepSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                SleepSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (sleepRecord in response.records) {
        // Retrieve relevant sleep stages from each sleep record
        val sleepStages = sleepRecord.stages
    }
}

Delete a sleep session

This is how to delete a session. For this example, we've used a sleep session:

suspend fun deleteSleepSession(
    healthConnectClient: HealthConnectClient,
    sleepRecord: SleepSessionRecord,
) {
    val timeRangeFilter = TimeRangeFilter.between(sleepRecord.startTime, sleepRecord.endTime)
    healthConnectClient.deleteRecords(SleepSessionRecord::class, timeRangeFilter)
}

Exercise sessions

Exercise sessions can include anything from running to badminton.

Write exercise sessions

This is how to build an insertion request that includes a session:

suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                title = "My Run"
            ),
            DistanceRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                distance = 5000.meters
            ),
            // ... other records
        )
    )
}

Note how in the previous example, a record is added for Distance, which spans the entire duration of the session, but data can be added with different granularity.

If the app had measured distance regularly during the run, then another approach would be to include many Distance records, each representing the distance covered in a portion of the run.

Read an exercise session

Here's an example of how to read an exercise session:

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                ExerciseSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (exerciseRecord in response.records) {
        // Process each exercise record
        // Optionally pull in with other data sources of the same time range.
        val distanceRecord =
            healthConnectClient
                .readRecords(
                    ReadRecordsRequest(
                        DistanceRecord::class,
                        timeRangeFilter =
                            TimeRangeFilter.between(
                                exerciseRecord.startTime,
                                exerciseRecord.endTime
                            )
                    )
                )
                .records
    }
}

Write subtype data

Sessions can also be comprised of non-mandatory subtype data, that enrich the session with additional information.

For example, exercise sessions can include the ExerciseSegment, ExerciseLap and ExerciseRoute classes:

val segments = listOf(
  ExerciseSegment(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    segmentType = ActivitySegmentType.BENCH_PRESS,
    repetitions = 373
  )
)

val laps = listOf(
  ExerciseLap(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    length = 0.meters
  )
)

ExerciseSessionRecord(
  exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
  startZoneOffset = ZoneOffset.UTC,
  endZoneOffset = ZoneOffset.UTC,
  segments = segments,
  laps = laps,
  route = route
)

Delete an exercise session

There are two ways to delete an exercise session:

  1. By time range.
  2. By UID.

Here's how you delete subtype data according to time range:

suspend fun deleteExerciseSession(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    val timeRangeFilter = TimeRangeFilter.between(sleepRecord.startTime, sleepRecord.endTime)
    healthConnectClient.deleteRecords(SleepSessionRecord::class, timeRangeFilter)
    // delete the associated distance record
    healthConnectClient.deleteRecords(DistanceRecord::class, timeRangeFilter)
}

You can also delete subtype data by UID. Doing so, however, will only delete the exercise session, not the associated data:

suspend fun deleteExerciseSession(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    healthConnectClient.deleteRecords(
        ExerciseSessionRecord::class,
        recordIdsList = listOf(exerciseRecord.metadata.id),
        clientRecordIdsList = emptyList()
    )
}