Speed up your build: Non-transitive R files

Speed up your build: Non-transitive R files

Non-transitive R files became available to all modules in AGP 4.2. Non-transitive R files allow your builds to be incrementally faster and your AAB/APK’s smaller. This post will explain how to implement and build your app with non-transitive R files.

Non-transitive what?

In mathematics, non-transitivity is a property of binary relations that are not transitive relations. This may include any relation that is not transitive, or the stronger property of antitransitivity, which describes a relation that is never transitive.

https://en.wikipedia.org/wiki/Intransitivity

Does that math quote helps us at all? Probably not. Let’s try this: If A depends on B and B depends on C, A does not know about C. In the world of Android, non-transitive R files:

Non-transitive R classes enable namespacing of each library’s R class so that its R class includes only the resources declared in the library itself and none from the library’s dependencies, thereby reducing the size of the R class for that library.

This blog post is not about how the R file works and the history involved, if you are interested in the background I would recommend this blog post here.

Non-transitive modules

If module A depends on B and B depends on C. How can we reference resources? Let’s see:

Module A can reference its own resources (like normal):

R.string.hello_world

Module A can reference Module B resources (fully qualified package):

com.my.moduleB.R.string.hello_neighbour

Module A cannot reference Module C‘s resources.


You can use non-transitive R classes with the Android Gradle plugin (>4.2) to build faster builds for applications with multiple modules. … This leads to more up-to-date builds and the corresponding benefits of compilation avoidance.

https://developer.android.com/studio/releases#refactor-nontransitive-rclasses

Benefits of non-transitive R classes?

  • Decrease in AAB/APK size, including DEX field reference count
  • Decrease in incremental build speed as less dependencies can be included when changes made
  • Increase in modularity, dependencies become more explicit
  • Decrease in complexity, resources cannot come from transitive dependencies
  • Decrease in full build duration as less code included

Let’s do it!

You can turn this setting on for yourself by editing your /gradle.properties file to include:

android.nonTransitiveRClass=true

You may have read about android.namespacedRClass in the past. Note that in August 2020; android.namespacedRClass property was renamed to android.nonTransitiveRClass. As shown here.

OR

You can use the Android Studio automated refactor, this will turn on the above setting and search through your modules attempting to fully qualify any R references it finds.

Note that this refactor was added in Android Studio Arctic Fox | 2020.3.1 “automated refactoring for non-transitive R files”. As shown here.

Android Studio > Refactor > Migrate to Non-Transitive R Classes…

This auto-refactor is not a silver bullet, it may get some resource references wrong, i.e. it’ll add the wrong package name before the R class or it will fail to choose one at all, or it’ll add one that your module doesn’t have a dependency upon. In my experience these issues are caught at build time, and you can choose from the three solution steps below, to finish off what the refactoring tool started:

Once that is done, you will have to build your project and fix the errors. Errors can come in a few formats:

  1. You use a resource from another module.
    Fix: add the fully qualifed package, or import the R file, or (Kotlin only) use an alias.

    // Fully qualifed package
    val foo = com.my.moduleB.R.string.hello_neighbour
    // Import then use string.hello_neighbour
    import com.my.moduleB.R
    // Alias then use RB.string.hello_neighbour
    import com.my.moduleB.R as RB

  2. You use a resource from another module, but you do not declare as dependency on that module.
    Fix: Do as #1 but also add the dependency in gradle.

    implementation project(":libraries:moduleB")
  3. You use a resource from another module, but you do not declare a dependency on that module, and you do not want to declare a dependency.
    Fix: The solution here is to either generate a reference and use the same name or copy/create a new resource of what you want.
Generate a reference in your module, when the implementation is in another “transitive” module.

Conclusion

That’s all there is too it, which is quite easy for me to say but in practice depending on the size of your project, you may have many hours ahead of you, debugging/searching where your resources come from and what is the correct fully qualified package name to use. I’d highly recommend for any new projects you turn on nonTransitiveRClass before you have to deal with the fallout down the line.