DEV Community

loading...
Cover image for Carthographing Jetpack Compose: text

Carthographing Jetpack Compose: text

Thomas Künneth
Developer. Speaker. Listener. Loves writing. GDE Android. Confessing mobile computing addict ;-)
・4 min read

Welcome to the third part of Carthographing Jetpack Compose. In the previous episode we took a look at images and saw how to draw using Canvas(). Today we focus on text. As you will see, text-related features are present in quite a few packages. So, the aim of this post is to give you an understanding of what to find where.

Easy to use

Showing text in Jetpack Compose is really easy. The following code snippet will give you a centered italic text. Its size is provided as scale-independent pixels (sp).

Text(
  modifier = Modifier.fillMaxWidth(),
  text = "Hello Compose",
  textAlign = TextAlign.Center,
  fontStyle = FontStyle.Italic,
  fontSize = 64.sp
)
Enter fullscreen mode Exit fullscreen mode

Text() belongs to androidx.compose.material. If you don't need the Material goodness, there is a nice BasicText() composable in androidx.compose.foundation.text.

BasicText(
  text = "Hello Compose"
)
Enter fullscreen mode Exit fullscreen mode

Have you noticed that I did not pass textAlign, fontStyle or fontSize? Well, you can't. As parameters, that is. To configure the appearance of your text you use annotated strings. Take a look:

val l = mutableListOf<AnnotatedString.Range<SpanStyle>>()
l.add(AnnotatedString.Range(SpanStyle(Color.Red), 0, 5))
l.add(AnnotatedString.Range(SpanStyle(Color.Blue), 6, 13))
BasicText(
  text = AnnotatedString(
    text = "Hello Compose",
    spanStyles = l
  )
)
Enter fullscreen mode Exit fullscreen mode

A BasicText() in red and blue

The immutable class AnnotatedString belongs to androidx.compose.ui.text. The docs say:

The basic data structure of text with multiple styles. To
construct an AnnotatedString you can use Builder.

The parameter spanStyles is

a list of Ranges that specifies SpanStyles on certain
portion of the text. These styles will be applied in the order
of the list. And the SpanStyles applied later can override the
former styles.

My example defines two distinct Ranges inside the annotated string. Ranges contain an item of some type (for example SpanStyle), a start (when the range takes effect), and an end. Please note that end refers to the position where the item is no longer in effect. My ranges differ in color. SpanStyle can be configured extensively, for example using fontSize, fontStyle, or background.

Now let's see how the builder the docs mentioned works:

BasicText(
  text = buildAnnotatedString {
    withStyle(
      style = SpanStyle(
        fontSize = 64.sp,
        color = Color.Yellow,
        background = Color.LightGray
      )
    ) {
      append("Hello Compose")
    }
    addStyle(
      style = ParagraphStyle(textAlign = TextAlign.Center),
      start = 0, end = length
    )
  }
)
Enter fullscreen mode Exit fullscreen mode

Example using a builder

buildAnnotatedString() belongs to androidx.compose.ui.text, just like AnnotatedString. In fact they currently share the same source file, AnnotatedString.kt. The class AnnotatedString.Builder allows the construction of an AnnotatedString using methods such as withStyle(), append(), and addStyle(). My example sets the basic appearance using withStyle() and adds th text with append(). addStyle() centers my text. Have you noticed end = length? length saves you from calculating the length of the text on your own.

Clickable text

You can make a text clickable the same way as you would with any other composable.

BasicText(
  text = "Hello Compose",
  modifier = Modifier.clickable {
    println("Hello")
  }
)
Enter fullscreen mode Exit fullscreen mode

This is super convenient for things like Click to start. But what if you need to know which part of the text the user has clicked?

ClickableText(
  text = AnnotatedString(text = "Click here"),
  onClick = {
    println(it)
  }
)
Enter fullscreen mode Exit fullscreen mode

The composable ClickableText() belongs to androidx.compose.foundation.text. The docs say:

A continent version of BasicText component to be able to
handle click event on the text.

This is a shorthand of BasicText with pointerInput to be
able to handle click event easily.

onClick is executed when users click the text. The callback receives clicked character's offset.

Other text-related functions

So far I have shown you text-related functions inside Jetpack Compose. But there is another Jetpack (not compose) package dealing with text: androidx.core.text. It contains some nice extension functions, e.g. htmlEncode(). Let's see how it works.

Source code of htmlEncode

So the extension function passes its parameter to TextUtils.htmlEncode(). The class TextUtils belongs to package android.text. We thus jump into the Android framework. It is worth mentioning that androidx.core.text contains a class called TextUtilsCompat which also features a static htmlEncode() function. If Jetpack Compose used this, it might eliminate one dependency to the platform, which could help porting to Compose for Desktop. I'll follow up on this shortly.

But what does htmlEncode() do? The Android docs say:

Html-encode the string.

This leaves room for explanations. 😎 Let's peek into the androidx.core.text.TextUtilsCompat version.

Source code of androidx.core.text.TextUtilsCompat.htmlEncode()

So we learn two things:

  1. htmlEncode() makes certain characters html-friendly
  2. On newer platform versions even TextUtilsCompat invokes the platform so my assumption stated above does not hold. To do so the code would need to be altered to always doing the else part.

Before finishing this part I would like to mention that extension function capitalize() is available in both kotlin.text (yet another text-related package) and androidx.compose.ui.text. The first one is deprecated.

Screenshot of a deprecation warning

The second one is not. So in your Compose apps you might consider using androidx.compose.ui.text.capitalize() which receives a androidx.compose.ui.text.intl.Locale.

Conclusion

Text support in Jetpack Compose is distributed over quite a few packages and sub packages. Regarding your Compose apps the most important decision will be if you use the Material version or the more basic ones. Did I miss something important? Kindly share your thoughts in the comments.

Discussion (0)