How I made my legacy app modular - part 2

dbottillo

Daniele Bottillo

Posted on September 9, 2019

How I made my legacy app modular - part 2

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:

Three modules architecture

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: Sibling modules architecture

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.
App about page

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

The extraction is completed! Almost…because probably inside the legacy module somewhere we must have something like:

startActivity(this, AboutActivity::class.java)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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))
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

And now we can add both modules to the app component:

@Component(modules = [
    AndroidSupportInjectionModule::class,
    AppModule::class,
    AboutModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {
}
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
dbottillo
Daniele Bottillo

Posted on September 9, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related