Navigation in android multi-modular project

The context of this writing is to exclusively discuss my way of implementing navigation in a multi-modular android project.

It is advisable that you are familiar with the Android Navigation Component and Deep Linking to understand this.

Apparently, I ran into a situation where I was considering single responsibility principle in my project set-up and the thought of having more than one module struck me. To make it even smoother, I decided that it would be nice to have a dedicated module for navigation.

After reading some proffered solutions online like the one in the link below. I thought of how to implement mine in a better way

https://itnext.io/android-multimodule-navigation-with-the-navigation-component-99f265de24

Before I move on to my implementation, I will like to highlight the challenge I have with the solution in the link above. Even as I agree that it was an intuitive approach and also a starting point for me. You have to create a decoy graph for every graph you create in other modules in your dedicated navigation module. This means that if you have 100 modules you will have to create 100 graphs decoys.

Ok, let’s get to my implementation. I am of the opinion that the navigation module should be independent of other modules and still be able to serve them equally without any bias. The diagram below illustrate the relationship between the modules.

Relationship between four modules

From the diagram, you will notice that all other modules depends on the navigation module and not the other way round at any point.

interface Navigator{
var navHostFragment:NavHostFragment
var navController:NavController
fun goto(destination: Int)
fun goto(uri: Uri)
fun goto(destination: Int, graphId:Int){
val myNavHostFragment: NavHostFragment = navHostFragment
val inflater = myNavHostFragment.navController.navInflater
val graph = inflater.inflate(graphId)
myNavHostFragment.navController.graph = graph

goto(destination)
}
fun goto(uri: Uri, graphId:Int){
val myNavHostFragment: NavHostFragment = navHostFragment
val inflater = myNavHostFragment.navController.navInflater
val graph = inflater.inflate(graphId)
myNavHostFragment.navController.graph = graph
goto(uri)
}

fun graphSpecificNavigation(graphId:Int)
}

The navigator interface resides in the navigation module with methods on how to navigate from one fragment to another using uri, fragment resource Id and also navigating from one graph to another.
The navHostFragment and navController are initialised in the entry activity which is the Main Activity in this project as below.

class MainActivity() : AppCompatActivity(), Navigator {
override lateinit var navHostFragment: NavHostFragment
override lateinit var navController:NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

navController = findNavController(R.id.fragment)
navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment

}

override fun goto(destination: Int) {
//Using fragmentId
navController.navigate(destination)
}

override fun goto(uri: Uri) {
//Using uri directly
navController.navigate(uri)
//or using NavDeepLinkRequest
val request = NavDeepLinkRequest.Builder
.fromUri(uri)
.build()
navController.navigate(request)

}

override fun graphSpecificNavigation(graphId: Int) {
val myNavHostFragment: NavHostFragment = navHostFragment
val inflater = myNavHostFragment.navController.navInflater
val graph = inflater.inflate(graphId)
navController.graph.addAll(graph)
}
}

Basically, navigation is done with the navigator using deep linking for movement between fragment of different graphs. So what I mean is that, it is possible to move from a fragment in home module graph to a fragment in dashboard module graph. This is possible by ensuring that the fragments are both in a common graph which is the navigation graph in the navigation module as below. Needless to say that using NavDirections is also possible with safe args.

Embedding fragments from different graph in navigation module

Kindly ignore the red highlight of compilation error, I think that might be android studio issue, the project actually compiles.
An illustration of navigating from the home fragment(home module)to the dashboard fragment(dashboard module) is shown below.

/**
* A simple [Fragment] subclass.
* Use the [HomeFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class HomeFragment : Fragment() {
lateinit var binding: FragmentHomeBinding


override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}

override fun onStart() {
super.onStart()
binding.btn.setOnClickListener {
(requireActivity() as Navigator).goto(Uri.parse("myApp://dashboard"), R.navigation.main_navigation)
}
}
}

You can go through the project to see more of the navigation control and also the test with the link below.
https://github.com/darothub/multi-module-navigation/tree/master

Thanks.

A versatile software engineer