February 25, 2024

After Duplo modularization, we observed that the duty producing a transitive R class was taking a big period of time to execute. To eradicate this process altogether, and because the non-transitive R class is marketed to have as much as 40% incremental build time improvement, we determined emigrate our codebase to make use of it.

When you’re not aware of nonTransitiveRClass, beforehand often known as namespacedRclass, it’s an Android Gradle Plugin flag that allows namespacing R courses so that each module’s R class solely consists of assets declared within the module itself, and never assets from the modules or libraries it depends upon. This flag is enabled by default for brand new initiatives since Android Studio Bumblebee.

On this put up, we’ll stroll via how we transitioned to non-transitive R class and a number of the advantages — each anticipated and sudden — that we noticed.

Establishing conventions

Kotlin

Since assets can’t all be referenced utilizing the present module’s R class anymore, we first wanted to align on how we needed them to be referenced within the non-transitive R class world. Utilizing the fully-qualified identify for R courses to reference assets, e.g. getString(slack.l10n.R.string.at_everyone), was cumbersome and verbose, so we settled on utilizing import aliases as an alternative for that objective:

import slack.l10n.R as L10nR

class Clazz(context: Context) 
  init
    context.getString(L10nR.string.at_everyone)
  

Java

Sadly, import aliases are a Kotlin-specific function and never accessible in Java. Nevertheless, contemplating that solely 4% of our codebase was nonetheless in Java, we agreed that utilizing the fully-qualified identify to reference assets not declared within the present module was a ok resolution, and would function motivation emigrate these courses to Kotlin down the road.

Migration

Preliminary technique

We had been planning on utilizing Android Studio’s Refactor > Migrate to Non-Transitive R Classes function to refactor all R references to be fully-qualified, after which use this script to undergo the refactored recordsdata and alter the fully-qualified references to make use of import aliases.

Nevertheless, on the scale of our codebase, with greater than 400 modules, Android Studio took all evening on an Intel Macbook Professional (we hadn’t but moved to M1 Macs) to do a part of the work and precipitated the UI to turn out to be non-responsive after, so we pivoted to a special migration technique.

Chosen technique

Nearly all of our assets are outlined in two modules: the strings are in :l10n-strings and most different assets are in :slack-kit-resources. So, we proceeded with the next technique:

  1. We first used Android Studio’s Discover & Substitute with the regex ([[|(|]| )R.string. to interchange string useful resource references with $1L10nR.string. and the regex ([[|(|]| )R.(shade|dimen|drawable|font|uncooked|type|attr).sk_  to interchange different useful resource references with $1SlackKitR.$2.sk_.
  2. Then we ran a modified model of the aforementioned script to iterate over recordsdata that used both L10nR or SlackKitR as a useful resource reference and added the mandatory import aliases to them. The wanted dependencies had been added to the modules that had been beforehand relying on both :l10n-strings or :slack-kit-resources transitively via the R class. The undertaking began compiling efficiently once more.
  3. We enabled non-transitive R class by including android.nonTransitiveRClass=true to the basis gradle.properties file, and manually up to date the few remaining useful resource references that had been failing compilation.
  4. As the ultimate step of the migration, we enabled a non-transitive R class dependent optimization that generates the compile time solely R class utilizing the app’s native assets by including android.enableAppCompileTimeRClass=true to the identical gradle.properties file.

Developer expertise

Discoverability enhancements

To assist builders work as successfully within the non-transitive R class world as they did earlier than, we discovered the 4 most typical import aliases within the codebase, by working grep -o -h -r -E 'import w+(.w+)+.R as [a-zA-Z0-9]+R' . | type | uniq -c | type -b -n -r, and added them as live templates that begin with r for discoverability.

Aliases live templates

Not like file and code templates, stay templates can’t be made accessible to everybody who works on the undertaking via Intellij. To work round this, we copied the stay templates file from Android Studio’s templates listing in ~/Library/Software Help/Google/AndroidStudio<model> and checked it into our Git repository underneath the config/templates listing. As a method to import it into Android Studio every time a brand new model is put in, we then used this script, which runs as a part of bootstrapping the native improvement atmosphere, to repeat the live templates file again to the templates listing.

Conference enforcement

Along with the stay templates, we additionally developed three lint checks, that constructed on high of the conventions that we agreed on, to enforced the next:

  1. Solely the native module’s R class could be imported with out an import alias.
    • For instance, slack.uikit.assets.R can’t be imported outdoors :slack-kit-resources with out an import alias.
  2. Import aliases for R courses must be constant throughout the codebase.
    • For instance, slack.uikit.assets.R can solely be imported as SlackKitR, not SKR or the rest.
  3. R courses/assets can’t be referenced utilizing their fully-qualified names, however relatively via import aliases.
    • For instance, getString(L10nR.string.at_everyone) must be used as an alternative of getString(slack.l10n.R.string.at_everyone).

All three lint checks have auto-fixes, so fixing any ensuing lint points is a breeze.

Advantages 

Migrating to non-transitive R class had many advantages, probably the most notable being a ~14% enchancment in incremental construct instances following a useful resource or structure change.

It additionally lowered APK/DEX measurement by ~8.5%, which is about 5.5MB of code!

APK size difference graph

Along with the advantages lined above, which had been anticipated, the transition had some oblique advantages that had been sudden:

  • It uncovered cases the place some assets had been declared in a single module however solely utilized in one other, which allowed us to maneuver them for elevated module cohesion. 
  • It made it simpler to determine the place assets are coming from, which enabled us to slender down inaccessible UI parts and triage them.

Conclusion 

All in all, the migration to non-transitive R class has been a giant success for us, and developer sentiment is constructive because of all its advantages and the tooling we applied as assist.

We suggest you make the transition to non-transitive R class to reap the advantages we’ve outlined if you’re engaged on a multi-module undertaking or plan on introducing modules to your undertaking.

When you like engaged on stuff like this, we’re hiring!