Android Modules Crash Course

This year in Google IO event, app modules has been one of the main features in Android development tools Google couldn’t stress enough. With the latest improvements in the toolchain, they are now #1 solution for a scalable development experience. In my first years with Android, I ignored them entirely and now that I have seen the power granted upon me, nobody can make me return to those years.

You are already using them in your project!

You heard it right! Go on, create a brand new application project in Kotlin and take a look at your app’s build.gradle file.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version'
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

Let’s explain each of these items.

  • implementation fileTree(dir: 'libs', include: ['*.jar'])

This line says every jar file in a directory named libs in the root of your project will become a module to be compiled with your app.

  • implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
  • implementation 'com.android.support:appcompat-v7:28.0.0'
  • implementation 'com.android.support.constraint:constraint-layout:1.1.3'

These lines add a total of 3 modules to your project, excluding the inner dependencies.

  • testImplementation 'junit:junit:4.12'

This line adds one module that can only be used for your unit tests, hence the keyword testImplementation.

  • androidTestImplementation 'com.android.support.test:runner:1.0.2'
  • androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }

These lines adds 2 modules that can only be used for your instrumentation tests.

So, we create them in our build.gradle?

Actually, no. You depend on them in your gradle files. Modules in a project have no implicit hierarchies unless you define a dependency layout in your gradle files.

You create them by either using existing third party dependencies like shown above, or adding a new module directory to your project. Each module can only refer to the code it encapsulates and the public classes from its dependencies. In a project, a module is a unit of isolation, a boundary to make sure a bunch of code files are self sufficient.

Of course, 3rd party dependencies are not the only way to have modules in a project. You can also create brand new ones, each having its own dependencies from the outside world or inside your project.

Why should I have many custom modules when a single one is enough?

What your are describing is called a monolith, as in one big chunk of code, and a perfectly valid solution for small to medium size projects. However if you have multiple projects that share code or a project with multiple target variants (i.e free and premium) or just a really big, code-heavy project, you will eventually want to avoid duplication and fully benefit from a well defined module structure.

A monolith quickly becomes a bottleneck and causes:

  • unnecessary build cache invalidation
  • no multi-threading during builds
  • larger app bundle sizes
  • code duplication over multiple projects

When you change a single source file in a module, that module is marked as dirty and will be recompiled if you rebuild your app. In a multi module project, between two consecutive builds, modules with no changes are cached and omitted from recompilation entirely. This means, testing small changes shouldn’t suffer from very long build times. The impact here is really significant and Google showed some numbers which made the crowd cry in theirs seats during the talk.

Modules are built in parallel thanks to the latest improvements in gradle. This is only possible if the built modules do not depend on each other. As an app developer, you already know which parts of your app have no direct access to other parts. Modularizing those parts will enable you to benefit from all the CPU cores in your developer machine.

If your project is built for multiple targets such as different devices or paid/free clients each having its own feature set, not having a modular project will cause your APKs go large very quickly. In a modular project, if a module is not a part of a target, it is automatically omitted from the final build, keeping your APK sizes smaller.

Just like 3rd party dependencies, you can treat your own projects as dependencies for your other projects, making it possible to put them in different source control systems without worrying about keeping multiple copies in sync. When one project needs code from the other, just fetch the relevant module from the source control into your root and your are done.

Bonus: Google announced dynamic modules in the latest IO event. With dynamic modules, you are able to update parts of your app without ever leaving your app. I’m already in love!

I’m convinced. What do I do?

Actually, very little. Most of the benefits I talked about comes free and requires no extra configuration if you are using the latest build tools. The only part that needs your attention is your own custom modules. Android Studio makes it very easy to create modules in a project, just with a few mouse clicks. Here is a very quick tutorial for people in a hurry:

1. Create an application (or library) project, it will come with a single module called app (or library).

2. Right click app.

3. Click Open Module Settings in the drop down menu.

4. Go to modules section and click the + (plus sign).

5. Select Java Library. (the list scrolls)

6. Specify your module properties, then submit.

7. Go to Dependencies section.

8. Select the module you want to add this new module as a dependency.

9. Below the title Declared Dependencies, click the + (plus sign).

10. Select Module Dependency from the drop down menu.

11. Choose which modules you want to depend on.

12. Select scope if necessary. (implementation is default)

13. Click OK twice and sync project.

14. Commit to source control.

Conclusion

Modules are great! Have fun with them and share your use cases if you come up with something creative. If you are curious about a set of known good practices, check out the talks from the event. Links are in the references section. Have a great day!

References

  1. Build a Modular Android App Architecture by Yigit Boyar & Florina Muntenescu

  2. What’s New in the Android Studio Build System by Jerome Dochez, Xavier Ducrohet & Leo Sei