Reduce Recomposition for Images/Icons In Jetpack Compose

A simple gotcha to improve your Compose app’s performance

ilyas ipek
Teknasyon Engineering
2 min readSep 25, 2023

--

While inspecting the app to improve our list’s scroll performance, I realized that the Images and icons were always recompositioned, even when their state wasn’t changed! Quite weird, isn’t it? For example…

var counter: Int by remember { mutableStateOf(0) }

Column(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = null
)
Text(text = "Text1") // text1
Button(onClick = { counter += 1 }) {
Text(text = "Click me ")
}
Text("I've been clicked $counter times")
}

When the counter state changes, Text1 is skipped where the Image is recomposed all the time.

The numbers on the left represent recompositions, while those on the right signify skipped recompositions.

Apparently, the issue lies in the fact that Painter is not considered a stable type. This basically means the compiler can’t reliably determine whether the object has changed, resulting in recomposing the Image all the time.

Checkout Jetpack Compose Stability Explained by Ben Trengrove for more.

Solution

For Vector resources, we can use ImageVector instead of Painter, Because ImageVector is considered a stable type since it’s marked with @Immutable.

// From resources
Image(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_launcher_background),
...
)

// For material icons
Image(
imageVector = Icons.Default.ArrowForward,
...
)
The image is not recomposed at all.

For raster images, you can create a wrapper component that accepts the drawableResId instead of a painter

@Composable
fun GetcontactImage(
drawableResId: Int,
modifier: Modifier = Modifier,
...
) {
val painter = painterResource(id = drawableResId)
Image(
painter = painter,
contentDescription = "",
modifier = modifier
)
}

Since GetcontactImage (or Icon) accepts stable parameters (drawableResId: Int) it’s considered skippable and if its params don’t change it will be skipped 🎉

GetContactImage is skipped since it’s param hasn’t changed

Note: When designing reusable components prefer passing drawableResId/color types as param instead of Painter. Check Android docs.

That’s it and I hope you loved the blog ❤️

--

--

Android developer @teknasyon, writes about Android development and productivity.