Navigation/StartActivity in a multi-module Android project

Ali Doran
3 min readOct 29, 2023

I’m working on a project that has not migrated to the Jetpack Compose yet.
The project used a library to start an activity on the other module.
I’m not a fan of using an extra library for some primary actions, such as StartActivity!
I have some reasons, such as library deprecation, changing needs according to the Gradle version, and hard changing on the structure.
So, let’s start.
First of all, I prefer to use feature projects. The app module had imported all modules.
On the other hand, each module imports the Base Module.

Okay
Let’s call the startActivity method.

startActivity(Intent(this, SecondActivity))

Ops. Where is the Activity in the Feature Module 2? If you look at the diagram, both Feature Modules are at the same level.

What is the Android Suggestion?
Fortunately, we have the following method to call the startActivity on the other module.

try {
val intent = Intent(
this,
Class.forName("com.alidoran.second_library.SecondActivity")
)
startActivity(intent)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}

Okay. Let’s see.
Calling in this way is not appropriate for the whole project. The Base Module can help us to create a common method.

fun activityNavigator(context: Context, dest: String){
try {
val intent = Intent(
context,
Class.forName(dest)
)
context.startActivity(intent)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}

Okay. We can put all addresses in an enum and put it on the navigator.
It’s good but not enough. Why?
Hardcoding. I hate of hardcode.
What if the programmer changes the class name?
Ops
The project will face a run time error.

Where do we have access to all modules’ class names? App module. Let’s use it.

Ops. Circular dependency. Search on the internet.
Gaël Marhic’s article may be the best document.
Ah, plugin. I prefer not to replace a dependency with a plugin.

What is the Google suggestion? Navigation
It’s not a good idea for my project.
Let’s make a way.

Import the Base Module into the App Module.

Dependency injection is my favorite. We use it to reach this goal.

Base Module

1- Make an Enum class for each destination class.

enum class ActivitiesNameEnum {
FirstActivityEnum,
SecondActivityEnum,
ThirdActivityEnum
}

2- Make a data class that includes the activity address and activity string address.

data class ActivityAddress(
val requestedActivity: ActivitiesNameEnum,
val activityAddress: String
)

3- Make a common startActivity method

fun startActivity(context: Context, classNameEnum: ActivitiesNameEnum) {
val address = classAddresses.first { it.requestedActivity == classNameEnum }.activityAddress
try {
val intent = Intent(
context,
Class.forName(address)
)
context.startActivity(intent)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}

App Module

Make an address generator

object AddressGenerator {
fun generateAddressList(): List<ActivityAddress> {
val list = ArrayList<ActivityAddress>()
ActivitiesNameEnum.values().forEach {
val classAddresses = ActivityAddress(it, getClassName(it))
list.add(classAddresses)
}
return list
}

private fun getClassName(classNameEnum: ActivitiesNameEnum) = when (classNameEnum) {
ActivitiesNameEnum.FirstActivityEnum -> FirstActivity::class.java.name
ActivitiesNameEnum.SecondActivityEnum -> SecondActivity::class.java.name
ActivitiesNameEnum.ThirdActivityEnum -> ThirdActivity::class.java.name
}
}

Define the address list injection

@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Singleton
@Provides
fun providesAddressString(): List<ModuleNavigator.ActivityAddress> = generateAddressList()
}

Let’s use it

@AndroidEntryPoint
class FirstActivity : AppCompatActivity() {

@Inject
lateinit var moduleNavigator: ModuleNavigator

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityFirstBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.btnGoToSecondActivity.setOnClickListener {
moduleNavigator.startActivity(this, SecondActivityEnum)
}

binding.btnGoToThirdActivity.setOnClickListener {
moduleNavigator.startActivity(this, ThirdActivityEnum)
}
}
}

Explanation

All classes have access to the Base Module methods. We define Navigator and enum class there.
On the other hand, the App module has access to all modules.
So we can generate the correct address by accessing the App Module to classes.
How do we give access to the app module in other modules? Dependency Injection

GitHub: https://github.com/alidoran/MultiModuleNavigation.git

--

--