Replace Java file from main in just one product flavor

Viewed 1105

I'm trying to configure a build for Android. The project has many flavors, and in one flavor, I want to replace some classes from main. The structure is like this:

main - MyClass.java

flavorA, flavorB... flavorF use main's MyClass.java, no override

flavorG needs its own MyClass.java

I don't want to copy MyClass.java into each flavor's directory & remove it from main entirely, that would not be maintainable. Is there a way to use the exclude command to accomplish this? Something like...

 + src
   + main
     + java
       MyClass.java
       other common files
     AndroidManifest.xml
   + flavorA
     + java
       other flavor-specific files
     AndroidManifest.xml
   + flavorG
     + java
        MyClass.java
        other flavor-specific files
     AndroidManifest.xml

With a gradle setup like this...

productFlavors {
    flavora {
    }
    ...
    flavorg {
    }
}

sourceSources {
    flavora {
    }
    flavorb {
    }
    ...
    flavorg {
       main.java {
          exclude 'com/myapp/mypackage/MyClass.java'
       }
    }
}

The above doesn't work, am I doing something wrong? Is there another alternative?

1 Answers

You can use the following notation to control the JAR manifest (and specifically the 'Main-Class' attribute)

jar {
    manifest {
        attributes 'Main-Class': 'com.example.MyMain'
    }
}

And like everything in Gradle, it's a groovy script, so you can wrap portions of it with conditions to fork the behavior according to your needs.

Assuming you're passing the flavor as a -P flag (See Gradle properties and system properties) e.g. gradle build -Pflavor=flavorg the following should work:

jar {
    manifest {
        if (flavor == "flavorg") {
             attributes 'Main-Class': 'com.example.FlavoredMain'
        } else {
             attributes 'Main-Class': 'com.example.GenericMain'
        }
    }
}

P.S, keep in mind that the Jar closure as stated above is evaluated in configuration time, not execution timel meaning the main class condition is decided before tasks are being evaluated (See Build lifecycle).


Android

Per @Opals comment (sorry didn't notice that). To address the same issue in Android the quickest option is to have an AndroidManifest.xml per flavorg and the rest

android {
    sourceSets {
        main {
            manifest.srcFile 'main/AndroidManifest.xml'
        }
        flavorg {
            manifest.srcFile 'main/other/AndroidManifest.xml'
        }
    }
}

In the AndroidManifest.xml you have your main activity defined.

A better option would be to ensure you adhere to a proper folder structure

+ src
    + main
        + java 
        + res
        + AndroidManifest.xml
    + flavorg
        + java
        + res
        + AndroidManifest.xml

The AndroidManifest.xml in flavorg should contain only the difference from the main AndroidManifest.xml (namely the main activity change) and they will be merged according to the selected flavor

I suggest you review the following Merge multiple manifest files and Gradle flavors for android with custom source sets - what should the gradle files look like?


Android - Revised question

I would instead use a factory pattern that returns the right instance of "MyClass" in accordance to the activated flavor:

productFlavors {
    main {
       buildConfigField "string", "APP_FLAVOR", "main"
    }
    flavorg {
       buildConfigField "string", "APP_FLAVOR", "flavorg"
    }
}

Then, in the factory you can use:

if (BuildConfig.APP_FLAVOR.Equals("flavorg") {
   return new FlavorgMyClass();
} 
return new MyClass();

See Gradle Tips, specifically section "Share custom fields and resource values with your app's code"

Related