How to define different dependencies for different product flavors
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Android builds, product flavors let you ship multiple variants of the same app from one codebase. If each flavor needs a different SDK or library, Gradle supports flavor-specific dependency configurations such as freeImplementation and paidImplementation.
Define the flavors first
Start by declaring a flavor dimension and the flavors that belong to it. In the Android Gradle plugin, dependencies become flavor-aware only after the flavors exist.
This creates two variants in the tier dimension: free and paid.
Add flavor-specific dependencies
Once the flavors exist, Gradle exposes matching configurations. Shared libraries still go in implementation, while flavor-only libraries go in the generated flavor configurations:
That means:
- every variant gets the shared
implementationdependencies, - only the
freeflavor gets the ads SDK, - only the
paidflavor gets the premium SDK, - and only the
freeDebugvariant gets LeakCanary in this example.
How Gradle resolves these configurations
Gradle combines build type and flavor information into a variant. For example, freeRelease inherits:
- '
implementation' - '
freeImplementation' - '
releaseImplementation'
This layered model is why flavor-specific dependency declarations are so useful. You do not need duplicate modules or manual if statements in the build script.
Combine flavor-specific code and resources
Dependencies are only part of the story. Product flavors are usually paired with matching source sets:
If the free flavor depends on an ads SDK, put the ad-specific classes under src/free. That keeps the paid build from even compiling code that references the ad library.
For example, a free-only helper class might live in:
while the paid flavor provides a no-op replacement in:
Both variants compile cleanly, but each uses the implementation appropriate to its dependency set.
Common Pitfalls
The most common issue is using the old compile style or placing a flavor-only library under plain implementation. If you do that, every flavor sees the dependency, which defeats the point of variant-specific builds.
Another problem is forgetting to assign a dimension to every flavor. Gradle requires all product flavors to belong to a declared flavor dimension.
Be careful with code references across source sets. If main/ imports a class from a dependency that only exists in freeImplementation, the paid variant will fail to compile.
Finally, keep version alignment in mind. If two flavors depend on incompatible transitive versions of the same library, you may need explicit dependency constraints or exclusions to keep the build stable.
It is also worth checking the generated Gradle tasks and variant graph in Android Studio. When a dependency only fails in paidRelease or freeDebug, the problem is often a variant-specific configuration rather than the dependency declaration itself.
For teams maintaining many variants, naming discipline matters. Clear flavor names and consistent source-set structure make dependency issues much easier to trace when a build starts failing months later.
That upfront consistency pays off every time a new dependency is introduced.
Summary
- Define product flavors first, then use the generated flavor-specific configurations.
- Put shared libraries in
implementationand flavor-only libraries infreeImplementation,paidImplementation, and similar configurations. - Remember that Gradle resolves dependencies per variant, not just per flavor.
- Use flavor-specific source sets alongside flavor-specific dependencies so code stays separated cleanly.
- Check dimensions, source-set references, and transitive dependency conflicts when a flavored build fails.

