Draw sleep timeline graph in Compose

Viktor Mykhailiv on 2025-01-06

Draw sleep timeline graph in Compose

Gemini generated

Custom drawing is useful for when the built-in components just don’t cover exactly what our app needs. This article provides a guide to create a custom sleep timeline graph, similar to those you can find in the Fitbit app.

Screenshot from Fitbit Android app

How do we draw in Compose?

To get started with drawing in Compose we can use drawing modifiers or Canvas composable which gives us DrawScope — a declarative, stateless API to draw shapes and paths without requiring consumers to maintain underlying state. DrawScope implementations are also provided sizing information and transformations are done relative to the local translation.

Note: Jetpack Compose (Android only) and Compose Multiplatform (Desktop, Android, iOS, web) have similar drawing API. Screenshots below are made on Desktop (macOS), but the result is the same on all platforms (check the last screenshot).

What is the sleep timeline?

We can read or write sleep data in Health Connect. Sleep data is displayed as a session, and can be divided into 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.

Math

During the sleep session we can be in the same stage many times at different moments in time. We need to calculate the start and end points relative to the sleep session.

To draw a rect in Compose we need topOffset and size .

Drawing

Let’s build our custom Canvas to draw one stage of the sleep session, e.g. deep.

If we run the project with the previously defined sleep session, we will see 3 rects: 1 grey rect for background and 2 purple rects for deep sleep stage.

To draw all stages of the sleep session (awake, REM, light, and deep) we need to make a few adjustments to draw each stage type as Column component, vertically, by drawing line by line and applying some offset for the next line.

Draw text

To draw a text in Compose, we can typically use the Text composable. However, in our example we are in a DrawScope and we can use the DrawScope.drawText() method.

Drawing text works a bit differently from other drawing commands. Normally, we give the drawing command the size (width and height) to draw the shape/image as. With text, there are a few parameters that control the size of the rendered text, such as font size, font, ligatures, and letter spacing. We need to use a TextMeasurer to get access to the measured size of text, depending on the above factors.

Please find the full example in my repository: https://github.com/vitoksmile/Sleep-timeline-graph