Creating Interactive HTML Content in Jetpack Compose

Serhii Hrabas
3 min readJun 20, 2024

--

Displaying HTML content in Android Compose, particularly with clickable links, might seem challenging at first. However, with Kotlin and Jetpack Compose, it’s manageable and even enjoyable. This guide will walk you through building a custom composable function called HtmlText to parse and display HTML strings with clickable links.

Adding Dependencies

Before we start, ensure you have the required dependencies in your build.gradle file:

dependencies {
implementation "androidx.compose.ui:ui:1.0.0"
implementation "androidx.compose.material:material:1.0.0"
implementation "androidx.compose.ui:ui-tooling-preview:1.0.0"
implementation "androidx.compose.runtime:runtime-livedata:1.0.0"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.compose.foundation:foundation:1.0.0'
}

Building the HtmlText Composable Function

Let’s dive into creating the HtmlText composable, breaking it down step by step.

Photo by Oliver Sjöström on Unsplash

Within the function, get the current context and create an annotated string from the HTML content. The HtmlCompat.fromHtml method converts the HTML string to a Spanned object.

val context = LocalContext.current
val annotatedText = remember(html) {
val spanned = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
val text = spanned.toString()
buildAnnotatedString {
append(text)
// Additional code will be added here
}
}

Parsing Links

Extract the URL spans from the Spanned object and annotate them within the buildAnnotatedString block. This ensures that links are styled and clickable.

val urlSpans = spanned.getSpans(0, spanned.length, android.text.style.URLSpan::class.java)
urlSpans.forEach { urlSpan ->
val start = spanned.getSpanStart(urlSpan)
val end = spanned.getSpanEnd(urlSpan)
val url = urlSpan.url
addStyle(
style = SpanStyle(
color = linkColor,
fontSize = fontSize,
fontWeight = FontWeight.SemiBold,
textDecoration = TextDecoration.Underline
), start = start, end = end
)
addStringAnnotation(
tag = "URL",
annotation = url,
start = start,
end = end
)
}

Handling Clicks

Use ClickableText to render the annotated string and handle link clicks. When a link is clicked, it will open in the device's browser.

ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))
context.startActivity(intent)
}
},
style = TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight
)
)

Complete HtmlText Function

Here is the complete HtmlText function:

import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import androidx.core.text.HtmlCompat

@Composable
fun HtmlText(
html: String,
linkColor: Color = Color(0xFF6200EE), // Default link color
textColor: Color = Color.DarkGray,
fontSize: TextUnit = 11.sp,
fontWeight: FontWeight = FontWeight.Normal
) {
val context = LocalContext.current
val annotatedText = remember(html) {
val spanned = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
val text = spanned.toString()
buildAnnotatedString {
append(text)
val urlSpans = spanned.getSpans(0, spanned.length, android.text.style.URLSpan::class.java)
urlSpans.forEach { urlSpan ->
val start = spanned.getSpanStart(urlSpan)
val end = spanned.getSpanEnd(urlSpan)
val url = urlSpan.url
addStyle(
style = SpanStyle(
color = linkColor,
fontSize = fontSize,
fontWeight = FontWeight.SemiBold,
textDecoration = TextDecoration.Underline
), start = start, end = end
)
addStringAnnotation(
tag = "URL",
annotation = url,
start = start,
end = end
)
}
}
}

ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))
context.startActivity(intent)
}
},
style = TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight
)
)
}

Using the HtmlText Composable

To use the HtmlText composable in your Compose UI, simply call it with your HTML content.

@Composable
fun HelloWorldScreen() {
val htmlContent = """
<p>This is a <a href="https://medium.com/">link</a> in HTML.</p>
""".trimIndent()

HtmlText(
html = htmlContent,
linkColor = Color.Blue,
textColor = Color.Black,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}

Conclusion

By following these steps, you’ve created a custom HtmlText composable function in Jetpack Compose that can parse and display HTML content with clickable links. This function leverages Android's HtmlCompat to convert HTML strings into spanned text and Compose's ClickableText to handle link interactions, making it a powerful tool for displaying rich text content in your Compose applications. Additionally, it is possible to extend this composable by adding a custom click listener, enabling more complex interactions with HTML content.

--

--