Blog Infos
Author
Published
Topics
, , , ,
Published

In today’s world, many applications require users to accept their terms, conditions, privacy policy, distance sales contract and more. These documents often contain a clickable text in a sentence and it plays a role in navigates to agreement screen, display a popup or bottom sheet when you click it and generally have different color and font weight to represent clickable texts. In this article I’ll try to focus on managing clicks on localized text in your Jetpack Compose applications.

Terms and Privacy Policy dialog from Facebook app
What Jetpack Compose Offers

Jetpack Compose provides a builder class for annotating text, which makes easier the process of styling and appending text. If your app supports only one language, you can easily make clickable texts directly within the annotated strings.

Let’s say we have the string of “By clicking the continue, you agree to our Terms and Privacy Policy.” You could simply annotate “Terms” and “Privacy Policy” together for clickability if you provide just one language. But things get a bit tricky when dealing with other languages. The word order changes, making it hard to use the same structure. You can start with “prefix_terms,” but it’s not always that straightforward.

 

<resources>
<string name="prefix_terms">By clicking the continue, you agree to our\u0020</string>
<string name="terms">Terms</string>
<string name="privacy_policy">Privacy Policy</string>
<string name="and">\u0020and\u0020</string>
<string name="agreement_text">By clicking the continue, you agree to our Terms and Privacy Policy</string>
</resources>
<resources>
<string name="prefix_terms">Devam ederek\u0020</string>
<string name="terms">Şartlarımızı</string>
<string name="privacy_policy">Gizlilik Politikamızı</string>
<string name="and">\u0020ve\u0020</string>
<string name="agreement_text">Devam ederek Şartlarımızı ve Gizlilik Politikamızı kabul etmiş olursunuz.</string>
</resources>
view raw strings.xml hosted with ❤ by GitHub

Like I said above we can use them directly if an an application has one language. Let say default language is en-US.

val annotatedString = buildAnnotatedString {
append(stringResource(id = R.string.prefix_terms))
pushStringAnnotation(tag = stringResource(id = R.string.terms), annotation = "https://termify.io/terms-and-conditions-generator")
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
)
) {
append(stringResource(id = R.string.terms))
}
append(stringResource(id = R.string.and))
pushStringAnnotation(tag = stringResource(id = R.string.privacy_policy), annotation = "https://termify.io/privacy-policy-generator")
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
)
) {
append(stringResource(id = R.string.privacy_policy))
}
pop()
}
ClickableText(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
text = annotatedString,
style = MaterialTheme.typography.bodyMedium,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "policy", start = offset, end = offset)
.firstOrNull()?.let {
Timber.d("policy URL: ${it.item}")
}
annotatedString.getStringAnnotations(tag = "terms", start = offset, end = offset)
.firstOrNull()?.let {
Timber.d("terms URL: ${it.item}")
}
})
The result when we use with en-US
The Problem About Internationalized Texts

Directly embedding text can cause to incomplete or incorrect sentences due to differences in word order and sentence structure across languages. This causes a problem below, resulting in an incomplete Turkish sentence. Therefore, we need a generic way to handle it.

Incompleted sentence in Turkish

 

Firstly, we need tag, annotation and style to use buildAnnotatedString in our case. So, a simple data class to hold them is enough to use in ClickableLocalizedText function.

/**
* Data class representing a link annotation.
*
* @property tag The tag that will be used to identify this annotation in the text.
* @property annotation The actual annotation(url) that will be attached to the tag in the text.
* @property style The style that will be applied to the text span that matches the tag.
*/
@Immutable
data class StringAnnotation(
val tag: String,
val annotation: String,
val style: SpanStyle
)

Secondly we need a function to proceed each item inside LinkAnnotation. We can implement this in 3 steps:

  1. Build an annotated string by given text, annotation and style.
  2. Create a ClickableText composable using the annotated string from step 1, and apply the annotation and style.
  3. When a section of the text is clicked, the function finds the annotation that corresponds to that section and calls the onClick callback using the item of the clicked annotation.

Job Offers

Job Offers


    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

No results found.

Jobs

/**
* Composable function that creates a text with clickable annotations.
*
* @param text The text that will be displayed. It should contain tags that match the tags in the annotations list.
* @param textStyle The style that will be applied to the text. If not provided, the default style is `MaterialTheme.typography.titleSmall`.
* @param stringsAnnotations A list of `LinkAnnotation` objects. Each `LinkAnnotation` contains a tag, an annotation, and a style. The tag is a substring of the text that will be annotated. The annotation is the actual annotation that will be attached to the tag in the text. The style is the style that will be applied to the text span that matches the tag.
* @param onClick A function that will be called when a text span with an annotation is clicked. The function takes the annotation as a parameter.
*/
@Composable
fun ClickableLocalizedText(
text: String,
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
stringsAnnotations: List<StringAnnotation>,
onClick: (String) -> Unit
) {
// Build an annotated string from the text and the annotations.
val annotatedString =
buildAnnotatedString {
append(text)
stringsAnnotations.forEach { stringAnnotation ->
val startIndex = text.indexOf(stringAnnotation.tag)
val endIndex = startIndex + stringAnnotation.tag.length
addStyle(
style = stringAnnotation.style,
start = startIndex,
end = endIndex
)
addStringAnnotation(
tag = stringAnnotation.tag,
annotation = stringAnnotation.annotation,
start = startIndex,
end = endIndex
)
}
}
ClickableText(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
text = annotatedString,
style = textStyle,
onClick = { position ->
stringsAnnotations.forEach { annotation ->
annotatedString
.getStringAnnotations(annotation.tag, position, position)
.firstOrNull()?.let { stringAnnotation ->
onClick.invoke(stringAnnotation.item)
return@forEach
}
}
}
)
}
How Can We Use It?

Now, let’s see how to apply this article in practice. Below is the code showing the implementation of the ClickableLocalizedText function. It allows us to create clickable text elements with given localization and styling in Jetpack Compose applications.

val annotations = listOf(
StringAnnotation(
tag = stringResource(id = R.string.terms),
annotation = "https://termify.io/terms-and-conditions-generator",
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontStyle = MaterialTheme.typography.bodyMedium.fontStyle,
textDecoration = TextDecoration.Underline,
)
),
StringAnnotation(
tag = stringResource(id = R.string.privacy_policy),
annotation = "https://termify.io/privacy-policy-generator",
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontStyle = MaterialTheme.typography.bodyMedium.fontStyle,
textDecoration = TextDecoration.Underline,
)
)
)
ClickableLocalizedText(
text = stringResource(id = R.string.agreement_text),
stringsAnnotations = annotations,
onClick = { url ->
// ...
}
)

Now we are able to use clickable text for localized strings and get the annotation.

Demonstration
Conclusion

Handling clickable localized text in Jetpack Compose give out some challenges, but by applying dynamic text construction and styling strategies, developers can offer smooth user experience across languages. Using these methods shows that you care about making your apps accessible to everyone, which helps them reach more people around the world.

Resource

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu