Product Flavor: що це таке, конфігурація та приклади в Gradle

Автор: IT Sectr Опубліковано: 2026-05-30 Час читання: 9 хв

Product Flavor в Android-розробці — це механізм Gradle, який дозволяє створювати кілька варіантів одного застосунку зі спільної кодової бази. Кожен flavor може мати власний applicationId, ресурси, залежності та функціональність — наприклад, безкоштовну та платну версії. За даними Google Android Developers, 2025, Product Flavors входять до системи Build Variants і об'єднуються з Build Types через flavorDimensions. Це стандартний підхід для публікації кількох версій застосунку в Google Play.

Головне

  • Product Flavor — варіант продукту з унікальним applicationId, ресурсами та кодом.
  • Flavor Dimensions групують flavor-и в незалежні осі для багатовимірної конфігурації.
  • Source sets для flavor-у перевизначають головні ресурси: іконки, рядки, маніфест.
  • Gradle автоматично генерує Build Variant для кожної комбінації flavor + build type.
  • Google Play підтримує публікацію кількох flavor-ів як окремих застосунків або одного з різними конфігураціями.

Що таке Product Flavor?

Product Flavor — це конфігурація Gradle в блоці android.productFlavors, яка описує варіант продукту. Кожен flavor може перевизначати applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig та інші параметри defaultConfig. Product Flavors не мають обмежень за кількістю: проєкт може містити 2, 5, 10 flavor-ів — Gradle обробить всі комбінації.

Product Flavor вирішує завдання codebase reuse — коли з одного репозиторію потрібно зібрати кілька різних застосунків. Типові сценарії: безкоштовна версія з рекламою та платна без; демо-версія з обмеженим функціоналом; корпоративна та споживча версії; white-label застосунки для різних клієнтів. Без Product Flavors кожну версію довелося б підтримувати в окремому проєкті, що призводить до дублювання коду на 60-70%.

Історично Product Flavors з'явилися в Android Gradle Plugin 0.9 (2013) як заміна ant-конфігураціям. До цього розробники використовували окремі проєкти для різних версій або ручну заміну ресурсів перед збіркою. Впровадження flavor-ів в AGP уніфікувало підхід і зробило його стандартом. За даними опитування JetBrains, 2024, 78% Android-проєктів з кількома версіями використовують Product Flavors, решта — ручне перемикання через BuildConfig або reflection.

Product Flavor vs Build Type

Build Type керує процесом збірки (debug з налагодженням, release з оптимізацією). Product Flavor керує вмістом збірки (free без платних функцій, paid з ними). Build Type — це інфраструктурне налаштування, Product Flavor — продуктове. Обидва поняття ортогональні: debug збірка free-flavor відрізняється від release збірки free-flavor лише параметрами компіляції, але не функціональністю. Product Flavor не можна використовувати для вимкнення налагоджувача — це завдання Build Type.

Flavor Dimensions: організація вимірів

Порядок вимірів та пріоритет

Flavor Dimensions (виміри) — це механізм групування Product Flavors у незалежні категорії. Якщо застосунок має безкоштовну/платну версію та окремо американський/європейський регіон, flavor-и групуються у два виміри: "tier" (free, paid) та "region" (us, eu). Gradle створює декартів добуток вимірів: freeUs, freeEu, paidUs, paidEu — 4 варіанти. Без вимірів Gradle сприймав би всі чотири flavor-и як одну площину, і вибрати можна було б лише один.

Виміри оголошуються в блоці flavorDimensions рядком або списком рядків. Порядок вимірів впливає на пріоритет source sets: перший вимір має найвищий пріоритет. Якщо вимір A (tier) вказано першим, то src/free/ буде перевизначати src/us/ у випадку конфлікту ресурсів. Також порядок впливає на те, як формується ім'я Variant: спочатку йдуть flavor-и першого виміру, потім другого, потім Build Type: freeUsDebug.

