Jan 11, 2024, Mobile

Composing Navigation in multi-module apps

Łukasz Szczepański Android Developer
Composing navigation
Ok, so we’ve been using compose for a while now. Basically every new app we create here in iteo has the UI written in this technology. Also, every redesign or refactor that we’re doing is now committed to Compose. We love this tech however it can be troublesome - especially in refactor and migration from xml files. There were many articles about migrating to Compose and most of them are helpful. However while researching the compose navigation we couldn’t find anything covering that topic in a multi module app. Here we’d like to focus on that specific case.

Introduction to Compose in iteo

As a rule we create our apps in a multimodular form. It has a number of advantages from a programmer’s standpoint. Separation of concerns and faster builds are among the most important. Also it is often easier to read. Every major part of the program is divided and encapsulated. We usually implement the following approach: separate data layer, business logic in domain module and our UI part of the code. It’s worth mentioning that, depending on the code complexity, our UI layer is also separated to either UI flows or even separate screens. 

Advantages of Multi-Modular App Development

What are the advantages from a business standpoint? Separation of concerns means that while working on many aspects of the program we as developers are independent of each other. It causes much less conflicts when merging the code in the repository which also means faster development. It also allows the creation of a feature delivery approach. You can deploy an application once the new features are ready to be released. In the meantime, you are free to freeze the work on other features or focus on bug fixing. 

Speaking of things done quickly: the build time also shortens. It adds up during a workday. Let’s assume that a developer runs the building process like ten times a day. When the building process lasts something like three minutes that means a half an hour waiting time for a project to build per workday. What if that time can be shortened to one minute or thirty seconds? It could turn out that it’s a whole daily meeting for free! 😀 Savings done using the multi module architecture accumulate during the development time.

What is even more interesting is that a multi module approach also gives a method of developing apps for specific users. One project can contain a logic for many types of users. If let’s say we have a shopping app, we can prepare modules for a seller and a customer. They both work on the same set of data but in a different way. A taxi driver and a passenger can have the same app installed but with different views and possibilities thanks to multi modular architecture. A specific module of logic can also be extracted and created as a library to use in another app. Awesome, right? We can create a module containing common UI elements such as buttons, text fields and so on and they can be reused in another app later on saving a bunch of development time.

Case Study: Data and Domain Modules with UI Modules

Let’s look at a real-life scenario when we have data and domain modules and also let’s say two UI modules, login and home. The UI modules depend on logic contained in the domain which depends on logic contained in the data module. Again the login and home don’t know about the existence of the data module and once it and the domain module build, they can build parallelly. That gives us the serious topic to discuss: how to navigate in Compose between screens that are in different modules? 

diagram

The topic is actually older than the Compose itself. When first introduced, Jetpack navigation library provided an easy way to navigate between fragments and pass arguments between them. It was probably the cornerstone for “one activity – many fragments” architecture. The main drawback of this approach showed up quickly when using a multi module app structure. Because if you separate your UI modules to a module-per-feature style then how can you provide a fragment id that is in another independent UI module?

One of the strategies to mitigate this issue was to create a separate navigator module that will contain all of the ids of fragments and graphs in xml. Also you’d need to provide empty xml files for nav graphs of ui modules. And lastly all of the UI modules (including the app module) need to “know” of the existence of this navigation module. It will now play the role of an interface between modules.

diagram

Applying Compose Navigation to Multi-Module Architecture

A very similar approach can be applied to the Compose navigation. Again we create a navigation module in which we create a sealed class representation of navigation destinations.

navigation destination

We can now simply navigate between screens that are in the independent modules. If you want to pass the arguments – you can do it like this:

sms navigation code

Handling Nested Navigation in Compose

Ok. But what about nested navigation? You’ll need a representation of navigation graphs (like empty xml files in jetpack navigation) and here comes a nested graph interface to the rescue.

navigation nested interface code

As you can see we provide NavHostController and NavGraphBuilder to inform the navigation mechanism how to navigate within the screen and also how to create nav arguments or even if to disable back press. In the body of the create() function we implement the composable screens along with the route provided by the previously mentioned sealed classes. We can now gather all those nested graphs into one place in the app module (which by the way “knows” about every UI module).

navigation provider code

Incorporating Dependency Injection for Navigation

Now let’s add some dependency injection mechanism to the picture. Every module has some provider for the nested graph, and the main app module has a nested navigation provider presented above. And now the final touch: the one composable to rule them all – AppNavGraph.

main navigation graph

This is the composable which you call in your main activity’s set content block. You can define here your main NavHost and its start destination. All of the inner navigation is performed within nested graphs. And that’s basically it! Your whole flow for navigation with Compose for a multi module app with nested graphs. Not quite scary as it sounds, right? 🙂

Conclusion: Embracing Multi-Module Navigation in Compose

Let’s now recap what we’ve learned here today. First of all: multi module apps have some serious advantages from both developers and business standpoints. The probability of working with that architecture grows as more and more companies decide to do some “big rewrites” in order to make their applications build faster. Also the Jetpack Compose library has numerous fans among the developers community and most probably your next redesign will be done in this technology. What’s probably the most important here is you need not to use external libraries for navigation in Compose.

Share