How to navigate to activity located in different module - android

I am working on application which is divided by features into modules.
App structure looks like so:
app (application)
MainActivity
MainApplication
featureOne (module)
FirstActivity
featureTwo (module)
SecondActivity
Feature modules cannot depend on each other, but I can edit them freely.
My goal is to navigate from FirstActivity to SecondActivity.
I cannot use startActivity(Intent(com.example.featureTwo.SecondActivity)), because SecondActivity class is not visible to FirstActivity(different independent module).
Question is what is the proper way to navigate from FirstActivity to SecondActivity?
I was thinking about using:
Broadcast - I would send broadcast from FristActivity and register broadcast receiver, in featureTwos manifest. From broadcastReceiver I would launch SecondActivity.
Deep Links - Similar to the broadcastReceiver.
Creating function in application class and an enum within app package containint activies I want to launch. I would call this function whenever I want to launch activity like so: launchActivityFromDifferentModule(EnumWithActivities.SecondActivity).
Which method should I use, which one I shouldn't and why?

have many approaches for lunching activity in another module
Reflection
Props: easily navigate to another class without define class in the app module.
Cons: reflection is running at runtime.
DeepLink
Props: create a unique link for any item in another module like openFragmentA, addCreditToUserAccount, etc.
Cons: not have a serious concern.
Broadcast
Props: declare determined activity in the app module (if have nav module setup inside).
Cons: need more time to change and define another module.
Conclusion
Deeplink is suitable for a dynamic feature (onDemand feature)
Broadcast is suitable for a main feature, permanently feature
Reflection is suitable when not confident for a feature like A/B test feature

