CodeNewbie Community 🌱

Cover image for Adding icons to text in Jetpack compose with Kotlin and Android
Tristan
Tristan

Posted on

Adding icons to text in Jetpack compose with Kotlin and Android

Table of contents

  1. The UI we are fixing
  2. Step 1: id and buildAnnotatedString
  3. Step 2: Create a map
  4. Step 3: Add InlineContent to text
  5. A more complicated example

The code

My app on the Google play store

Resources

Without InlineTextContent

text and icons without InlineTextContent

With InlineTextContent

text and icons with InlineTextContent

  • Notice how the text now nicely wraps around the icons, instead looking like a separate entity

1) Create an id and buildAnnotatedString

  • So we need to do two things in this step. First create an id which will represent the icon you are trying to place inside of the text. Second use buildAnnotatedString to create our text
val modId = "modIcon"
val text = buildAnnotatedString {

        appendInlineContent(modId, "[icon]")
        append("user message")

  }

Enter fullscreen mode Exit fullscreen mode
  • We will talk more about appendInlineContent() in step 3 but just know that it what compose will use to swap out with our icon

2) Create a map

  • Now we need to create a map. This is the map that compose will use to swap out our ids with our composables. So inside of this map the keys will be our ids(modId in my case) and the values will be the icons
 val inlineContent = mapOf(
        Pair(
            // This tells the [CoreText] to replace the placeholder string "[icon]" by
            // the composable given in the [InlineTextContent] object.
            modId,
            InlineTextContent(
                // Placeholder tells text layout the expected size and vertical alignment of
                // children composable.
                Placeholder(
                    width = 20.sp,
                    height = 20.sp,
                    placeholderVerticalAlign = PlaceholderVerticalAlign.Center
                )
            ) {
                // This Icon will fill maximum size, which is specified by the [Placeholder]
                // above. Notice the width and height in [Placeholder] are specified in TextUnit,
                // and are converted into pixel by text layout.

                Icon(Icons.Filled.Face,"",tint = Color.Red,modifier = Modifier.fillMaxSize())
            }
        )
)


Enter fullscreen mode Exit fullscreen mode

-a Placeholder is also needed for text layout to reserve space for our icon.

  • Also, notice the modifier on the Icon, Modifier.fillMaxSize(), we do this so it fills up all the space reserved by the Placeholder

3) Add InlineContent to text

  • when we add our inlineContent to text, Compose will try to find a InlineTextContent in the map we defined as inlineContent, whose key equals to our defined id.
Text(text = text,
        inlineContent = inlineContent,
        modifier = Modifier.fillMaxWidth().padding(5.dp),

    )

Enter fullscreen mode Exit fullscreen mode

A more complicated example

  • So here is the actual code of the Composable that is running in my application. It adds AsyncImages conditionally to my text:
@Composable
fun ChatBadges(
    username:String,
    message: String,
    isMod: Boolean,
    isSub:Boolean,
    color:Color,
    textSize: TextUnit
){
    val modBadge = "linkToTwitchsModIcon"
    val subBadge = "linkToTwitchsSubIcon"
    val modId = "modIcon"
    val subId = "subIcon"
    val text = buildAnnotatedString {
        // Append a placeholder string "[icon]" and attach an annotation "inlineContent" on it.
        if(isMod){
            appendInlineContent(modId, "[icon]")
        }
        if(isSub){
            appendInlineContent(subId, "[subicon]")
        }
        withStyle(style = SpanStyle(color = color, fontSize = textSize)) {
            append(username)
        }
        withStyle(style = SpanStyle(color = Color.White)) {
            append(message)
        }

    }

    val inlineContent = mapOf(
        Pair(

            modId,
            InlineTextContent(

                Placeholder(
                    width = 20.sp,
                    height = 20.sp,
                    placeholderVerticalAlign = PlaceholderVerticalAlign.Center
                )
            ) {


                AsyncImage(
                    model = modBadge,
                    contentDescription = "Moderator badge",
                    modifier = Modifier.fillMaxSize().padding(2.dp)
                )
            }
        ),
        Pair(

            subId,
            InlineTextContent(

                Placeholder(
                    width = 20.sp,
                    height = 20.sp,
                    placeholderVerticalAlign = PlaceholderVerticalAlign.Center
                )
            ) {


                AsyncImage(
                    model = subBadge,
                    contentDescription = "Subscriber badge",
                    modifier = Modifier.fillMaxSize().padding(2.dp)
                )
            }
        )
    )


    Text(text = text,
        inlineContent = inlineContent,
        modifier = Modifier.fillMaxWidth().padding(5.dp),
        color = color,
        fontSize = textSize
    )
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter

Top comments (0)