76

I've got a project, structured like this:

project/
   |
   |---src/
        |---flavorA2/
        |      |
        |      |---java/
        |      |     |---com.abc.flavorA.mk2
        |      |                 |-----classA.java
        |      |                 |-----classB.java
        |      |---res/
        |      |---AndroidManifest.xml
        |
        |---main
        |      |---java/
        |      |     |---com.abc.flavorA
        |      |                 |-----classA.java
        |      |                 |-----classB.java
        |      |                 |-----classC.java
        |      |                 |-----classD.java
        |      |---res/
        |      |    |---drawable/
        |      |    |---layout/
        |      |    |---values/
        |      |         
        |      |---AndroidManifest.xml
        |
        |---flavorA

flavorA will use the source and assets from main completely while flavorA2 has some small changes in classA and classB and the package name is also changed to com.abc.flavorA.mk2.

I had the build.gradle file like this:

...
buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
    productFlavors {
        flavorA2 {
            packageName "com.abc.flavorA.mk2"
            versionCode 2
            versionName "1.0.1"
        }

        flavorA {
            packageName "com.abc.flavorA"
        }
    }
...

I run the code by selecting the build variant to flavorA2. However the running results shows that the gradle still choose the classes (classA and classB) from main instead of using the changed version inside flavorA2.

Am I missing something here?

dumbfingers
  • 7,001
  • 5
  • 54
  • 80

4 Answers4

128

Since you have the classes under 2 different packages, these are totally different classes. So the classes aren't replacing each other.

With flavors, you can't override class files. So, one way to accomplish what you want is move these classes out of main, and into flavorA.

So you would have something like this:

project/
   |
   |---src/
        |---flavorA2/
        |      |
        |      |---java/
        |      |     |---com.abc
        |      |                 |-----classA.java
        |      |                 |-----classB.java
        |      |---res/
        |      |---AndroidManifest.xml
        |
        |---main/
        |      |---java/
        |      |     |---com.abc.flavorA
        |      |                 |-----classC.java
        |      |                 |-----classD.java
        |      |---res/
        |      |    |---drawable/
        |      |    |---layout/
        |      |    |---values/
        |      |         
        |      |---AndroidManifest.xml
        |
        |---flavorA/
        |      |---java/
        |      |     |---com.abc
        |      |                 |-----classA.java
        |      |                 |-----classB.java

This way, whenever you pick a flavor, only one version of ClassA and ClassB will be visible.

476rick
  • 2,764
  • 4
  • 29
  • 49
Hassan Ibraheem
  • 2,329
  • 1
  • 17
  • 23
  • 1
    In my case, classes in flavors are not visible by classes in main. Any idea how to solve this? – Barışcan Kayaoğlu Aug 07 '14 at 14:45
  • 1
    They should be visible if you're building the flavor in which they exist. It may be helpful if you can post more details about your setup and build script in another SO question. – Hassan Ibraheem Aug 08 '14 at 11:46
  • What if you have separate fragments, one with ads and one without ads? – Jared Burrows Jun 13 '15 at 17:33
  • There are many ways to implement this. You could have a base Fragment in main source set, that implements the core common functionality, and then extend or wrap that in 2 different classes with the same name (each in its own flavor). – Hassan Ibraheem Jun 13 '15 at 17:41
  • You could also just implement everything once in one Fragment, and check if the advertisement layout exists to decide whether to fetch ads or not. You would then just provide different layout of the Fragment in each flavor. – Hassan Ibraheem Jun 13 '15 at 17:43
  • http://tools.android.com/tech-docs/new-build-system/build-system-concepts#TOC-Build-Variants "Build Types and Product Flavors cannot provide different versions of the same class." – Y2i Jan 06 '16 at 18:54
  • 1
    Correct, but that statement is talking about Build Variants. It means you can't define the same class in both the build type (debug, release...) and flavors, as these would conflict with each other, because by default Gradle combines the source code from main flavor, current flavor and build type, so these three locations should have no conflicting class definitions. – Hassan Ibraheem Jan 06 '16 at 19:04
  • so what I would say is key is either not placing the duplicated file in "main" or only put it in "main" and use something like http://stackoverflow.com/a/23699131/1815624 suggests or see -> http://blog.brainattica.com/how-to-work-with-flavours-on-android/ – CrandellWS Mar 31 '16 at 23:27
  • @maveň Apologies, here you go: https://blog.robustastudio.com/mobile-development/building-multiple-editions-of-android-app-gradle/ Although by now I'd consider it outdated, and there are probably more recent guides out there. – Hassan Ibraheem Oct 06 '16 at 17:22
  • if i keep variant A and B with for variant A - java/com.abc and for Variant B - java/com.abc it gives me duplicate compile time error.. please help – Amit Nov 30 '16 at 06:54
  • 3
    @HassanIbraheem great post! Do you know if there is a way to use the same java class for two flavors withour duplicating them? My use case: I have 3 flavors, but two of them should use the same java class, only for one of them is different. – Fahim Feb 09 '17 at 12:43
  • Same question as Fahim here, exscept it's for 3 different BuildTypes. (Debug, Alpha, Release), where Release should have a class with no method implementations, which SHOULD be implemented for the other 2. – P Kuijpers Jun 20 '17 at 12:32
  • You can achieve this in easier ways using DI (with Dagger for example, or using your own DI), to provide different implementations. To do it with Gradle, without copying code, you'd need to use something like flavor dimensions, to combine multiple flavors into one variant. You can also manually override java.srcDirs in sourceSets block to configure multiple java directories for each flavor. If that small change will be the only difference, consider an if condition over BuildConfig fields, and you can also define your own using buildConfigField. – Hassan Ibraheem Jun 20 '17 at 12:53
  • I structured my classes like this but I am getting a duplicate class error. Anything else needs to be done? – Guy S Dec 03 '17 at 13:41
  • @GuyS That's probably because you have that file in the main folder as well. You cannot store the file in main. it's either main OR the copies of the files in each flavor folder. It's a shame that Android Studio doesn't let developers right click on a java file and "Flavorize" it to auto copy paste it out of the main into each flavor directory. That would make life so much easier for devs. – Saifur Rahman Mohsin Dec 04 '17 at 23:04
  • 2
    @HassanIbraheem This is an issue for me because it makes maintaining the source code more difficult. I have to switch flavor each time to make edits and also copy paste the same code when I make edits. Is there no better alternative besides dagger? For example, I think having something like annotated methods like @Target(flavor='flavorA') to override the method with same name for that flavor is a better solution. Know of any existing library that does this? – Saifur Rahman Mohsin Dec 04 '17 at 23:05
  • is it possible to write class double like this for a class that is part of included dependency? (i.e. jar)... basically I want to change behaviour of specific class in library in debug flavor – Ewoks Jul 14 '18 at 22:19
  • Why did you include ".flavorA" in main/java? – Rowan Gontier May 24 '19 at 01:11
  • @RowanGontier I was building on top of the same package structure in the question. You can consider the answer a refactoring of the question, and then you could rename the package as well, since `classC` and `classD` are actually unflavored. – Hassan Ibraheem May 31 '19 at 04:04
