This is part two of https://dev.to/dbottillo/how-i-have-modularized-my-legacy-app---part1-4mde.
In part 1, I've shown how to move from one big module to three:
The idea behind these three modules is that we can start to move our codebase in three directions:
- everything that is shared across modules should be moved to
core
(eg. util extensions, app constants) - everything that is app level configuration should be moved to
app
(eg. dagger configuration, navigation) - everything that is related to one feature should have its own module as a sibling of legacy:
The reason behind this approach is that we can move code away from legacy
which will force us to think about the dependencies of feature
: being a sibling of legacy
means that we can't access anything inside legacy
so we have to move those dependencies either to core
or app
.
A good example of this approach of creating siblings of legacy
is the extraction of a small feature on my own project: the about page of the app.
It's a small screen with few buttons, a title, some texts and a box with the libraries used inside the app; it makes the perfect example of how to extract it considering that it has very few dependencies on legacy
.
Let's first create the gradle module for the about
module feature:
feature_about/build.gradle
feature_about/src/main/AndroidManifest.xml
feature_about/src/main/kotlin/com/dbottilo/mtgsearchfree/
That's it! The last folder will contain all the logic for the module. To make the module "alive" we need to add its definition to the settings.gradle
:
include ':legacy', ':core', ':app'
include ':feature_about' <- add this
So now syncing the project will make the feature_about
module available in Android Studio. Let's start moving the AboutActivity
which is inside the legacy
module:
move
legacy/src/main/kotlin/com/dbottilo/mtgsearchfree/about/AboutActivity.kt
to
feature_about/src/main/kotlin/com/dbottillo/mtgsearchfree/about/AboutActivity.kt
Move also all the drawables that are used from AboutActivity from legacy to feature_about.
eg: move
/legacy/src/main/res/drawable-hdpi/library_icon.png
to
/feature_about/src/main/res/drawable-hdpi/library_icon.png
Right, so AboutActivity
is now out of legacy
, exciting! But this is also where the problem starts: what if you have a drawable that is shared with the rest of the application? or a dimension?
There are two options to handle those cases:
- move the shared resources into
core
- duplicate the resources from
legacy
I don't think there is a generic solution that works here, it really depends on the specific use case. I would advice to move dimensions like base_margin
to core
, whilst I would prefer to duplicate things like specific drawables so that the module has full control over that.
Right, next step is to update the manifest because the AboutActivity
is not visible anymore in legacy
: we can move it from the legacy
manifest to the about module one:
File: app/src/main/AndroidManifest.xml
<application>
...
<activity android:name=".ui.about.AboutActivity"/> <- remove
...
</application>
File: feature_about/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.dbottillo.mtgsearchfree.about"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".AboutActivity"/>
</application>
</manifest>
The extraction is completed! Almost…because probably inside the legacy
module somewhere we must have something like:
startActivity(this, AboutActivity::class.java)
but AboutActivity
is no longer visible in legacy
module. Here is where the app
module comes into play, we need to wire the navigation between modules now. The difficulty comes from the fact that the only module that has visibility to all the features is app
but we need to find a way to call one module from its sibling. My personal solution around navigation is to define an interface in core
called Navigator:
file: core/src/main/kotlin/com/dbottillo/mtgsearchfree/Navigator.kt
interface Navigator {
fun openAboutScreen(origin: Activity)
}
which is visible from all the modules
and have the implementation in app
:
file: app/src/main/kotlin/com/dbottillo/mtgsearchfree/Navigator.kt
class AppNavigator : Navigator {
override fun openAboutScreen(origin: Activity) {
origin.startActivity(Intent(origin,
AboutActivity::class.java))
}
}
And in each module we can inject Navigator
whose implementation doesn't really matter at feature module level: the important part is that they have a class to request to move between screens, the actual implementation is at runtime from the app
module.
We are almost there! I just mentioned "inject Navigator", so how do we achieve that? how does Dagger work? It's actually quite simple!
Let's first create a Dagger module in the about module:
file: feature_about/src/main/kotlin/com/dbottillo/mtgsearchfree/dagger/AboutModule.kt
@Module
abstract class AboutModule {
@ActivityScope
@ContributesAndroidInjector(modules = [(BasicAboutModule::class)])
abstract fun contributeAboutActivityInjector(): AboutActivity
}
@Module
class BasicAboutModule
We also need to create an AppModule
Dagger module to provide the navigator dependency:
file:
app/src/main/kotlin/com/dbottillo/mtgsearchfree/dagger/AppModule.kt
@Module
class AppModule {
@Provides
@Singleton
fun provideNavigator(): Navigator {
return AppNavigator()
}
}
And now we can add both modules to the app component:
@Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
AboutModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {
}
And that's it! Now Dagger knows how to provide dependencies inside the about
module. Of course this is a very simple example as it doesn't have any API request or database, so the module is very simple but is a good starting point :)
If you want to see the real commit behind this story: https://github.com/dbottillo/MTGCardsInfo/commit/a9a8059838d2886ed02eb61602953fe83a96c460
As for part 1, in the real code legacy
module is actually called MTGSearch
and the complexity of that commit is slightly higher than the one described here.
Happy modularisation!
Top comments (0)