How I made my legacy app modular - part 1
Tags: android, multi, module, modularization
Published: 2019-01-21 11:12:01

---
title: How I made my legacy app modular - part 1
published: true
description: A modularization walkthrough of a real Android application
tags: android, multi, module, modularization
---

We all have been there. Whatever is the team getting bigger or your app becoming old, there is a point at which to scale up the development of your Android application you start to consider how to modularize the codebase. 

When you reach that point things start to become messy, you may start to think to ditch Gradle in favour of Bazel, or you may think to apply one of the many suggestions on how to create modular apps. But then what about app bundles? and instant app? maybe even dynamic feature? Dagger anyone? More importantly, what about a strategy that works for your own app and your own team? The really question is, how do you move from a monolithic app to anything that can scale up and have more than one module?

You can't definitely do that over night, even for a small application because things will be broken and overlooked, so I would recommend to take a more step by step approach.

Having to tackle this process for the big app of my daily job, I've decided to first modularize a small open source app of my own. It's an application to browse cards for a collectible card game called Magic The Gathering (https://github.com/dbottillo/MTGCardsInfo ). The app is fairly small: less than 10 activities, lots of logic around the database with 1k daily active users so I thought it could have been a good use case to learn the process.

So where to start from? First we should consider the structure of the modules that we want to have, I think the easiest structure is having three modules called `app`, `legacy` and `core` :

* `app` is the top level module that it's generating the `APK` of your project, the idea is that app knows everything below and ideally should contain only the `application` class which in the beginning can live in any other module
* `core` is a library module that contains everything that needs to be shared across modules, eg. api model, util classes, etc.. . 
* `legacy` is your current codebase, it lives below `app` and above `core`, the reason for this is that ideally your `legacy` module will be converted in many siblings modules that have `core` below and will provide your features to your `app` module

![module structure](https://thepracticaldev.s3.amazonaws.com/i/cffzuh0xwjczb6c7j3y4.jpg)


There are alternatives to this approach, for example you can think of creating `core` above `legacy` and pull out code from it but I don't think it's a good idea: if `core` is above `legacy` then if you move one class from `legacy` to `core` then its dependencies will still be available (remember that each module can still access everything from a module below) so it will not help you to untangle any dependency really.

In the beginning I recommend to make all your modules `android` modules so it's easier to migrate (in the future you can think of removing the `android` dependency from them). I also suggest to create a `config-android.gradle` to import in every other module to share the android configuration, the setup should look something like this:

```
app/src/main/AndroidManifest.xml
app/build.gradle
app/src/main/java/… → empty
legacy/src/main/AndroidManifest.xml → existing manifest
legacy/build.gradle → existing gradle file
legacy/src/main/java/… → all your existing codebase
core/src/main/AndroidManifest.xml
core/build.gradle
core/src/main/java/… → empty
build.gradle → generic configurations across modules
settings.gradle → project configuration
config-android.gradle → shared android configuration across modules
```

Let's have a look into the details of each file.

```xml
File: app/src/main/AndroidManifest.xml

<manifest package="com.dbottillo.mtgsearchfree.app" 
   xmlns:android="http://schemas.android.com/apk/res/android" 
   xmlns:tools="http://schemas.android.com/tools">
    <application        
           android:name="com.dbottillo.mtgsearchfree.MTGApp"
           android:icon="@drawable/ic_launcher"                   
           android:label="@string/app_name"         
           android:theme="@style/MTGSearchTheme">
    </application>
</manifest>
```

Nothing particular here, the manifest is pretty much empty, it just refers the Application class that at the moment lives in the `legacy` module. One consequence of moving application here is that it requires to also move the icon `@drawable/ic_launcher` to the `app` src directory as well.

```groovy
File: app/src/build.gradle

apply plugin: 'com.android.application'
apply from: '../config-android.gradle'
android {    
   defaultConfig {        
      applicationId 'com.dbottillo.mtgsearchfree'        
   }
   signingConfigs {       
      debugConfig { ... }
      releaseConfig { ... }
   }
   buildTypes{
       debug {          
           applicationIdSuffix ".debug"            
           versionNameSuffix "_dev"            
           signingConfig signingConfigs.debugConfig            
           debuggable true
       }
       release {            
           debuggable false            
           signingConfig signingConfigs.releaseConfig        
       }
   }
}
dependencies {    
    implementation project(':MTGSearch')
    // dependencies
}
```

`apply from` lets you import the content of another `.gradle` file, I'm going to go through the details later, for now you can see that some info regarding android are missing (min sdk, target sdk, etc..). That's because they are defined in this other gradle file, this file then just specify thing that are related to the `app` module. Note that this is the module that you need to use in order to create an APK or run the app on a device.

```xml
File: core/src/main/AndroidManifest.xml

<manifest    
    package="com.dbottillo.mtgsearchfree.core"     
    xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
```

The manifest of core is empty, which at this stage makes sense because the module is empty and therefore no activity, service, etc.. are defined in this module.

```groovy
File: core/build.gradle
apply plugin: 'com.android.library'
apply from: '../config-android.gradle'
dependencies{    
    // dependencies
}
```

As the manifest, even the gradle file is quite minimal: it's defined as an `com.android.library` and it inherits the android configuration.

```groovy
File: build.gradle
buildscript {
    repositories {        
         google()        
         mavenCentral()        
         maven { url "https://maven.google.com" } 
         jcenter()    
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'        
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3"
    }
}
allprojects {    
    repositories {        
         google()        
         mavenCentral()        
         jcenter()        
         maven { url "https://maven.google.com" }
     }
     dependencies {
         // your dependencies
     }
}
File: settings.gradle
include ':legacy', ':core', ':app'
```

In the root `build.gradle` you can define all the repository and dependencies for your other modules and in `settings.gradle` all the modules available, this is quite a straightforward gradle configuration.
More interesting is the `config-android.gradle`:

```groovy
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {    
    compileSdkVersion 28    
    buildToolsVersion '28.0.3'
    defaultConfig {       
          minSdkVersion 21        
          targetSdkVersion 28        
          versionCode 101        
          versionName "3.6.0"    
    }
    buildTypes {        
          release {}        
          debug {}   
    }
}
androidExtensions {    
     experimental = true
}
kapt {    
     useBuildCache = true
}
```

As I mention earlier, this gradle file is shared across modules so it's defining all those property across them, it's just an easier way to handle changes: otherwise every time you want to bump the version code, for example, you need to do it in every module.

For the `legacy` module, `build.gradle` and `AndroidManifest.xml` are pretty much the same: the `legacy/build.gradle` file needs to import `config-android.gradle` and change the the plugin from application to library. It is also required to remove the definition of the application class from the AndroidManifest.xml since it's not defined in the `app` module.

I think this is the minimal amount required to move from one module to three modules! The next steps are to move the application class to the `app` module and then to disentangle dependencies when moving classes from `legacy` to `core`. Will write about those steps in the next parts :)

If you want to check real code, you can look at the commit of the application that I've mentioned in the beginning: https://github.com/dbottillo/MTGCardsInfo/commit/6ec4a1bd72d79fe13f0b7c602a56aeca32a8c43e
Bear in mind that `legacy` is actually called `MTGSearch` and because the project is an actually real project, the complexity of each file is slightly bigger than the one described here.