Reduce Recomposition for Images/Icons In Jetpack Compose
A simple gotcha to improve your Compose app’s performance
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.
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,
...
)
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 🎉
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 ❤️