aster.cloud aster.cloud
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
aster.cloud aster.cloud
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
  • Mobile
  • Programming

Jetpack Compose: Debugging Recomposition

  • aster.cloud
  • September 15, 2022
  • 4 minute read

In this post, I wanted to show you how I looked into a performance issue in Jetsnack and how I went about debugging and fixing it in Jetpack Compose. This post is available as a video as well if you prefer.

Let’s get started by looking at the Jetsnack sample.


Partner with aster.cloud
for your next big idea.
Let us know here.



From our partners:

CITI.IO :: Business. Institutions. Society. Global Political Economy.
CYBERPOGO.COM :: For the Arts, Sciences, and Technology.
DADAHACKS.COM :: Parenting For The Rest Of Us.
ZEDISTA.COM :: Entertainment. Sports. Culture. Escape.
TAKUMAKU.COM :: For The Hearth And Home.
ASTER.CLOUD :: From The Cloud And Beyond.
LIWAIWAI.COM :: Intelligence, Inside and Outside.
GLOBALCLOUDPLATFORMS.COM :: For The World's Computing Needs.
FIREGULAMAN.COM :: For The Fire In The Belly Of The Coder.
ASTERCASTER.COM :: Supra Astra. Beyond The Stars.
BARTDAY.COM :: Prosperity For Everyone.

The details screen has a fancy collapsing toolbar effect, where as we scroll up the content moves up and resizes and as we scroll back down the content returns to how it started.

Jetsnack details screen — Collapsing toolbar

We’ve had reports that on lower-end devices this can appear quite janky as it scrolls. So, let’s look into why that could be.

Compose phases recap

Before we start debugging though, let’s quickly go over some required knowledge for debugging this issue. Remember that Compose has 3 phases:

  1. Composition determines what to show by building a tree of Composables.
  2. Layout takes that tree and works out where on screen they will be shown.
  3. Drawing then draws the composables to the screen.
The three phases of Compose

Here is the cool part: Compose can skip a phase entirely if no state has changed in it. So there should be no need to recompose just to relayout the screen. If we can avoid changing our composition tree, Compose will skip the composition phase entirely and this can lead to performance gains.

This is why our performance documentation states, “prefer lambda modifiers when using frequently changing state”. Using lambdas is what allows work to be deferred to a later phase and for composition to be skipped.

Using lambda modifiers

Why does using a lambda modifier mean we can skip composition? Let’s return to the Composition tree and see.

Read More  PyCon 2019 | Supporting Engineers with Mental Health Issues
Lambdas changing the composition tree

The composition tree is also built up of any modifiers that are applied to the composables.

Modifiers are effectively immutable objects. When the translation changes as the user scrolls up, the modifier is reconstructed, the old one is removed and the new one is added to the composition tree. This happens every time the offset changes. Because the Composition tree has changed, recomposition occurs.

So remember, you shouldn’t have to recompose just to relayout a screen, especially while scrolling, which will lead to janky frames. Whenever you see unnecessary recomposition, think about how you could move the work to a later phase.

Fixing Jetsnack’s scrolling performance issue

Now that we have gone over the theory, we can dive into the actual fix in Jetsnack.

Using the Layout Inspector in Android Studio, we can see the recomposition and skip counts of composable functions. If we go to the details page of Jetsnack and scroll it up and down, it can be seen that the Title composable is recomposing a lot. Most likely on every frame 😨

Note: If you upgrade to Android Studio Electric Eel, you can also see highlights when a composable recomposes in the Layout Inspector.

Layout inspector showing recomposition happening

If we have a look at the SnackDetail composable:

 /* Copyright 2022 Google LLC.
  SPDX-License-Identifier: Apache-2.0 */

@Composable
fun SnackDetail(
    snackId: Long,
    upPress: () -> Unit
) {
    val snack = remember(snackId) { SnackRepo.getSnack(snackId) }
    val related = remember(snackId) { SnackRepo.getRelated(snackId) }

    Box(Modifier.fillMaxSize()) {
        val scroll = rememberScrollState(0)
        Header()
        Body(related, scroll)
        Title(snack, scroll.value)
        Image(snack.imageUrl, scrollProvider = { scroll.value })
        Up(upPress)
        CartBottomBar(modifier = Modifier.align(Alignment.BottomCenter))
    }
}

We can see Title reads the current scroll value. This means that any time the scroll changes, this composable will have to be recomposed.

Read More  How To Develop Microservices In Kubernetes

Now looking at the Title composable.

 /* Copyright 2022 Google LLC.
  SPDX-License-Identifier: Apache-2.0 */

@Composable
private fun Title(snack: Snack, scroll: Int) {
    val maxOffset = with(LocalDensity.current) { MaxTitleOffset.toPx() }
    val minOffset = with(LocalDensity.current) { MinTitleOffset.toPx() }
    val offset = (maxOffset - scroll).coerceAtLeast(minOffset)

    Column(
        verticalArrangement = Arrangement.Bottom,
        modifier = Modifier
            .heightIn(min = TitleHeight)
            .statusBarsPadding()
            .graphicsLayer (translationY = offset)
            .background(color = JetsnackTheme.colors.uiBackground)
    ) { … }
}

It takes the scroll value and computes an offset with it, then that offset is used in a graphicsLayer modifier to achieve the translation on the screen.

The first step we can take is to defer the read of the scroll value into the Title composable. We can do this by converting the scroll parameter to instead take a lambda.

 /* Copyright 2022 Google LLC.
  SPDX-License-Identifier: Apache-2.0 */

private fun Title(snack: Snack, scrollProvider: ()->Int) {
    val maxOffset = with(LocalDensity.current) { MaxTitleOffset.toPx() }
    val minOffset = with(LocalDensity.current) { MinTitleOffset.toPx() }
    val offset = (maxOffset - scrollProvider()).coerceAtLeast(minOffset)

   …
}

