Ang Product Flavor sa Android development ay isang Gradle mechanism na nagbibigay-daan sa paggawa ng maraming variant ng isang app mula sa iisang codebase. Ang bawat flavor ay maaaring magkaroon ng sariling applicationId, resources, dependencies at functionality — halimbawa, libre at bayad na bersyon. Ayon sa Google Android Developers, 2025, ang Product Flavors ay bahagi ng Build Variants system at pinagsama sa Build Types sa pamamagitan ng flavorDimensions. Ito ang karaniwang approach para sa pag-publish ng maraming bersyon ng app sa Google Play.
Mga Pangunahing
Product Flavor ay isang Gradle configuration sa android.productFlavors block na naglalarawan ng variant ng produkto. Ang bawat flavor ay maaaring mag-override ng applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig at iba pang defaultConfig parameters. Ang Product Flavors ay walang limitasyon sa bilang: ang isang project ay maaaring maglaman ng 2, 5, 10 flavor — ipoproseso ng Gradle ang lahat ng kombinasyon.
Ang Product Flavor ay lumulutas sa problema ng codebase reuse — kapag mula sa isang repository ay kailangang gumawa ng maraming iba't ibang apps. Mga karaniwang scenario: libreng bersyon na may ads at bayad na walang; demo version na may limitadong functionality; corporate at consumer versions; white-label apps para sa iba't ibang kliyente. Kung walang Product Flavors, ang bawat bersyon ay kailangang mapanatili sa hiwalay na project, na nagdudulot ng 60-70% code duplication.
Sa kasaysayan, lumitaw ang Product Flavors sa Android Gradle Plugin 0.9 (2013) bilang kapalit ng ant configurations. Bago ito, ang mga developer ay gumamit ng hiwalay na projects para sa iba't ibang bersyon o manual na pagpapalit ng resources bago ang compilation. Ang pagpapakilala ng flavors sa AGP ay nag-isa ng approach at ginawa itong standard. Ayon sa survey ng JetBrains, 2024, 78% ng Android projects na may maraming bersyon ay gumagamit ng Product Flavors, ang natitira — manual switching sa pamamagitan ng BuildConfig o reflection.
Build Type ay namamahala sa proseso ng compilation (debug na may debugging, release na may optimization). Product Flavor ay namamahala sa nilalaman ng build (free na walang bayad na functions, paid na may mga ito). Build Type ay isang infrastructural configuration, Product Flavor — isang product configuration. Ang parehong konsepto ay orthogonal: ang debug build ng free-flavor ay naiiba sa release build ng free-flavor lamang sa compilation parameters, hindi sa functionality. Ang Product Flavor ay hindi maaaring gamitin para i-disable ang debugger — iyon ay gawain ng Build Type.
Flavor Dimensions (dimensions) — isang mekanismo para sa pag-grupo ng Product Flavors sa mga independent na kategorya. Kung ang app ay may libre/bayad na bersyon at hiwalay na American/European region, ang flavour ay nag-grupo sa dalawang dimensions: „tier” (free, paid) at „region” (us, eu). Gumagawa ang Gradle ng cartesian product ng dimensions: freeUs, freeEu, paidUs, paidEu — 4 variants. Kung walang dimensions, ituturing ng Gradle ang lahat ng apat na flavour bilang isang plane at isa lamang ang maaaring piliin.
Ang dimensions ay idinedeklara sa flavorDimensions block bilang string o listahan ng strings. Ang pagkakasunod-sunod ng dimensions ay nakakaapekto sa priority ng source sets: ang unang dimension ay may pinakamataas na priority. Kung ang dimension A (tier) ang una, ang src/free/ ay mag-o-override sa src/us/ sa kaso ng resource conflict. Ang pagkakasunod-sunod ay nakakaapekto rin sa pagpapangalan ng Variant: una ang flavor ng unang dimension, pagkatapos ang ikalawa, pagkatapos ang Build Type: freeUsDebug.
Ang bilang ng dimensions ay hindi limitado, ngunit ang bawat bagong dimension ay nagpaparami ng bilang ng Build Variants. Para sa project na may 4 na dimensions (bawat isa ay 2 flavour) at 2 build types: 2 × 2 × 2 × 2 × 2 = 32 variants. Praktikal na limitasyon — 3 dimensions (maximum 8-12 variants). Higit pa dito — bumagal ang Gradle configuration at ang Build Variants panel sa Android Studio ay nagiging hindi nababasa.
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
}
}
}
// Resulta: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Bawat isa × debug/release = 8 Build Variants
Para gumawa ng Product Flavor, kailangang magdagdag ng productFlavors block sa loob ng android, tukuyin ang pangalan ng flavor at mga parameter nito. Ang pinakamababang deklarasyon ng flavor ay pangalan at dimension. Ang lahat ng iba pang parameter ay minana mula sa defaultConfig at maaaring i-override. Ang Flavor ay nagmamana ng defaultConfig nang buo, kabilang ang applicationId, versionCode at testInstrumentationRunner.
Ang bawat flavor ay maaaring mag-override ng applicationId — ito ay nagbibigay-daan sa pag-install ng maraming bersyon ng app sa isang device nang sabay. Halimbawa, ang free version ay magiging com.example.app.free, at ang paid — com.example.app.paid. Kung hindi na-override ang applicationId, lahat ng flavour ay magkakaroon ng parehong identifier at hindi mai-install nang parallel. Ang applicationId ay dapat tumugma sa package sa manifest (kung hindi ginagamit ang applicationIdSuffix).
Ang AGP 8+ ay nagrerekomenda ng paggamit ng Kotlin DSL sa halip na Groovy para sa build.gradle. Ang Kotlin DSL ay nagbibigay ng type-safe access sa configuration: ang IDE ay nagmumungkahi ng mga parameter name, nagche-check ng types sa compilation stage at nagha-highlight ng errors. Ang migration mula Groovy patungong Kotlin DSL para sa Product Flavors ay karaniwang binubuo ng pagpapalit ng quotes ng parentheses at pagdagdag ng types. Ang AGP ay backward compatible — parehong syntax ay gumagana nang parallel sa parehong project.
// 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")
}
}
}
Ang bawat Product Flavor ay gumagawa ng sarili nitong source set — ang directory na src/<flavorName>/. Sa directory na ito maaaring ilagay ang na-override na resources, source code at manifest. Ang source set ng flavor ay gumagana bilang overlay sa ibabaw ng main: ang mga file mula sa src/free/res/ ay nag-o-override ng mga file mula sa src/main/res/ na may parehong pangalan. Ito ay nagbibigay-daan sa iba't ibang strings, icons, colors at layouts para sa bawat flavor nang hindi binabago ang main code.
Para sa pag-override ng Java/Kotlin classes, may dalawang approach: flavor-specific implementation (implementasyon ng abstract class sa bawat flavor) at BuildConfig field (branching sa code). Ang unang approach ay mas malinis: tinutukoy mo ang isang interface o abstract class sa main, at mga konkretong implementasyon sa src/free/ at src/paid/. Sa panahon ng compilation, tanging ang implementasyon ng kasalukuyang flavor ang na-compile. Ito ay nagbibigay ng sabay na mga bentahe: mas maliit na APK size (ang bayad na code ay hindi pumapasok sa free version) at seguridad (ang bayad na function ay hindi maaaring aksidenteng matawag).
AndroidManifest.xml sa source set ng flavor ay hindi pumapalit, ngunit pinagsasama sa main manifest. Ang pagsasama ay nangyayari ayon sa Android rules: ang parehong attributes sa isang elemento ay na-o-override, ang mga natatangi ay idinaragdag. Halimbawa, kung sa main manifest ay idineklara ang INTERNET permission, at sa free ay wala, mananatili ang internet. Ngunit ang tools:node="replace" ay nagbibigay-daan sa pagpapalit ng buong manifest block para sa isang partikular na flavor. Ito ay kapaki-pakinabang kapag ang iba't ibang flavour ay nangangailangan ng iba't ibang permissions (pagsulat sa SD para sa paid, camera para sa 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>
Tingnan natin ang isang tipikal na scenario: free — bersyon na may ads at pangunahing functions, paid — walang ads, na may pinahabang functionality. Para sa free version, nakatakda ang applicationId „com.example.app.free”, at para sa paid — „com.example.app.paid”. Ang parehong bersyon ay maaaring i-install sa parehong device nang sabay, dahil ang applicationId ay ang natatanging identifier ng app sa Android system.
Sa arkitektura, ang paghihiwalay ay batay sa interface + flavor implementation. Sa main source set, ang interface na PaymentService ay idineklara. Sa src/free/ ay matatagpuan ang implementasyon na nagpapakita ng mga ads bago ang pagbabayad sa pamamagitan ng AdMob. Sa src/paid/ — ang implementasyon na diretso sa payment gateway. Ang code na gumagamit ng PaymentService ay hindi alam kung aling implementasyon ang na-load — ito ay napagpasyahan sa compilation stage. Ang ganitong approach ay ginagarantiyahan na ang subscription management code ay hindi mapupunta sa free version, kahit na aksidenteng tawagin ito ng developer.
Ang laki ng APK para sa iba't ibang flavor ay maaaring mag-iba ng 5-15 MB dahil sa pagsasama/pagbubukod ng dependencies. Para ibukod ang isang library mula sa isang partikular na flavor, ginagamit ang flavor-specific dependencies sa build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ang dependency na ito ay idaragdag lamang para sa free variant at hindi tataas ang laki ng paid version. Para sa mga shared dependencies, ginagamit ang implementation — ang mga ito ay kasama sa lahat ng 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)
}
}
Sa multi-module projects, ang library modules ay maaaring walang sariling Product Flavors, na lumilikha ng problema: ang library ay na-compile nang isang beses (bilang release), ngunit ang app module na may flavor ay inaasahan ang library na may kaukulang variant. Simula sa AGP 8.1, ang mga library ay maaaring mag-publish ng multiple variants sa pamamagitan ng publishing.multipleVariants block — ito ay nagbibigay-daan sa pag-publish ng lahat ng flavor variant ng library sa isang maven repository, at ang app module ay awtomatikong pipili ng kinakailangan.
Ang alternatibong approach — ideklara ang parehong flavorDimensions at productFlavors sa library tulad ng sa app module. Awtomatikong itinutugma ng AGP ang flavour ayon sa buong pagkakatugma ng pangalan sa isang dimension. Kung ang pangalan ng flavor sa library ay tumutugma sa pangalan sa app, gagawa ang AGP ng consistent variants. Para sa kadalian ng pagpapanatili, inirerekomenda na ilagay ang mga karaniwang definisyon ng flavour sa Convention Plugin — isang Gradle plugin na inilalapat sa lahat ng modules ng project.
Para sa mga library na hindi nilayon para sa pag-publish (internal modules), sapat na upang i-synchronize ang flavour sa pamamagitan ng build.gradle ng root project. Nagbibigay ang Gradle ng subprojects method na nagbibigay-daan sa paglapat ng configuration sa lahat ng subprojects. Gayunpaman, tandaan na ang masyadong malaking configuration sa subprojects ay nagpapabagal sa configuration phase. Inirerekomenda ang paggamit ng Convention Plugins — ang mga ito ay na-compile nang isang beses at ginagamit muli, na nagbabawas ng configuration time ng 15-30%.
Mga Madalas Itanong
Walang limitasyon sa bilang, ngunit ang bawat dimension ay nagpaparami ng bilang ng Build Variants. 4 flavour sa isang dimension + 2 build types = 8 variants. 4 + 4 sa dalawang dimensions = 16 variants. Inirerekomenda ang max na 3 dimensions at 10-12 total variants.
Oo, sa pamamagitan ng source set src/<flavor>/AndroidManifest.xml. Ang manifest ay pinagsasama sa main manifest. Para palitan ang buong block, gamitin ang tools:node="replace". Halimbawa, para baguhin ang app label o permissions para sa isang partikular na flavor.
Gamitin ang configuration na <flavorName>Implementation. Halimbawa: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Ang dependency na ito ay isasama lamang sa pag-compile ng free variant. Para sa paid: paidImplementation. Ang mga shared dependencies ay tinutukoy sa pamamagitan ng implementation.
Ang Product Flavor ay tumutukoy sa bersyon ng produkto (free, paid, demo), Build Type — paraan ng pag-compile (debug, release). Ang Flavour ay maaaring mag-override ng applicationId, versionName, resources. Ang Build Type ay namamahala ng debuggable, minification, signing. Ang pareho ay orthogonal at pinagsama sa Build Variant.
Oo, ang Product Flavors ay gumagana sa Compose nang walang limitasyon. Ang iba't ibang flavor ay maaaring magkaroon ng iba't ibang Compose screens sa pamamagitan ng source sets o implementasyon ng abstract classes. Maaari ring magdagdag ng flavor-specific na Compose dependencies: freeImplementation 'androidx.compose.ui:ui-tooling'.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din