Gradle flavors for android with custom source sets - what should the gradle files look like?
Asked Answered
S

6

60

I've got an old eclipse project I've moved into android studio and setup to use flavors. It seemed to be working fine till I started trying to use different java files between my flavors.

My project setup is this:

ProjectRoot
+- acitonbarsherlock
+- facebook
+- myLib1
+- myProject
   +- src
      +- commonFiles
         +- flavor1
         +- flavor2
   +- res
      +- flavor1
      +- flavor2

The innards of the myProject gradle file android closure looks like this:

android {
compileSdkVersion 17
buildToolsVersion "18.0.1"

signingConfigs {
     ...
}

productFlavors {
    flavor2 {
    }
    flavor1 {
    }
}

sourceSets{
    main {
        manifest.srcFile 'AndroidManifest.xml'
        java.srcDirs = ['src/commonFiles/java']
        resources.srcDirs = ['src/commonFiles/java']
        aidl.srcDirs = ['src/commonFiles/java']
        renderscript.srcDirs = ['src/commonFiles/java']
        res.srcDirs = ['res']
        assets.srcDirs = ['assets']
    }

    flavor2 {
        manifest.srcFile 'AndroidManifest-flavor2.xml'
        res.srcDirs = ['res-flavor2', 'res']
        java.srcDirs = ['src/flavor2/java','src/commonFiles/java']
        resources.srcDirs = ['src/flavor2/java','src/commonFiles/java']
        aidl.srcDirs = ['src/flavor2/java','src/commonFiles/java']
        renderscript.srcDirs = ['src/flavor2/java','src/commonFiles/java']
    }

    flavor1 {
        manifest.srcFile 'AndroidManifest.xml'
        java.srcDirs = ['src/flavor1/java','src/commonFiles/java']
        resources.srcDirs = ['src/flavor1/java','src/commonFiles/java']
        aidl.srcDirs = ['src/flavor1/java','src/commonFiles/java']
        renderscript.srcDirs = ['src/flavor1/java','src/commonFiles/java']
        res.srcDirs = ['res-flavor1','res']
        assets.srcDirs = ['assets']
    }

    // Move the tests to tests/java, tests/res, etc...
    instrumentTest.setRoot('tests')

    // Move the build types to build-types/<type>
    // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
    // This moves them out of them default location under src/<type>/... which would
    // conflict with src/ being used by the main source set.
    // Adding new build types or product flavors should be accompanied
    // by a similar customization.
    debug.setRoot('build-types/debug')
    release.setRoot('build-types/release')

}

buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}

}

With my setup like this gradle complains about not being able to find classes I'm trying to inherit from commonFiles in flavor1 or flavor2.

From the various other topics I've looked at on here I see others not even defining source sets, and I feel like what I'm doing in them is perhaps too much.

Has anyone experimented with this before and know how this should properly be configured?

Subordinate answered 19/10, 2013 at 1:1 Comment(5)
I don't know if it will help anyone but I used flavorDimensions and it works like magicCaldera
android flavors demo goo.gl/5NSnEMCraver
@nitesh : your demo is for Gradle defined structure, not for custom sourceset that you get when you export Eclipse project as gradle .Thorbert
@Thorbert this demo is to support the answer of Saad Farooq which is marked as correct.Craver
For anyone looking at this for android projects: The sourceSets{...} part has to be within android{...}!Yesseniayester
A
69

I think you'd be better off not defining custom sourceSets but using the default gradle configuration. I used to do custom sourcesets until I realized the conventions are, well, convenient.

You'll want something like this:

+ src
    + main // this is your common code
        + java 
        + res
    + flavor1
        + java
        + res
    + flavor2
        + java
        + res

Then you can just go ahead and remove the sourcesets closure from your build.gradle

NOTE: For the gradle configuration, resources are merged or overridden whereas all java code is put on the same class-path. For example, the AndroidManifest.xml files for each flavor need only have the differences from main's manifest file. Having an asset, for example, ic_launcher in a flavor overrides the ic_launcher from main if such file exists. However, having a file HomeActivity.java in both main and the flavor is not possible and will give a duplicate file error.

Aquilar answered 3/12, 2013 at 8:1 Comment(5)
Can you expound on why your answer is following convention? From my understanding, Gradle has incorporated a convention of product flavors and source sets. To me, your answer seems like a ton of duplicated code.Energetic
I added a note to the answer. In fact, code duplication is not possible with this setup.Aquilar
Ah interesting, thanks that better helps me understand, very convenient indeed.Energetic
Yup... and actually there was some debate about whether java files in the flavors should override java files in main, for example, if you are making an app where only the HomeActivity is different from the flavor, you could just write one file and be done with it. As it stands there are other ways to get around that problem that might be addressed somewhere on SO if you are interested.Aquilar
How this is gonna work if I want to have specific dependencies for different flavors. I mean we still end up having a single gradle fileRopedancer
M
47

