1. Introduction
Compose for Wear OS lets you translate the knowledge you've learned building apps with Jetpack Compose to wearable devices.
With built-in support for Material Design, Compose for Wear OS simplifies and accelerates UI development and helps you create beautiful apps with less code.
For this codelab, we expect you to have some knowledge of Compose, but you certainly don't need to be an expert.
You will create several Wear specific composables (both simple and complex), and, by the end, you can start writing your own apps for Wear OS. Let's get started!
What you will learn
- Similarities/differences between your previous experience with Compose
- Simple composables and how they work on Wear OS
- Wear OS specific composables
- Wear OS's
LazyColumn
(TransformingLazyColumn
) - Wear OS's version of the
Scaffold
What you will build
You'll build a simple app that displays a scrollable list of composables optimized for Wear OS.
Because you will be using AppScaffold and ScreenScaffold,you'll also get a curved text time at the top and a scrolling indicator tied to the side of the device.
Here's what it will look like when you are finished with the code lab:
Prerequisites
- Basic understanding of Android development
- Basic understanding of Kotlin
- Basic knowledge of Compose
2. Getting Set Up
In this step, you will set up your environment and download the starter project.
What you will need
- Latest stable version of Android Studio
- Wear OS device or emulator (New to this? Here's how to set it up.)
Download code
If you have git installed, you can simply run the command below to clone the code from this repo. To check whether git is installed, type git --version
in the terminal or command line and verify that it executes correctly.
git clone https://github.com/android/codelab-compose-for-wear-os.git cd codelab-compose-for-wear-os
If you do not have git, you can click the following button to download all the code for this codelab:
At any time you can run either module in Android Studio by changing the run configuration in the toolbar.
Open project in Android Studio
- On the Welcome to Android Studio window select
Open...
- Select the folder
[Download Location]
- When Android Studio has imported the project, test that you can run the
start
andfinished
modules on a Wear OS emulator or physical device. - The
start
module should look like the screenshot below. It's where you will be doing all your work.
Explore the start code
build.gradle
contains a basic app configuration. It includes the dependencies necessary to create a Composable Wear OS App. We'll discuss what's similar and different between Jetpack Compose and the Wear OS version.main > AndroidManifest.xml
includes the elements necessary to create a Wear OS application. This is the same as a non-Compose app and similar to a mobile app, so we won't review this.main > theme/
folder contains theColor
,Type
, andTheme
files used by Compose for the theme.main > MainActivity.kt
contains boilerplate for creating an app with Compose. It also contains the top-level composables (like theScaffold
andTransformingLazyColumn
) for our app.main > ReusableComponents.kt
contains functions for most of the Wear specific composables we'll create. We will do a lot of our work in this file.
3. Review the dependencies
Most of the Wear related dependency changes you make will be at the top architectural layers (highlighted in red below).
That means many of the dependencies you already use with Jetpack Compose don't change when targeting Wear OS. For example, the UI, Runtime, Compiler, and Animation dependencies will remain the same.
However, you will need to use the proper Wear OS Material, Foundation, and Navigation libraries which are different from the libraries you have used before.
Below is a comparison to help clarify the differences:
Wear OS Dependency(androidx.wear.*) | Comparison | Standard Dependency(androidx.*) |
instead of | androidx.compose.material:material ₁ | |
instead of | androidx.navigation:navigation-compose | |
in addition to | androidx.compose.foundation:foundation | |
in addition to | androidx.compose.ui:ui-tooling-preview |
1. Developers can continue to use other material related libraries like material ripple and material icons extended with the Wear Compose Material library. Alternatively to compose-material3 version, you can use androidx.wear.compose:compose-material but you shouldn't be mixing Material 3 and Material 2.5 in the same app. We advise you to use material3 as it supports Material 3 Expressive design.
Open the build.gradle
, search for "TODO: Review Dependencies
" in your start
module. (This step is just to review the dependencies, you will not be adding any code.)
start/build.gradle:
dependencies {
val composeBom = platform(libs.androidx.compose.bom)
// General compose dependencies
implementation(composeBom)
implementation(libs.androidx.activity.compose)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.material.icons.extended)
// Compose for Wear OS Dependencies
implementation(libs.wear.compose.material)
// Foundation is additive, so you can use the mobile version in your Wear OS app.
implementation(libs.wear.compose.foundation)
// Compose preview annotations for Wear OS.
implementation(libs.androidx.compose.ui.tooling)
implementation(libs.horologist.compose.layout)
coreLibraryDesugaring(libs.desugar.jdk.libs)
debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
debugImplementation(composeBom)
}
You should recognize many of the general Compose dependencies, so we won't cover those.
Let's move to the Wear OS dependencies.
Just as outlined earlier, only the Wear OS specific version of material
(androidx.wear.compose:compose-material3
) is included. That is, you will not see or include androidx.compose.material:material3
in your project.
It's important to call out that you can use other material libraries with Wear Material. We actually do that in this codelab by including androidx.compose.material:material-icons-extended
.
Finally, we include the Wear foundation
library for Compose (androidx.wear.compose:compose-foundation
) . This is additive, so you can use it with the standard foundation
you've used before. In fact, you probably already recognized we included it in the general compose dependencies!
Ok, now that we understand the dependencies, let's have a look at the main app.
4. Review MainActivity
We will do all our work in the
start
module, so make sure every file you open is in there.
Let's start by opening MainActivity
in the start
module.
This is a pretty simple class that extends ComponentActivity
and uses setContent { WearApp() }
to create the UI.
From your previous knowledge of Compose, this should look familiar to you. We are just setting up the UI.
Scroll down to the WearApp()
composable function. Before we talk about the code itself, you should see a bunch of TODOs scattered throughout the code. These each represent steps in this codelab. You can ignore them for now.
It should look something like this:
Code in fun WearApp():
WearAppTheme {
/* *************************** Part 4: Wear OS Scaffold *************************** */
// TODO (Start): Create a AppScaffold (Wear Version)
// TODO: Swap to TransformingLazyColumnState
val listState = rememberLazyListState()
/* *************************** Part 4: Wear OS Scaffold *************************** */
// TODO (Start): Create a ScreenScaffold (Wear Version)
/* *************************** Part 3: ScalingLazyColumn *************************** */
// TODO: Swap a TransformingLazyColumn (Wear's version of LazyColumn)
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
state = listState,
) {
// TODO: Remove item; for beginning only.
item { StartOnlyTextComposables() }
/* ******************* Part 1: Simple composables ******************* */
item { ButtonExample() }
item { TextExample() }
item { CardExample() }
/* ********************* Part 2: Wear unique composables ********************* */
item { ChipExample() }
item { ToggleChipExample() }
}
// TODO (End): Create a ScreenScaffold (Wear Version)
// TODO (End): Create a AppScaffold (Wear Version)
}
We start by setting the theme, WearAppTheme { }
. This is exactly the same way you've written it before, that is, you set a MaterialTheme
with colors, typography, and shapes.
However, in the case of Wear OS, we generally recommend using the default Material Wear shapes which are optimized for round devices, so if you dive into our theme/Theme.kt
, you can see we don't override shapes.
If you wish, you can open the theme/Theme.kt
to explore it further, but, again, it's the same as on the phone.
We then create a LazyColumn
which is used to produce a vertically scrolling list for a bunch of items (just like you did before).
Code:
item { StartOnlyTextComposables() }
/* ******************* Part 1: Simple composables ******************* */
item { ButtonExample() }
item { TextExample() }
item { CardExample() }
/* ********************* Part 2: Wear unique composables ********************* */
item { ChipExample() }
item { ToggleChipExample() }
For the items themselves, only StartOnlyTextComposables()
produces any UI. (We will populate the rest throughout the code lab.)
These functions are actually in the ReusableComponents.kt
file, which we will visit in the next section.
Let's get started with Compose for Wear OS!
5. Add Simple Composables
We'll start with three composables (Button
, Text
, and Card
) that you are probably already familiar with.
First, we are going to remove the hello world composable.
Search for "TODO: Remove item
" and erase both the comment and the line below it:
Step 1
// TODO: Remove item; for beginning only.
item { StartOnlyTextComposables() }
Next, let's add our first composable.
Create a Icon Button composable
Open ReusableComponents.kt
in the start
module and search for "TODO: Create a Icon Button Composable
" and replace the current composable method with this code.
Step 2
// TODO: Create a Icon Button Composable
@Composable
fun IconButtonExample(
modifier: Modifier = Modifier,
) {
FilledIconButton(
onClick = { /* ... */ },
modifier = modifier,
) {
Icon(
imageVector = Icons.Rounded.Phone,
contentDescription = "triggers phone action",
)
}
}
The IconButtonExample()
composable function (where this code exists) will now generate a centered button.
Let's walk through the code.
We use a FilledIconButton
which is a circular, icon-only button with a colored background and a contrasting content color. It offers a single slot to take an icon or image.
After that, we set the click event to an empty lambda. In our case, these composables are just for a demo, so we won't need that. However, in a real app, we'd communicate with a, for example, ViewModel to perform business logic.
Then we set an Icon inside our button. This code is the same as you have seen for an Icon
before. We are also getting our icon from the androidx.compose.material:material-icons-extended
library.
Finally, you don't need to worry about centering the button at this stage since it will be fixed later automatically when migrating from LazyColumn
to TransformingLazyColumn.
If you run the app, you should get something like this, don't worry if the button is not centered for now:
This is code you have probably already written before (which is great). The difference is now you get a button optimized for Wear OS.
Pretty straightforward, let's look at another one.
Create a Text composable
In ReusableComponents.kt
, search for "TODO: Create a Text Composable
" and replace the current composable method with this code.
Step 3
// TODO: Create a Text Composable
@Composable
fun TextExample(modifier: Modifier = Modifier) {
ListHeader{
Text(
modifier = modifier
.fillMaxWidth(),
textAlign = TextAlign.Center,
text = stringResource(R.string.hello_compose_codelab),
)
}
}
We create the Text
composable, set its modifier, align the text, set a color, and finally set the text itself from a String resource. Since we are going to add a List later, we are wrapping this text into a ListHeader
so that the content will be start and end padded.
Text composables should look very familiar to Compose developers and the code is actually identical to the code you've used before.
Let's see what it looks like:
The TextExample()
composable function (where we placed our code) now produces a Text composable in our main material color. The string is pulled from our res/values/strings.xml
file.
So far, so good. Let's look at our last similar composable, Card
.
Create a Card composable
In ReusableComponents.kt
, search for "TODO: Create a Card
" and replace the current composable method with this code.
Step 4
// TODO: Create a Card (specifically, an AppCard) Composable
@Composable
fun CardExample(
modifier: Modifier = Modifier,
iconModifier: Modifier = Modifier
) {
AppCard(
modifier = modifier,
appImage = {
Icon(
imageVector = Icons.AutoMirrored.Rounded.Message,
contentDescription = "triggers open message action",
modifier = iconModifier
)
},
appName = { Text("Messages") },
time = { Text("12m") },
title = { Text("Kim Green") },
onClick = { /* ... */ }
) {
Text("On my way!")
}
}
Wear is a little different in that we have two major cards, AppCard
and TitleCard
.
In our case, we want an Icon
in our card, so we are going to use AppCard
. (TitleCard
has less slots, see Cards guide for more information.)
We create the AppCard
composable, set its modifier, add an Icon
, add several Text
composable parameters (each for a different space on the card), and finally set the main content text at the end.
Let's see what it looks like, don't worry about adding additional bottom padding as this will be fixed when migrating from LazyColumn
to TransformingLazyColumn
:
At this point, you probably recognize that for these composables the Compose code is virtually the same as you've used before which is great, right? You get to reuse all that knowledge you've already gained!
Ok, let's look at some new composables.
6. Add Wear Unique Composables
For this section, we will explore the Chip
and ToggleChip
composables.
Create a Chip composable
Chips are meant to be a quick, one tap action, which makes especially good sense for a Wear device with limited screen real estate.
You can implement a Chip by using a Button
. Here's a couple variations of the Button
composable function to give you an idea of what you can create:
Let's write some code.
In ReusableComponents.kt
, search for "TODO: Create a Chip
" and replace the current composable method with this code.
Step 5
// TODO: Create a Chip Composable
@Composable
fun ChipExample(
modifier: Modifier = Modifier,
) {
Button(
modifier = modifier,
onClick = { /* ... */ },
icon = {
Icon(
imageVector = Icons.Rounded.SelfImprovement,
contentDescription = "triggers meditation action",
)
},
) {
Text(
text = "5 minute Meditation",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
The Button
composable uses many of the same parameters as you are used to with other composables (modifier and onClick
), so we don't need to review those.
It also takes an icon and a content Slot for composable body content displayed on the Button (which we create a Text
composable for).
The Icon
code should look exactly the same as the code you saw in other composables, but for this one, we are pulling the Self Improvement
icon from the androidx.compose.material:material-icons-extended
library.
Let's see what it looks like (remember to scroll down), don't worry about adding additional bottom padding as this will be fixed when migrating from LazyColumn
to TransformingLazyColumn
:
Ok, let's look at a variation on Button
, the SwitchButton
composable.
Create a SwitchChip composable
SwitchButton
is just like a Button
but allows the user to interact with a switch.
In ReusableComponents.kt
, search for "TODO: Create a SwitchChip
" and replace the current composable method with this code.
Step 6
// TODO: Create a Switch Chip Composable
@Composable
fun SwitchChipExample(modifier: Modifier = Modifier) {
var checked by remember { mutableStateOf(true) }
SwitchButton(
modifier = modifier.fillMaxWidth(),
label = {
Text(
"Sound",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.semantics {
this.contentDescription = if (checked) "On" else "Off"
},
)
},
checked = checked,
onCheckedChange = { checked = it },
enabled = true,
)
}
Now the SwitchChipExample()
composable function (where this code exists) generates a SwitchChip
using a switch toggle (instead of a checkbox or radio button).
First, we create a MutableState
. We haven't been doing this in the other functions, because we are mainly doing UI demos so you can see what Wear offers.
In a normal app, you would probably want to pass in the checked state and the lambda for handling the tap, so the composable can be stateless ( more info here).
In our case, we are just keeping it simple to show off what the SwitchChip
looks like in action with a working toggle (even though we don't do anything with the state).
Next, we set the checked state, and the toggle control to give us the switch we want.
We then create a lambda for changing the state and finally set the label with a Text
composable (and some basic parameters).
Let's see what it looks like:
Ok, now you've seen a lot of Wear OS specific composables and, as stated before, most of the code is almost the same as what you've written before.
Let's look at something a little more advanced.
7. Migrate to TransformingScalingLazyColumn
You probably have used LazyColumn
in your mobile apps to produce a vertically scrolling list.
Because a round device is smaller at the top and bottom, there is less space to show items. Therefore, Wear OS has its own version of LazyColumn
to better support those round devices.
TransformingLazyColumn
extends LazyColumn
to support both scaling and transparency at the top and bottom of the screen to make the content more readable to the user.
Here's a demo:
Notice how as the item gets near the center it scales up to its full size and then as it moves away it scales back down (along with getting more transparent).
Here is a more concrete example from an app:
We've found this really helps with readability.
Now that you've seen TransformingLazyColumn
in action, let's get started converting our LazyColumn
.
Convert to a TransformingLazyColumnState
In MainActivity.kt
, search for "TODO: Swap to TransformingLazyColumnState
" and replace that comment and line below with this code, note how we specify which ones are the first and last component so that the best padding values are used to avoid any content clipping.
Step 7
// TODO: Swap to TransformingLazyColumnState
val listState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
The names are almost identical. Just like LazyListState
handles state for a LazyColumn
, TransformingLazyColumnState
handles it for a TransformingLazyColumn
.
We also specify a transformationSpec
here so that we can add transformations for the items as they scroll across the screen.
Convert to a TransformingLazyColumn
Next we swap in TransformingLazyColumn
.
In MainActivity.kt
, search for "TODO: Swap a TransformingLazyColumn
". First, replace LazyColumn
with TransformingLazyColumn
.
Then remove contentPadding, verticalArrangement
and modifier
altogether - TransformingLazyColumn
already provides default settings that guarantee a better default visual effect as most of the viewport will be filled with list items. In most cases default parameters will be sufficient, if you have header on top we recommend putting it into ListHeader
as a first item.
Step 8
// TODO: Swap a TransformingLazyColumn (Wear's version of LazyColumn)
TransformingLazyColumn(
state = listState,
contentPadding = contentPadding)
Add a scroll transform effect to items
Now we add a ShapeMorphing effect to our TransformingLazyColumn
elements by using the following Modifier
and SurfaceTransformation
. Apply the same code to all the components (
except IconButtonExample
which doesn't support this feature yet).
Step 9
TextExample(
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
)
That's it! Let's see what it looks like:
You can see the content is scaled and the transparency is adjusted at the top and bottom of the screen as you scroll with very little work to migrate!
You can really notice it with the meditation composables as you move it up and down.
Now onto the last topic, Wear OS's Scaffold
.
8. Add a Scaffold
AppScaffold and ScreenScaffold provide a layout structure to help you arrange screens in common patterns, just like mobile, but instead of an App Bar, FAB, Drawer, or other mobile specific elements, it supports three Wear specific layouts with top-level components: time, scroll/position indicator and the page indicator.
Here's what they look like:
We'll look at the first three components in detail, but, first, let's put the scaffold in place.
The scaffold components AppScaffold
and ScreenScaffold
lay out the structure of a screen and coordinate transitions of the ScrollIndicator
and TimeText
components.
AppScaffold
allows static screen elements such as TimeText
to remain visible during in-app transitions such as swipe-to-dismiss. ScreenScaffold
displays the ScrollIndicator
at the center-end of the screen by default and coordinates showing/hiding TimeText
and ScrollIndicator
Add a Scaffold
Let's add the boilerplate for the AppScaffold
and ScreenScaffold
now.
Find "TODO (Start): Create a AppScaffold (Wear Version)
" and add the code below it.
Step 9
WearAppTheme {
// TODO (Start): Create a AppScaffold (Wear Version)
AppScaffold {
Find "TODO (Start): Create a ScreenScaffold (Wear Version)"
and add the code below it.
// TODO (Start): Create a ScreenScaffold (Wear Version)
ScreenScaffold(
scrollState = listState,
contentPadding = rememberResponsiveColumnPadding(
first = ColumnItemType.IconButton,
last = ColumnItemType.Button,
),
){contentPadding ->
Next, make sure you add the closing bracket to the right location.
Find "TODO (End): Create a ScreenScaffold (Wear Version)"
and add the closing bracket there:
Step 10
// TODO (End): Create a ScreenScaffold (Wear Version)
}
Find "TODO (End): Create a AppScaffold (Wear Version)"
and add the closing bracket there:
Step 10
// TODO (End): Create a AppScaffold (Wear Version)
}
Let's run it first. You should see something like this:
Notice that it adds:
- A
TimeText
which uses curved text under the hood and gives developers an easy way to show the time without placing the composable or having to do any work with time related classes. Additionally, the Material Guidelines recommend that you display the time at the top of any screen within the app and it fades away while scrolling. - A
ScrollIndicator
which is an indicator on the right side of the screen to show the current indicator location based on the type of state object you pass in. In our case, that will be theTransformingLazyColumnState
.
Ok, let's see what this looks like now:
Try scrolling it up and down. You should only see the scrolling indicator show up when you are scrolling.
Add a Edge Hugging button
The EdgeButton
is a new expressive button from the M3 Compose material for Wear OS release. The edge-hugging container is a new shape that embraces round and maximises the space within the circular form factor.
ScreenScaffold
provides a slot for an EdgeButton that takes the available space below a scrolling list. It will scale up and fade in when the user scrolls to the end of the list, and scale down and fade out as the user scrolls up. Let's add an EdgeButton
to our code:
Step 11
ScreenScaffold(
scrollState = listState,
contentPadding = rememberResponsiveColumnPadding(
first = ColumnItemType.IconButton,
last = ColumnItemType.Button,
),
/* *************************** Part 11: EdgeButton *************************** */
// TODO: Add a EdgeButton
edgeButton = {
EdgeButton(
onClick = { /* ... */ },
buttonSize = EdgeButtonSize.Medium) {
Text(stringResource(R.string.more))
}
}
You can specify 4 different sizes for an EdgeButton
: ExtraSmall
, Small
, Medium
and Large
.
Let's see what this looks like now:
Nice job, you have finished a UI demo of most of the Wear OS composables!
9. Congratulations
Congratulations! You learned the basics of using Compose on Wear OS!
Now you can reapply all your Compose knowledge to making beautiful Wear OS apps!
What's next?
Check out the other Wear OS codelabs:
Further reading
- Compose Starter sample on Github
- Build apps for the wrist with Wear OS for more guides
- Use Jetpack Compose on Wear OS
Feedback
We'd love to hear from you about your experiences using Compose for Wear OS and what you are able to build! Join the discussion in the Kotlin Slack #compose-wear channel and keep providing feedback on the issue tracker.
Happy coding!