Search code examples
androidandroid-studiogradleandroid-productflavors

Android Studio Gradle specifying output directory specific to Flavor


I have implemented Flavors in Android Studio and am now trying to place each Flavor in it's own directory, with its own unique name - sadly with a name different, in some cases, than the flavor name. :(

We have tools depending on it being the same, so if I can pull that off in gradle, all the better.

I have a sample that is using the version name suffix value as the directory name and that works. But what I would like to do is specify a value somewhere in the flavor config that would be used, however I find that when you set a property with the same name the last one wins - rather than each being used as specified in the config.

So, for example, lets say I have two Flavors : Jimbo and Randolph. However I want to place the Jimbo.apk in the "jimmy" folder and the Randolph.apk in the "randy" folder. How can I specify a value (directory) for each that will be picked up and used to store the generated APK? To add to the complexity I am renaming the APK current in the applicationVariants.all .

In the code below I am looking to somehow replace the versionNameSuffix with a variable I can somehow specify.

Here is what I have:

apply plugin: 'com.android.application'

    android {
        compileSdkVersion 25
        buildToolsVersion '25.0.2'
        defaultConfig {
            applicationId "com.mycompany.default"
            minSdkVersion 14
            targetSdkVersion 23
            versionCode 11
            versionName "1.0.11"
            compileOptions {
                sourceCompatibility JavaVersion.VERSION_1_7
                targetCompatibility JavaVersion.VERSION_1_7
            }
            signingConfig signingConfigs.config
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            }
        }
        productFlavors {
            Randolph {
                applicationId 'com.mycompany.randy'
                versionNameSuffix 'randy'
            }
            Jimbo {
                applicationId 'com.mycompany.jimmy'
                versionNameSuffix 'jimmy'
            }
        }


        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].versionNameSuffix + "/"
                logger.error("Path = " + path)
                def SEP = "-"
                def flavor = variant.productFlavors[0].name
                def version = variant.versionCode
                def newApkName = path + version + SEP + flavor
                logger.error("newApkName = " + newApkName)
                output.outputFile = new File(newApkName + ".apk")
            }
        }
    }

    dependencies {
        }

}

UPDATE

Per the question of using a task, I tried this approach but the problem of setting the directory remains - using a property (archivesBaseName) the last one set is used so all the files are copied to that directory. Here is a sample of that. Since I have upwards of 100 flavors to create I want each sent to it's own directory and config driven. Here is what I tried:

productFlavors {
    Randolph {
        applicationId 'com.mycompany.randy'
        setProperty("archivesBaseName", "randy")
    }
    Jimbo {
        applicationId 'com.mycompany.jimmy'
        setProperty("archivesBaseName", "jimmy")
    }
}


   applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def path = "C:/AndroidBuilds/MyCompany.Build/" + archivesBaseName + "/"
            logger.error("Path = " + path)
            def SEP = "-"
            def flavor = variant.productFlavors[0].name
            def version = variant.versionCode
            def newApkName = path + version + SEP + flavor
            logger.error("newApkName = " + newApkName)
            output.outputFile = new File(newApkName + ".apk")
            def copyApkTask = tasks.create(name: "copy" + variant.name + "Apk") {
                copy {
                    def newName = newApkName + ".apk"
                    logger.error("from = " + newName)
                    logger.error("into = " + path)
                    logger.error("old name = " + version + SEP + flavor + ".apk")
                    logger.error("new name = " + flavor + ".apk")
                    from newName
                    into path
                    rename (version + SEP + flavor + ".apk", flavor + ".apk")
                }
            }
            copyApkTask.mustRunAfter variant.assemble
        }
    } 

In the example above I added a task to additionally copy the APK with different name to a flavor specific directory. All the APKs end up copied to the last specified `archivesBaseName, which is "jimmy". So last one wins. I was hoping it would act like a variable. I would prefer not to have to have 100+ if statements to do this and would prefer to do this in Gradle. I am starting to wonder if I will need to make an external Ant call to make this all work.


Solution

  • Ok, in the end this specific link REALLY helped on the variable assignment which is what I needed:

    Android Studio: Gradle Product Flavors: Define custom properties

    Basically you can assign variables within the flavor. Here is what I ended up doing, which actually went a bit further than when I started, since now I can use the Flavor as the APK name or specify one (I know, it is messed up, but history can be that way!):

    apply plugin: 'com.android.application'
    
        android {
            compileSdkVersion 25
            buildToolsVersion '25.0.2'
            defaultConfig {
                applicationId "com.mycompany.default"
                minSdkVersion 14
                targetSdkVersion 23
                versionCode 11
                versionName "1.0.11"
                compileOptions {
                    sourceCompatibility JavaVersion.VERSION_1_7
                    targetCompatibility JavaVersion.VERSION_1_7
                }
                signingConfig signingConfigs.config
            }
            buildTypes {
                release {
                    minifyEnabled false
                    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
                }
            }
    
            productFlavors.whenObjectAdded { flavor ->
                // Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
                flavor.ext.set('directoryPath', '')
                flavor.ext.set('apkName', '')
            }
    
            productFlavors {
                Randolph {
                    applicationId 'com.mycompany.randy'
                    directoryPath =  'randy'
                    apkName = 'RandyBoy'  // If you want the APK name be different than the flavor
                }
                Jimbo {
                    applicationId 'com.mycompany.jimmy'
                    directoryPath =  'jimmy'
                }
            }
    
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].directoryPath + "/"
                    def SEP = "-"
                    def apkName = variant.productFlavors[0].apkName
                    def flavor = variant.productFlavors[0].name
                    if (apkName != '')
                        flavor = apkName;
                    def version = variant.versionCode
                    def newApkName = path + version + SEP + flavor
                    logger.error("newApkName = " + newApkName)
                    output.outputFile = new File(newApkName + ".apk")
                }
            }
        }
    
        dependencies {
            }
    
    }
    

    So productFlavors.whenObjectAdded sets the default values for each flavor, which are then overridden by each flavor. In the applicationVariants.all a check is made to see if the apkName has been overridden, if so it uses it, otherwise it uses the flavor name (and the version code is tacked in front of it). The directory is set directly by the flavor.

    Big thanks to @lionscribe. He got me thinking this thru more clearly.