Choco is all about making things as efficient and streamlined as possible. So when we saw some confusion around ownership and responsibility of code between individual squads, we knew we had to act. That’s when we decided to commit to modularization— a journey we started a year ago while working from a single Android codebase.
Since embarking on this adventure, we’ve encountered several obstacles. The biggest challenge we’ve faced while trying to build a multi-module application? The act of navigating between Feature A to Feature B without creating a dependency between them.
In this article, we’ll walk you through our initial approach, why it didn’t work, and how our second approach ultimately led us to success. Let’s dive in.
Our initial approach to multi-module navigation
We first implemented the Java Reflection approach, which examines or modifies a class’s runtime behavior. At the time, this made the most sense, because any module we develop has a dependency on a common module. We constructed a common:navigation module that has navigation object classes and Parcelable argument data classes.
However, this approach proved to be inconvenient for everyone in the team. In addition to the cost of Java Reflection in runtime, writing the classpath as a string wasn’t very “clean.” As a result, the app became more prone to errors. This would have been especially problematic if another developer had accidentally changed the path of one feature to another without changing the string path in the navigation file. To put it simply, the app would crash – a major issue.
Second time’s the charm
Our second approach was based on the SOLID principle - D for Dependency Inversion. For this, we used well-known Dependency Injection Libraries such as Koin, Dagger, and Hilt – just like every other Android project out there right now. Here’s what our process looked like:
We preserved common:navigation and included the interfaces and parameter classes instead of the actual navigation class implementation.
We’d like to use this navigation to move from the main page (Feature A) to settings (Feature B). But because we’re in Feature A, we don’t know how the actual navigation works; we only have access to the Navigation interface via the common:navigation module. The actual navigation implementation, on the other hand, is housed in each feature, as shown below.
The above code resides in common:navigation
In the setting feature module, we will apply the implementation of this navigation interface, as shown below.
Provide this to your DI and you can inject it into other features as needed.
That’s it! This is how successful navigation between features should look. Our users can now navigate from one screen to another quickly and easily.
The end results
Thanks to our optimized approach, writing code is much cleaner. Technically, it isn’t as challenging as the first, and the best part is that it’s less error-prone – the app is less likely to crash if someone accidentally makes changes. Hope that this guide has been informative for fellow Android developers. Happy coding!