Designing Modular Apps - Circular Dependency problem in navigation
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}
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?
android
add a comment |
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?
android
add a comment |
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?
android
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?
android
android
edited Jan 4 at 11:21
Fantômas
32.9k156491
32.9k156491
asked Jan 4 at 10:34
savepopulationsavepopulation
8,02543753
8,02543753
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
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.
add a comment |
For a different approach -actually similar which I mentioned in my question- which implementation belongs to sanogueralorenzo
Create a loader
which laods the module classes
const val PACKAGE_NAME = "com.example.android"
private val classMap = mutableMapOf<String, Class<*>>()
private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T
internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrReturnNull()
Create a String extension function
for loading Intents
dynamically.
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)
internal fun String.loadIntentOrReturnNull(): Intent? =
try {
Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
} catch (e: ClassNotFoundException) {
null
}
Create another String extension function
for loading Fragments
dynamically
internal fun String.loadFragmentOrReturnNull(): Fragment? =
try {
this.loadClassOrReturnNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
Create an Feature
interface for your feature implementations
interface Feature<T> {
val dynamicStart: T?
}
I assume that you have a Messages
feature. Implement your dynamic feature interface
object Messages : Feature<Fragment> {
private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"
override val dynamicStart: Fragment?
get() = MESSAGES.loadFragmentOrReturnNull()
}
And finally use it in another module without dependency
Messages.dynamicStart?.let {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fl_main, it)
.commit()
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54037244%2fdesigning-modular-apps-circular-dependency-problem-in-navigation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
add a comment |
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.
add a comment |
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.
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.
answered Jan 8 at 6:29
malik_cesurmalik_cesur
1235
1235
add a comment |
add a comment |
For a different approach -actually similar which I mentioned in my question- which implementation belongs to sanogueralorenzo
Create a loader
which laods the module classes
const val PACKAGE_NAME = "com.example.android"
private val classMap = mutableMapOf<String, Class<*>>()
private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T
internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrReturnNull()
Create a String extension function
for loading Intents
dynamically.
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)
internal fun String.loadIntentOrReturnNull(): Intent? =
try {
Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
} catch (e: ClassNotFoundException) {
null
}
Create another String extension function
for loading Fragments
dynamically
internal fun String.loadFragmentOrReturnNull(): Fragment? =
try {
this.loadClassOrReturnNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
Create an Feature
interface for your feature implementations
interface Feature<T> {
val dynamicStart: T?
}
I assume that you have a Messages
feature. Implement your dynamic feature interface
object Messages : Feature<Fragment> {
private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"
override val dynamicStart: Fragment?
get() = MESSAGES.loadFragmentOrReturnNull()
}
And finally use it in another module without dependency
Messages.dynamicStart?.let {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fl_main, it)
.commit()
}
}
add a comment |
For a different approach -actually similar which I mentioned in my question- which implementation belongs to sanogueralorenzo
Create a loader
which laods the module classes
const val PACKAGE_NAME = "com.example.android"
private val classMap = mutableMapOf<String, Class<*>>()
private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T
internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrReturnNull()
Create a String extension function
for loading Intents
dynamically.
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)
internal fun String.loadIntentOrReturnNull(): Intent? =
try {
Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
} catch (e: ClassNotFoundException) {
null
}
Create another String extension function
for loading Fragments
dynamically
internal fun String.loadFragmentOrReturnNull(): Fragment? =
try {
this.loadClassOrReturnNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
Create an Feature
interface for your feature implementations
interface Feature<T> {
val dynamicStart: T?
}
I assume that you have a Messages
feature. Implement your dynamic feature interface
object Messages : Feature<Fragment> {
private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"
override val dynamicStart: Fragment?
get() = MESSAGES.loadFragmentOrReturnNull()
}
And finally use it in another module without dependency
Messages.dynamicStart?.let {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fl_main, it)
.commit()
}
}
add a comment |
For a different approach -actually similar which I mentioned in my question- which implementation belongs to sanogueralorenzo
Create a loader
which laods the module classes
const val PACKAGE_NAME = "com.example.android"
private val classMap = mutableMapOf<String, Class<*>>()
private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T
internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrReturnNull()
Create a String extension function
for loading Intents
dynamically.
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)
internal fun String.loadIntentOrReturnNull(): Intent? =
try {
Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
} catch (e: ClassNotFoundException) {
null
}
Create another String extension function
for loading Fragments
dynamically
internal fun String.loadFragmentOrReturnNull(): Fragment? =
try {
this.loadClassOrReturnNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
Create an Feature
interface for your feature implementations
interface Feature<T> {
val dynamicStart: T?
}
I assume that you have a Messages
feature. Implement your dynamic feature interface
object Messages : Feature<Fragment> {
private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"
override val dynamicStart: Fragment?
get() = MESSAGES.loadFragmentOrReturnNull()
}
And finally use it in another module without dependency
Messages.dynamicStart?.let {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fl_main, it)
.commit()
}
}
For a different approach -actually similar which I mentioned in my question- which implementation belongs to sanogueralorenzo
Create a loader
which laods the module classes
const val PACKAGE_NAME = "com.example.android"
private val classMap = mutableMapOf<String, Class<*>>()
private inline fun <reified T : Any> Any.castOrReturnNull() = this as? T
internal fun <T> String.loadClassOrReturnNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrReturnNull()
Create a String extension function
for loading Intents
dynamically.
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className)
internal fun String.loadIntentOrReturnNull(): Intent? =
try {
Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) }
} catch (e: ClassNotFoundException) {
null
}
Create another String extension function
for loading Fragments
dynamically
internal fun String.loadFragmentOrReturnNull(): Fragment? =
try {
this.loadClassOrReturnNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
Create an Feature
interface for your feature implementations
interface Feature<T> {
val dynamicStart: T?
}
I assume that you have a Messages
feature. Implement your dynamic feature interface
object Messages : Feature<Fragment> {
private const val MESSAGES = "$PACKAGE_NAME.messages.presentation.MessagesFragment"
override val dynamicStart: Fragment?
get() = MESSAGES.loadFragmentOrReturnNull()
}
And finally use it in another module without dependency
Messages.dynamicStart?.let {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fl_main, it)
.commit()
}
}
answered Apr 5 at 7:45
savepopulationsavepopulation
8,02543753
8,02543753
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54037244%2fdesigning-modular-apps-circular-dependency-problem-in-navigation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown