Android Gradle: Lessons learnt using missingDimensionStrategy
TL;DR:
- If a library module includes a flavor dimension that the app flavor does not, then use
missingDimensionStrategy
to specify default flavors from the missing dimension - More generally, you can use this any time a consumed module includes a flavor that the consumer module does not. Remember,
missingDimensionStrategy
DSL should be used in thebuild.gradle
of the consumer module. - However, point 2 might not always do what you think it is doing. In particular, when the consumer module which is missing the flavor dimension is in itself a dependency for another module, you are probably better off introducing this flavor dimension in this module, mirroring that of the consumed module.
Introduction
The Android Gradle Plugin introduces the concept of product flavors. When you have a project that contains multiple modules, it is possible that your modules do not agree on the number of flavors or flavor dimensions. The DSL offers ways to handle such situations.
The important portions of the DSL are matchingFallbacks
and missingDimensionStrategy
. Which one you choose depends on which module declares more flavors: the consuming module, or the consumed module. This table in the Android Gradle documentation explains this in more detail.
I had a slightly more complex project structure where I thought missingDimensionStrategy
would be my tool of choice, but it turns out I was wrong. This post describes my situation and why I was wrong.
Project structure
The project that I use for this blog post is here. The app itself is a shell. It has no activities, no functionality and almost no Android code. It has the following modules:
app
module.- Includes a flavor dimension
"target"
, with values"emulator"
and"realdevice"
- Depends on
intermediate-1
,intermediate-2
andleaf
- Includes a flavor dimension
intermediate-1
module.- Does not know about flavors
- Depends on
leaf
intermediate-2
module.- Does not know about flavors
- Depends on
leaf
leaf
module.- Includes the same flavor dimension and flavors as
app
module - Has no dependencies
- Includes the same flavor dimension and flavors as
String resources
To demonstrate how flavors work, I’ve created some string resources (using gradle’s resValue
feature) in every flavor, in every module that does care about the flavor. When I build an APK for a particular flavor, I can inspect the string resources in APK analyzer to figure out which variant was used.
// app/build.gradle
flavorDimensions "target"
productFlavors {
emulator {
dimension "target"
resValue("string", "name_app_module", "App Emulator")
}
realdevice {
dimension "target"
resValue("string", "name_app_module", "App Real Device")
}
}
// leaf/build.gradle
flavorDimensions "target"
productFlavors {
emulator {
dimension "target"
resValue("string", "name_leaf_module", "Leaf module Emulator")
}
realdevice {
dimension "target"
resValue("string", "name_leaf_module", "Leaf module Real Device")
}
}
The build error
This is the starting point of my experiment, and it fails to build. You can git checkout checkpoint-2
if you want to see it in action. The build error you see looks like this:
The reason for this build error is that both the intermediate
modules know nothing flavors but they depend on leaf
which does.
Configuring missingDimensionStrategy
My first attempt at fixing this was to configure the intermediate
modules to specify missingDimensionStrategy
//intermediate-1/build.gradle and intermediate-2/build.gradle
//this goes inside android { defaultConfig block
missingDimensionStrategy 'target', 'emulator'
Here we’re saying that when trying to build the intermediate modules, choose the emulator
variant of the leaf
module. This works, kind of. We can build the APKs for both flavors and we can see the correct strings get included in the built APKs.
This is what the emulatorDebug
flavor looks like. Note that it has included the strings "App Emulator"
and "Leaf module Emulator"
And here’s the realdeviceDebug
APK. Note that it has included the strings "App Real Device"
and "Leaf module Real Device"
You can git checkout checkpoint-3
if you want to see it in action.
The problem
The problem with this solution becomes obvious when you see the build output window. It contains this warning
Module ‘leaf’ has variant ‘realdeviceDebug’ selected, but the modules [‘intermediate-1’, ‘intermediate-2’] depend on variant ‘emulatorDebug’
The problem is this: When you chose realdeviceDebug
variant for app
module, the same variant is selected for leaf
module. However, for the intermediate
modules, we specified we want to use emulator
flavor.
This is not a problem for us because we explicitly stated that the intermediate flavors don’t know or care about flavors. But, how do you fix those warnings?
Making intermediate module flavor-aware
Unfortunately, the only correct way to achieve this was to make the intermediate module flavor-aware. This might seem tedious and against the original intent, but I would argue that for this situation, it is a future-proof approach compared to missingDimensionStrategy
. Because with missingDimensionStrategy
if for some reason an intermediate module had to become flavor-aware, it could end up silently choosing the wrong flavor of the leaf module.
So, the fix is to actually introduce the same flavor dimensions and flavors in the intermediate modules as in the leaf modules. You can git checkout checkpoint-4
to see it in action. If you are worried about the repetitive flavor configuration code in the intermdiate modules, you can extract it into a flavors.gradle
and then use it in the build.gradle files.
// root flavors.gradle
android {
flavorDimensions "target"
productFlavors {
emulator {
dimension "target"
}
realdevice {
dimension "target"
}
}
}
// build.gradle of intermediate modules
apply from: '../flavors.gradle'
Conclusion
Use missingDimensionStrategy
mainly if your app module is missing flavor dimensions compared to library modules. Think twice before using it in a module that is itself a dependency for another module. It might look like it works, but it might be doing the wrong thing.
Credits
Thanks to Xavier Ducrohet (@droidxav) for pointing out the potential pitfalls of using missingDimensionStrategy
for my use case.