Creating Interactive HTML Content in Jetpack Compose
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.
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.