Product Flavor: co to je, konfigurace a příklady v Gradle

Autor: IT Sectr Publikováno: 2026-05-30 Doba čtení: 9 min

Product Flavor w programowaniu na Androida to mechanizm Gradle, który pozwala tworzyć wiele wariantów jednej aplikacji z wspólnej bazy kodu. Każdy flavor może mieć własny applicationId, zasoby, zależności i funkcjonalność — na przykład darmową i płatną wersję. Według Google Android Developers, 2025, Product Flavors wchodzą w skład systemu Build Variants i łączą się z Build Types poprzez flavorDimensions. To standardowe podejście do publikowania wielu wersji aplikacji w Google Play.

Najważniejsze

  • Product Flavor — wariant produktu z unikalnym applicationId, zasobami i kodem.
  • Flavor Dimensions grupują flavor-y w niezależne osie dla wielowymiarowej konfiguracji.
  • Source sets dla flavor-a nadpisują główne zasoby: ikony, napisy, manifest.
  • Gradle automatycznie generuje Build Variant dla każdej kombinacji flavor + build type.
  • Google Play obsługuje publikację wielu flavor-ów jako osobnych aplikacji lub jednej z różnymi konfiguracjami.

Co to jest Product Flavor?

Product Flavor to konfiguracja Gradle w bloku android.productFlavors, która opisuje wariant produktu. Każdy flavor może nadpisywać applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig i inne parametry defaultConfig. Product Flavors nie mają ograniczeń ilościowych: projekt może zawierać 2, 5, 10 flavor-ów — Gradle przetworzy wszystkie kombinacje.

Product Flavor rozwiązuje problem codebase reuse — gdy z jednego repozytorium trzeba zbudować kilka różniących się aplikacji. Typowe scenariusze: darmowa wersja z reklamami i płatna bez; wersja demo z ograniczoną funkcjonalnością; wersje korporacyjna i konsumencka; white-label aplikacje dla różnych klientów. Bez Product Flavors każdą wersję trzeba by utrzymywać w osobnym projekcie, co prowadzi do duplikacji kodu w 60-70%.

Historycznie Product Flavors pojawiły się w Android Gradle Plugin 0.9 (2013) jako zamiennik konfiguracji ant. Przedtem programiści używali osobnych projektów dla różnych wersji lub ręcznej zamiany zasobów przed kompilacją. Wprowadzenie flavor-ów w AGP zunifikowało podejście i uczyniło je standardem. Według ankiety JetBrains, 2024, 78% projektów Android z wieloma wersjami używa Product Flavors, reszta — ręcznego przełączania przez BuildConfig lub reflection.

Product Flavor vs Build Type

Build Type zarządza procesem kompilacji (debug z debugowaniem, release z optymalizacją). Product Flavor zarządza zawartością kompilacji (free bez płatnych funkcji, paid z nimi). Build Type to konfiguracja infrastrukturalna, Product Flavor — produktowa. Oba pojęcia są ortogonalne: debugowa kompilacja free-flavor różni się od release’owej free-flavor tylko parametrami kompilacji, ale nie funkcjonalnością. Product Flavor nie można używać do wyłączania debuggera — to zadanie Build Type.

Flavor Dimensions: organizacja wymiarów

Kolejność wymiarów i priorytet

Flavor Dimensions (wymiary) to mechanizm grupowania Product Flavors w niezależne kategorie. Jeśli aplikacja ma wersję darmową/płatną oraz osobno region amerykański/europejski, flavour grupuje się w dwa wymiary: „tier” (free, paid) i „region” (us, eu). Gradle tworzy iloczyn kartezjański wymiarów: freeUs, freeEu, paidUs, paidEu — 4 warianty. Bez wymiarów Gradle postrzegałby wszystkie cztery flavour jako jedną płaszczyznę i można by wybrać tylko jeden.

Wymiary deklaruje się w bloku flavorDimensions jako ciąg lub listę ciągów. Kolejność wymiarów wpływa na priorytet source sets: pierwszy wymiar ma najwyższy priorytet. Jeśli wymiar A (tier) jest pierwszy, to src/free/ będzie nadpisywać src/us/ w przypadku konfliktu zasobów. Kolejność wpływa również na nazwę Variant: najpierw flavour pierwszego wymiaru, potem drugiego, a następnie Build Type: freeUsDebug.