The approach Google recommends by the moment is to use reflection to navigate between feature modules.
In my case, I have created a new navigation module to hosts the different classes of navigation. My App module depends on this module so every feature module can access the navigation.
I use a file with functions to instantiate Intent's through reflection:
private const val PACKAGE_NAME = "com.your_app_package_name"
private fun intentTo(className: String): Intent =
Intent(Intent.ACTION_VIEW).setClassName(PACKAGE_NAME, className)
internal fun String.loadIntentOrNull(): Intent? =
try {
Class.forName(this).run { intentTo(this#loadIntentOrNull) }
} catch (e: ClassNotFoundException) {
null
}
Note as the loadIntentOrNull String extension is internal, it will be only available in the navigation module.
Then you can create objects for each module to handle the navigation.
object SearchNavigation : Navigation {
private const val SEARCH = "com.your_search_activity_package"
override fun getIntent(): Intent? = SEARCH.loadIntentOrNull()
}
The Navigation interface just defines the getIntent method:
interface Navigation {
fun getIntent(): Intent?
}
Then you can inject this Navigation object in every module as your feature module depends on the app module, and at the same time, it depends on the navigation module.
Following your structure, it would be something like this
app (application)
MainActivity
MainApplication
featureOne (module)
FirstActivity
featureTwo (module)
SecondActivity
navigation (module)
featureOneNavigation (object)
featureTwoNavigation (object)
The app module will depend on the navigation module. featureOne and featureTwo will depend on app (because they are feature modules) and will have access to the navigation.
You can also avoid creating a new module for the navigation and implement this just in the app module.
This method can be also applied to instantiate Fragments, so you can have your host activity with a DrawerLayout in your app module, and each of its Fragments in a different module.

Add your modules in the build.gradle file
android {
...
}
dependencies {
..
implementation project(':featureOne')
implementation project(':featureTwo')
}
Note:
Libraries/modules should be developed in a way that they are independent, and they should be divided by functionality.

Related

Unexpected activity is returned from fragments context in nested navigation graph

I'm building a modular project with 3 relevant modules for this problem. app, core and launch.
The app module depends on all other modules, core module depends on neither app nor launch. And launch depends on core.
I'm using Jetpack's Navigation component with a nav_app acting as the app's root navigation graph and nav_launch included in it acting as the launch feature's navigation graph. (Noting that nav_launch is located in the launch module and app_nav is located in the app module.
Each module has its own root Activity. The problem is that in LaunchFragment, nav_launch's starting destination when I ask for context it returns an instance of MainActivity which is the root activity for nav_app navigation graph, shouldn't it return LaunchActivity which is the direct parent of it?
What is causing this behavior and how can I access LaunchActivity?

#UserScope with dagger-android

I have been forcing myself to use dagger-android for my new project, in order to reduce all the boiler plate for subComponents for activities and fragments.
But struggling with getting my #UserScope deps to be injected in those activities.
Reading up dagger-android it seems to support this kind of object graph:
Application -> Activity -> Fragment -> Sub-fragments
In my case I need a UserScope to sit between Application and Activity. i.e
|-> Activity -> Fragment -> Sub-fragments`
Application -> User - |
|-> Activity -> Fragment -> Sub-fragments`
I was wondering if there is way to achieve this WITH using ContributesAndroidInjector along with a custom subcomponent.
Any advice.
Couple of similar threads:
Make a UserScope with Dagger2 that lives for multiple activities and fragments
https://github.com/google/dagger/issues/1267
If you want to use AndroidInjection and ContributesAndroidInjector I don't think that is possible, if not in some degenerate way.
To be more precise, when you use AndroidInjection the code is getting the Application instance from your class, and then using the HasActivityInjector the App has.
Therefore the activities' sub-components must be sub-components of the Component which injected the Application class at startup.
And when the Application class is created you can't really be in a scope different than an Application/singleton scope.
Probably the question is: what is in the User scope (and not in the application scope) and where does that come from?
If you can get that when the app starts before starting any activities, then just merge the application and the user components.
If you 'create' then user scope from some data you get from, say, an activity, then you'll have the activities pass some data using intents when they launch each other.

launch activity in library module from main module

I have 2 different modules, module A and module B is library module. In module A, there is a button that will launch activity library module B when user click the button. How do i link the button to launch activity in module B?
You can pass the package name in the Intent like this:
Intent intent = new Intent(YourClassA.this, com.your.package.name.ClassB.class);
startActivity(intent);
There's two scenarios I see here:
Scenario #1:
Module A includes module B in its build.gradle file. If so, module A can directly start the Activity because the Activity is visible to it. This would be (in Kotlin):
button.setOnClickListener {
val intent = Intent(this, ActivityInModuleB::class.java)
// Possibly add extras here
startActivity(intent)
}
Scenario #2:
Module A does NOT include module B, but both are in the same project. In this case, the ActivityInModuleB class would not be visible to module A, so you could not import it and use it directly. In this case, there's a few possible solutions...
Option #1:
Use <activity-alias> in your AndroidManifest.xml for module B to associate ActivityInModuleB to a string. Then you can create the Intent in module A and call intent.setAction(actionStringInManifestGoesHere) and thus you can indirectly refer to this class.
I personally haven't used this approach, though I wanted to in a previous project. It's just that that project was already using option #2 and it would have taken a while to switch over.
Option #2:
Setup a LocalBroadcastReceiver in a module that includes both module A and module B. Then, each module can register themselves as handling broadcasts. When a broadcast is sent, an action is attached (similar to option #1). The broadcast receiver will call each module's handler and give it an opportunity to start the appropriate Activity, if it finds the associated action in its supported list. At the point a module starts an Activity it can return true to say it handled the action and your main module can decide to either short circuit calling any other modules' handlers OR let all modules have an opportunity.
This approach definitely works (it's used in an app with millions of installs). BUT, I think it's basically just recreating option #1, so probably is overkill.

Should I use one Component for each Activity in Dagger 2?

I have the following dependencies in my Android application. What is the best approach to do this in Dagger 2?
Activity A ---- Adapter A and Adapter B and SharedPreferences
Activity B ---- Adapter B and SharedPreferences
Activity C ---- Adapter C and SharedPreferences
Will I have to make a distinct Component for each Activity? Will there have to be three separate Components?
There is some confusion about the role of Components and Modules in Dagger 2 in an Android app.
Components are for grouping similar lifecycles together.
Modules can be organized along functional lines and for testing (as per the official instructions for testing).
Some of the best examples of this are in the Google Android Architecture Blueprints Github repo. If you examine the source code there, you can see there is one single app-scoped Component (with a lifecycle of the duration of the whole app) and then separate Activity-scoped Components for the Activity and Fragment corresponding to a given functionality in a project. The Activity-scoped Components will, of course, have a lifecycle that corresponds to their respective Activity.
Let's move to your own particular use case. While one activity-scoped component that can inject all the dependencies for Activity A, B, and C may be acceptable for the simple example in the question, the situation will quickly become more complicated if requirements change and Activity A suddenly needs a new dependency with a complicated object graph. You'll then have to create a new Module for the new dependency which will only be useful for one of the three injection sites in your Component. This will, in turn, make things more difficult for testing if you are using a mock Component to test Activity B and Activity C.
Hence, I would argue that from the start it is better to maintain one Component per Activity. Activity-scoped components are cheap and easy to maintain so it is not an issue to err on the side of caution and start with one activity-scoped component per activity.
For the example you have specified, I would create an app-scoped component:
#Component( modules = { SharedPreferencesModule.class } )
#PerApp
interface AppComponent {
SharedPreferences sharedPreferences(Application app);
}
You can have your Activity components become dependent components of the app component. That way they will not have to be concerned with SharedPreferences since this is bound in the app-component and exposed to dependents:
#Component( dependencies = { AppComponent.class }, modules = { AdapterAModule.class } )
#PerActivityA
interface ActivityAComponent {
}
Note that this is the style encouraged by the dagger.android package using #ContributesAndroidInjector:
#PerActivityA
#ContributesAndroidInjector(modules = {ActivityAModule.class})
abstract YourActivity contributeYourActivityInjector();

Get application context from a secondary module

I am working on a project that has one main module(here I have the activities and the controllers..) and some secondary modules where I have some calendar and other implementations.
In the main module I have a Application singleton class where I store the application context and I can get the app context statically from everywhere within my main module.
The question is how can I make another application class in the secondary module? Currently I am using circular dependencies between the main module and the module where I want the app context, and I don't like too much to use this approach.
#David Wasser wrote:
Why can't the code in the secondary modules call MainApp.getInstance() to get the application context? Obviously the secondary module is dependent on the main module so I don't see how this is a circular dependency.
If not, then pass the singleton application context from the main module to the secondary modules (either as a parameter in method calls or as a parameter in the constructor of the component in the secondary module. Then you won't have code in the secondary modules calling MainApp.getInstance(). In any case you cannot have another application class as there is only one application class.

Categories

Resources