Product Flavor: wat het is, configuratie en voorbeelden in Gradle

Auteur: IT Sectr Gepubliceerd: 2026-05-30 Leestijd: 9 min

Product Flavor in Android-ontwikkeling is een Gradle-mechanisme waarmee je meerdere varianten van een app kunt maken vanuit een gedeelde codebasis. Elke flavor kan een eigen applicationId, bronnen, afhankelijkheden en functionaliteit hebben — bijvoorbeeld een gratis en een betaalde versie. Volgens Google Android Developers, 2025 maken Product Flavors deel uit van het Build Variants-systeem en worden ze gecombineerd met Build Types via flavorDimensions. Dit is de standaardaanpak voor het publiceren van meerdere versies van een app in Google Play.

Belangrijkste

  • Product Flavor — productvariant met unieke applicationId, bronnen en code.
  • Flavor Dimensions groeperen flavors in onafhankelijke assen voor multidimensionale configuratie.
  • Source sets voor een flavor overschrijven main-bronnen: pictogrammen, teksten, manifest.
  • Gradle genereert automatisch een Build Variant voor elke combinatie van flavor + build type.
  • Google Play ondersteunt het publiceren van meerdere flavors als aparte apps of als één met verschillende configuraties.

Wat is Product Flavor?

Product Flavor is een Gradle-configuratie in het android.productFlavors-blok dat een productvariant beschrijft. Elke flavor kan applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig en andere defaultConfig-parameters overschrijven. Product Flavors hebben geen kwantitatieve beperkingen: een project kan 2, 5 of 10 flavors bevatten — Gradle verwerkt alle combinaties.

Product Flavor lost het probleem van codebase reuse op — wanneer uit één repository meerdere verschillende apps moeten worden gebouwd. Typische scenario’s: gratis versie met advertenties en betaalde versie zonder; demoversie met beperkte functionaliteit; zakelijke en consumentenversies; white-label apps voor verschillende klanten. Zonder Product Flavors zou elke versie in een apart project moeten worden onderhouden, wat leidt tot 60-70% codeduplicatie.

Historisch gezien verschenen Product Flavors in Android Gradle Plugin 0.9 (2013) als vervanging van ant-configuraties. Daarvoor gebruikten ontwikkelaars aparte projecten voor verschillende versies of handmatige vervanging van bronnen voor het compileren. De introductie van flavors in AGP verenigde de aanpak en maakte het de standaard. Volgens een enquête van JetBrains, 2024 gebruikt 78% van de Android-projecten met meerdere versies Product Flavors, de rest — handmatig schakelen via BuildConfig of reflection.

Product Flavor vs Build Type

Build Type beheert het compilatieproces (debug met debugging, release met optimalisatie). Product Flavor beheert de inhoud van de build (free zonder betaalde functies, paid met deze). Build Type is een infrastructurele configuratie, Product Flavor — een productconfiguratie. Beide concepten zijn orthogonaal: een debug-build van free-flavor verschilt van een release-build van free-flavor alleen in compilatieparameters, niet in functionaliteit. Product Flavor kan niet worden gebruikt om debugging uit te schakelen — dat is de taak van Build Type.

Flavor Dimensions: organisatie van dimensies

Volgorde van dimensies en prioriteit

Flavor Dimensions (dimensies) — een mechanisme om Product Flavors in onafhankelijke categorieën te groeperen. Als een app een gratis/betaalde versie heeft en apart een Amerikaanse/Europese regio, worden de flavours in twee dimensies gegroepeerd: „tier” (free, paid) en „region” (us, eu). Gradle maakt het cartesisch product van de dimensies: freeUs, freeEu, paidUs, paidEu — 4 varianten. Zonder dimensies zou Gradle alle vier flavours als één vlak beschouwen en kon er maar één worden geselecteerd.

Dimensies worden gedeclareerd in het flavorDimensions-blok als een string of een lijst strings. De volgorde van de dimensies beïnvloedt de prioriteit van source sets: de eerste dimensie heeft de hoogste prioriteit. Als dimensie A (tier) als eerste komt, overschrijft src/free/ src/us/ bij een resourceconflict. De volgorde beïnvloedt ook de naamgeving van Variant: eerst de flavor van de eerste dimensie, dan de tweede, dan Build Type: freeUsDebug.

Het aantal dimensies is niet beperkt, maar elke nieuwe dimensie vermenigvuldigt het aantal Build Variants. Voor een project met 4 dimensies (elk 2 flavours) en 2 build types: 2 × 2 × 2 × 2 × 2 = 32 varianten. Praktische limiet — 3 dimensies (maximaal 8-12 varianten). Meer — en de Gradle-configuratie vertraagt, en in Android Studio wordt het Build Variants-paneel onleesbaar.

groovy
android {
    flavorDimensions "tier", "api"

    productFlavors {
        free {
            dimension "tier"
            applicationId "com.example.app.free"
            versionNameSuffix "-free"
        }
        paid {
            dimension "tier"
            applicationId "com.example.app.paid"
        }
        minApi21 {
            dimension "api"
            minSdk 21
        }
        minApi26 {
            dimension "api"
            minSdk 26
        }
    }
}