You are welcome to use the custom sourceSets and flavours (or buildTypes) if you wish.

As an example, you can set them in your Gradle file as follows:-

productFlavors {
    flavor2 {
    }
    flavor1 {
    }
}

sourceSets{
    main {
        manifest.srcFile 'AndroidManifest.xml'
        java.srcDirs = ['src/commonFiles/java']
        resources.srcDirs = ['src/commonFiles/java']
        aidl.srcDirs = ['src/commonFiles/java']
        renderscript.srcDirs = ['src/commonFiles/java']
        res.srcDirs = ['res']
        assets.srcDirs = ['assets']
    }
  flavor1 {
       java.srcDirs = ['src-flavor1'] 
       res.srcDirs = ['res-flavor1']
       ...
    }

}
Mischiefmaker answered 10/2, 2014 at 13:46 Comment(1)
How would I do this dynamically? I have sourceSets.whenObjectAdded { sourceSet -> sourceSet.java.srcDirs = "someDir"} and then the source sets defined per flavor like this: sourceSets { flavor1{} flavor2{}}. I want to do something like: sourceSet.debug.java.srcDirs = "someDir" for each product flavor and per buildType.Pardue
T
8

Here is what my Gradle looks like :

   productFlavors {
    // Still keeping the productFlavors closure in case we decide to add flavors later
    normal {
        applicationId 'com.akshat'
    }
    qa {
        applicationId 'com.akshat.qa'
    }
}

 sourceSets {
    main {
        manifest.srcFile 'AndroidManifest.xml'
        java.srcDirs = ['src']
        resources.srcDirs = ['src']
        aidl.srcDirs = ['src']
        renderscript.srcDirs = ['src']
        res.srcDirs = ['res']
        assets.srcDirs = ['assets']
        jni.srcDirs = [] // Set empty since we use our own ndkBuild task
        jniLibs.srcDirs = ['libs']
    }

    normal {
        java.srcDirs = ['src_normal']
    }
    qa{
        java.srcDirs = ['src_qa']
    }

And here is how my Directory structure is :

MyApplication
    - res
    - libs
    - jni 
    - src
         -com/akshat/
    - src_qa
         - com/akshat/util/FlavorConst.java
    - src_normal
         - com/akshat/util/FlavorConst.java
Thorbert answered 21/5, 2016 at 5:57 Comment(0)
W
8

This way works for me. Enjoy

sourceSets {
        main {
            manifest.srcFile 'src/AndroidManifest.xml'
            java.srcDirs = ['src/java']
            resources.srcDirs = ['srs/others']
            res.srcDirs = ['src/res']
            assets.srcDirs = ['src/assets']
            jniLibs.srcDirs = ['jniLibs']
        }
        development{
            res.srcDirs += ['development/src/res']
        }
        standford{
            res.srcDirs += ['standford/src/res']
        }

        commercial{
            res.srcDirs += ['commercial/src/res']
        }

    }
    productFlavors {
        development{
            flavorDimensions "default"
        }
        standford{
            flavorDimensions "default"
        }
        commercial{
            flavorDimensions "default"
        }
    }
Wallford answered 31/12, 2018 at 7:16 Comment(0)
K
1

You can code like the example below i taking

sourceSets {
    main {
        manifest.srcFile 'AndroidManifest.xml'
        res.srcDirs = ['res']
        java.srcDirs = ['src', '../3party/Alohalytics/src/android/java', 'ttsearch/jni/out']
        assets.srcDirs = ['assets', 'flavors/mwm-ttf-assets']
        jniLibs.srcDirs = ['libs', 'jniLibs', 'ttsearch/libs']
    }
    flavor {
        manifest.srcFile 'flavor'
        assets.srcDirs = ['flavor/assets']
        res.srcDirs = ['flavor/res']
        res.srcDirs = ['flavor/res']
        ....
    }
}
Kathyrnkati answered 23/11, 2017 at 4:15 Comment(1)
It's good practice on Stack Overflow to add an explanation as to why your solution should work. For more information read How To Answer.Tolbert
S
0

Variant with custom sourceSets have an issue. For example,if we want change only several lines for some flavors,we have structure xxx/res-paid/values/strings.xml with two strings,which use in manifest file. in this case we have android linking faild error for manifest file. So variant with custom sources not works,as it must. Maybe i will report issue about it to google and gradle.

Sake answered 19/11, 2021 at 1:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.