In Android development, product flavors allow you to create different versions of your app with different configurations, resources, and code, but with the same base functionality. Product flavors are used when you want to create multiple versions of the same app that differ in some way, such as a free and a paid version, or versions for different countries or languages.
For example, suppose you are creating a language-learning app that supports multiple languages, such as English, Spanish, and French. You could create three different product flavors, one for each language, and configure each flavor to include the appropriate language resources, such as strings, images, and audio files. Each flavor would share the same core codebase but would have different resources and configurations (or consider ABC 123 Learn and Akshar Learn apps, for which I am handling these use cases).
Android Product Flavors
Product flavors are defined in the build.gradle file of your app module. You can define the configuration for each flavor, including things like applicationId, versionName, versionCode, and buildConfigField values. You can also specify which source sets to include for each flavor, which allows you to create different versions of the app with different code, resources, or assets.
Build variants, on the other hand, are different versions of your app that are created by combining one or more product flavors with a build type. Build types are used to specify the build configuration, such as whether to enable debugging or optimize for size, while product flavors are used to specify the appโs functionality and resources.
For example, if you have two product flavors, โfreeโ and โpaidโ, and two build types, โdebugโ and โreleaseโ, you would have four different build variants: โfreeDebugโ, โfreeReleaseโ, โpaidDebugโ, and โpaidReleaseโ. Each build variant would have its own configuration, resources, and code, and could be signed with a different key or configured for different deployment channels.
Resource Merging & Flavor Dimensions
Resource merging is an important part of using product flavors in Android development. When you have multiple product flavors with their own resources, such as layouts, strings, and images, the Android build system needs to merge them together to create the final APK.
Hereโs an example of how resource merging works with product flavors:
android {
// Define the flavor dimensions
flavorDimensions "language", "version"
// Define the product flavors
productFlavors {
englishFree {
dimension "language"
applicationId "com.softaai.app.en"
resValue "string", "app_name", "My App (English)"
}
spanishFree {
dimension "language"
applicationId "com.softaai.app.es"
resValue "string", "app_name", "Mi Aplicaciรณn (Espaรฑol)"
}
pro {
dimension "version"
applicationId "com.softaai.app.pro"
resValue "string", "app_name", "My App Pro"
}
free {
dimension "version"
applicationId "com.softaai.app.free"
resValue "string", "app_name", "My App Free"
}
}
}
In this example, we have two flavor dimensions, โlanguageโ and โversionโ. We define four product flavors, โenglishFreeโ, โspanishFreeโ, โproโ, and โfreeโ, each with their own application ID and app name.
Well, What is Flavor Dimension?
Flavor dimension is a concept in Android Gradle build system that allows the grouping of related product flavors. It is used when an app has multiple sets of product flavors that need to be combined together. For example, if an app is available in multiple countries and each country has multiple build types, then we can use flavor dimensions to group the country-specific flavors together
When we build the app, the Android build system will merge the resources for each product flavor into the final APK. For example, if we have a layout file called โactivity_main.xmlโ in the โres/layoutโ folder for both โenglishFreeโ and โspanishFreeโ, the build system will merge them into a single โactivity_main.xmlโ file that includes the appropriate resources for each language.
Now, letโs take a look at how we can use flavor dimensions to create more complex combinations of product flavors:
android {
// Define the flavor dimensions
flavorDimensions "language", "version"
// Define the product flavors
productFlavors {
en {
dimension "language"
applicationId "com.softaai.app.en"
resValue "string", "app_name", "My App (English)"
}
es {
dimension "language"
applicationId "com.softaai.app.es"
resValue "string", "app_name", "Mi Aplicaciรณn (Espaรฑol)"
}
pro {
dimension "version"
applicationId "com.softaai.app.pro"
resValue "string", "app_name", "My App Pro"
}
free {
dimension "version"
applicationId "com.softaai.app.free"
resValue "string", "app_name", "My App Free"
}
enPro {
dimension "language"
dimension "version"
applicationId "com.softaai.app.enpro"
resValue "string", "app_name", "My App Pro (English)"
}
esPro {
dimension "language"
dimension "version"
applicationId "com.softaai.app.espro"
resValue "string", "app_name", "Mi Aplicaciรณn Pro (Espaรฑol)"
}
}
}
In this example, we have two flavor dimensions, โlanguageโ and โversionโ, and six product flavors. The โenโ and โesโ flavors represent the English and Spanish versions of the app, while the โproโ and โfreeโ flavors represent the paid and free versions of the app. We also define two additional product flavors, โenProโ and โesProโ, which combine both language and version dimensions.
When we build the app, the Android build system will merge the resources for each product flavor into the final APK. For example, if we have a layout file called โactivity_main.xmlโ in the โres/layoutโ folder for both โenโ and โesโ flavors, the build system will merge them into a single โactivity_main.xmlโ file that includes the appropriate resources for each language. Similarly, if we have a string resource called โapp_nameโ in the โproโ and โenโ flavors, the build system will merge them into a single โapp_nameโ resource that includes the appropriate version and language.
We can also use flavor dimensions to create more complex combinations of product flavors. In the example above, we define two additional product flavors, โenProโ and โesProโ, which combine both language and version dimensions. This means that we can create four different versions of the app: โenFreeโ, โesFreeโ, โenProโ, and โesProโ, each with their own application ID and app name.
Hereโs an example of how we can reference resources for different flavor dimensions in our code:
// Get the app name for the current flavor
String appName = getResources().getString(R.string.app_name);
// Get the app name for the "enPro" flavor
String enProAppName = getResources().getString(R.string.app_name, "en", "pro");
// Get the app name for the "esFree" flavor
String esFreeAppName = getResources().getString(R.string.app_name, "es", "free");
In this example, we use the getResources()
method to get a reference to the appโs resources. We then use the getString()
method to get the app name for the current flavor, as well as for the โenProโ and โesFreeโ flavors, which have different values for the โlanguageโ and โversionโ dimensions.
Pre-Variant Dependencies
When we use product flavors and flavor dimensions to create different variants of our app, we may also need to use different dependencies for each variant. This is where pre-variant dependencies come into play.
Pre-variant dependencies are dependencies that are defined outside of the product flavors and flavor dimensions. These dependencies are applied to all variants of the app, regardless of the product flavor or flavor dimension. We can define pre-variant dependencies in the build.gradle
file, outside of the productFlavors
and flavorDimensions
blocks.
Hereโs an example of how we can define pre-variant dependencies:
dependencies {
// Pre-variant dependencies
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
// Flavor-specific dependencies
flavorDimensions 'version', 'language'
productFlavors {
pro {
dimension 'version'
}
free {
dimension 'version'
}
en {
dimension 'language'
}
es {
dimension 'language'
}
}
// Dependencies for specific flavor dimensions
enImplementation 'com.squareup.retrofit2:retrofit:2.9.0'
esImplementation 'com.squareup.okhttp3:okhttp:4.9.3'
proImplementation 'com.google.firebase:firebase-crashlytics:18.4.1'
}
In this example, we define two pre-variant dependencies: com.google.android.material:material:1.5.0
and androidx.appcompat:appcompat:1.4.1
. These dependencies will be applied to all variants of the app, regardless of the product flavor or flavor dimension.
We then define four product flavors, two for the โversionโ dimension and two for the โlanguageโ dimension. We also define flavor-specific dependencies for each flavor dimension. For example, we define enImplementation 'com.squareup.retrofit2:retrofit:2.9.0'
for the โenโ flavor, which means that this dependency will only be applied to variants that include the โenโ flavor.
Finally, we define a pro-variant dependency using the proImplementation
keyword. This dependency will be applied only to variants that include the โproโ flavor.
Summary
Overall, product flavors and build variants provide a powerful and flexible way to create different versions of our Android app for different use cases. By combining different flavors and types, we can create highly customizable builds that meet the specific needs of our users.