Product Flavor у Android развоју је Gradle механизам који омогућава креирање више варијаната једне апликације из заједничке базе кода. Сваки flavor може имати сопствени applicationId, ресурсе, зависности и функционалности — на примјер, бесплатну и плаћену верзију. Према Google Android Developers, 2025, Product Flavors улазе у систем Build Variants и комбинују се са Build Types-ом путем flavorDimensions. Ово је стандардни приступ за објављивање више верзија апликације у Google Play-у.
Главно
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.
Build Type управља процесом компилације (debug са отклањањем, release са оптимизацијом). Product Flavor управља садржајем компилације (free без плаћених функција, paid са њима). Build Type је инфраструктурна конфигурација, Product Flavor — производна. Оба појма су ортогонална: debug компилација free-flavor-а разликује се од release компилације free-flavor-а само по параметрима компилације, не по функционалности. Product Flavor се не може користити за искључивање отклаћивача — то је задатак Build Type-а.
Flavor Dimensions (димензије) — механизам груписања Product Flavors у независне категорије. Ако апликација има бесплатну/плаћену верзију и одвојено амерички/европски регион, flavour-и се групишу у двије димензије: „tier” (free, paid) и „region” (us, eu). Gradle ствара декартов производ димензија: freeUs, freeEu, paidUs, paidEu — 4 варијанте. Без димензија, Gradle би перцепирао све четири flavour-а као један раван и само један би могао бити изабран.
Димензије се декларишу у блоку flavorDimensions као низ или листа низова. Редослед димензија утиче на приоритет source sets: прва димензија има највећи приоритет. Ако је димензија A (tier) прва, онда ће src/free/ прегазити src/us/ у случају конфликта ресурса. Редослед такође утиче на назив Variant-а: прво flavour прве димензије, затим друге, затим Build Type: freeUsDebug.
Број димензија није ограничен, али свака нова димензија множи број Build Variants-а. За пројекат са 4 димензије (по 2 flavour) и 2 build types: 2 × 2 × 2 × 2 × 2 = 32 варијанте. Практична граница — 3 димензије (максимално 8-12 варијаната). Више од тога — конфигурација Gradle-а успорава, а у Android Studio-у панел Build Variants постаје нечитѓив.
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 Flavor, потребно је додати блок productFlavors унутар android, навести назив flavor-а и његове параметре. Минимална декларација flavor-а је назив и dimension. Сви остали параметри се насљеђују из defaultConfig и могу бити прегажени. Flavor насљеђује defaultConfig у потпуности, укључујући applicationId, versionCode и testInstrumentationRunner.
Сваки flavor може да прегази applicationId — ово омогућава инсталацију више верзија апликације на једном уређају истовремено. На примјер, free верзија ће бити com.example.app.free, a paid — com.example.app.paid. Ако applicationId није прегашен, сви flavour-и ће имати исти идентификатор и неће моћи да се инсталирају паралелно. applicationId мора да се поклапа са package-ом у манифесту (ако се не користи applicationIdSuffix).
AGP 8+ препоручује коришћење Kotlin DSL уместо Groovy за build.gradle. Kotlin DSL пружа тип-безбедан приступ конфигурацији: IDE предлаже називе параметара, провјерава типове у фази компилације и истиче грешке. Миграција са Groovy на Kotlin DSL за Product Flavors обично подразумије замену наводника заградама и додавање типова. AGP је уназад компатибилан — оба синтаксна раде паралелно у истом пројекту.
// 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")
}
}
}
Сваки 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 дозвола, а у free није, интернет ће остати. Али tools:node="replace" омогућава замену цијелог блока манифеста за одређени flavor. Ово је корисно када различити flavour-и захтевају различите дозволе (упис на SD за paid, камера за free).
<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. У главном source set-у декларише се интерфејс PaymentService. У src/free/ се налази имплементација која приказује огласе пре плаћања путем AdMob-а. У src/paid/ — имплементација која одмах прелази на платну капију. Код који користи PaymentService не зна која је имплементација учитана — то се одлучује у фази компилације. Овакав приступ гарантује да у free верзију неће доспјети код за управљање претплатама, чак и ако га програмер случајно позове.
Величина APK-а за различите flavor-е може да се разликује за 5-15 MB због укључивања/искључивања зависности. Да бисте искључили библиотеку из одређеног flavor-а, користе се flavor-specific dependencies у build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ова зависност ће бити додата само за free варијанту и неће повећати величину paid верзије. За заједничке зависности користи се implementation — оне су укључене у свим flavor-има.
// 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 Flavors-е, што ствара проблем: библиотека се компилира једном (као release), а апп модул са flavor-ом очекује библиотеку са одговарајућом варијантом. Од AGP 8.1, библиотеке могу да објаве multiple variants кроз блок publishing.multipleVariants — ово омогућава објављивање свих flavor варијаната библиотеке у један maven репозиторијум, а апп модул ће аутоматски изабрати потребну.
Алтернативни приступ је декларисање истих flavorDimensions и productFlavors у библиотеци као и у апп модулу. AGP аутоматски поклапа flavour-е по потпуном поклапању имена у једној димензији. Ако се назив flavor-а у библиотеци поклапа са називом у апп, AGP ће створити консистентне варијанте. Ради лакшег одржавања, препоручује се смештање заједничких дефиниција flavour-а у Convention Plugin — Gradle плагин који се примењује на све модуле пројекта.
За библиотеке које нису намењене за објављивање (унутрашњи модули), довољно је синхронизовати flavour-е кроз build.gradle кореног пројекта. Gradle пружа методу subprojects која омогућава примену конфигурације на све подпројекте. Ипак, имајте на уму да превелика конфигурација у subprojects успорава фазу конфигурације. Препоручује се коришћење Convention Plugins — они се компилирају једном и поново користе, што скраћује време конфигурације за 15-30%.
Често постављана питања
Нема ограничења по броју, али свака димензија множи број Build Variants. 4 flavour у једној димензији + 2 build types = 8 варијаната. 4 + 4 у двије димензије = 16 варијаната. Препоручује се највише 3 димензије и 10-12 укупних варијаната.
Да, кроз source set src/<flavor>/AndroidManifest.xml. Манифест се спаја са главним. За замену цијелог блока користите tools:node="replace". На примјер, за промену назива апликације или дозвола за одређени flavor.
Користите конфигурацију <flavorName>Implementation. Примјер: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ова зависност ће бити укључена само при компилацији free варијанте. За paid: paidImplementation. Заједничке зависности наводе се кроз implementation.
Product Flavor одређује верзију производа (free, paid, demo), Build Type — начин компилације (debug, release). Flavour-и могу да прегазе applicationId, versionName и ресурсе. Build Type управља debuggable, minification, signing-ом. Оба су ортогонална и комбинују се у Build Variant-у.
Да, Product Flavors раде са Compose-ом без ограничења. Различити flavor-и могу имати различите Compose екране кроз source sets или имплементацију апстрактних класа. Такође можете додати зависности Compose специфичне за flavor: freeImplementation 'androidx.compose.ui:ui-tooling'.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође