Modularisation (yes I spelt that correctly) is all the rage at the moment and for good reason. I have always been most passionate about a project’s architecture and constantly strive to keep on top of best practices and learn new techniques. After watching IO 19, I decided to modularise the app I’m currently working on.

Whilst this post isn’t about the difficulties of such a decision, it is about making life easier in a multi-module project. The amount of duplicate configuration statements in the different modules “smelled” to me, for example, defining minSdkVersion and targetSdkVersion.

I found this great post by Jeroen Mols whilst searching for a solution. His whole series on modularisation is well worth a read. Jeroen explains how to modify each android module’s build.gradle file with certain properties.

After looking at the modules in my project, it was apparent that I needed fine grained control over each android property as not each module’s android property was the same. For example, my feature module’s may have needed vectorDrawables.useSupportLibrary = true whereas my data module did not.

The following solution can be tailored to your needs. It allows you to set a value with defaults if its not set in the module and conditionally set properties depending on the module name; useful when you have slight variations between modules.

subprojects {
    afterEvaluate { project ->
        println("Found project: $project.name")

        def setIfNotExists = { object, propertyName, value ->
            if (!object.hasProperty(propertyName) || object[propertyName] == null) {
                println("Setting $propertyName  to $value")
                object[propertyName] = value
            }
        }

        if (project.hasProperty("android")) {
            def androidProperty = project["android"]
            androidProperty.setProperty("compileSdkVersion",28)
            if (androidProperty.hasProperty("defaultConfig")) {
                def defaultConfig = androidProperty["defaultConfig"]
                setIfNotExists(defaultConfig,"minSdkVersion",minSdkVersion)
                setIfNotExists(defaultConfig,"targetSdkVersion",28)
                setIfNotExists(defaultConfig,"testInstrumentationRunner","androidx.test.runner.AndroidJUnitRunner")
            }

            if (project.name != "pageindicatorview") {
                androidProperty.invokeMethod("sourceSets", {
                    main.java.srcDirs += 'src/main/kotlin'
                    debug.java.srcDirs += 'src/debug/kotlin'
                    release.java.srcDirs += 'src/release/kotlin'
                    test.java.srcDirs += 'src/test/kotlin'
                    test.kotlin.srcDirs += 'src/test/kotlin'
                    test.resources.srcDirs += 'src/test/resources'
                })
            }

            if (project.name == "core" || project.name == "calendar") {
                // currently these are the two library modules we need to specify
                // flavors for.
                androidProperty.invokeMethod("flavorDimensions", "branding")
                // for those wanting to supply multiple dimensions
              //androidProperty.invokeMethod("flavorDimensions", ["branding","environment"].toArray())
                androidProperty.invokeMethod("productFlavors", {
                    brandOne {
                        dimension "branding"
                    }

                    brandTwo {
                        dimension "branding"
                    }
                })
            }
        }
    }
}

The method called setIfNotExists is pretty self explanatory, but the property is only set if it isn’t already present in the module object. The println statement in the method was useful for debugging and they get logged in the console of the Build tab.

Gradle output

You are left with being able module build.gradle files like the following.

android {
    defaultConfig {
        kapt {
            arguments {
                arg("room.schemaLocation", "$projectDir/schemas".toString())
            }
        }
    }
}

repositories {
    maven { url "https://kotlin.bintray.com/kotlinx/" }
}

dependencies {
....
}

Leave a comment

Your email address will not be published. Required fields are marked *