Resolving Conflicts in android gradle dependencies
Gradle has made the lives of Android developers quite easy- just add one dependency in the build.gradle, and the required library is seamlessly included in the build. But what happens when two dependencies have a dependency on different versions of the same library? Consider the following example. Both libraries in the below code have an internal dependency on a library “org.hamcrest:hamcrest-core”
So if both the dependencies are internally using different versions of the same library, then which version finally gets included in the build? The answer is- the highest version gets included in the build. Run the following command in the module in which the dependencies are declared.
The output of the above command is given below which clearly indicates that gradle automatically upgrades the hamcrest library version from 1.1 to 1.3 in the final build.
Dependency Conflict Error
Earlier, both the dependencies were test dependencies, so gradle was able to resolve the conflict automatically. But if both the dependencies belonged to different configurations i.e app and test, then gradle will throw an error. Consider the following code snippet.
Now the first dependency belongs to the app configuration, while the second dependency belongs to the test configuration. So, while building the project, it will fail with the following exception.
According to the official documentation of Android gradle, the reason for the above failure is:
When instrumentation tests are run, both the main APK and test APK share the same classpath. Gradle build will fail if the main APK and the test APK use the same library (e.g. Guava) but in different versions. If gradle didn’t catch that, your app could behave differently during tests and during normal run (including crashing in one of the cases).
Solving Dependency conflicts
Once, there is a dependency conflict in the build, the developer needs to decide which version of the library to finally include in the build. There are many ways of resolving the conflict.
- Exclude the conflicted module/library from one of the dependencies.
While declaring a dependency, we can specify the modules which we do not want. For e.g.- in our case, if we don’t want the latest version 1.3, and instead want version 1.1 of the hamcrest library, then we can exclude that module while declaring “junit” dependency.
Alternatively, if we want to include version 1.3 in the build, then we can exclude it from “mockito” dependency.
In a real time project, there will be many dependencies which will have conflicting versions of the same library. In that case, for each and every dependency, we need to have the exclude tag.
2. Explicitly define the conflicted library in build.gradle
This is another way of resolving the conflict. In this case, we need to explicitly mention the version of the library which we want to include in the final build for any one of the configurations.
If multiple dependencies are having a dependency or transitive dependency which is having a conflicted version, then instead of excluding modules from each dependency, we can simply define the conflicted dependency with the desired version number.
This approach is a cleaner approach to resolve the conflicts, but the downside is that while updating the actual dependencies like junit and mockito, the developer needs to update conflicted library as well.
3. Force resolution of the library
This is another way of resolving the conflict in which instead of declaring for one configuration, we force it for all the configurations.
But in my view, this approach should be used with extreme caution. Although it serves the purpose of resolving the conflict, there are some serious repercussions of using this approach. If the actual dependencies (junit and mockito) are updated and these libraries update the version of hamcrest library, we would still be forcing to use a backward version. Although the scenario holds true for the 2nd approach also, but in this approach we are forcing the dependency version on all the configurations instead on a single configuration which can be an issue.