skip to content

What are product flavors in AGP, and how do flavor dimensions let you combine them?

level: middleimportance: must knowfreq 60%

answer

  1. what the product is, not how it's built
  2. every flavor needs a dimension
  3. Cartesian product across dimensions
  4. dimension order = priority
  5. <flavor>Implementation deps + combined source sets

basics

~10 s

Product flavors define what version of an app you build (e.g. free vs paid). Flavor dimensions group flavors so AGP combines one flavor from each dimension into the final app.

solid answer

~40 s

A **product flavor** captures a different *variant of the product itself* — different app id, branding, API endpoints, or feature set — as opposed to a build type, which only changes *how* the same product is packaged. Flavors are declared in `android { productFlavors { ... } }` and each must belong to a **flavor dimension** declared via `flavorDimensions += "..."`. Dimensions let you take a *combinatorial product*: with a `tier` dimension (`free`, `paid`) and an `env` dimension (`staging`, `prod`) you get four flavor combinations, each then multiplied by build types. Each flavor contributes its own source set (`src/free`, plus combined `src/freeProd`) and can override `applicationId`, `versionName`, `buildConfigField`, `manifestPlaceholders`, and dependencies via `freeImplementation(...)`. Flavors are an AGP concept built on Gradle source sets — Gradle core knows nothing about them.

code

kotlin · 17 lines
kotlin
android {
  flavorDimensions += "tier"
  productFlavors {
    create("free") {
      dimension = "tier"
      applicationIdSuffix = ".free"
    }
    create("paid") {
      dimension = "tier"
      applicationIdSuffix = ".paid"
    }
  }
}

dependencies {
  "paidImplementation"("com.example:premium-sdk:1.0.0")
}

go deeper

for a junior

Know that flavors are like free/paid and produce different app variants.

for a middle

Explain dimensions, the Cartesian product, per-flavor source sets and dependencies, and that every flavor needs a dimension.

for a senior

Discuss dimension priority/merge order, white-label scaling, missingDimensionStrategy, and combinatorial explosion management.

for a principal

Govern flavor strategy org-wide: limiting variant blow-up, white-label modularization, and treating flavors as an AGP layer over Gradle source sets.

## Flavors vs build types AGP exposes **two orthogonal axes**: - **Build type** = *how* it's built (debug/release) — covered separately. - **Product flavor** = *what* product it is. Think `free` vs `paid`, `clientA` vs `clientB` (white-label), or `mock` vs `prod` backends. Different flavors can ship different `applicationId`s, icons, strings, endpoints, and even different code. ## Flavor dimensions — why they exist Without dimensions, AGP wouldn't know how to combine multiple independent flavor concerns. A **flavor dimension** is a named group; you put each flavor into exactly one dimension, and AGP forms the **Cartesian product** of one flavor per dimension. ```kotlin android { flavorDimensions += listOf("tier", "env") productFlavors { create("free") { dimension = "tier"; applicationIdSuffix = ".free" } create("paid") { dimension = "tier"; applicationIdSuffix = ".paid" } create("staging") { dimension = "env"; buildConfigField("String", "API", "\"https://staging\"") } create("prod") { dimension = "env"; buildConfigField("String", "API", "\"https://prod\"") } } } ``` This yields flavor combinations: `freeStaging`, `freeProd`, `paidStaging`, `paidProd`. The **dimension declaration order matters**: it sets *priority*, so if two flavors set the same property, the flavor from the earlier-listed dimension wins. ## Source sets created Each flavor and each combination gets a source set merged on top of `src/main`: - per-flavor: `src/free/`, `src/paid/`, `src/staging/`, `src/prod/` - per-combination: `src/freeProd/`, `src/paidStaging/`, … - combined with build types too: `src/freeProdRelease/` Merge order (lowest→highest priority): main → dimensions in declared order → build type → combination overlays. Higher priority wins on conflicts (manifests merge, resources override). ## Per-flavor dependencies & config You can add dependencies only to a flavor with `<flavor>Implementation(...)`, e.g. `paidImplementation("com.example:premium-sdk:1.0")`. You can also set `applicationId`, `versionNameSuffix`, `manifestPlaceholders`, and `missingDimensionStrategy` (for matching against a library's dimensions). ## Relationship to Gradle core Flavors compile down to additional Gradle source sets, configurations, and tasks. They are an AGP-only abstraction — go to the Android docs for deep flavor/manifest-merging/resource-overlay rules.

  • What happens if you declare a product flavor without assigning a dimension?
    AGP fails the build: once any flavor dimension exists, every product flavor must specify `dimension = "..."`. There is no implicit single dimension once you've declared one.
  • If two flavors from different dimensions both set a BuildConfig field, which wins?
    The flavor from the dimension listed earlier in `flavorDimensions` has higher priority and wins.
  • How do per-flavor dependencies work?
    AGP generates configurations like `freeImplementation`/`paidImplementation`; dependencies added there apply only to variants built from that flavor.

Flavors are like ordering a coffee: 'size' and 'milk type' are two dimensions, and one choice from each combines into your specific drink — many drinks from a few independent options.

saying these in an interview costs you the question

  • Confusing flavors (what) with build types (how)
  • Saying dimension order doesn't matter — it sets priority
  • Thinking you can mix flavors freely from the same dimension into one app (only one per dimension)

context