All about AnnotatedString (Jetpack Compose)

Ranjan Rukhaya
3 min readMar 30, 2023

AnnotatedString is the basic data structure of text with multiple styles. We can use AnnotatedString for creating all special texts like

  • Multiple styles in a text
  • Clickable text
  • Text with some icon

We have seen these texts in almost every application right. Let’s build them with compose.

Multiple styles in a text

To set different styles within the same Text composable, you have to use an AnnotatedString, a string that can be annotated with styles of arbitrary annotations.

AnnotatedString is a data class containing:

  • A Text value
  • A List of SpanStyleRange, equivalent to inline styling with position range within the text value
  • A List of ParagraphStyleRange, specifying text alignment, text direction, line height, and text indent styling

TextStyle is for use in the Text composable , whereas SpanStyle and ParagraphStyle is for use in AnnotatedString.

The difference between SpanStyle and ParagraphStyle is that ParagraphStyle can be applied to a whole paragraph, while SpanStyle can be applied at the character level. Once a portion of the text is marked with a ParagraphStyle, that portion is separated from the remaining as if it had line feeds at the beginning and end.

@Composable
fun SpanStyleInText() {
Text(
buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Blue)) {
append("J")
}
append("etpack ")

withStyle(style = SpanStyle(fontWeight = FontWeight.Bold,
color = Color.Red)) {
append("C")
}
append("ompose")
}
)
}

We can set ParagraphStyle in the same way:

@Composable
fun ParagraphStyleInText() {
Text(
buildAnnotatedString {
withStyle(style = ParagraphStyle(lineHeight = 40.sp)) {
withStyle(style = SpanStyle(color = Color.Blue)) {
append("Hello\n")
}
append("World\n")
withStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold, color = Color.Red
)
) {
append("Compose")
}
}
}
)
}

Clickable Text

This is also very common behaviour where make some part of text is clickable. Let’s try to build one.

@Composable
fun AnnotatedClickableText() {
val annotatedText = buildAnnotatedString {
append("Click ")

// We attach this *URL* annotation to the following content
// until `pop()` is called
pushStringAnnotation(
tag = "URL",
annotation = "https://developer.android.com/jetpack/compose"
)
withStyle(
style = SpanStyle(
color = Color.Blue, fontWeight = FontWeight.Bold
)
) {
append("here for compose documentation")
}

pop()
}

ClickableText(text = annotatedText, onClick = { offset ->
// We check if there is an *URL* annotation attached to the text
// at the clicked position
annotatedText.getStringAnnotations(
tag = "URL", start = offset, end = offset
).firstOrNull()?.let { annotation ->
// If yes, we log its value
Log.d("Clicked URL", annotation.item)
}
})
}

Text With Icon

We have often seen icons within some text. One way is use Text and Icon composables and use them in single Row. Better way is to use single Text composable and embed Icon into it with the help of AnnotatedString .

@Composable
fun TextWithIcon() {
val annotatedString = buildAnnotatedString {
append("Delhi ")
appendInlineContent(id = "icon")
append(" Mumbai")
}
val inlineContentMap = mapOf(
"icon" to InlineTextContent(
Placeholder(12.sp, 8.sp, PlaceholderVerticalAlign.TextCenter)
) {
Image(
painter = painterResource(id = R.drawable.ic_arrow_roundtrip),
modifier = Modifier.fillMaxSize(),
contentDescription = "roundTrip"
)
}
)
Text(
text = annotatedString,
inlineContent = inlineContentMap,
textAlign = TextAlign.Center,
)
}
Text with round trip icon

The good thing about AnnotatedString is that we can append one to another while adding new modifications!🤘

If you find this post helpful, don’t forget to clap 😊

Happy Composing! 👋

--

--

Ranjan Rukhaya

Android Engineer, I write what I learned about Kotlin and Android programming.