// Resultaat: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Elk × debug/release = 8 Build Variants

Product Flavors maken in build.gradle

Kotlin DSL voor Product Flavors

Om een Product Flavor te maken, moet het productFlavors-blok worden toegevoegd binnen android, met de naam van de flavor en de parameters. De minimale declaratie van een flavor is naam en dimension. Alle andere parameters worden overgeërfd van defaultConfig en kunnen worden overschreven. Flavor erft defaultConfig volledig, inclusief applicationId, versionCode en testInstrumentationRunner.

Elke flavor kan applicationId overschrijven — dit maakt het mogelijk om meerdere versies van de app tegelijk op één apparaat te installeren. De free-versie wordt bijvoorbeeld com.example.app.free en paid — com.example.app.paid. Als applicationId niet wordt overschreven, hebben alle flavours dezelfde identifier en kunnen ze niet parallel worden geïnstalleerd. applicationId moet overeenkomen met de package in het manifest (tenzij applicationIdSuffix wordt gebruikt).

AGP 8+ beveelt het gebruik van Kotlin DSL aan in plaats van Groovy voor build.gradle. Kotlin DSL biedt type-veilige toegang tot configuratie: de IDE stelt parameternamen voor, controleert types tijdens compilatie en markeert fouten. Migratie van Groovy naar Kotlin DSL voor Product Flavors bestaat meestal uit het vervangen van aanhalingstekens door haakjes en het toevoegen van types. AGP is achterwaarts compatibel — beide syntaxen werken parallel in hetzelfde project.

kotlin
// build.gradle.kts — Kotlin DSL
android {
    flavorDimensions += "tier"

    productFlavors {
        register("free") {
            dimension = "tier"
            applicationId = "com.example.app.free"
            versionNameSuffix = "-free"
            buildConfigField("boolean", "IS_PREMIUM", "false")
        }
        register("paid") {
            dimension = "tier"
            applicationId = "com.example.app.paid"
            versionNameSuffix = "-paid"
            buildConfigField("boolean", "IS_PREMIUM", "true")
        }
    }
}

Bronnen en code voor verschillende flavors

Elke Product Flavor maakt zijn eigen source set aan — de map src/<flavorName>/. In deze map kunnen overschreven bronnen, broncode en manifest worden geplaatst. De source set van een flavor werkt als een overlay boven main: bestanden uit src/free/res/ overschrijven bestanden uit src/main/res/ met dezelfde naam. Dit maakt verschillende teksten, pictogrammen, kleuren en layouts mogelijk voor elke flavor zonder de hoofdcode te wijzigen.

Voor het overschrijven van Java/Kotlin-klassen zijn er twee benaderingen: flavor-specific implementation (implementatie van een abstracte klasse in elke flavor) en BuildConfig field (vertakking in code). De eerste benadering is schoner: je definieert een interface of abstracte klasse in main, en concrete implementaties in src/free/ en src/paid/. Tijdens het compileren wordt alleen de implementatie van de huidige flavor gecompileerd. Dit biedt gelijktijdige voordelen: kleinere APK-grootte (betaalde code komt niet in de free-versie) en beveiliging (een betaalde functie kan niet per ongeluk worden aangeroepen).

AndroidManifest.xml in de source set van een flavor vervangt niet, maar wordt samengevoegd met het main-manifest. Samenvoegen gebeurt volgens Android-regels: dezelfde attributen in een element worden overschreven, unieke worden toegevoegd. Als bijvoorbeeld in het main-manifest INTERNET-permissie is gedeclareerd en in free niet, blijft internet behouden. Maar tools:node="replace" maakt het mogelijk om een heel manifestblok te vervangen voor een specifieke flavor. Dit is handig wanneer verschillende flavours verschillende permissies nodig hebben (schrijven naar SD voor paid, camera voor free).

xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:label="Free App"
        tools:replace="android:label">
    </application>
</manifest>

Voorbeeld: gratis en betaalde versie van de app

Laten we een typisch scenario bekijken: free — versie met advertenties en basisfuncties, paid — zonder advertenties, met uitgebreide functionaliteit. Voor de free-versie wordt applicationId „com.example.app.free” ingesteld, voor paid — „com.example.app.paid”. Beide versies kunnen tegelijk op hetzelfde apparaat worden geïnstalleerd, omdat applicationId de unieke identifier van de app in het Android-systeem is.

Architectonisch is de scheiding gebaseerd op interface + flavor implementation. In de main source set wordt de interface PaymentService gedeclareerd. In src/free/ bevindt zich de implementatie die advertenties toont vóór betaling via AdMob. In src/paid/ — de implementatie die direct naar de betalingsgateway gaat. Code die PaymentService gebruikt, weet niet welke implementatie is geladen — dit wordt beslist tijdens het compileren. Een dergelijke aanpak garandeert dat abonnementsbeheercode niet in de free-versie terechtkomt, zelfs niet als de ontwikkelaar deze per ongeluk aanroept.

