Developer ergonomics in Compose

Jetpack Compose accelerates UI development and makes Android engineers more productive. However, adding a new tool to a project requires some consideration as it can affect the APK, and build and runtime performance.

APK size and build times

In this section, we compare how two different apps were impacted when adding or migrating to Jetpack Compose:

  • Tivi fully migrated to Compose, removing AppCompat and Material Design Components libraries.
  • Sunflower (compose_recyclerview branch) adds Compose only using it for items of a RecyclerView. All other dependencies are the same.

APK size

Adding libraries to your project affects the APK size. Let's see how Compose affects the size of the projects mentioned above. The following results are for the minified release APK of each project with resource and code shrinking enabled, using R8 full mode, and measured using APK Analyzer.

Tivi saw a 46% reduction in APK size, from 4.49 MB to 2.39 MB. It also saw a 17% reduction in method count after fully migrating to Compose. The XML lines of code decreased by 76%.

Sunflower's APK size increased 575 KB when Compose was added to the project, from 2407 KB to 2982 KB. Since Compose is barely used in that Sunflower branch, the project doesn't benefit from Compose-only APK size benefits such as removing the AppCompat dependency from the project.

Build time

Using the same projects, let's measure how Compose affects build performance.

This considers debug build times, which is the time you care more about while developing.

The mean build time of Tivi before migrating to Compose was 108.71 seconds. After fully migrating to Compose, the mean build time dropped to 76.96 seconds! A 29% decrease in build time. This time reduction was largely influenced by being able to remove data binding and Epoxy from the project which use kapt annotation processors, and Hilt being faster in AGP 7.0.

Sunflower however experienced a 7.6% increase of its median build time, from 11.57 seconds to 12.45 seconds.

Final thoughts

When you start adopting Compose in your app, you can see an increase in APK size and build time which will decrease and surpass the original metric once the project is fully migrated to Compose.

Runtime performance

This section covers topics related to runtime performance in Jetpack Compose so that you can understand how Jetpack Compose compares to the View system's performance, and how you can measure it.

Smart recompositions

When portions of the UI are invalid, Compose does its best to recompose just the portions that need to be updated. Read more about this topic in the Lifecycle of composables guide.

Comparison with the View system

Due to the design and lessons learned from the View system, Jetpack Compose outperforms it.

Everything extends View

Every View that draws on the screen, such as TextView, Button, or ImageView, requires memory allocations, explicit state tracking, and various callbacks to support all use cases. Furthermore, the custom View owner needs to implement explicit logic to prevent redrawing when it's not necessary, for example, for repetitive data processing.

Jetpack Compose addresses this in a few ways. Compose doesn't have explicit updatable objects for drawing views, UI elements are simple composable functions whose information is written to the Composition in a replayable way. This helps cut down explicit state tracking, memory allocations, and callbacks to only the composables that require said features instead of requiring them by all extensions of a given View type.

Furthermore, Compose provides smart recompositions out of the box, replaying the previously drawn result if no changes need to be made.

Multiple layout passes

Traditional ViewGroups have lots of expressiveness in their measure and layout APIs that make it easy to cause multiple layout passes. These multiple layout passes can cause exponential work if done at specific nested points in the view hierarchy.

Jetpack Compose enforces a single layout pass for all layout composables via its API contract. This allows Compose to efficiently handle deep UI trees. If multiple measurements are needed, Compose has a special system in place, intrinsic measurements.

View startup performance

The View system needs to inflate XML layouts when showing a particular layout for the first time. This cost is saved in Jetpack Compose since layouts are written in Kotlin and compiled just like the rest of your app.

Benchmarking Compose

In Jetpack Compose 1.0, there are notable differences between the performance of an app in debug and release modes. For representative timings, always use the release build instead of debug when profiling your app.

To check how your Jetpack Compose code is performing, you can use the Jetpack Macrobenchmark library. The Jetpack Compose team also uses it to catch any regressions that could happen. For example, see this benchmark for lazy column and its dashboard to track regressions.

Compose profile installation

Since Jetpack Compose is an unbundled library, it does not benefit from Zygote which preloads the View system’s UI Toolkit classes and drawables. Jetpack Compose 1.0 utilizes profile installation for release builds. Profile installers allow apps to specify critical code to be AOT compiled at installation time. Compose ships profile installation rules which reduce startup time and jank in Compose apps.