Mastering Text in Jetpack Compose

From Simple Strings to Rich HTML

Stefano Natali
ProAndroidDev
5 min readJun 30, 2024

--

Jetpack Compose has established itself as a powerful and expressive toolkit for building beautiful user interfaces. Its focus on composability and state management has streamlined the development process, allowing us to craft pixel-perfect experiences. However, one area that previously lacked the flexibility developers craved was text manipulation. While handling basic text was always possible, the inability to render HTML strings limited the potential for truly dynamic and visually engaging content.

This all changes today! Finally we can direct render HTML in Compose. This seemingly simple addition unlocks us from tedious manual formatting or wrestling with complex text manipulation libraries. With HTML, you can leverage the power and familiarity of a well-established standard to create rich and engaging experiences for your users.

This article dives deep into exploring how HTML strings support empowers developers in Jetpack Compose. We’ll uncover the technical details of this new feature, showcase its potential through practical examples, and discuss the broader implications for the UI development in Jetpack Compose.

Analyse the current status

Jetpack Compose already offered a solid foundation for managing strings. The Text composable paired with AnnotatedString provided a decent level of control over text appearance. We could define styles for the entire string or specific portions. For instance, we could highlight a crucial term within a sentence by splitting the string into three parts: the beginning, the word to be emphasized (styled in bold), and the remaining text.

  Column(Modifier.padding(12.dp)) {
val annotatedString = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Black)) {
append("This is a ")
}
withStyle(style = SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) {
append("styled")
}
append(" text.")
}

Text(text = annotatedString)
}

Where we can simply extract the strings like this:

  <string name="part1">"This is a "</string>
<string name="part2_styled">styled</string>
<string name="part3">" text."</string>

While the AnnotatedString approach provided a workable solution for basic text formatting, it presented significant limitations as project complexity increased.

This approach suffers from three main drawbacks:

  • Manual string manipulation and error proneness: Imagine crafting a paragraph with numerous sections requiring diverse styles. You’d need to meticulously split the string into numerous parts, apply styles individually, and then reassemble them. This process is not only tedious but also error-prone. A single misplaced style definition could throw off the entire formatting. Maintaining large code blocks with intricate string manipulations can quickly become a maintenance nightmare.
  • Readability challenges: As the number of styled sections grows, the code can become difficult to read and understand. Imagine a long sentence with multiple styled words scattered throughout. The logic behind the formatting gets lost in a sea of string splits and style applications. This diminishes code clarity and makes collaboration with other developers more challenging.
  • Translation woes: This approach presents a significant hurdle for internationalization. Translating strings split into numerous parts becomes a cumbersome task for localization teams. The order and structure of words can change drastically in different languages. Managing a multitude of string resources, each representing a fragment of the original formatted string, becomes very inefficient.

Work with HTML strings

To address these limitations, a possible solution is to using HTML strings within string resources. This approach involved embedding HTML code within a string resource, like this:

    <string name="completed_text"><![CDATA[This is a <font color="red"><b>styled</b></font> text.]]></string>

However, this method lacked a direct integration with Jetpack Compose. Parsing these HTML strings within the UI required external libraries or custom logic, adding complexity to the development process.

Introduction of fromHtml

Thankfully, the new version of the Jetpack Compose foundation API provides a native solution for HTML rendering. In this section, we’ll explore the implementation details and demonstrate how to integrate this functionality into your Compose applications.

While this article is being written, the latest beta version for the foundation API is 1.7.0-beta04. Be sure to consult the official documentation for the most up-to-date version information.

[versions]
...
foundation = "1.7.0-beta04"

[libraries]
...
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }

With the latest foundation library dependency added, we can now leverage the built-in HTML parsing functionality. This eliminates the need for custom logic or external libraries. Let’s see how this works in practice:

Text(
text = AnnotatedString.fromHtml(
htmlString =
stringResource(id = R.string.completed_text)
)
)

This code snippet demonstrates how to integrate HTML rendering within your Compose component. We utilize the AnnotatedString.fromHtml extension, passing the desired HTML string retrieved from your string resource. This function parses the HTML content and automatically generates the corresponding AnnotatedString with the appropriate styles.

This approach achieves the same styled text we were aiming for, but with a cleaner implementation.

Hyperlink Support within HTML Strings

The beauty of AnnotatedString.fromHtml extends beyond basic styling. It also offers built-in support for hyperlinks. Define a link within your HTML string, and the functionality is automatically handled by Compose.

<string name="completed_text"><![CDATA[This is a <a href="https://medium.com/@stefanoq21">link</a>.]]></string>
Text(
text = AnnotatedString.fromHtml(
htmlString =
stringResource(id = R.string.completed_text),
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
fontStyle = FontStyle.Italic
)
)
)
)

When you render this HTML string using fromHtml, the word “link” will be automatically treated as a clickable element. Users can interact with it as they would with any standard hyperlink in a web browser.

While fromHtml provides default behavior for links, we can further customize this functionality using the linkInteractionListener parameter. This allows us to define custom actions when a link is clicked within your Compose UI.
For example, we can open the link in a custom tab.

Text(
text = AnnotatedString.fromHtml(
htmlString =
stringResource(id = R.string.completed_text),
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
fontStyle = FontStyle.Italic
)
),
linkInteractionListener = {
val urlString = (it as LinkAnnotation.Url).url
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
val customTabsIntent: CustomTabsIntent = builder.build()
customTabsIntent.launchUrl(context, Uri.parse(urlString))
}
)
)

Conclusions

HTML rendering in Jetpack Compose isn’t just a fancy new feature. It eliminates the need for manual string manipulation, which means writing cleaner, easier-to-read code.

This feature also makes creating versions of your app in different languages a breeze. Forget messing around with tiny bits of text! HTML lets you handle everything in one place, making your app truly global and easier to maintain for different languages.

In short, HTML rendering in Jetpack Compose makes building beautiful and user-friendly UIs faster and easier than ever before.

Feel free to share your comments, or if you prefer, you can reach out to me on LinkedIn.

Have a great day!

--

--