Android widgets have evolved with Jetpack Compose, and the new Compose Glance API makes it easier to create widgets using a declarative approach. Sadly, there are notable differences between Compose for Apps and Compose for Widgets (Glance), where a lot less things are supported (probably due to the widget API limitations inside Android).
These differences can make development tricky, as I discovered during the development of my own widget. To bridge some gaps, I developed a few helper functions to make working with text styles, fonts, and colors in Glance more intuitive. In this article, I’ll briefly explain these utilities.
Converting Compose TextStyle
to Glance
One of the key differences is how TextStyle
is handled between Compose and Glance. To facilitate conversion, I wrote the following function:
fun androidx.compose.ui.text.TextStyle.toGlance(): androidx.glance.text.TextStyle =
androidx.glance.text.TextStyle(
color = ColorProvider(color),
fontStyle = fontStyle?.toGlance(),
fontSize = fontSize,
fontWeight = fontWeight?.toGlance(),
textAlign = textAlign.toGlance(),
textDecoration = textDecoration?.toGlance(),
fontFamily = fontFamily?.toGlance()
)
This function simply maps a Compose TextStyle
to a Glance TextStyle
.
Funnily enough, the conversion is completely fine, even though we have to use the original compose TextStyle
in our glance widget to even use this function.
I promise the other toGlance
functions are a bit more interesting (weird!) from a programming point of view.
Handling Font Styles
FontStyle
in Compose is different from Glance, requiring explicit mapping:
fun androidx.compose.ui.text.font.FontStyle.toGlance(): androidx.glance.text.FontStyle =
when (value) {
1 -> androidx.glance.text.FontStyle.Italic
else -> androidx.glance.text.FontStyle.Normal
}
I checked in the code, there are just these two values, anything else in "Invalid"
…
Similarly, for FontWeight
:
fun androidx.compose.ui.text.font.FontWeight.toGlance(): androidx.glance.text.FontWeight =
when (weight) {
in 700..1000 -> androidx.glance.text.FontWeight.Bold
in 500..699 -> androidx.glance.text.FontWeight.Medium
else -> androidx.glance.text.FontWeight.Normal
}
While normal Compose supports any weight in 1..1000
, according to the check
in the init
-block, Glance only has three predefined weights. The ranges convert each possible weight to the most probably expected glance alternative.
Text Alignment Mapping
Since Justify
, Unspecified
, and Invalid
are not part of the glance API, I decided to make it nullable, to enforce choosing a default, where it is necessary (e.g. ...toGlance() ?: androidx.glance.text.TextAlign.Start
).
fun androidx.compose.ui.text.style.TextAlign.toGlance(): androidx.glance.text.TextAlign? =
when (toString()) {
"Left" -> androidx.glance.text.TextAlign.Left
"Right" -> androidx.glance.text.TextAlign.Right
"Center" -> androidx.glance.text.TextAlign.Center
"Start" -> androidx.glance.text.TextAlign.Start
"End" -> androidx.glance.text.TextAlign.End
else -> null
}
Also: TextAlign
is weird: It appears to be a value class of Int
, but then all valid values are numbered inside the companion object. This looks like it could have been an enum class!
Text Decorations
Text decorations underline and line-through are handled using their masks, while all other options are ignored. They simply do not exist on the glance side:
fun androidx.compose.ui.text.style.TextDecoration.toGlance(): androidx.glance.text.TextDecoration =
when (mask) {
0x1 -> androidx.glance.text.TextDecoration.Underline
0x2 -> androidx.glance.text.TextDecoration.LineThrough
else -> androidx.glance.text.TextDecoration.None
}
Fun fact: you can use the +
operator or the or
infix function to combine the decorations!
Font Family Conversion
I have no clue why I did things for this one, but it sure needed conversion!
I do remember that GenericFontFamily
was the only FontFamily
that had easily convertible differentiation for glance. This is probably not the most elegant solution, but it works:
fun androidx.compose.ui.text.font.FontFamily.toGlance(): androidx.glance.text.FontFamily =
when (this) {
is androidx.compose.ui.text.font.GenericFontFamily ->
when (name) {
"cursive" -> androidx.glance.text.FontFamily.Cursive
"serif" -> androidx.glance.text.FontFamily.Serif
"monospace" -> androidx.glance.text.FontFamily.Monospace
else -> androidx.glance.text.FontFamily.SansSerif
}
is androidx.compose.ui.text.font.SystemFontFamily ->
androidx.glance.text.FontFamily.SansSerif
else ->
androidx.glance.text.FontFamily.SansSerif
}
Color Handling
To apply colors with Glance, I created this helper function, since androidx.glance.Image
needs a ColorFilter
instead of a Color
:
fun androidx.compose.ui.graphics.Color.toColorFilter() =
androidx.glance.ColorFilter.tint(androidx.glance.unit.ColorProvider(color = this))
The ColorProvider()
function call is marked as an error by the IDE, but I am pretty sure this is a mistake. While many things around the function are marked as library-internal, the function itself is not.
Handling Strings with Localization
Since Glance doesn’t provide a stringResource
function, and LocalContext.current.getString
is way harder to type:
@androidx.compose.runtime.Composable
fun glanceString(@androidx.annotation.StringRes resId: Int, vararg formatArgs: Any): String =
androidx.glance.LocalContext.current.getString(resId, *formatArgs)
This should cover anything you can throw at it!
Conclusion
While Compose Glance makes widget development much nicer, there are a few annoying differences from standard Jetpack Compose. I hope that we can bridge the gap by using helper functions like these.