Liczba wymiarów nie jest ograniczona, ale każdy nowy wymiar mnoży liczbę Build Variants. Dla projektu z 4 wymiarami (po 2 flavour) i 2 build types otrzymamy 2 × 2 × 2 × 2 × 2 = 32 warianty. Praktyczny limit to 3 wymiary (maksymalnie 8-12 wariantów). Więcej — a konfiguracja Gradle zwalnia, a w Android Studio panel Build Variants staje się nieczytelny.

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
        }
    }
}

// Result: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Each × debug/release = 8 Build Variants

Tworzenie Product Flavors w build.gradle

Kotlin DSL dla Product Flavors

Aby utworzyć Product Flavor, należy dodać blok productFlavors wewnątrz android, podać nazwę flavor i jego parametry. Minimalna deklaracja flavor to nazwa i dimension. Wszystkie pozostałe parametry są dziedziczone z defaultConfig i mogą być nadpisane. Flavor dziedziczy defaultConfig w całości, łącznie z applicationId, versionCode i testInstrumentationRunner.

Każdy flavor może nadpisywać applicationId — pozwala to na zainstalowanie kilku wersji aplikacji na jednym urządzeniu jednocześnie. Na przykład wersja free będzie com.example.app.free, a paid — com.example.app.paid. Jeśli applicationId nie zostanie nadpisane, wszystkie flavour będą mieć ten sam identyfikator i nie będzie można zainstalować ich równolegle. applicationId musi zgadzać się z package w manifeście (jeśli nie używa się applicationIdSuffix).

AGP 8+ zaleca używanie Kotlin DSL zamiast Groovy dla build.gradle. Kotlin DSL zapewnia bezpieczny typowo dostęp do konfiguracji: IDE podpowiada nazwy parametrów, sprawdza typy na etapie kompilacji i podświetla błędy. Migracja z Groovy na Kotlin DSL dla Product Flavors zwykle polega na zastąpieniu cudzysłowów nawiasami i dodaniu typów. AGP jest wstecznie zgodne — oba składnie działają równolegle w jednym projekcie.

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")
        }
    }
}

Zasoby i kod dla różnych flavor

Każdy Product Flavor tworzy własny source set — katalog src/<flavorName>/. W tym katalogu można umieszczać nadpisane zasoby, źródła i manifest. Source set flavor działa jak nakładka na main: pliki z src/free/res/ nadpisują pliki z src/main/res/ o tych samych nazwach. Pozwala to na różne napisy, ikony, kolory i układy dla każdego flavor bez zmiany głównego kodu.

Do nadpisywania klas Java/Kotlin istnieją dwa podejścia: flavor-specific implementation (implementacja abstrakcyjnej klasy w każdym flavor) i BuildConfig field (rozgałęzienie w kodzie). Pierwsze podejście jest czystsze: definiujesz interfejs lub abstrakcyjną klasę w main, a konkretne implementacje w src/free/ i src/paid/. Podczas kompilacji kompilowana jest tylko implementacja bieżącego flavor. Daje to jednoczesne korzyści: mniejszy rozmiar APK (kod płatny nie trafia do wersji free) i bezpieczeństwo (nie ma możliwości przypadkowego wywołania płatnej funkcji).

