6

Jetpack Compose — Before and after

 3 years ago
source link: https://medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Image for post
Image for post

Jetpack Compose — Before and after

How the build speed, APK size and source line count changed after migrating the Tivi sample app to Jetpack Compose

Since the start of this year, I’ve been slowly migrating the UI in Tivi to be written in Jetpack Compose, and this week, the first stage of that migration is now complete! 🎉

In this blog post, we’ll take a look back and compare a number of key metrics to see how well Compose compares: APK size, build speed and lines of code.

The app

Before I go any further into the Compose side of things, let me quickly describe the app.

Tivi is pretty heavily modularized, with each UI ‘screen’ in its own Gradle module (named ui-$NAME). Each of those screens was implemented in a Fragment, and then pieced together using AndroidX Navigation in the main app module. To give you an idea of the structure, here’s a graph of the modules in the app:

Image for post
Image for post
Graph of Tivi’s module structure. Generated using Jake Wharton’s handy Gradle task

Most of the fragments had no knowledge of each other, since the navigation graph was implemented using deep link URIs, ensuring decoupling. Perhaps more importantly, it also allows independent module compilation which aids build parallelism.

Note: the module structure of Tivi is not perfect by any means. There are too many dependencies of UI modules (at the top) to base modules (at the bottom). Ideally each layer should be seperated. Something for me to work on.

Before I started the migration to Compose, Tivi used all the cool 🌈 UI 💫 things available to Android developers: Data Binding, Epoxy, Material Design Components, Insetter DBX, MotionLayout to name a few. But unfortunately most of these came with a build cost since they use annotation processing.

First stage?

Earlier I mentioned that we had just completed the ‘first stage’ of the migration, so what do I mean by that? Well the app looks practically the same as when I started in January 2020.

The modularized nature of the app meant that the migration itself could be completed in pieces, one fragment at at a time, and that’s exactly what happened over the course of the past 11 months, covering 46 pull requests.

I started with a simple screen: Episode details, then migrated the Show details, then ‘Discover’, then ‘Search’, then ‘Followed shows’, etc. With the recent addition of Paging3 support for Compose, I could migrate the final screens: the ‘list’ grids:

Video showing the Tivi app, before and after the migration

Last week I removed AppCompat, Material Design Components, and other AndroidX widget libraries from the app, marking the point where Tivi’s UI isentirely Compose based.

The app is still using Fragments and Navigation, and the next logical step is to migrate away from fragments, and use the new Navigation Compose component directly.

I’ll likely write another blog post soon about how the migration went, but to quote myself from the summary below:

It feels like a no-brainer to me that Compose is the future of UI development on Android.

For now let’s look at some metrics… 📊

Metrics

For each of the metrics below, we’re going to compare three different versions of the app:

  1. Pre-Compose. This is the commit before I landed the first PR adding Compose support to Tivi, back in February 2020.
  2. Mid-transition. This is from this commit, where all of the UI screens implemented in Compose, but we’re still depending on AppCompat, MDC, etc (directly and transitively).
  3. Entirely Compose. This is using this PR which forcefully removes all traces of AppCompat, MDC, etc.

APK Size 🗜

The metric which your users will care the most about: APK size.

The following results are for the minified release APK (using R8) with resource shrinking enabled, measured using APK Analyzer.

Chart shwing APK size of Tivi. Pre-Compose: 4.49MB, Mid-transition: 4.66MB, Entirely Compose: 3.21MB, Adjusted: 2.63MB
Chart shwing APK size of Tivi. Pre-Compose: 4.49MB, Mid-transition: 4.66MB, Entirely Compose: 3.21MB, Adjusted: 2.63MB
Chart showing APK size of Tivi
Chart showing method count of Tivi. Pre-Compose: 40029, Mid-transition: 43812, Entirely Compose: 33254
Chart showing method count of Tivi. Pre-Compose: 40029, Mid-transition: 43812, Entirely Compose: 33254
Chart showing method count of Tivi

Some notes about the numbers:

  • We use the reported ‘APK file size’ (not download size) from APK Analyzer.
  • For both ‘Mid-transition’ and ‘Entirely Compose’ (marked with a *) the total APK size and res folder size include 560KB of bundled TTF font files, which are not in the ‘Pre-Compose’ APK.This is because Compose doesn’t support downloadable fonts yet (tracking issue) so we need to bundle them in the APK. The ‘adjusted’ column creates a fairer comparison by removing the font files for the test.
  • ‘Mid-transition’ has an increased size because it includes both Jetpack Compose and AppCompat, MDC, etc.

APK Size Analysis

When comparing the adjusted values vs ‘Pre-Compose’ we see a 41% reduction in APK size, and 17% reduction in method count when using Compose. 🤯

