0

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.

Stephen McCormick
  • 1,706
  • 22
  • 38
  • 1
    Would an afterTask that moves the apk work for you? – lionscribe Aug 17 '17 at 00:56
  • @lionscribe If it could use an element in the config. It would be very nice to be able to not have a secondary action needed after building (if so, I would stick to my old Ant scripts which worked nicely). – Stephen McCormick Aug 17 '17 at 14:38
  • Not sure what you mean, the afterTask is added to your Gradle file, and is automatically called by Gradle. – lionscribe Aug 17 '17 at 14:41
  • @lionscribe I have updated the question to show the use of a task - but the problem is really outside of tasks. It is more about trying to set a variable I can access when running the gradle for all flavors (sort of like overriding with version name, for example). It may be in Gradle - I just have not found it. Appreciate the help, Thanks! – Stephen McCormick Aug 17 '17 at 15:14

2 Answers2

4

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.

Stephen McCormick
  • 1,706
  • 22
  • 38
  • Glad it works. Please accept your own answer, rather than mine, so future users will find it. – lionscribe Aug 18 '17 at 00:07
  • Will do. Appreciate the help...Android Studio and Gradle are killing. :) – Stephen McCormick Aug 18 '17 at 00:16
  • Well, that is the age old choice, ability versus simplicity. I choose the former. – lionscribe Aug 18 '17 at 00:35
  • @lionscribe More just my ignorance. Now that I know how to do it, it is pretty elegant. I found that if is something is too complicated, even if popular, will eventually be abandoned (EJBs for example), not sure if that is the case here though. thanks again. – Stephen McCormick Aug 18 '17 at 00:40
  • Please change the `productFlavors*.apkName[0]` to `productFlavors[0].apkName`. It should work, and makes the code more understandable and consistent. I will explain. It seems that `productFlavors` is an array or list. Therefore you have to use `variant.productFlavors[0].apkName` to get value. Now if you use variant.productFlavors*.apkName, the asterisk is a Groovy command, that calls apkName on all the items in the array, and returns a List of them. Therefore you needed to add an [0] at end, to get the first one. Which is all a waste of time, and confusing code in this situation. – lionscribe Aug 18 '17 at 11:57
  • Thanks, I will make the change and try it out. – Stephen McCormick Aug 18 '17 at 15:08
  • Worked - changed in the answer above. – Stephen McCormick Aug 18 '17 at 17:13
1

The problem is that setProperty is setting the Project property, so it is always being overwritten. A simple solution is to rather use the Varients Extra Property. Something like this.

productFlavors {
     Randolph { 
         applicationId 'com.mycompany.randy'
        ext.archivesBaseName = "randy" }
    Jimbo { 
        applicationId 'com.mycompany.jimmy'
        ext.archivesBaseName=jimmy" } 
    }

Then you will access it in the task as

def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.ext.archivesBaseName + "/" 

I haven't tested it, and it may have a bug, and need some tweaking.

Update
This is not enough, as Gradle will set to the ext property of the flavor object, only if it is defined in the flavor object. Otherwise it will set it in the parent or root object, which is the project. So for this to work, we first have to define the property in the flavor object. This can be done as @Stephen has answered below. Follow his tested method.

There are 3 more options:
1. Use a different variable name for each flavir, by pre-pending the flavor name, like "Jimbo_archivesBaseName". Then access it using property(flavorName + "_archivesBaseName);
2. Use a global HashMap variable, setting a path for each flavor name.
3. Using a function, that returns a path based on flavor name.

lionscribe
  • 3,413
  • 1
  • 16
  • 21