Glance for Wear OS (Jetpack Compose for Wear OS Part-III)

Anmol Sahi
6 min readOct 25, 2022

--

In this part, we’ll build a Wear OS Tile Using Glance with the help of Jetpack Compose.

Link to Part-I: Jetpack Compose For Wear OS Part-I

Link to the Part-II: Jetpack Compose for Wear OS Part-II

Let’s see what is a Tile in Wear OS:

Tiles provide easy access to the information and actions users need in order to get things done. With a simple swipe from the watch face, a user can find out the latest forecast or start a timer.

A tile runs as a part of the system UI instead of running in its own application container. We use a Service to describe the layout and content of the tile. The system UI will then render the tile when needed.

Let’s have a look at what we’re going to build in this article

Glance

With the help of Glance, we can build layouts for remote surfaces using a Jetpack Compose-style API, for example, AppWidgets and Wear-Tiles. Glance is currently in alpha and we’re limited with some basic initial set of composables: Box, Row, Column, Text, Image, Spacer, CurvedRow, CurvedText.

Add Wear-Tiles Glance dependency to your app build.gradle

implementation("androidx.glance:glance-wear-tiles:1.0.0-alpha05")

Let’s start building a Wear-Tile

Step-1

Create a TileService class

class FitWatchTile : GlanceTileService() {
@Composable
override fun Content() {
...
}
}

Step-2

Add the Tile service to the manifest file

<service
android:name="com.example.fitwatch.presentation.tiles.FitWatchTile"
android:exported="true"
android:label="FitWatch Tile">

<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>

<!-- The tile preview shown when configuring tiles on your phone -->
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/tile_placeholder" />
</service>

That’s pretty much it with the service part, now, let’s start building the UI part. We’ll be adding the code in the Content() composable.

Step-3

Add the Top Text, with title “Today” and subtitle “Summary

import androidx.glance.text.TextColumn {
Text(
text = "Today",
style = TextStyle(fontWeight = FontWeight.Bold)
)
Text(
text = "Summary",
style = TextStyle(
color = ColorProvider(color = SubtitleTextColor)
)
)
}

Please Note: Each component in this file should be imported from the Glance library

Step-4

Let’s build the CircularProgressIndicator shaped item.

For this, we would want to use a Box with CircularProgressIndicator and a column with two Text composables in it. But, we have an issue here, we do not have a Circular Progress Indicator in the Glance Library yet. As a workaround, we need to create our own custom composable. Thanks to Compose, this is gonna be easy.

Box(
modifier = GlanceModifier
.size(58.dp),
contentAlignment = Alignment.Center
) {
// Calculates the progress
val sweepProgress = (value.toFloat() / target.toFloat()) * 360f
// This shows the background of Circular progress indicator
CurvedRow {
curvedLine(
color = ColorProvider(NutrientsProgressBGColor),
curvedModifier =
GlanceCurvedModifier
.sweepAngleDegrees(360f)
.thickness(3.dp)
)
}

//
This shows the color of actual progress in the indicator
CurvedRow(
anchorDegrees = 270f,
anchorType = AnchorType.Start
) {
curvedLine(
color = ColorProvider(CustomBlue),
curvedModifier =
GlanceCurvedModifier
.sweepAngleDegrees(sweepProgress)
.thickness(3.dp)
)
}

// TODO: Add a Column with Text composables. Add the Step 5 code here
}

The idea here is to have a Box with two CurvedLine on top of each other. One shows the background of the progress indicator, the other one is to show the current progress(in Blue color).

The Box uses a GlanceModifier. It’s a special modifier designed for Glance components.

Let’s see what exactly CurvedRow and curvedLine composables are

CurvedRow

It’s a curved layout container designed for Wear OS. This container will fill itself to a circle, which fits inside its parent container, and all of its children will be placed on that circle. The parameters anchorDegrees and anchorType can be used to specify where to draw children within this circle.

anchorDegrees

The angle for the anchor in degrees, used with anchorType to determine where to draw children. Note that 0 degrees is the 3 o’clock position on a device, and the angle sweeps clockwise. Values do not have to be clamped to the range 0–360; values less than 0 degrees will sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values >360 will be placed at X mod 360 degrees.

Since we want our blue progress indicator to start from the 12 o’clock position, I have used 270 degrees as the value. However, we can also use -90 degrees here.

anchorType

Alignment of the contents of this container relative to anchorDegrees. By default its value is AnchorType.Center. We want it to be AnchorType.Start so that our progress indicator doesn’t go behind 12 o’clock.

Try to play with AnchorDegrees and anchorType values to understand it better.

curvedLine

It’s a curved scope composable (a line) that can be used in a CurvedRow and renders as a curved bar. It uses a curvedModifier parameter. We can access GlanceCurvedModifier to set the sweep angle.

Alright, that’s a lot of information. If you are still confused, just copy the above code and try to run it inside a TileService. Play with the code by changing some values and I believe within a couple of minutes you’ll understand all of it.

Step-5

Add a Column with Text composables.

Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = value.toString(),
style = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
)

Text(
text = text,
style = TextStyle(
fontSize = 12.sp,
color = ColorProvider(color = Color(0xFF949aa1))
)
)
}

That’s the usual compose code. Just make sure to import the Text composable from the Glance library.

Step-6

Add the code from Step-4 and Step-5 inside a custom composable

@Composable
fun GlanceProgressContainer(value: Int, text: String, target: Int{ // Step-4 and Step-5 code here
}

As per the design, we need 5 of these custom Circular Progress indicators. i.e. for Carbs, fat, protein, calories and water. Also, we need to put those in the two Rows to get the desired outcome.

Step-7

Now, make sure your final content composable looks like this

@Composable
override fun Content() {
Column(
modifier = GlanceModifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalAlignment = Alignment.Top
) {
GlanceTopTile(title = "Today", subtitle = "Summary")

GlanceVerticalSpacer(height = 8.dp)

Row(
modifier = GlanceModifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {

GlanceProgressContainer(value = 350, text = "carbs", target = 500)

GlanceHorizontalSpacer(width = 4.dp)

GlanceProgressContainer(10, "fat", target = 30)

GlanceHorizontalSpacer(width = 4.dp)

GlanceProgressContainer(32, "protein", target = 60)

}

Row(
modifier = GlanceModifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
GlanceProgressContainer(780, "cal", target = 1800)

GlanceHorizontalSpacer(width = 4.dp)

GlanceProgressContainer(6, "water", target = 10)

}
}
}

Add the missing GlanceHorizontalSpacer and GlanceVerticalSpacer

@Composable
fun GlanceVerticalSpacer(height: Dp) {
Spacer(
modifier = GlanceModifier.height(height = height)
)
}

@Composable
fun GlanceHorizontalSpacer(width: Dp) {
Spacer(
modifier = GlanceModifier.width(width = width)
)
}

That’s it, now run the Tile Service and you’ll be able to see your tile by swiping left from the watch face.

To learn how to directly run tile service from Android Studio, follow this link: https://developer.android.com/codelabs/wear-tiles?hl=en#3

Resources:

https://developer.android.com/jetpack/androidx/releases/glance

Checkout the GitHub Repo: https://github.com/mutualmobile/FitWatchCompose

--

--