Build Configurations based on App Variant (BuildType + Flavor)
Asked Answered
C

1

9

I am trying to set signingConfig, manifestPlaceholders, buildConfigField for Application variant. I can set them for each buildType or! productFlavor independently, but what i need is to set them based on both productFlavor and! buildType.

buildTypes{
  getByName("debug"){}
  getByName("release"){}
  create("staging"){}
}

productFlavors {
  create("global"){}
  create("local"){}
}

On the above example, there are 3 different buildTypes and 2 different productFlavors. That means 6 total APK variants. For each of this APK (globalRelease, globalStaging, globalDebug, localRelease, localStaging, localDebug), i want to use different signingConfig for example. How do i set it?

Tried:

  • If i set it in buildType, then there will be only 3 different signingConfigs
  • If i set it in flavor, then there will be only 2 different signingConfigs
  • If i try to set it in applicationVariants.all{}, there is no setter functions, only getters on Variant object (link)
  • setting fields on (variant.mergedFlavor as DefaultProductFlavor) does not add buildConfigField values to BuildConfig.java (link)
Contain answered 1/4, 2020 at 15:13 Comment(1)
Unanswered similar: #32742684Contain
C
16

After Gradle 8.x


Error: "Build Type contains custom BuildConfig fields, but the feature is disabled"

Need to enable buildConfig for variant.buildConfigFields.set(), it's default value was true but now is false:

android {
    buildFeatures {
        buildConfig = true
    }
}

Error: "Could not create an instance of type com.android.build.api.variant.impl.LibraryVariantBuilderImpl ... Namespace not specified. Specify a namespace in the module's build file"

namespace is required in module level build scripts now. You need to specify it for each module separately, and it should be same as package name of that module (not appId).

This is also related to generated R class, it will become my.module.package.R. Another breaking change is you can't use this.module.package.R for referring resources (drawable, string...) of another module you depend on, you have to use my.another.module.R.drawable.login. Because previously default value of android.nonTransitiveRClass flag was false and R included drawables of every module you depend on. Now its default value is true, so R only contains resources of that module (improves build time).

Additionally you need to remove package="" from AndroidManifest.xml of each module.

After Gradle 7.x


Instead of applicationVariants.all{}, we now use androidComponents { onVariants{ .. }} outside android{} block. This code should work on Gradle 7.0.2 and AGP 7.0.1:

androidComponents {
    onVariants { variant ->
        variant.buildConfigFields.put("MY_CUSTOM_FIELD", BuildConfigField("String", "MyCustomValue", null))
        variant.manifestPlaceholders.put("MY_MANIFEST_FIELD", "MyManifestValue")
    }
}

On AGP 7.0.x, there is no way to set signingConfig for mergedFlavor (buildType+flavor). You can set for buildType or flavor individually, but not for combination.

On AGP 7.1.x, you can do it. But it requires AGP 7.1.0-alpha10, Gradle 7.2-rc-3, AndroidStudio BumbleBee 2021.1.1 alpha10:

androidComponents {
    onVariants { variant ->
        variant.signingConfig?.setConfig(android.signingConfigs.getByName("buildTypeXFlavorA"))
    }
}

‎‎ ‎

Before Gradle 7.x


To make changes on different variants (buildType+productFlavor), i had to use android.applicationVariants.all{}. But different paths used to achieve multiple signingConfig, manifestPlaceholders, buildConfigField

1) manifestPlaceholders

applicationVariants.all{
    val variant = this
}

There is no getter/setter for manifestPlaceholders on variant object. Following this, we can make variant.mergedFlavor mutable. Setting manifestPlaceholders on variant.mergedFlavor did work.

import com.android.builder.core.DefaultProductFlavor

applicationVariants.all{
    val manifestPlaceholders: Map<String, String>
    val variant = this
    val mutableMergedFlavor = variant.mergedFlavor as DefaultProductFlavor
    mutableMergedFlavor.addManifestPlaceholders(manifestPlaceholders)
}

2) buildConfigField

Using the same approach, calling addBuildConfigField(ClassFieldImpl(type, name, value)) on mutableMergedFlavor did not work. But instead, it can be set directly on variant.

import com.android.builder.internal.ClassFieldImpl

applicationVariants.all{
    val buildConfigFields: List<ClassFieldImpl>
    val variant = this
    buildConfigFields.forEach { 
        variant.buildConfigField(it.type, it.name, it.value) 
    }
}

3) signingConfig signingConfig can be set on mutableMergedFlavor shown above, except on debug variants. All debug variants use default signing options, even if you set it on variant.mergedFlavor. But if you set default as null, then you can override it as well.

import com.android.builder.core.DefaultProductFlavor

signingConfigs {
    create("myDebug") {}
}
buildTypes {
    getByName("debug") {
        signingConfig = null // to override
    }
}
applicationVariants.all{
    val variant = this
    val mutableMergedFlavor = variant.mergedFlavor as DefaultProductFlavor
    mutableMergedFlavor.signingConfig = signingConfigs.getByName("myDebug")
}

To put all together:

import com.android.build.gradle.api.ApplicationVariant
import com.android.builder.internal.ClassFieldImpl
import com.android.builder.model.SigningConfig
import com.android.builder.core.DefaultProductFlavor
import java.util.*

fun configureVariant(variant: ApplicationVariant,
                     signingConfig: SigningConfig,
                     buildConfigFields: List<ClassFieldImpl>,
                     manifestPlaceholders: Map<String, String>) {

    println("configureVariant: ${variant.name}")
    buildConfigFields.forEach {
        variant.buildConfigField(it.type, it.name, it.value)
    }

    val mutableMergedFlavor = variant.mergedFlavor as DefaultProductFlavor
    mutableMergedFlavor.signingConfig = signingConfig
    mutableMergedFlavor.addManifestPlaceholders(manifestPlaceholders)
}

android {
    signingConfigs {
        create("myDebug") {}
        create("myRelease") {}
    }

    flavorDimensions("region")
    productFlavors {
        create("global") {
            setDimension("region")
            setApplicationId("")
        }
        create("local") {
            setDimension("region")
            setApplicationId("")
        }
    }

    buildTypes {
        getByName("debug") {
            signingConfig = null
        }
    }

    applicationVariants.all {
        val variant = this
        when {
            variant.name.equals("localDebug", true) -> {
                configureVariant(
                        variant,
                        signingConfigs.getByName("localDebug"),
                        listOf(ClassFieldImpl("String", "NAME", "\"VALUE\"")),
                        mapOf("KEY" to "VALUE")
                )
            }
            variant.name.equals("globalStaging", true) -> {
                configureVariant(
                        variant,
                        signingConfigs.getByName("globalStaging"),
                        listOf(ClassFieldImpl("String", "NAME", "\"VALUE2\"")),
                        mapOf("KEY" to "VALUE2")
                )
            }
        }
    }
}

Contain answered 2/4, 2020 at 13:35 Comment(3)
After Android Studio 4.0.0 (gradle 6.1.1), DefaultProductFlavor can not be resolved. AbstractProductFlavor can be used as workaroundContain
signingConfig part does not work on gradle 7Contain
On Gradle 7.4 and AGP 7.3.1 we can use another approach: https://mcmap.net/q/1176934/-how-to-set-signingconfig-for-different-flavor-dimensionsFacet

© 2022 - 2024 — McMap. All rights reserved.