De APK-grootte voor verschillende flavors kan 5-15 MB verschillen door het opnemen/uitsluiten van afhankelijkheden. Om een bibliotheek uit een specifieke flavor te weren, worden flavor-specific dependencies in build.gradle gebruikt: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Deze afhankelijkheid wordt alleen toegevoegd voor de free-variant en vergroot de paid-versie niet. Voor gedeelde afhankelijkheden wordt implementation gebruikt — deze worden in alle flavors opgenomen.

kotlin
// src/main/kotlin/com/example/payment/PaymentService.kt
interface PaymentService {
    fun processOrder(amount: Double, callback: (PaymentResult) -> Unit)
}

// src/free/kotlin/.../FreePaymentService.kt
class FreePaymentService : PaymentService {
    override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
        AdManager.showInterstitial {
            PaymentGateway.charge(amount, callback)
        }
    }
}

// src/paid/kotlin/.../PaidPaymentService.kt
class PaidPaymentService : PaymentService {
    override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
        PaymentGateway.charge(amount, callback)
    }
}

Product Flavor in multimoduleprojecten

In multimoduleprojecten kunnen bibliotheekmodules geen eigen Product Flavors hebben, wat een probleem oplevert: de bibliotheek wordt één keer gecompileerd (als release), terwijl de app-module met flavor de bibliotheek met de bijbehorende variant verwacht. Vanaf AGP 8.1 kunnen bibliotheken multiple variants publiceren via het publishing.multipleVariants-blok — dit maakt het mogelijk om alle flavor-varianten van de bibliotheek in één maven-repository te publiceren, en de app-module selecteert automatisch de juiste.

Een alternatieve benadering is het declareren van dezelfde flavorDimensions en productFlavors in de bibliotheek als in de app-module. AGP matcht automatisch flavours op basis van volledige naamovereenkomst in één dimensie. Als de naam van de flavor in de bibliotheek overeenkomt met de naam in de app, maakt AGP consistente varianten. Voor onderhoudsgemak wordt aanbevolen om gemeenschappelijke flavour-definities in een Convention Plugin — een Gradle-plugin die op alle projectmodules wordt toegepast — te plaatsen.

Voor bibliotheken die niet voor publicatie zijn bedoeld (interne modules), volstaat het om flavours te synchroniseren via de build.gradle van het hoofdproject. Gradle biedt de subprojects-methode waarmee configuratie op alle subprojecten kan worden toegepast. Houd er echter rekening mee dat te veel configuratie in subprojects de configuratiefase vertraagt. Het gebruik van Convention Plugins wordt aanbevolen — deze worden één keer gecompileerd en hergebruikt, waardoor de configuratietijd met 15-30% wordt verkort.

Veelgestelde vragen

Hoeveel Product Flavors kunnen er worden gemaakt?

Er is geen limiet, maar elke dimensie vermenigvuldigt het aantal Build Variants. 4 flavours in één dimensie + 2 build types = 8 varianten. 4 + 4 in twee dimensies = 16 varianten. Maximaal 3 dimensies en 10-12 totale varianten worden aanbevolen.

Kan het manifest worden overschreven voor een flavour?

Ja, via de source set src/<flavor>/AndroidManifest.xml. Het manifest wordt samengevoegd met het hoofdmanifest. Gebruik tools:node="replace" om een heel blok te vervangen. Bijvoorbeeld om het app-label of permissies voor een specifieke flavor te wijzigen.

Hoe voeg ik flavour-specifieke afhankelijkheden toe?

Gebruik de configuratie <flavorName>Implementation. Voorbeeld: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Deze afhankelijkheid wordt alleen toegevoegd bij het compileren van de free-variant. Voor paid: paidImplementation. Gemeenschappelijke afhankelijkheden worden via implementation gespecificeerd.

Wat is het verschil tussen Product Flavor en Build Type?

Product Flavor bepaalt de productversie (free, paid, demo), Build Type — de bouwmethode (debug, release). Flavours kunnen applicationId, versionName en bronnen overschrijven. Build Type beheert debuggable, minification en signing. Beide zijn orthogonaal en worden gecombineerd in Build Variant.

Kan ik Product Flavor gebruiken met Jetpack Compose?

Ja, Product Flavors werken met Compose zonder beperkingen. Verschillende flavors kunnen verschillende Compose-schermen hebben via source sets of implementatie van abstracte klassen. Ook kunnen flavor-specifieke Compose-afhankelijkheden worden toegevoegd: freeImplementation 'androidx.compose.ui:ui-tooling'.

Samenvatting

  • Product Flavor — Gradle-mechanisme om meerdere app-versies uit één code te maken.
  • Flavor Dimensions groeperen flavors in dimensies, waardoor verschillende aspecten van de app kunnen worden gecombineerd.
  • Source sets voor een flavor overschrijven bronnen, code en manifest zonder de hoofdmap te wijzigen.
  • Interface + flavor implementation — schone architectuuraanpak voor functiescheiding.
  • Flavor-specifieke afhankelijkheden voorkomen dat onnodige bibliotheken in ongeschikte versies terechtkomen.
  • Multimoduleprojecten vereisen synchronisatie van flavours via Convention Plugins of multiple variants publishing.
  • Aanbeveling: maximaal 3 flavourdimensies en maximaal 10 totale Build Variants in het project.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook