As you know, designing Android app as modules is one of the popular practices nowadays in the Android development world. But this trend comes with some challenges. One of them is Circular Dependency.
For example, I have a navigation module which opens HomeActivity
from Home Feature module. Also, I have to open another activity such as ProductListActivity from products module.
Home feature must include navigation module and navigation module should include HomeFeature if i navigate between activities like the following:
val intent = Intent(activity, HomeActivity::class.java)
This'll cause circular dependency
problem.
For a fastest solution to figure out this problem is creating intents like the following and build navigation system on this approach.
Intent(Intent.ACTION_VIEW).setClassName(PACKAGE_NAME, className)
So my questions are, what other possible problems we'll face off with this navigation approach? Are there another practises to handle navigation in modular android apps?
There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.
Circular dependencies also make code difficult to read and maintain over time, which opens the door to error-prone applications that are difficult to test. If circular dependencies proliferate an architecture, any changes to a single module will likely cause a large ripple effect of errors for others.
In software engineering, a circular dependency is a relation between two or more modules which either directly or indirectly depend on each other to function properly. Such modules are also known as mutually recursive.
A circular dependency occurs when two classes depend on each other. For example, class A needs class B, and class B also needs class A. Circular dependencies can arise in Nest between modules and between providers. While circular dependencies should be avoided where possible, you can't always do so.
Here is my solution for stiuation. This enables the use of explicit intents. You can also apply this approach to single activity application with navigation component with a little modification.
Here is navigation object for module B
object ModuleBNavigator {
internal lateinit var navigationImpl: ModuleBContract
fun setNavigationImpl(navigationImpl: ModuleBContract) {
this.navigationImpl = navigationImpl
}
interface ModuleBContract {
fun navigateModuleA(self: Activity, bundle: Bundle?)
}
}
And here is module B Activity
class ModuleBActivity : Activity() {
companion object {
private const val BUNDLE = "BUNDLE"
fun newIntent(context: Context, bundle: Bundle?) = Intent(context, ModuleBActivity::class.java).apply {
putExtra(BUNDLE, bundle)
}
}
}
And here is app module class to inject navigation impl to module A navigation object
class ApplicationModuleApp : Application() {
// Can also inject with a DI library
override fun onCreate() {
super.onCreate()
ModuleBNavigator.setNavigationImpl(object : ModuleBNavigator.ModuleBContract {
override fun navigateModuleA(self: Activity, bundle: Bundle?) {
self.startActivity(ModuleBActivity.newIntent(self, bundle))
}
})
}
}
And finally you can navigate from module A -> module B with provided implementation
class ModuleAActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... Some code
ModuleBNavigator.navigationImpl.navigateModuleA(this, Bundle())
// .. Some code
}
}
This approact avoids circler dependency and you don't have to use implicit intents anymore. Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With