12

In the main build variant, Class A is com.abc.flavorA.classA, and in flavorA2 it's com.abc.flavorA.mk2.classA. These are two different fully-qualified class names and therefore two different classes.

You can't really override entire classes in a flavor. Depending on what you want to do, you might want to look into the BuildConfig mechanism -- in short, this is a class that's generated by the build system which can have values or statements that vary depending on the build type and flavor. You can have runtime code that looks at constants in that class and varies its behavior.

See Android Studio Update 0.4.0 could not find buildConfig() for more information on the syntax, but in brief, it looks like this:

productFlavors {
    flavor {
      buildConfigField "boolean", "MY_FLAG", "true"
    }
}
Community
  • 1
  • 1
Scott Barta
  • 79,344
  • 24
  • 180
  • 163
6

You need to specify sourceSets in your build file. You need to modify your directory structure to make it so that only the folder names are different, everything under the java directory should be the same, so remove the mk2 from the class name. I'm not sure if the syntax is entirely correct but it should look like this:

android {
    sourceSets {
        flavorA {
            java {
                srcDirs = ['src/flavorA/java']
            }
        }

        flavorA2 {
            java {
                srcDirs = ['src/flavorA2/java']
            }
        }
    }
}
ashishduh
  • 6,629
  • 3
  • 30
  • 35
  • 1
    This should be unnecessary to accomplish what he's got in his sample source, and what's more, you're renaming all the files and folders, which should also be unnecessary. – Scott Barta May 16 '14 at 15:25
  • I think in addition to productFlavors{} block, we can also specify the sourceSets{} for each of the product flavor. – ashishdhiman2007 Nov 15 '17 at 11:25
1

Posting this answer here hoping some one might have to implement the same, In my case, I had two static constants where I want to assign different values based on the build variant. So, I have created an object and declared the constants then placed the object in both variants by creating different source sets.

Say the variants are 'Variat_A' & 'Variant_B'

Variat_A/java/package_name.util.variantspec

object ConfigConstant {
    // Carousels
    /**
     * Changing PERCENT_SCALE_X_IN or PERCENT_SCALE_Y_IN should also change scale animation
     * percentages accordingly.
     */
    const val PERCENT_SCALE_X_IN = 0.10 // 10% percentage
    const val PERCENT_SCALE_Y_IN = 0.10 // 10% percentage
}

Variat_B/java/package_name.util.variantspec

object ConfigConstant {
    // Carousels
    /**
     * Changing PERCENT_SCALE_X_IN or PERCENT_SCALE_Y_IN should also change scale animation
     * percentages accordingly.
     */
    const val PERCENT_SCALE_X_IN = 0.15 // 15% percentage
    const val PERCENT_SCALE_Y_IN = 0.15 // 15% percentage
}

Accessing from some other class

class VerticalCarouselAdapter() {
   companion object {
        /**
         * Changing PERCENT_SCALE_X_IN or PERCENT_SCALE_Y_IN should also change scale animation
         * percentages accordingly.
         */
        const val PERCENT_SCALE_X_IN = ConfigConstant.PERCENT_SCALE_X_IN
        const val PERCENT_SCALE_Y_IN = ConfigConstant.PERCENT_SCALE_Y_IN
  }
}
Anoop M Maddasseri
  • 10,213
  • 3
  • 52
  • 73