Better way of using a few different resources files per build flavor
Categories:
Streamlining Resource Management with Build Flavors in Android Gradle

Discover how to efficiently manage different resource files for various build flavors in your Android projects using Gradle, enhancing flexibility and reducing code duplication.
Android applications often require different configurations, assets, or strings depending on the target environment, user group, or distribution channel. Gradle's build flavors provide a powerful mechanism to achieve this, allowing you to create distinct versions of your app from a single codebase. A common challenge is managing resources that vary across these flavors, such as app icons, API keys, or specific layout adjustments. This article will guide you through a robust approach to organize and utilize different resource files per build flavor, ensuring a clean and maintainable project structure.
Understanding Android Build Flavors
Build flavors are a core feature of the Android build system that allow you to define custom product variations. Each flavor can have its own set of source code, resources, and manifest entries, which are merged with the main
source set during the build process. This modularity is crucial for projects that need to cater to multiple requirements without maintaining separate branches or projects.
flowchart TD A[Base Project] --> B{Build Flavor Selection} B --> C1[Flavor 1 Source Set] B --> C2[Flavor 2 Source Set] C1 --> D[Merged with Main Source Set] C2 --> D D --> E[Compiled Application (APK/AAB)] subgraph Source Sets C1 C2 end subgraph Build Process B D E end
Android Build Flavor Merging Process
Structuring Resources for Build Flavors
The key to managing flavor-specific resources is understanding Gradle's source set merging rules. For each build flavor you define, Gradle automatically looks for a corresponding source set directory. If a resource exists in both a flavor's source set and the main
source set, the flavor's resource takes precedence. This hierarchical merging allows you to override or add resources as needed.
Consider a scenario where you have free
and paid
flavors. You might want different app icons, different string values (e.g., app name), or even different layouts for each. The recommended directory structure for this is as follows:
app/
âââ src/
â âââ main/
â â âââ java/
â â âââ res/
â â â âââ drawable/
â â â âââ layout/
â â â âââ values/
â â â âââ ...
â â âââ AndroidManifest.xml
â âââ free/
â â âââ java/
â â âââ res/
â â â âââ drawable/
â â â â âââ ic_launcher.xml // Free flavor icon
â â â âââ values/
â â â â âââ strings.xml // Free flavor strings
â â â âââ ...
â â âââ AndroidManifest.xml
â âââ paid/
â âââ java/
â âââ res/
â â âââ drawable/
â â â âââ ic_launcher.xml // Paid flavor icon
â â âââ values/
â â â âââ strings.xml // Paid flavor strings
â â âââ ...
â âââ AndroidManifest.xml
âââ build.gradle
Recommended directory structure for flavor-specific resources
main/res
directory. Only place resources that differ in their respective flavor directories. This minimizes duplication and keeps your project organized.Configuring Build Flavors in build.gradle
To enable and define your build flavors, you need to configure them within your module-level build.gradle
file (typically app/build.gradle
). The productFlavors
block is where you declare each flavor and can customize properties like applicationIdSuffix
, versionNameSuffix
, and resValue
for simple resource overrides.
android {
compileSdk 34
defaultConfig {
applicationId "com.example.myapp"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
flavorDimensions "version"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
versionNameSuffix "-free"
resValue "string", "app_name", "My App Free"
// Add flavor-specific buildConfigField for API keys or other constants
buildConfigField "String", "API_BASE_URL", "\"https://api.free.example.com\""
}
paid {
dimension "version"
applicationIdSuffix ".paid"
versionNameSuffix "-paid"
resValue "string", "app_name", "My App Paid"
buildConfigField "String", "API_BASE_URL", "\"https://api.paid.example.com\""
}
}
}
dependencies {
// ...
}
Example build.gradle
configuration for free
and paid
flavors
In the example above, we define two flavors, free
and paid
, under the version
flavor dimension. Each flavor gets a unique applicationIdSuffix
and versionNameSuffix
. Crucially, we use resValue
to define a flavor-specific app_name
string directly in the Gradle file. This is useful for simple string overrides. For more complex resource changes (like drawables or layouts), placing the files in the flavor's res
directory is the preferred method.
resValue
for strings, ensure that the string resource is not also defined in a strings.xml
file within the same flavor's res/values
directory, as this can lead to unexpected behavior or build errors.Accessing Flavor-Specific Resources
Once configured, accessing these flavor-specific resources in your Android application is straightforward. You simply reference them by their standard resource ID, and the Android build system automatically picks the correct resource based on the active build variant.
XML Layout
Kotlin Code
// In your Activity or Fragment import android.os.Bundle import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
// Accessing string resource
val appName = getString(R.string.app_name)
println("App Name: $appName")
// Accessing drawable resource (example for programmatic use)
val launcherIcon = getDrawable(R.drawable.ic_launcher)
// Use launcherIcon as needed
// Accessing BuildConfig field
val apiBaseUrl = BuildConfig.API_BASE_URL
println("API Base URL: $apiBaseUrl")
}
}
When you build the freeDebug
variant, R.string.app_name
will resolve to "My App Free" and R.drawable.ic_launcher
will be the icon from free/res/drawable
. Similarly, for paidDebug
, it will resolve to "My App Paid" and the icon from paid/res/drawable
.
build.gradle
or adding new resource directories to ensure the IDE recognizes the new configurations.