Multi flavor app based on multi flavor library in Android Gradle
Asked Answered
E

9

115

My app has several flavors for several markets in-app-billing systems.

I have a single library which shares the base code for all of my projects. So I decided to add those payment systems to this library as product flavors.

The question is can android library have product flavors?

If so, how can I include different flavors in respective flavor of the app?

I searched a lot, and I couldn't find anything about this scenario. The only close thing I found was this in http://tools.android.com/tech-docs/new-build-system/user-guide:

dependencies {
    flavor1Compile project(path: ':lib1', configuration: 'flavor1Release')
    flavor2Compile project(path: ':lib1', configuration: 'flavor2Release')
}

I changed configuration to different things but it did not work!

I'm using android studio 0.8.2.

Educe answered 21/7, 2014 at 8:27 Comment(1)
after many search, I found not way to achieve this, even i upgrade android plugin to the lastest 3.4.2 version and gradle to the lastest 5.5.1, it still failed with compile time, or resource linking failed in aapt, or cannot find the symbol that inside the library moduleSatori
E
146

Finally I found out how to do this, I will explain it here for others facing same problem:

If App and Library have same Flavor name(s)

It's possible since Gradle Plugin 3.0.0 (and later) to do something like:

Library build.gradle:

apply plugin: 'com.android.library'

// Change below's relative-path
// (as the `../` part is based on my project structure,
// and may not work for your project).
apply from: '../my-flavors.gradle'

dependencies {
    // ...
}

android {
    // ...
}

Project build.gradle:

buildscript {
    // ...
}

apply plugin: 'com.android.application'
// Note that below can be put after `dependencies`
// (I just like to have all apply beside each other).
apply from: './my-flavors.gradle'

dependencies {
    api project(':lib')
}

android {
    productFlavors {
        // Optionally, configure each flavor.
        market1 {
            applicationIdSuffix '.my-market1-id'
        }
        market2 {
            applicationIdSuffix '.my-market2-id'
        }
    }
}

My flavors .gradle:

android {
    flavorDimensions 'my-dimension'
    productFlavors {
        market1 {
            dimension 'my-dimension'
        }
        market2 {
            dimension 'my-dimension'
        }
    }
}

If App or Library has different Flavor-name (old answer)

The key part is to set publishNonDefault to true in library build.gradle, Then you must define dependencies as suggested by user guide.

Update 2022; publishNonDefault is now by default true, and setting it to false is ignored, since said option is deprecated.

The whole project would be like this:

Library build.gradle:

apply plugin: 'com.android.library'

android {        
    ....
    publishNonDefault true
    productFlavors {
        market1 {}
        market2 {}
        market3 {}
    }
}

project build.gradle:

apply plugin: 'com.android.application'

android {
    ....
    productFlavors {
        market1 {}
        market2 {}
        market3 {}
    }
}

dependencies {
    ....
    market1Compile project(path: ':lib', configuration: 'market1Release')
    market2Compile project(path: ':lib', configuration: 'market2Release')

    // Or with debug-build type support.
    android.buildTypes.each { type ->
        market3Compile project(path: ':lib', configuration: "market3${type.name}")
    }

}

Now you can select the app flavor and Build Variants panel and the library will be selected accordingly and all build and run will be done based on the selected flavor.

If you have multiple app module based on the library Android Studio will complain about Variant selection conflict, It's ok, just ignore it.

enter image description here