// Call site would also be updated to Title(snack, scrollProvider = { snack.value })

This will defer the reading of state and at least limit the scope of recomposition to just the Title composable. This is good, but we can do better!

As we now know, we should prefer lambda modifiers when passing in a frequently changing state. Our scroll value definitely counts as frequently changing!

graphicsLayer has a lambda version we can switch to. If we use that instead we can defer the reading of state to the draw phase and this will mean composition can be skipped entirely. It won’t be enough to just switch to the lambda modifier though. As the scroll state is still being read outside of the lambda to calculate the offset, we will still require recomposition.

Read More  What Does It Mean For Code To "Work"?

To fully defer the read to the draw phase, we need to move the read into the graphicsLayer {} modifier as well. We will move the calculation of the offset inside the lambda which looks like this.

 /* Copyright 2022 Google LLC.
  SPDX-License-Identifier: Apache-2.0 */

@Composable
private fun Title(snack: Snack, scrollProvider: ()->Int) {
    val maxOffset = with(LocalDensity.current) { MaxTitleOffset.toPx() }
    val minOffset = with(LocalDensity.current) { MinTitleOffset.toPx() }

    Column(
        verticalArrangement = Arrangement.Bottom,
        modifier = Modifier
            .heightIn(min = TitleHeight)
            .statusBarsPadding()
            .graphicsLayer {
                val offset = (maxOffset - scrollProvider()).coerceAtLeast(minOffset)
               translationY = offset
             }
            .background(color = JetsnackTheme.colors.uiBackground)
    ) { … }
}

Now that our Compose state is only being read inside the graphicsLayer modifier, we have deferred the read outside of composition and composition can be skipped.

If we re-run the app and open the Layout Inspector we can see that composition has been skipped entirely. There is no recomposing or even skipping of individual composables.

Layout inspector showing no recomposition

That’s it! The problem is now solved and we have achieved the same collapsing toolbar effect without using recomposition.

For more Jetpack Compose performance tips, see our performance documentation.

By Ben Trengrove
Source Medium


For enquiries, product placements, sponsorships, and collaborations, connect with us at [email protected]. We'd love to hear from you!

Our humans need coffee too! Your support is highly appreciated, thank you!

aster.cloud

Related Topics
  • Android
  • Compose
  • Jetpack Compose
  • Jetsnack
  • Medium
  • SnackDetail
You May Also Like
View Post
  • Architecture
  • Data
  • Engineering
  • People
  • Programming
  • Software Engineering
  • Technology
  • Work & Jobs

Predictions: Top 25 Careers Likely In High Demand In The Future

  • June 6, 2023
View Post
  • Programming
  • Software Engineering
  • Technology

Build a Python App to Alert You When Asteroids Are Close to Earth

  • May 22, 2023
View Post
  • Programming

Illuminating Interactions: Visual State In Jetpack Compose

  • May 20, 2023
View Post
  • Computing
  • Data
  • Programming
  • Software
  • Software Engineering

The Top 10 Data Interchange Or Data Exchange Format Used Today

  • May 11, 2023
View Post
  • Gears
  • Mobile
  • Technology

Apple Watch Pride Edition Celebrates The LGBTQ+ Community

  • May 10, 2023
View Post
  • Architecture
  • Programming
  • Public Cloud

From Receipts To Riches: Save Money W/ Google Cloud & Supermarket Bills – Part 1

  • May 8, 2023
View Post
  • Programming
  • Public Cloud

3 New Ways To Authorize Users To Your Private Workloads On Cloud Run

  • May 4, 2023
View Post
  • Programming
  • Public Cloud

Buffer HTTP Requests With Cloud Tasks

  • May 4, 2023

Stay Connected!
LATEST
  • 1
    Just make it scale: An Aurora DSQL story
    • May 29, 2025
  • 2
    Reliance on US tech providers is making IT leaders skittish
    • May 28, 2025
  • Examine the 4 types of edge computing, with examples
    • May 28, 2025
  • AI and private cloud: 2 lessons from Dell Tech World 2025
    • May 28, 2025
  • 5
    TD Synnex named as UK distributor for Cohesity
    • May 28, 2025
  • Weigh these 6 enterprise advantages of storage as a service
    • May 28, 2025
  • 7
    Broadcom’s ‘harsh’ VMware contracts are costing customers up to 1,500% more
    • May 28, 2025
  • 8
    Pulsant targets partner diversity with new IaaS solution
    • May 23, 2025
  • 9
    Growing AI workloads are causing hybrid cloud headaches
    • May 23, 2025
  • Gemma 3n 10
    Announcing Gemma 3n preview: powerful, efficient, mobile-first AI
    • May 22, 2025
about
Hello World!

We are aster.cloud. We’re created by programmers for programmers.

Our site aims to provide guides, programming tips, reviews, and interesting materials for tech people and those who want to learn in general.

We would like to hear from you.

If you have any feedback, enquiries, or sponsorship request, kindly reach out to us at:

[email protected]
Most Popular
  • Understand how Windows Server 2025 PAYG licensing works
    • May 20, 2025
  • By the numbers: How upskilling fills the IT skills gap
    • May 21, 2025
  • 3
    Cloud adoption isn’t all it’s cut out to be as enterprises report growing dissatisfaction
    • May 15, 2025
  • 4
    Hybrid cloud is complicated – Red Hat’s new AI assistant wants to solve that
    • May 20, 2025
  • 5
    Google is getting serious on cloud sovereignty
    • May 22, 2025
  • /
  • Technology
  • Tools
  • About
  • Contact Us

Input your search keywords and press Enter.