Use Jetpack Compose on Wear OS

Compose for Wear OS is similar to Compose for mobile. However, there are some key differences. This guide will walk you through the similarities and differences.

Compose for Wear OS is part of the Jetpack library, and just like the other Wear Jetpack libraries you use, it helps you write better code faster.

Compatibility

Compose for Wear OS works on watches that support Wear OS 3.0 (API Level 30) and watches that use Wear OS 2.0.

Surfaces

Compose for Wear OS is meant for the overlay surface, which is similar to the app surface on mobile. For more information, see Overlays.

This surface is well suited for complex interactions.

Get started

Before you begin developing with Compose for Wear OS, make sure that you use the correct dependencies.

Dependencies

Dependencies for Compose for Wear OS are somewhat different from mobile Compose dependencies.

What is the same

You can use the mobile version of the UI compose dependency. Runtime, Compiler, and Animation dependencies are the same. Much of the development philosophy and tools available will also be the same.

What is different

Use the WearComposeMaterial version of APIs where possible. While it's technically possible to use the mobile version of Compose Material, it is not optimized for the unique requirements of Wear OS. In addition, mixing Compose Material with Compose Material for Wear OS can result in unexpected behavior. For example, because each library has its own MaterialTheme class, there's the possibility of colors, typography, or shapes being inconsistent if both versions are used.

The following table clears up the dependency differences between Wear OS and Mobile:

Wear OS Dependency

(androidx.wear.*)

Comparison Mobile Dependency

(androidx.*)

androidx.wear.compose:compose-material instead of androidx.compose.material:material1
androidx.wear.compose:compose-navigation instead of androidx.navigation:navigation-compose
androidx.wear.compose:compose-foundation in addition to androidx.compose.foundation:foundation

Here's an example build.gradle file:

// Example project in app/build.gradle file
dependencies {
    // Standard Compose dependencies...

    // Wear specific Compose Dependencies
    implementation "androidx.wear.compose:compose-material:$rootProject.wearVersion"
    implementation "androidx.wear.compose:compose-foundation:$rootProject.wearVersion"

    // For navigation within your app...
    implementation "androidx.wear.compose:compose-navigation:$rootProject.wearVersion"

    // Other dependencies...
}

Composables

The Compose Wear OS Material library offers many of the same composables as mobile, but optimized for the watch. You can also override Material theme and customize your colors, typographies, and shapes. Wear composables include buttons, cards, icons, text, chips, toggle chips, curved text, and time text.

Buttons

A Button is a compact element that allows users to take actions and make choices, with a single tap.

Use buttons in the same way that you would for mobile, as shown in the following example:

Button(
    modifier = Modifier.size(ButtonDefaults.LargeButtonSize),
    onClick = { ... },
    enabled = enabledState
) {
    // ...
}

Although much of the code for buttons is the same as mobile, the results are optimized for Wear OS.

For more information, see Buttons.

Cards

A Card takes content and actions about a single subject.

There are two main types of cards, title cards and app cards. For more information, see Cards.

The following is an example of an app card:


AppCard(
    appImage = {
        Image(painter = painterResource(id = R.drawable.ic_message), ... )
    },
)

The following code snippet shows how you would set text to appear on the card:

AppCard(
    appImage = {
        Image(painter = painterResource(id = R.drawable.ic_message), ... )
    },
    appName = { Text("Messages") },
    time = { Text("12m") },
    title = { Text("Kim Green") },
    onClick = { ... },
)

Last, set the content text as shown in the following example:

AppCard(
    appImage = {
        Image(painter = painterResource(id = R.drawable.ic_message), ... )
    },
    appName = { Text("Messages") },
    time = { Text("12m") },
    title = { Text("Kim Green") },
    onClick = { ... },
    content = {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text("On my way!")
        }
    },
)

Chips

Chips are a composable that is available only on Compose for Wear OS. Chips are not available on mobile. Use a Chip for a single tap action. For more information, see Chips.

Create chips with a label and icon, as shown in the following example:

