Adding icons to text in Jetpack compose with Kotlin and Android
Published: 2023-10-10 16:03:20

### Table of contents
1. [The UI we are fixing](#fix)
2. [Step 1: id and buildAnnotatedString](#one)
3. [Step 2: Create a map](#two)
4. [Step 3: Add InlineContent to text ](#three)
4. [A more complicated example ](#comp)


### The code
- [GitHub](https://github.com/thePlebDev/Clicker)

### My app on the Google play store
- [The app](https://play.google.com/store/apps/details?id=elliott.software.clicker)

### Resources
-  [Stack Overflow comment](https://stackoverflow.com/questions/67605986/add-icon-at-last-word-of-text-in-jetpack-compose)

- [InlineTextContent documentation](https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/InlineTextContent)

### Without InlineTextContent <a name="fix"></a>

![text and icons without InlineTextContent ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jbuogk9y0cshmbhi0nr5.jpg)

### With InlineTextContent 

![text and icons with InlineTextContent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d85fk33i8d8ezts7qldz.jpg)

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



###1) Create an id and buildAnnotatedString <a name="one"></a>
- 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

```kotlin
val modId = "modIcon"
val text = buildAnnotatedString {

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

  }

```
- 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 <a name="two"></a>
- 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

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


```
-a [Placeholder](https://developer.android.com/reference/kotlin/androidx/compose/ui/text/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 <a name="three"></a>
- 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. 

```kotlin
Text(text = text,
        inlineContent = inlineContent,
        modifier = Modifier.fillMaxWidth().padding(5.dp),
        
    )

```


### A more complicated example <a name="comp"></a>
- So here is the actual code of the Composable that is running in my application. It adds AsyncImages conditionally to my text:
```kotlin
@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
    )
}

``` 

### 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](https://twitter.com/AndroidTristan).