Кількість вимірів не обмежена, але кожен новий вимір множить кількість Build Variants. Для проєкту з 4 вимірами (по 2 flavor) та 2 build types вийде 2 × 2 × 2 × 2 × 2 = 32 варіанти. Практичний ліміт — 3 виміри (максимум 8-12 варіантів). Більше — і конфігурація Gradle сповільнюється, а панель Build Variants в Android Studio стає нечитабельною.

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

// Результат: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Кожен × debug/release = 8 Build Variants

Створення Product Flavors у build.gradle

Kotlin DSL для Product Flavors

Для створення Product Flavor необхідно додати блок productFlavors всередині android, вказати ім'я flavor та його параметри. Мінімальне оголошення flavor — це ім'я та dimension. Всі інші параметри успадковуються з defaultConfig і можуть бути перевизначені. Flavor успадковує defaultConfig повністю, включаючи applicationId, versionCode, testInstrumentationRunner.

Кожен flavor може перевизначати applicationId — це дозволяє встановлювати кілька версій застосунку на один пристрій одночасно. Наприклад, free-версія буде com.example.app.free, paid — com.example.app.paid. Якщо applicationId не перевизначено, всі flavor-и матимуть однаковий ідентифікатор, і встановлювати їх паралельно не вийде. applicationId має збігатися з package у маніфесті (якщо не використовується applicationIdSuffix).

AGP 8+ рекомендує використовувати Kotlin DSL замість Groovy для build.gradle. Kotlin DSL дає type-safe доступ до конфігурації: IDE підказує імена параметрів, перевіряє типи на етапі компіляції та підсвічує помилки. Міграція з Groovy на Kotlin DSL для Product Flavors зазвичай складається із заміни лапок на дужки та додавання типів. AGP зворотно сумісний — обидва синтаксиси працюють паралельно в одному проєкті.

kotlin
// build.gradle.kts — DSL Kotlin
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")
        }
    }
}

Ресурси та код для різних flavor

Кожен Product Flavor створює власний source set — директорію src/<flavorName>/. У цій директорії можна розміщувати перевизначені ресурси, вихідні коди та маніфест. Source set flavor працює як оверлей поверх main: файли з src/free/res/ перевизначають файли з src/main/res/ з тими ж іменами. Це дозволяє мати різні рядки, іконки, кольори та макети для кожного flavor без зміни основного коду.

Для перевизначення Java/Kotlin-класів існує два підходи: flavor-specific implementation (реалізація абстрактного класу в кожному flavor) та BuildConfig field (розгалуження в коді). Перший підхід чистіший: ви визначаєте інтерфейс або абстрактний клас в main, а конкретні реалізації — в src/free/ та src/paid/. При збірці компілюється лише реалізація поточного flavor. Це дає одночасні переваги: менший розмір APK (платний код не потрапляє в free-версію) та безпека (неможливо помилково викликати платну функцію).

