Carthographing Jetpack Compose: text
Tags: android, kotlin, jetpackcompose
Published: 2021-06-19 08:28:27

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`).

```kotlin
Text(
  modifier = Modifier.fillMaxWidth(),
  text = "Hello Compose",
  textAlign = TextAlign.Center,
  fontStyle = FontStyle.Italic,
  fontSize = 64.sp
)
```

`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`.

```kotlin
BasicText(
  text = "Hello Compose"
)
```

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:

```kotlin
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
  )
)
```

![A BasicText() in red and blue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kquh2pqr74ozs11cspjy.png)

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 `Range`s that specifies `SpanStyle`s on certain
> portion of the text. These styles will be applied in the order
> of the list. And the `SpanStyle`s applied later can override the
> former styles. 

My example defines two distinct `Range`s 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:

```kotlin
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
    )
  }
)
```

![Example using a builder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kffoqnrqe6cb60r5r301.png)

`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.

```kotlin
BasicText(
  text = "Hello Compose",
  modifier = Modifier.clickable {
    println("Hello")
  }
)
```

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?

```kotlin
ClickableText(
  text = AnnotatedString(text = "Click here"),
  onClick = {
    println(it)
  }
)
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3attd4kaiwy9apguzknf.png)

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](https://developer.android.com/reference/android/text/TextUtils#htmlEncode(java.lang.String)):

> 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()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dzm3kotvh9ylv2evdjhg.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvd31lzxprl8o9iatcsw.png)

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.