Reusing Gradle modules in several applications
Tags: android, gradle, git, architecture
Published: 2023-02-26 18:46:05

# Reusing Gradle modules in several applications

Hey guys! I’ve been working as an Android developer for quite some time now and I have to say: today’s apps have clearer architecture and are modular, as compared to the apps of ten years ago. Multi-modularity (or, at least, a couple of Gradle modules in a project) is de facto a modern standard. Any project, as a rule, has the main app module, several feature modules, as well as *core/domain/data* modules.


![https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqwe81qbuhtewccjxm3y.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqwe81qbuhtewccjxm3y.png)

These modules are connected in a quite standard way:

![https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7rn8o6z9szf023fuely.jpg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7rn8o6z9szf023fuely.jpg)

When you have one app, it’s not a problem: all the modules co-exist and we don’t really care about the connections between modules, though, of course, we should avoid *circular dependencies*.

As a matter of fact, we can surely have several apps in one project. To do this, we just need to add another `:app` Gradle module, declare the `android.application` plugin in it, and — ta da! Android Studio shows another launch option.

For example, we are working on two apps: **CatsApp**, for cat-lovers, and another one, **DogsApp**, for dog persons. All the features are the same (we have a screen to display a list of cats and dogs and a screen for detailed information); only the main modules differ. Among other things, different network configurations and `themes.xml` files may be declared and DI trees initialise differently in these modules. You can find an example of such a project [here](https://github.com/rchugunov/gradle_modules_reuse/tree/main/one_codebase).

![https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euikebi5cbzwz74iqqv9.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euikebi5cbzwz74iqqv9.png)

A more common example is probably the case when a project has its own design system, and we need a demo app to view our UI components, aside from the main app.

## Option 2

This is the case when we have two independently developed apps within the same company, and the business requirements dictate that different features were united or all the features of one app were reused in the other.
Here, multi-modularity will save the day because we already have an isolated piece of code that is partially ready for reuse. At the same time, these modules exist in different repos, are not associated with each other, and have different dependency lists of their own.
How do we unite modules from different projects? First of all, we can just set up visibility of a project for the other one, by simply uploading them into neighbouring directories and updating `settings.gradle` of the main project by including the other app’s modules into it.

```groovy
project(':feature-list').projectDir = new File('../app_to_be_reused/feature-list')

include ':feature-details'
project(':feature-details').projectDir = new File('../app_to_be_reused/feature-details')

include ':domain'
project(':domain').projectDir = new File('../app_to_be_reused/domain')

```

Unfortunately, if we only need `:feature-list`, we will also have to add dependencies of that module into our project, i.e. to pull `:domain` and `:feature-details`.

![https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9e3e3cbliejsm5ogmb1.jpg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9e3e3cbliejsm5ogmb1.jpg)

If we haven’t prepared the modules of the reused project beforehand, it will cause lots of issues even when we just try to import everything we need and synchronise the main project. For instance, if our reusable `:feature-1` module has Activities they won’t be able to use themes defined in the main project `:cats-app`.
Also, both projects are different Git repositories, so we’ll have to take care about being on the correct branches, while synchronising modules. It is highly possible that the Gradle configuration has changed in the reused project, which is why we need to always check whether we work in the right version of the project. It, in turn, causes problems with CI builds that now have to fetch two projects instead of one and build and test two projects in the same pipeline.

Git submodules can somewhat help us. They allow us to include another repo into the existing one and tag the correct version of the project with dependencies. You can learn more about submodules [here](https://git-scm.com/book/en/v2/Git-Tools-Submodules). In this case, the dependency diagram will be similar to the one from the previous example.

![https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uf4bljovqatrtsp3176r.jpeg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uf4bljovqatrtsp3176r.jpeg)

Anyways, you as the developer of another app will have to dive deep into another team’s code, with the learning curve quite possibly being rather steep. Please see an example of using two projects where one links the other’s modules [here](https://github.com/rchugunov/gradle_modules_reuse/tree/main/separate_codebases).

## Option 3

This option is based on publication of a part of the reused app in a separate Android library so that the other app could link it and use it as any other third-party SDK. If the app with dependencies is rather modular, you can try to export the modules you need, and then add entry points for them from the other app. As a rule, we use the [maven-publish](https://docs.gradle.org/current/userguide/publishing_maven.html) Gradle plugin for publishing pure Java/Kotlin libraries, but Android Gradle Plugin has recently started supporting it, and we can now [export Android modules](https://developer.android.com/studio/publish-library), and not merely Java libraries.
Here’s an example. Our **DogsApp** consists of the modules `app`, `feature-list`, `feature-details` and `domain`. `Feature-list` is responsible for printing out information about and the image of a certain dog. `domain` includes general information reused in all the other modules.

At some point, our company, for example, acquires another start-up business that has developed **CatsApp**. The business requirements now dictate that we support the new app and update its features by adding the same screen for viewing the details of cats, like in **DogsApp**. Since the apps were developed separately, they have different dependency trees, and we cannot just use the first two approaches, i.e. merge the codebases or link the modules we need from **DogsApp** into **CatsApp**. But we can take advantage of library publication. In this case, we need to publish `feature-details`.
To do this, we’ll use the **maven-publish** plugin and add the following code into `feature-details` `build.gradle`:

```groovy
android {
   ...
   publishing {
       singleVariant('release')
   }
}

afterEvaluate {
   publishing {
       publications {
           release(MavenPublication) {
               from components.release
           }
       }
   }
}
```

`singleVariant` points that we want to publish only one build option. To make it possible to use both the debugging and release versions of our library, we need to use `allVariants()` and `components.default`:

```groovy
   ...
   publishing {
      multipleVariants {
         allVariants()
      }
   }
}

afterEvaluate {
   publishing {
       publications {
           allVariants(MavenPublication) {
               from components.default
           }
       }
   }
}
```

Now we can publish our module with the `gradle :feature-details:publishToMavenLocal` command. Let’s add a local maven repo to **CatsApp** so that we could use this dependency:

**settings.gradle:**

```groovy
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
       google()
       mavenCentral()
       mavenLocal()
   }
}
```

Now, we can add the dependency to the exported library in `cats_app/build.gradle`:

```groovy
dependencies {
   ...
   implementation 'com.example:feature-details:1.0.0'
}
```

However, when we try to build **CatsApp**, build errors will occur because the build system cannot find the `:domain` module, so we need to publish it too. In fact, we have to recursively publish all the dependencies that the `:feature-details` module uses. For this, delete the code that we’ve added before for the publication of `:feature-details` and add the following into the root **build.gradle**:

```groovy
subprojects {
   apply plugin: "maven-publish"

   afterEvaluate {
       if (!plugins.hasPlugin("android")) {

           if (plugins.hasPlugin("android-library")) {
               android {
                   publishing {
                       multipleVariants {
                           allVariants()
                       }
                   }
               }
           }

           publishing {
               publications {
                   allVariants(MavenPublication) {
                       afterEvaluate {
                           if (plugins.hasPlugin("java")) {
                               from components.java
                           } else if (plugins.hasPlugin("android-library")) {
                               from components.default
                           }
                       }
                   }
               }
           }
       }
   }
```

`Subprojects {` means that we want to apply our code to all the submodules. But we don’t want to publish the main module `:dogs-app`, so we filter it out by the presence of Android plugin `!plugins.hasPlugin("android")`. Please also note that `:domain` is a Java library and doesn’t have build options, which is why we don’t need to add this information to it. We will add build options for Android libraries only:

```groovy
 if (plugins.hasPlugin("android-library")) {
     android {
```

Also, since we have both Java and Android modules, we’ll have to use different publication methods. If a Java plugin has been applied to a module, it will be published as a Java library, and in case of the Android library plugin, we’ll publish a set of *.aar* archives with corresponding build options.

```groovy
if (plugins.hasPlugin("java")) {
    from components.java
} else if (plugins.hasPlugin("android-library")) {
    from components.default
}
```

Now, when we publish our library with the same `gradle :feature-details:publishToMavenLocal` command, all the dependencies will be placed into our local maven repository, and the client app **CatsApp** will be built with no issues. You can find the full code [here](https://github.com/rchugunov/gradle_modules_reuse/tree/main/publish_modules).

What about you? Have you ever had to merge different apps or reused another team’s Gradle dependencies? Please share the approach used in your company in the comments.