We see a 41% reduction in APK size, and 17% reduction in method count when using Compose

This number shows how much space AppCompat, MDC, etc take up in our apps, but also how little minification tools can help when you need to keep all View classes around, just in case they’re used from layout files.

Lines of code 📜

Now, I know that counting source lines of code isn’t a particularly useful statistic when comparing software projects, but it does provide an insight to how things are changing.

For this test I used the cloc tool, using the following command to exclude any build, generated and config files:

cloc . --exclude-dir=build,.idea,schemas
Chart showing lines of source code in Tivi. Pre-Compose: Kotlin 15827, XML 3651. Entirely Compose: Kotlin 15509, XML 845
Chart showing lines of source code in Tivi. Pre-Compose: Kotlin 15827, XML 3651. Entirely Compose: Kotlin 15509, XML 845
Chart showing lines of source code in Tivi

The cloc tool has built-in support for ignoring comments (although I didn’t verify this), so the results above are for actual ‘code’. Unsurprisingly the amount of XML lines decreased by a very large margin: 76%. Bye bye layout files, styles, themes and lots of other XML files. 👋

Also interesting is that the total lines in Kotlin went down too. My working theory for is that we now have less boilerplate in the app, and we were able to remove a lot of view helper & utility code. See this PR which removed nearly 3,000 lines which I’d written over the years:

Build speed ⏳

Build speed is a metric which developers care a lot about. Before starting this process, I had a feeling that removing lots of the annotation processors would help with the build speed, but I wasn’t sure by how much.

Test setup

Before I go any further, it’s important to know how I measured the numbers below. I followed a similar setup to Chris Horner when he measured build times on different CPUs.

The machine I tested on is a Lenovo P920 with 192GB RAM, and a very fast Xeon Gold 6154 CPU. Needless to say, this machine is not your typical developer setup, so to make the test more realistic I pinned the CPU to its minimum clock frequency:

# Use performance governor to allow tweaking of max freq
sudo cpupower frequency-set -g performance
# Set max frequency to CPU minimum: 1.2GHz
sudo cpupower frequency-set -u 1.2GHz

To prime all of the the remote artifact caches I then ran ./gradlew assembleDebug.

To run the tests, I ran the following command 5 times in a loop:

 ./gradlew --profile \
--offline \
--rerun-tasks \
--max-workers=4 \
assembleDebug

The --max-workers isn’t strictly necessary, but Gradle will use all 64 ‘cores’ available on this CPU by default. Limiting to 4 is more comparable to typical laptop CPUs.

Results

You can see the test results below, using the ‘Total Build Time’ value from each resulting profile report.

Chart showing median build time of Tivi in seconds. Pre-Compose: 108.71, Mid-transition: 104.15, Entirely Compose: 103.55
Chart showing median build time of Tivi in seconds. Pre-Compose: 108.71, Mid-transition: 104.15, Entirely Compose: 103.55
Chart showing median build time of Tivi

This result surprised me quite a bit because the numbers are so close 👀. To be honest, I was expected the ‘Entirely Compose’ to be quicker by a greater margin, due to the removal of many annotation processors.

Drilling down into the results shows that kapt execution time remains similar across the runs, likely because we’re still using Dagger/Hilt and Room.

Taking a step back, a 5% decrease in build time is actually very good when you think about everything which the Kotlin Compiler & Compose compiler plugin is doing for us now, such as positional memoization, and fine-grained recomposition. See this article from Leland for more information:

Caveats

There are some caveats in all of the results above:

Feature work

While I didn’t perform any major new feature work on Tivi during the 11 months, I didn’t particularly limit myself either. There are a number of changes I made which were not focused on the migration, and could skew the results.

Dependency updates

During the 11 months of migration, there were lots of dependency updates. Most of the dependency updates were runtime library dependencies, so most likely affect the APK Size metrics.

I also updated Gradle (from 6.0.1 to 6.7.0), the Android Gradle Plugin (3.6.0 to 4.2.0) and Kotlin (1.3.61 to 1.4.20), all of which can have a large effect on build speed.

Compose is in alpha

The obvious one. Compose is currently in alpha so all of the results are from a snapshot in time whilst it is being developed. Once it hits 1.0 stable next year, it will be interesting to re-run these tests and see if there are any differences.

Summary

If we look at the results and caveats we should not make too many judgements since we’re not comparing apples 🍎 to apples 🍏; it’s more like comparing apples 🍎 to their slightly sweeter cousin, pears 🍐.

Fruit analogies aside, I think the biggest takeaway for me is that Compose will have a positive (or neutral) effect on most developer metrics. With that in mind, along with the vastly increased developer productivity with Compose, it feels like a no-brainer to me that Compose is the future of UI development on Android.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK