Jetpack Compose for Wear OS

Learn about Jetpack Compose for Wear OS by building a dedicated app to manage breath-holding times, including a stopwatch to track new records and save them in the collection. In this tutorial, you’ll get to know all the essential components, such as Inputs, Dialogs, Progress Indicators and Page Indicators. You’ll also learn when to use a Vignette and a TimeText. By Lena Stepanova.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Adding a Vignette

Open OneBreathApp.kt and look at the parameters in Scaffold() again.
Set vignette parameter to:

vignette = {
  if (currentBackStackEntry?.destination?.route == Destination.Records.route) {
    Vignette(vignettePosition = VignettePosition.TopAndBottom)
  }
}

This condition means the vignette will be there only for the RecordsListScreen(). A vignette is a UI feature that dims an edge part of the screen. In your case, it’s TopAndBottom, as specified in vignettePosition.

Compare the record list screen with and without the vignette:

See the difference? In the right-hand version, the edges are slightly darker.

TimeText

Another essential Wear OS UI component is TimeText(). Still in OneBreathApp.kt, replace the empty timeText parameter in Scaffold() with:

timeText = {
  if (currentBackStackEntry?.destination?.route == Destination.TrainingDayDetails.route) {  // 1
    TimeText(
      startLinearContent = {  // 2
        Text(
          text = selectedDay?.date.toFormattedString(),
          color = colorPrimary,
          style = textStyle
        )
      },
      startCurvedContent = {  // 3
        curvedText(
          text = selectedDay?.date.toFormattedString(),
          color = colorPrimary,
          style = CurvedTextStyle(textStyle)
        )
      }
    )
  } else TimeText()  // 4
}

Here’s a breakdown of this code:

  1. You only want to show an additional text before the time in the training day details screen. This additional text will hold the date of the record.
  2. TimeText() adapts to round and square watches. For square watches, it uses TimeTextDefaults.timeTextStyle().
  3. For round watches, use CurvedTextStyle().
  4. All the screens except the training day details screen will still show the current time on top.

Build and run. You’ll see the current time on top now. Tap on one of the green chips. In the training day details screen, you’ll also see the date:

training day details screen

Progress Indicator

Wouldn’t it be nice to have something like a clock hand for the stopwatch? You can do that with a Wear OS CircularProgressIndicator.

Go to StopWatchScreen.kt and add the following to the top of the Box(), right above Column():

CircularProgressIndicator(
  progress = duration.toProgress(),
  modifier = Modifier
    .fillMaxSize()
    .padding(all = 1.dp)
)

This indicator will recompose every second and show the current duration of your breath hold. It’s usually recommended to leave a gap for the TimeText() by adding startAngle and endAngle parameters, but in OneBreath you’ll sacrifice those to make the indicator resemble a clock hand.

Build and run the app and start the stopwatch. You’ll see the clock ticking:

determinate progress indicator

This CircularProgressIndicator() is determinate, but you can also use its indeterminate version to show a loading indicator – just leave out the progress parameter. It would look like this:

indeterminate progress indicator

Page Indicator

While you’re still running the app, go to the record list screen and tap on one of the training day items. Here, in the details screen, you can page through all your breath holds on that day. Would be nice to know what page you’re on, right? A HorizontalPageIndicator will help you with that.

Go to TrainingDayDetailsScreen.kt. In SwipeToDismissBox(), add this below val pagerState = rememberPagerState() :

val pageIndicatorState: PageIndicatorState = remember {
  object : PageIndicatorState {
    override val pageOffset: Float
      get() = 0f
    override val selectedPage: Int
      get() = selectedPage
    override val pageCount: Int
      get() = maxPages
  }
}

Here, you create a PageIndicatorState that connects the HorizontalPager() and HorizontalPageIndicator(). The selectedPage is set when you scroll through the pager. The pageCount is the total number of attempts on the training day. The pageOffset is 0f in this case, but you can use it to animate the indicator.

To use it, add HorizontalPageIndicator() right below HorizontalPager:

HorizontalPageIndicator(
  pageIndicatorState = pageIndicatorState,
  selectedColor = colorAccent
)

Build and run. Pick a training day from the list. You’ll see a paging indicator at the bottom:

paging indicator

HorizontalPageIndicator() is an example of a horizontal paging indicator. If you need a vertical indicator, you can use a PositionIndicator(). Check out the official materials for more components.

Where to Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Congratulations! You can now track your breath-holding records with the app you’ve just built. Now, take a deep breath and set a new personal record! :]

If you’re interested in learning more about various Wear OS Composables, check out the official documentation documentation, as well as the Horologist library for advanced date and time pickers. And if you enjoy Wear OS development, don’t miss out on the Creating Tiles for Wear OS video course.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!