Educe answered 23/7, 2014 at 12:37 Comment(15)
Thanks for sharing, now I can get rid of my defaultPublishConfig workaround.Veto
From your gradle file looks like we need to have same name for library and application product flavors am I right?Subtend
@AshwinNBhanushali not necessarily, I just named them so for readability!Educe
Running AS 1.1.0, the above solution seems to still work, however 1) debug/release builds choice is lost and I seem to keep having issues with AIDL found in the library which fail to produce the appropriate code very often. Any thoughts on this?Thompkins
There seem to be a bug when there is an AIDL in a flavored library (didn't try in a flavored app). The AIDL is compiled into java, however it cannot be used: gradle build reports: package <aidl_pkg> does not exist and a lot of errors because of that. I can't use flavors.Thompkins
@Educe What about buildTypes{} block? Does that play any role in your solution.Linguistic
@Linguistic buildTypes has nothing to do with this. Every flavor has all the build types (usually debug and release) and all of them works with this approach.Educe
what is market1Compile ? normally you would use just 'compile'Mezuzah
@Mezuzah it defines the library to be used for market1 flavor!Educe
the market1Compile is not necessary doing something like compile project(path: ':lib', configuration: 'market1Release') is enoughTedford
Why it is set to "release" build type? Is the "release" build type chosen during debug builds?Gourami
After doing same stuff in My application, Error:java.lang.RuntimeException: Error: more than one library with package name , occouredHodgkins
I have added an answer for those who are looking to migrate to Android Plugin 3.0.0 or higher. Hope it helps.Tetreault
@Educe Can we do something for android studio to not complain about Variant selection conflict?Pearcy
In the latest gradle plugin version(4.2.1), this answer is WRONG. Do NOT add configuration parameter in your xImplementation calls, or you will encounter an error. Gradle will try to match the flavor automaticly, if it failed, you need add some matchingFallbacks, see the doc for details.Quadruplex
D
35

There are one problem with Ali answer. We are losing one very important dimension in our build variants. If we want to have all options (in my example below 4 (2 x 2)) we just have to add custom configurations in main module build.gradle file to be able to use all multi-flavor multi-buildType in Build Variants. We also have to set publishNonDefault true in the library module build.gradle file.

Example solution:

Lib build.gradle

android {

    publishNonDefault true

    buildTypes {
        release {
        }
        debug {
        }
    }
    productFlavors {
        free {
        }
        paid {
        }
    }
}

App build.gradle

android {

    buildTypes {
        debug {
        }
        release {
        }
    }
    productFlavors {
        free {
        }
        paid {
        }
    }
}

configurations {
    freeDebugCompile
    paidDebugCompile
    freeReleaseCompile
    paidReleaseCompile
}

dependencies {

    freeDebugCompile project(path: ':lib', configuration: 'freeDebug')
    paidDebugCompile project(path: ':lib', configuration: 'paidDebug')
    freeReleaseCompile project(path: ':lib', configuration: 'freeRelease')
    paidReleaseCompile project(path: ':lib', configuration: 'paidRelease')

}
Dysphemism answered 24/4, 2016 at 20:14 Comment(1)
After doing same stuff in My application, Error:java.lang.RuntimeException: Error: more than one library with package name , occouredHodgkins
T
23

Update for Android Plugin 3.0.0 and higher

According to the official Android Documentation - Migrate dependency configurations for local modules,

With variant-aware dependency resolution, you no longer need to use variant-specific configurations, such as freeDebugImplementation, for local module dependencies—the plugin takes care of this for you

You should instead configure your dependencies as follows:

dependencies {
    // This is the old method and no longer works for local
    // library modules:
    // debugImplementation project(path: ':library', configuration: 'debug')
    // releaseImplementation project(path: ':library', configuration: 'release')

    // Instead, simply use the following to take advantage of
    // variant-aware dependency resolution. You can learn more about
    // the 'implementation' configuration in the section about
    // new dependency configurations.
    implementation project(':library')

    // You can, however, keep using variant-specific configurations when
    // targeting external dependencies. The following line adds 'app-magic'
    // as a dependency to only the "debug" version of your module.

    debugImplementation 'com.example.android:app-magic:12.3'
}

So in Ali's answer, change

dependencies {
    ....
    market1Compile project(path: ':lib', configuration: 'market1Release')
    market2Compile project(path: ':lib', configuration: 'market2Release')
}

to

implementation project(':lib')

And plugin will take care of variant specific configurations automatically. Hope it helps to others upgrading Android Studio Plugin to 3.0.0 and higher.

Tetreault answered 10/2, 2018 at 6:57 Comment(1)
The new feature does only apply if all flavors have the exact same names (both the root App and each Library), hence even if a single library has a separate flavor-name, the old syntax should be used.Landaulet
V
11

My Android Plugin is 3.4.0,and I find that it doesn't need configurations now.All you need is to make sure the flavorDimensions and productFlavors in application contains one productFlavor of the same flavorDimensions and productFlavors in libraries.For sample:

In mylibrary's build.gradle

apply plugin: 'com.android.library'

android {        
    ....
    flavorDimensions "mylibFlavor"

    productFlavors {
        market1
        market2
    }
}

application's build.gradle:

apply plugin: 'com.android.application'

android {
    ....
    flavorDimensions "mylibFlavor", "appFlavor"
    productFlavors {
        market1 {
            dimension "mylibFlavor"
        }
        market2 {
            dimension "mylibFlavor"
        }
        common1 {
            dimension "appFlavor"
        }
        common2 {
            dimension "appFlavor"
        }
    }
}

dependencies {
    ....
    implementation project(path: ':mylibrary')
}

After sync,you can switch all options in Build Variants Window: enter image description here

Voight answered 21/5, 2019 at 8:1 Comment(3)
But what if I dont want to have the same flavors in my main app module? Suppose I have multiple app modules that have its own specific flavors and one common module that has its own flavors and I want to use in my app my lib with specific flavor. How would you do that? It does not make sense to copy my lib flavors to all apps.Galliwasp
@Galliwasp You don't need copy all,just keep one same productFlavor in app,for my sample,I can keep market1 or market2 in application's build.gradle.Voight
worked on gradle-7.4.2 and Android plugin 4.2.2Satori
P
3

I also ran into a problem compiling modules for various options.

What i've found:

It looks like we don't need add publishNonDefault true into lib's build.gradle file, since Gradle 3.0.1.

After decompiling a class BaseExtension found this:

public void setPublishNonDefault(boolean publishNonDefault) {
   this.logger.warn("publishNonDefault is deprecated and has no effect anymore. All variants are now published.");
}

And instead of:

dependencies {
...
   Compile project(path: ':lib', configuration: 'config1Debug')
}

We should use:

dependencies {
...
   implementation project(':lib')
}

Only the important thing, is to add a configurations {...} part to the build.gradle.

So, the final variant of app's build.gradle file is:

buildTypes {
   debug {
      ...
   }

   release {
      ...
   }
}

flavorDimensions "productType", "serverType"
productFlavors {
   Free {
      dimension "productType"
      ...
   }
   Paid {
      dimension "productType"
      ...
   }
   Test {
      dimension "serverType"
      ...
   }
   Prod {
      dimension "serverType"
      ...
   }
}

configurations {
   FreeTestDebug
   FreeTestRelease
   FreeProdDebug
   FreeProdRelease
   PaidTestDebug
   PaidTestRelease
   PaidProdDebug
   PaidProdRelease
}

dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation project(':lib')
   ...
}

Also, you can use Filter variants to restrict build variants.

P.s. don't forget to include modules in the settings.gradle file, like:

include ':app'
include ':lib'
project(':lib').projectDir = new File('app/libs/lib')
Presidency answered 23/3, 2018 at 16:9 Comment(3)
sir, can you explain how the scrip will determine weather to include library to a certain configuration or not? i mean i have a case when i need to use some lib for a certain flavor, but i don't need to use it for the other flavorPharmacognosy
Didn't get into such situation. But a google tutorial developer.android.com/studio/build/dependencies recommends to add a prefix before "implementation" command in the "dependencies {...}" block. I.e. dependencies { paidImplementation project(':lib') }, or dependencies { debugImplementation project(':lib') }, or any multiple variant combination dependencies { paidProdDebugImplementation project(':lib') }. Check it out and give us a feedback :)Presidency
Hi @Sergio, we are also having multiple build variant in our application, we can able run the app on multiple flavors but unable to create aar file for it. We are getting below error while generating .aar file for each flavors. * What went wrong: Execution failed for task ':sdk:explodeSample-sdkVariantPreprodDeviceDebug'. > Cannot expand ZIP 'xyz.aar' as it does not exist.Reproduce
T
2

To get the flavors working on an AAR library, you need to define defaultPublishConfig in the build.gradle file of your Android Library module.

For more information, see: Library Publication.

Library Publication

By default a library only publishes its release variant. This variant will be used by all projects referencing the library, no matter which variant they build themselves. This is a temporary limitation due to Gradle limitations that we are working towards removing. You can control which variant gets published:

android { defaultPublishConfig "debug" }

Note that this publishing configuration name references the full variant name. Release and debug are only applicable when there are no flavors. If you wanted to change the default published variant while using flavors, you would write:

android { defaultPublishConfig "flavor1Debug" }

Tuque answered 16/8, 2016 at 11:47 Comment(0)
V
1

At the moment it's not possible, although if I recall correctly its a feature they want to add. (Edit 2: link, link2 )

Edit: For the moment I'm using the defaultPublishConfig option to declare which library variant get's published:

android {
    defaultPublishConfig fullRelease
    defaultPublishConfig demoRelease 
}
Veto answered 21/7, 2014 at 8:32 Comment(5)
So, each time I'm going to compile the app I must change this in build.gradle of the library?Educe
Well, yeah...each time you want to compile the app with a different flavor.Veto
Actually when I define flavors for library module, the inherited R package is not found the app module.Educe
Did you sync the gradle files in AS?Veto
@Veto This seems like manual labor and very brittle (developers are lazy and forget to modify their build.gradle files).Linguistic
G
1

I know this subject has been closed, but just an update with gradle 3.0, see this : https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#variant_aware and grep matchingFallbacks and missingDimensionStrategy. Now it's way more simple to declare the dependencies between module flavors.

...and in this precise case with gradle3.0, as flavors share the same name, gradle would map them magically, there is no configuration required.

Georgeta answered 23/10, 2017 at 9:47 Comment(1)
For me it seems that that runtime generated stuff is skipped. As example simonvt-> schematics generation is not working anymore with the new way for me. :-/Gambia
M
0

In this situation. How could I import the dependency for a specific build. For example: market1Common1Debug market1Common1DebugImplementation 'androidx.appcompat:1.2.0'

enter image description here

Morocco answered 25/5, 2021 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.