AndroidManifest.xml у source set flavor не замінює, а зливається з головним маніфестом. Злиття відбувається за правилами Android: однакові атрибути в одному елементі перевизначаються, унікальні — додаються. Наприклад, якщо в головному маніфесті оголошено INTERNET permission, а в free — ні, інтернет залишиться. Але tools:node="replace" дозволяє замінити цілий блок маніфесту для конкретного flavor. Це корисно, коли різні flavor вимагають різних permissions (запис на SD-картку для paid, камера для 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>

Приклад: безкоштовна та платна версії застосунку

Розглянемо типовий сценарій: free — версія з рекламою та базовими функціями, paid — без реклами, з розширеним функціоналом. Для free-версії встановлюється applicationId "com.example.app.free", для paid — "com.example.app.paid". Обидві версії можуть бути встановлені на один пристрій одночасно, оскільки applicationId є унікальним ідентифікатором застосунку в системі Android.

Архітектурно розділення будується через interface + flavor implementation. У main source set оголошується інтерфейс PaymentService. У src/free/ лежить реалізація, яка показує рекламу перед оплатою через AdMob. У src/paid/ — реалізація, яка одразу переходить до платіжного шлюзу. Код, що використовує PaymentService, не знає, яка реалізація завантажена — це вирішується на етапі компіляції. Такий підхід гарантує, що у free-версію не потрапить код керування підписками, навіть якщо розробник випадково його викличе.

Розмір APK для різних flavor може відрізнятися на 5-15 МБ через включення/виключення залежностей. Щоб виключити бібліотеку з конкретного flavor, використовується flavor-specific dependencies в build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ця залежність буде додана лише для free-варіанту і не збільшить розмір paid-версії. Для спільних залежностей використовується implementation — їх включають всі 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 у багатомодульних проєктах

У багатомодульних проєктах бібліотечні модулі можуть не мати власних Product Flavors, що створює проблему: бібліотека збирається один раз (як release), а app-модуль з flavor очікує бібліотеку з відповідним варіантом. Починаючи з AGP 8.1, бібліотеки можуть публікувати multiple variants через блок publishing.multipleVariants — це дозволяє опублікувати всі flavor-варіанти бібліотеки в один maven-репозиторій, і app-модуль автоматично вибере потрібний.

Альтернативний підхід — оголосити ті самі flavorDimensions та productFlavors у бібліотеці, що й в app-модулі. AGP автоматично зіставляє flavor-и за повним збігом імені з одного виміру. Якщо ім'я flavor у бібліотеці збігається з іменем в app, AGP створить узгоджені варіанти. Для зручності підтримки рекомендується виносити спільні визначення flavor у Convention Plugin — Gradle-плагін, який застосовується до всіх модулів проєкту.

Для бібліотек, не призначених для публікації (внутрішні модулі), достатньо синхронізувати flavor через build.gradle кореневого проєкту. Gradle надає метод subprojects, який дозволяє застосувати конфігурацію до всіх підпроєктів. Однак варто пам'ятати, що занадто велика конфігурація в subprojects сповільнює configuration phase. Рекомендується використовувати Convention Plugins — вони компілюються один раз і перевикористовуються, що скорочує час конфігурації на 15-30%.

Поширені запитання

Скільки Product Flavors можна створити?

Обмежень за кількістю немає, але кожен вимір множить число Build Variants. 4 flavor в одному вимірі + 2 build types = 8 варіантів. 4 + 4 у двох вимірах = 16 варіантів. Рекомендується не більше 3 вимірів та 10-12 підсумкових варіантів.

Чи можна перевизначити маніфест для flavor?

Так, через source set src/<flavor>/AndroidManifest.xml. Маніфест зливається з основним. Для заміни цілого блоку використовуйте tools:node="replace". Наприклад, замінити label застосунку або дозволи для конкретного flavor.

Як додати flavour-specific залежності?

Використовуйте конфігурацію <flavorName>Implementation. Приклад: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Така залежність буде включена лише при збірці free-варіанту. Для paid: paidImplementation. Спільні залежності вказуються через implementation.

Чим Product Flavor відрізняється від Build Type?

Product Flavor визначає версію продукту (free, paid, demo), Build Type — спосіб збірки (debug, release). Flavor можуть перевизначати applicationId, versionName, ресурси. Build Type керує debuggable, minification, signing. Обидва ортогональні та комбінуються в Build Variant.

Чи можна використовувати Product Flavor з Jetpack Compose?

Так, Product Flavors працюють з Compose без обмежень. Різні flavor можуть мати різні Compose-екрани через source sets або реалізацію абстрактних класів. Також можна додавати flavor-specific Compose-залежності: freeImplementation 'androidx.compose.ui:ui-tooling'.

Підсумки

  • Product Flavor — механізм Gradle для створення кількох версій застосунку з одного коду.
  • Flavor Dimensions групують flavor у виміри, дозволяючи комбінувати різні аспекти застосунку.
  • Source sets для flavor перевизначають ресурси, код та маніфест без зміни main-директорії.
  • Interface + flavor implementation — чистий архітектурний підхід для розділення функціональності.
  • Flavor-specific dependencies запобігають потраплянню зайвих бібліотек у непідходящі версії.
  • Багатомодульні проєкти вимагають синхронізації flavor через Convention Plugins або multiple variants publishing.
  • Рекомендація: не більше 3 вимірів flavor і не більше 10 підсумкових Build Variants у проєкті.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також