Android Build Variants and Product Flavors: Multiple Brands From One Codebase
How flavor dimensions and build variants ship separate store listings without rebuilding the Android app
Published July 2026 by Batteries Included
Scope: This guide is for native Android apps built with Gradle (Kotlin, Jetpack Compose, or legacy XML). Product flavors are an Android Gradle packaging feature. They are not a Kotlin Multiplatform (KMP) sharing mechanism, and iOS has no Gradle equivalent. If you share logic with KMP, you still configure product flavors on the Android app module; iOS branding and store listings are set up separately in Xcode.
Your company wants a second consumer brand: its own icon, store listing, marketing story, and support expectations. Behind the scenes, accounts, messaging, safety tooling, and the flows that turn a download into a paying relationship are often the same product. The expensive mistake is treating that as a greenfield rewrite: two teams, two backlogs, and months before the second app reaches parity with the first.
On native Android, the answer is a combination of product flavors, flavor dimensions, and build variants: different versions of the same project that can coexist on a device and in Google Play, sharing common source code and resources while changing what must differ per brand. Each flavor sits in a flavor dimension; Gradle combines flavors with build types (debug, release) to produce the build variants you ship.
The usual sequence is to stabilize one app in the store first, then add one or more product flavors to the master codebase and launch additional brands without building the Android app from scratch.
Short answer
Use Android product flavors and build variants when you need separate store listings and brand identity but the same core product behavior. Define one flavor dimension (for example brand) with a flavor per listing. Keep shared logic, navigation, account lifecycle, and release discipline in main/; isolate per-brand packaging (application ID, icons, themes, copy, API endpoints, signing) in flavor source sets and Gradle config. Do not fork the codebase into two repositories unless the products are genuinely diverging.
Is this the same as Kotlin Multiplatform? No. This article is Android Gradle packaging. KMP is about sharing Kotlin code across platforms; flavors are about shipping different Android store listings from one Android project.
Build variants, flavor dimensions, and product flavors
Three terms show up together in Gradle docs and search results. If product flavors are the answers, the flavor dimension is the question they answer.
Product flavors are the specific variants you ship: brandA, brandB, and brandC. Each one can have its own application ID, icons, strings, and config.
A flavor dimension is the category those variants belong to. For multiple store listings from one codebase, you typically define one dimension named brand, then add one product flavor per listing (brandA, brandB, and so on). Every product flavor must sit inside a dimension; the dimension name is not the app itself. It tells Gradle how to group and combine flavors.
A build variant is what you actually build: one product flavor plus one build type (brandA + release → brandARelease).
Google's API reference defines product flavors as versions of your project you expect to coexist on a single device, in Google Play, or in a repository. Each flavor can specify different features, device requirements, resources, and application IDs while sharing everything else. Configuration lives in the productFlavors block of your app module's build file, similar to build types.
For a second consumer brand, one brand dimension with two or more flavors is typical. That yields build variants such as brandADebug, brandBRelease, and brandCRelease.
When one dimension is enough
Use a single dimension when the only meaningful split is which listing you ship. Dimension brand with flavors brandA and brandB covers separate application IDs, icons, and store presence without multiplying other axes.
When you need multiple dimensions
Add another dimension when the app varies on two independent kinds of difference and Gradle must combine one flavor from each. Gradle never merges two flavors from the same dimension; it picks exactly one per dimension, then adds a build type.
Google's build variants documentation uses a mode dimension (demo, full) for feature set and an api dimension (minApi21, minApi24) for minimum SDK level. Those are different questions, so the build matrix includes combinations like demoMinApi24Release, not just demoRelease.
Other multi-dimension cases include brand × tier (each brand ships free and paid SKUs) or brand × market (regional legal or API config per brand). Quick test: do you need every brand to also exist as free and paid (or US and EU)? If yes, use two dimensions. If you only need Brand A and Brand B as separate Play listings, one brand dimension is enough.
Do not add a dimension for debug vs release (that is build types) or for staging vs production unless that split must multiply with brand (many teams handle environment with build types or config flags instead).
What to change per brand (and what to keep shared)
Per brand: isolate these:
- Application ID. Google Play treats each application ID as a separate app. Use a distinct
applicationIdper flavor (or anapplicationIdSuffixif you prefer a shared base). The build variants guide explainsapplicationIdvs the manifestpackageattribute: you keep the manifest package forRclass resolution; the application ID is what Play and the device use for distribution. - Branding resources. Icons, colors, strings, splash assets, and store screenshots belong in flavor-specific source sets (for example
src/brandB/res/). Resources in a flavor folder overridemain/. - Manifest placeholders and app name. Use
manifestPlaceholdersor flavor-specific manifests for the launcher label, deep-link hosts, or analytics keys that must differ per listing. - Environment config.
buildConfigField,resValue, and flavor-specific dependencies (brandBImplementation) are the right place for API base URLs, feature flags, or SDK keys that differ by brand, not scatteredif (brand == …)checks in shared UI code. - Signing. Release builds for each brand typically need separate signing configs if the brands are owned or operated under different legal entities.
Shared: keep these in main/:
- Core domain logic, repositories, and networking models
- Account creation, login, and safety flows that behave the same
- Navigation structure and most screens
- Crash reporting, release automation, and CI entry points
- Bug fixes and feature work that should land for both brands unless product explicitly scopes a change to one listing
Google's source sets documentation is explicit: main/ is the default for everything shared across variants. Create additional source sets only where a file truly differs per flavor or build type.
Where the money is saved
Flavors do not make engineering free. They remove the duplicate cost of maintaining two independent apps when the product is largely the same.
In practice, savings show up as:
- One backlog for shared product work. A fix to checkout, notifications, or onboarding ships once and reaches both store listings after QA on both variants.
- One regression surface for shared code. You still test two binaries, but you are not merging the same security patch into two diverging repositories six months later.
- Faster second-brand launch. The second listing inherits years of edge-case handling from the first app instead of rediscovering it in a rewrite.
- Smaller team footprint. You do not need parallel Android squads for parity that is mostly packaging and positioning.
Android's modularization guide frames modules as building blocks that multiple apps or flavors can share. You do not have to modularize before your first flavor, but teams that already separate feature: modules often find flavor boundaries easier to reason about.
When flavors are the wrong tool
Product flavors are not a substitute for product strategy. Skip or delay them when:
- The second “brand” is a different product. Different user journeys, different data models, or different compliance regimes eventually fight a shared codebase. Forking, or a deliberate new app, may be cheaper than endless
when (flavor)branches. - The first app is not stable enough to clone. Adding a second variant on top of a fragile master branch doubles QA and release risk. Stabilize release discipline on one listing first; then add the second flavor from a codebase you trust.
- Brand differences infect every screen. If “only the logo changes” was the pitch but every sprint adds brand-specific exceptions, you are maintaining two apps with extra Gradle complexity.
- You only need internal QA vs production. That is usually a build type or environment dimension, not a consumer-facing second brand. Mixing
debug/releaseconcerns into brand flavors makes the variant matrix harder to read.
Adding flavors to an existing app: a practical sequence
This is a practical sequence when a live consumer app needs a second brand, not a greenfield white-label template.
- Pick one flavor dimension (
brand) and name flavors after the business entities, not build environments. - Assign distinct application IDs and confirm Play Console accounts, signing keys, and listing ownership for each.
- Extract the smallest brand-specific surface first: app name, icon, theme colors, and splash, before touching feature code.
- Move hard-coded brand strings and assets out of
main/into flavorres/folders orbuildConfigFieldvalues. - Wire CI to build and test both release variants on every mainline merge, not only the original brand.
- Run store submission as a checklist per flavor: content rating, data safety, screenshots, and deep links are per listing even when the APK pipeline is shared.
Pitfalls that erase the savings
- Flavor checks in shared Kotlin. A few
BuildConfig.FLAVORbranches become dozens. Prefer interfaces implemented in tiny flavor-specific source files, or resource overrides, over conditionals in ViewModels and composables. - Variant explosion. Each flavor dimension multiplies build variants. Use
variantFilterto drop combinations you never ship (for example astagingbuild type you do not need per brand). - Library flavor mismatches. Android library modules that define their own product flavors must align with the app via
missingDimensionStrategyormatchingFallbacks, documented in the ProductFlavor API reference. Misconfiguration shows up late as “could not resolve” variant errors, not as a clear product message. - Testing only the original brand. The second listing becomes the one that breaks silently in production because QA habits stayed on variant one.
Google Play and separate listings
To publish multiple consumer brands as separate apps on Google Play, each product flavor you ship as its own listing needs a distinct application ID. Google's build variants documentation describes a different pattern for legacy multi-APK apps (created before August 2021): multiple APKs under one listing can share an application ID if each APK has a unique version code. See Multiple APK support. That targets device configuration splits (API level, ABI, and similar), not separate consumer brands. With modern Android App Bundle (AAB) publishing, treat each Play listing as one application ID.
Android vitals and discoverability apply per listing. A strong rating on one brand does not transfer to the others; plan store presence work for each public app.
What about iOS and Kotlin Multiplatform?
Native iOS (Swift/SwiftUI): No Gradle product flavors. Multiple brands usually mean separate Xcode targets or schemes, xcconfig files, and bundle IDs per App Store listing. Android flavor work does not carry over automatically.
Kotlin Multiplatform (KMP): KMP can share business logic in Kotlin, but application IDs, bundle IDs, icons, signing, and store listings remain per platform. You typically still use product flavors on the Android app module and a parallel target or scheme setup on iOS. KMP may reduce duplicate logic work; it does not remove per-brand packaging on either store. See our KMP and Compose Multiplatform guide if you are weighing shared logic vs fully native apps.
Bottom line: Scoping a second brand on Android alone is its own effort. Adding iOS (native or KMP) is a separate line item; do not assume Android flavors halve total multi-platform cost.
Need a second brand from one Android codebase?
We help teams with live Android apps add product flavors, tighten release discipline, and launch additional store listings without a rewrite. For portfolio or platform scope that needs buy-in first, start with a Discovery Roadmap.
Related