Chip(
    modifier = Modifier.align(Alignment.CenterHorizontally),
    onClick = { ... },
    enabled = enabledState,
    label = {
        Text(
            text = "1 minute Yoga",
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
    },
    icon = {
        Icon(
            painter = painterResource(id = R.drawable.ic_yoga),
            contentDescription = "yoga icon",
            modifier = Modifier
                .size(24.dp)
                .wrapContentSize(align = Alignment.Center),
        )
    },
)

Toggle chips

Toggle chips are similar to chips but can also include a radio button, toggle or checkbox. The SplitToggleChip differs from the ToggleChip by having two tappable areas. For more information, see Toggle chips.

Create a toggle chip with a label marked Sound, as shown in the example below.

ToggleChip(
  modifier = Modifier.height(32.dp),
  checked = checkedState,
  onCheckedChange = { ... },
  label = {
    Text(
      text = "Sound",
      maxLines = 1,
      overflow = TextOverflow.Ellipsis
    )
  }
)

Curved text and time text

Curved text is optimized for a round screen. This is very important for watches with a round face. TimeText uses CurvedText.

Create a mutable state value to set placeholder text before you include the time, as shown in the following example:

var textBeforeTime by rememberSaveable { mutableStateOf("ETA 99 hours") }

TimeText(
    leadingCurvedContent = {
        BasicCurvedText(
            text = textBeforeTime,
            style = TimeTextDefaults.timeCurvedTextStyle()
        )
    },
)

Support non-circular devices using the leadingLinearContent argument and a regular text composable, as shown in the following example:

var textBeforeTime by rememberSaveable { mutableStateOf("ETA 99 hours") }

TimeText(
    leadingCurvedContent = {
        BasicCurvedText(
            text = textBeforeTime,
            style = TimeTextDefaults.timeCurvedTextStyle()
        )
    },
    leadingLinearContent =  {
        Text(
            text = textBeforeTime,
            style = TimeTextDefaults.timeTextStyle()
        )
    },
)

Lists

Lists contain a continuous, vertical set of elements. The List class on Wear OS is different from mobile. ScalingLazyColumn is built specifically for Wear and offers scaling at the top and bottom of the list, as well as transparency. This allows the content to shrink and fade at the top and button of the screen to help users see what to focus on.

Use scalingLazyListState as shown in the following example:

val scalingLazyListState: ScalingLazyListState =
    rememberScalingLazyListState()

ScalingLazyColumn(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(6.dp),
    state = scalingLazyListState,
) {
    items(messageList.size) { index ->
        val message = messageList[index]
        Card(...) { ... }
    }

    item {
        Card(...) { ... }
    }
}

For more information, see Lists.

Swipe to dismiss

Wear has its own version of Box, SwipeToDismissBox. This adds support for the swipe-to-dismiss gesture, which is similar to the back button on mobile.

SwipeToDismissBox is a composable that can be dismissed by swiping right.

To use SwipeToDismiss, you must first create a state. The state contains information on swipe direction, whether an animation is running, the current value and the target, and more. The following example shows how to design a simple swipe to dismiss action:

val state = rememberSwipeToDismissBoxState()
SwipeToDismissBox(
    state = state,
) { isBackground ->
    if (isBackground) {
        Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.secondaryVariant))
    } else {
        Column(
            modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ) {
            Text("Swipe to dismiss", color = MaterialTheme.colors.onPrimary)
        }
    }
}

For more information, see Swipe to dismiss.

Scaffold

Scaffold provides a layout structure to help you position your components in common patterns. It supports Wear-specific layouts with top-level components like Time, Vignette, and the position indicator.

Scaffold code is similar to what you would write for mobile, as shown in the following example:

val scalingLazyListState: ScalingLazyListState =
rememberScalingLazyListState()

MaterialTheme {
    Scaffold(
        modifier = Modifier.fillMaxSize(),
        timeText = { ... },
        vignette = { ... },
        positionIndicator = { ... }
    ) {
        // Content goes here
    }
}

The following example shows how to set the position indicator:

positionIndicator = {
    if (scalingLazyListState.isScrollInProgress) {
        PositionIndicator(scalingLazyListState =
            scalingLazyListState)
    }
}

Compose for Wear OS also includes a navigation composable, the SwipeDismissableNavHost function. This works just like NavHost on mobile with back navigation triggered by the swipe-to-dismiss gesture.

First create the MaterialTheme and then the Scaffold. Add a navigation controller and make sure that you are using the Wear-specific API, the SwipeDismissableNavHost as shown in the following example:

MaterialTheme {
    Scaffold( ... ) {
        val navController = rememberSwipeDismissableNavController()

        SwipeDismissableNavHost(
            navController = navController,
            startDestination = Screen.MainScreen.route
        ) {
            composable(route = Screen.MainScreen.route) {
                MyListScreen( ... )
            }
            composable(route = Screen.DetailsScreen.route + "/{$ID}", ...) {
                MyDetailScreen( ... )
            }
        }
    }
}

Feedback

Try out Compose for Wear OS and let us know what you think using the issue tracker or join the #compose-wear channel on Slack and let us know there.