Material avatar placeholder in Jetpack Compose | Anton Danshin

Material avatar placeholder in Jetpack Compose

October 17, 2021 | 4 min read

When you want to display different users in your app, it is a good idea to provide an image. For example, when scrolling through the list some people would mostly pay attention to the text, but some will look at images. That’s why a lot of apps that contain list of people, (e.g. a Contacts app), always provide an avatar.

Not all users might have avatars. If the avatar is missing, it is often replaced with a placeholder. I find that the best placeholder should contain user initials and have a different color per user. This way, it is easier to identify different people in the list just by looking at the avatar placeholder.

Material letter icons as avatar placeholders

In this article, I will look into how we can draw such an avatar placeholder in Jetpack Compose.

Requirements

Just list a few requirements to our avatar placeholder:

  • It should display the initials of the user on a circle with some background color.
  • Text must always be legible with a good contrast ratio – e.g., black text on a brown background is no good.
  • If two users have the same initials, the color should be different.
  • For the same user, it should use the same background color.

Implementation

Input: user ID and full name.

  1. Convert user data to color using HSV color space.
  2. Draw a circle.
  3. Draw Initials.
  4. Put it all together.

Generating color by user name

In our code, we represent color as a 32-bit integer (ARGB format). What if we use a hashcode of the user name as color? It might work, but we often end up having colors that are too dark, too light, or even too transparent.

Ideally, we want our color to have a fixed brightness – different users would have different color of the same brightness and saturation levels. Lucky for us there are color encodings that split the color into exactly these components.

HSL (for hue, saturation, lightness) and HSV (for hue, saturation, value; also known as HSB, for hue, saturation, brightness) are alternative representations of the RGB color model, designed in the 1970s by computer graphics researchers to more closely align with the way human vision perceives color-making attributes. In these models, colors of each hue are arranged in a radial slice, around a central axis of neutral colors which ranges from black at the bottom to white at the top. — Wikipedia

We can use HSL color space and fix saturation and lightness. In my app I found that lightness = 0.4f and saturation = 0.5f work the best.

Here is how we can convert a String into a color in Android using HSL color space:

@ColorInt
fun String.toHslColor(
    saturation: Float = 0.5f,
    lightness: Float = 0.4f
    ): Int {
    val hashCode = fold(0) { acc, char -> char.code + acc * 37 }
    val hue = (hue % 360).absoluteValue.toFloat()
    return ColorUtils.HSLToColor(floatArrayOf(hue, saturation, lightness))
}

We can then generate a color by user name:

// generate full name string
val name = listOf(firstName, lastName)
    .joinToString(separator = "")
    .uppercase()

val color = name.toHslColor()

The only problem here is that users that share the same name will be given the same color. This can be fixed by adding user ID into the mix.

val color = "$id / $name".toHslColor()

Drawing background circle

Now we can simply draw a circle for a given user full name on Canvas:

Canvas(modifier = Modifier.fillMaxSize()) {
    drawCircle(SolidColor(color))
}

Draw initials

To draw initials on top of our background, we need to wrap both of them with a Box layout.

Box(contentAlignment = Alignment.Center) {
    // ...

    val initials = (firstName.take(1) + lastName.take(1)).uppercase()
    Text(text = initials)
}

Putting it all together

When creating a component, it would be a good idea to provide some basic customization. In my implementation, I only need to have different sizes and text styles.

@Composable
fun UserHead(
    id: String,
    firstName: String,
    lastName: String,
    modifier: Modifier = Modifier,
    size: Dp = 40.dp,
    textStyle: TextStyle = MaterialTheme.typography.subtitle1,
) {
    Box(modifier.size(size), contentAlignment = Alignment.Center) {
        val name = listOf(firstName, lastName)
                .joinToString(separator = "")
                .uppercase()
        val color = Color("$id / $name".toHslColor())
        val initials = (firstName.take(1) + lastName.take(1)).uppercase()
        Image(
            painter = ColorPainter(color),
            modifier = Modifier.clip(CircleShape),
            contentDescription = "Avatar"
        )
        Canvas(modifier = Modifier.fillMaxSize()) {
            drawCircle(SolidColor(color))
        }
        Text(text = initials, style = textStyle, color = Color.White)
    }
}

Here is how the component is rendered:

Avatar placeholder with initials and color background example

Conclusion

With just 20 lines of code, we have created a simple Jetpack Compose component to display a colorful avatar placeholder with user initials. Follow this link to see all the code.


Profile picture

Written by Anton Danshin 🧑‍💻 Android developer, ☕️ Starbucks coffee addict

© 2022, Built with Gatsby