It is a non-transitive R class world
5 min read
After Duplo modularization, we seen that the duty producing a transitive R class was taking a major period of time to execute. To eradicate this activity 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.
Should you’re not acquainted with nonTransitiveRClass, beforehand generally known as namespacedRclass, it’s an Android Gradle Plugin flag that permits namespacing R courses so that each module’s R class solely contains sources declared within the module itself, and never sources from the modules or libraries it is dependent upon. This flag is enabled by default for brand new initiatives since Android Studio Bumblebee.
On this publish, we’ll stroll via how we transitioned to non-transitive R class and among the advantages — each anticipated and surprising — that we noticed.
Establishing conventions
Kotlin
Since sources 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 title for R courses to reference sources, 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 characteristic and never accessible in Java. Nevertheless, contemplating that solely 4% of our codebase was nonetheless in Java, we agreed that utilizing the fully-qualified title to reference sources 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 have been planning on utilizing Android Studio’s Refactor > Migrate to Non-Transitive R Classes characteristic to refactor all R references to be fully-qualified, after which use this script to undergo the refactored information 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 night time on an Intel Macbook Professional (we hadn’t but moved to M1 Macs) to do a part of the work and triggered the UI to turn into non-responsive after, so we pivoted to a distinct migration technique.
Chosen technique
Nearly all of our sources are outlined in two modules: the strings are in :l10n-strings
and most different sources are in :slack-kit-resources
. So, we proceeded with the next technique:
- We first used Android Studio’s Discover & Change with the regex
([[|(|]| )R.string.
to switch string useful resource references with$1L10nR.string.
and the regex([[|(|]| )R.(coloration|dimen|drawable|font|uncooked|model|attr).sk_
to switch different useful resource references with$1SlackKitR.$2.sk_
. - Then we ran a modified model of the aforementioned script to iterate over information that used both
L10nR
orSlackKitR
as a useful resource reference and added the required import aliases to them. The wanted dependencies have been added to the modules that have been beforehand relying on both:l10n-strings
or:slack-kit-resources
transitively via the R class. The venture began compiling efficiently once more. - 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 have been failing compilation. - 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 sources 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 operating grep -o -h -r -E 'import w+(.w+)+.R as [a-zA-Z0-9]+R' . | kind | uniq -c | kind -b -n -r
, and added them as live templates that begin with r for discoverability.
In contrast to file and code templates, reside templates can’t be made accessible to everybody who works on the venture via Intellij. To work round this, we copied the reside templates file from Android Studio’s templates listing in ~/Library/Software Help/Google/AndroidStudio<model> and checked it into our Git repository below the config/templates listing. As a technique 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 growth surroundings, to repeat the live templates file again to the templates listing.
Conference enforcement
Along with the reside templates, we additionally developed three lint checks, that constructed on high of the conventions that we agreed on, to enforced the next:
- Solely the native module’s R class could be imported with out an import alias.
- For instance,
slack.uikit.sources.R
can’t be imported exterior:slack-kit-resources
with out an import alias.
- For instance,
- Import aliases for R courses ought to be constant throughout the codebase.
- For instance,
slack.uikit.sources.R
can solely be imported asSlackKitR
, notSKR
or anything.
- For instance,
- R courses/sources can’t be referenced utilizing their fully-qualified names, however relatively via import aliases.
- For instance,
getString(L10nR.string.at_everyone)
needs to be used as an alternative ofgetString(slack.l10n.R.string.at_everyone)
.
- For instance,
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, essentially the most notable being a ~14% enchancment in incremental construct instances following a useful resource or structure change.
It additionally decreased APK/DEX measurement by ~8.5%, which is about 5.5MB of code!
Along with the advantages lined above, which have been anticipated, the transition had some oblique advantages that have been surprising:
- It uncovered cases the place some sources have 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 sources are coming from, which enabled us to slim 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 due to all its advantages and the tooling we carried out as help.
We advocate you make the transition to non-transitive R class to reap the advantages we’ve outlined in case you are engaged on a multi-module venture or plan on introducing modules to your venture.
Should you like engaged on stuff like this, we’re hiring!