AndroidManifest.xml w source set flavor nie zastępuje, ale scala się z głównym manifestem. Scalanie odbywa się według zasad Androida: te same atrybuty w elemencie są nadpisywane, unikalne — dodawane. Na przykład, jeśli w głównym manifeście zadeklarowano INTERNET permission, a w free — nie, internet pozostanie. Ale tools:node="replace" pozwala zastąpić cały blok manifestu dla konkretnego flavor. Jest to przydatne, gdy różne flavour wymagają różnych uprawnień (zapis na SD dla paid, kamera dla 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>

Przykład: darmowa i płatna wersja aplikacji

Rozważmy typowy scenariusz: free — wersja z reklamami i podstawowymi funkcjami, paid — bez reklam, z rozszerzoną funkcjonalnością. Dla wersji free ustawia się applicationId „com.example.app.free”, dla paid — „com.example.app.paid”. Obie wersje mogą być zainstalowane na jednym urządzeniu jednocześnie, ponieważ applicationId jest unikalnym identyfikatorem aplikacji w systemie Android.

Architektonicznie podział opiera się na interface + flavor implementation. W głównym source set deklaruje się interfejs PaymentService. W src/free/ znajduje się implementacja, która wyświetla reklamy przed płatnością przez AdMob. W src/paid/ — implementacja, która od razu przechodzi do bramki płatniczej. Kod używający PaymentService nie wie, która implementacja jest załadowana — rozstrzyga się to na etapie kompilacji. Takie podejście gwarantuje, że do wersji free nie trafi kod zarządzania subskrypcjami, nawet jeśli programista przypadkowo go wywoła.

Rozmiar APK dla różnych flavor może się różnić o 5-15 MB ze względu na dołączanie/wykluczanie zależności. Aby wykluczyć bibliotekę z konkretnego flavor, używa się flavor-specific dependencies w build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ta zależność zostanie dodana tylko dla wariantu free i nie zwiększy rozmiaru wersji paid. Dla wspólnych zależności używa się implementation — są one dołączane we wszystkich flavor.

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 w projektach wielomodułowych

W projektach wielomodułowych moduły biblioteczne mogą nie mieć własnych Product Flavors, co stwarza problem: biblioteka jest kompilowana raz (jako release), a moduł app z flavor oczekuje biblioteki z odpowiednim wariantem. Od AGP 8.1 biblioteki mogą publikować multiple variants przez blok publishing.multipleVariants — pozwala to opublikować wszystkie warianty flavor biblioteki do jednego repozytorium maven, a moduł app automatycznie wybierze odpowiedni.

Alternatywnym podejściem jest zadeklarowanie tych samych flavorDimensions i productFlavors w bibliotece co w module app. AGP automatycznie dopasowuje flavour według pełnej zgodności nazwy w jednym wymiarze. Jeśli nazwa flavor w bibliotece zgadza się z nazwą w app, AGP utworzy spójne warianty. Dla łatwiejszego utrzymania zaleca się umieszczenie wspólnych definicji flavour w Convention Plugin — wtyczce Gradle, która jest stosowana we wszystkich modułach projektu.

Dla bibliotek nieprzeznaczonych do publikacji (wewnętrzne moduły) wystarczy zsynchronizować flavour przez build.gradle głównego projektu. Gradle udostępnia metodę subprojects, która pozwala zastosować konfigurację do wszystkich podprojektów. Należy jednak pamiętać, że zbyt duża konfiguracja w subprojects spowalnia fazę konfiguracji. Zaleca się używanie Convention Plugins — są one kompilowane raz i wielokrotnie używane, co skraca czas konfiguracji o 15-30%.

Często zadawane pytania

Ile Product Flavors można utworzyć?

Nie ma ograniczeń ilościowych, ale każdy wymiar mnoży liczbę Build Variants. 4 flavour w jednym wymiarze + 2 build types = 8 wariantów. 4 + 4 w dwóch wymiarach = 16 wariantów. Zaleca się nie więcej niż 3 wymiary i 10-12 łącznych wariantów.

Czy można nadpisać manifest dla flavour?

Tak, przez source set src/<flavor>/AndroidManifest.xml. Manifest scala się z głównym. Do zastąpienia całego bloku użyj tools:node="replace". Na przykład, aby zmienić etykietę aplikacji lub uprawnienia dla konkretnego flavor.

Jak dodać zależności specyficzne dla flavour?

Użyj konfiguracji <flavorName>Implementation. Przykład: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Taka zależność zostanie dołączona tylko podczas kompilacji wariantu free. Dla paid: paidImplementation. Wspólne zależności podaje się przez implementation.

Czym Product Flavor różni się od Build Type?

Product Flavor określa wersję produktu (free, paid, demo), Build Type — sposób kompilacji (debug, release). Flavour mogą nadpisywać applicationId, versionName, zasoby. Build Type zarządza debuggable, minification, signing. Oba są ortogonalne i łączą się w Build Variant.

Czy można używać Product Flavor z Jetpack Compose?

Tak, Product Flavors działają z Compose bez ograniczeń. Różne flavor mogą mieć różne ekrany Compose przez source sets lub implementację abstrakcyjnych klas. Można też dodawać zależności Compose specyficzne dla flavor: freeImplementation 'androidx.compose.ui:ui-tooling'.

Podsumowanie

  • Product Flavor — mechanizm Gradle do tworzenia wielu wersji aplikacji z jednego kodu.
  • Flavor Dimensions grupują flavor w wymiary, umożliwiając łączenie różnych aspektów aplikacji.
  • Source sets dla flavor nadpisują zasoby, kod i manifest bez zmiany głównego katalogu.
  • Interface + flavor implementation — czyste podejście architektoniczne do podziału funkcjonalności.
  • Zależności specyficzne dla flavor zapobiegają przedostawaniu się zbędnych bibliotek do nieodpowiednich wersji.
  • Projekty wielomodułowe wymagają synchronizacji flavour przez Convention Plugins lub multiple variants publishing.
  • Zalecenie: nie więcej niż 3 wymiary flavour i nie więcej niż 10 łącznych Build Variants w projekcie.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také