Dependências duplicadas no Gradle

Dependendo do projeto as dependências do Gradle podem ser um problema para o desenvolvedor. Isso acontece quando algumas bibliotecas que importamos no projeto usam outras bibliotecas dentro delas em versões diferentes.. Quando isso acontece o Gradle exibe a mensagem de Conflict with dependency. Para resolver isso temos algumas opções.


Exemplo

Para exemplificar será usado um cenário onde o projeto compila a biblioteca de Suporte v7 e compila a biblioteca de testes Espresso.

dependencies {
compile 'com.android.support:appcompat-v7:25.1.0'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}

Quando tentado sincronizar o projeto o Android Studio irá avisar da duplicidade da biblioteca de Suporte de Anotações, pois ambas bibliotecas importadas a utilizam porém em versões diferentes (a Suporte V7 usa a versão 25.1.0 e a Espresso usa a versão 23.1.1).

Erro do Android Studio.

Analisando duplicidades

Linha de comando

Execute o comando no arquivo gradlew localizado na raiz do projeto:

  • ./gradlew app:dependencies (Linux e Mac OS X)
  • gradlew app:dependencies (Windows, usando gradlew.bat)

(Onde app: é o modulo).

O comando irá exibir a lista de dependências de cada biblioteca usada no projeto. 
Abaixo está o que há por dentro da biblioteca do Espresso e da Suporte V7, respectivamente.

\--- com.android.support.test.espresso:espresso-core:2.2.2
+--- com.squareup:javawriter:2.1.1
+--- com.android.support.test:rules:0.5
| \--- com.android.support.test:runner:0.5
| +--- com.android.support:support-annotations:23.1.1
| +--- junit:junit:4.12
| | \--- org.hamcrest:hamcrest-core:1.3
| \--- com.android.support.test:exposed-instrumentation-api-publish:0.5
+--- com.android.support.test:runner:0.5 (*)
+--- javax.inject:javax.inject:1
+--- org.hamcrest:hamcrest-library:1.3
| \--- org.hamcrest:hamcrest-core:1.3
+--- com.android.support.test.espresso:espresso-idling-resource:2.2.2
+--- org.hamcrest:hamcrest-integration:1.3
| \--- org.hamcrest:hamcrest-library:1.3 (*)
+--- com.google.code.findbugs:jsr305:2.0.1
\--- javax.annotation:javax.annotation-api:1.2
\--- com.android.support:appcompat-v7:25.1.0
+--- com.android.support:support-annotations:25.1.0
+--- com.android.support:support-v4:25.1.0
| +--- com.android.support:support-compat:25.1.0
| | \--- com.android.support:support-annotations:25.1.0
| +--- com.android.support:support-media-compat:25.1.0
| | +--- com.android.support:support-annotations:25.1.0
| | \--- com.android.support:support-compat:25.1.0 (*)
| +--- com.android.support:support-core-utils:25.1.0
| | +--- com.android.support:support-annotations:25.1.0
| | \--- com.android.support:support-compat:25.1.0 (*)
| +--- com.android.support:support-core-ui:25.1.0
| | +--- com.android.support:support-annotations:25.1.0
| | \--- com.android.support:support-compat:25.1.0 (*)
| \--- com.android.support:support-fragment:25.1.0
| +--- com.android.support:support-compat:25.1.0 (*)
| +--- com.android.support:support-media-compat:25.1.0 (*)
| +--- com.android.support:support-core-ui:25.1.0 (*)
| \--- com.android.support:support-core-utils:25.1.0 (*)
+--- com.android.support:support-vector-drawable:25.1.0
| +--- com.android.support:support-annotations:25.1.0
| \--- com.android.support:support-compat:25.1.0 (*)
\--- com.android.support:animated-vector-drawable:25.1.0
\--- com.android.support:support-vector-drawable:25.1.0 (*)

Ambas bibliotecas estão utilizando a com.android.support:support porém com versões diferentes (destacado em negrito).

Plugin do Relatório de Projeto

Para algo mais “visual” o Gradle oferece um plugin para gerar o mesmo relatório acima via página HTML, com opção de expandir e recolher as dependências internas para melhor navegação.

Para isso é necessário configurar o plugin no build.gradle do seu modulo (geralmente, app/build.gradle).

apply plugin: 'com.android.application'
apply plugin: 'project-report'

Depois de tentar sincronizar será possível rodar o comando

  • ./gradlew htmlDependencyReportTask (Linux e Mac OS X)
  • gradlew htmlDependencyReportTask (Windows, usando gradlew.bat)

Feito isso o você poderá abrir o relatório pelo seu Navegador. Ele está em:
{Seu Projeto}/app/build/reports/project/dependencies/index.html

Plugin Android Studio

Também existe um plugin para o Android Studio que permite ver a arvore de dependência dentro da IDE. Ele se chama Gradle View e para instalá-lo no Android Studio basta ir em File > Settings. Sessão de Plugins e clicar em Browser repositories…. Na nova janela buscar por Gradle View e Install.

Instalação do plugin.

Reinicie o Android Studio e agora aparecerá esta aba na parte inferior direita.

Plugin do Android Android para analisar dependências

Obrigado especial ao Ray Holder pela excelente biblioteca.


Resolvendo o problema

Ambas soluções propostas serão feitas no mesmo lugar em que se declara as dependências.

Exclude

Com a tag exclude configuramos para o Gradle excluir a dependência interna desejada. Podemos identificá-la por grupo (tag exclude group), ou por nome (tag exclude module) ou por ambos.

dependencies {
compile 'com.android.support:appcompat-v7:25.1.0'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude group: 'com.android.support'
exclude module: 'support-annotations'
exclude group: 'com.android.support', module: 'support-annotations'
}

Assim é configurado que a biblioteca não irá compilar a dependência citada no exclude e utilizará outra encontrada dentro do projeto como um todo.

Force

Outra opção seria forçar o projeto todo a usar uma versão especifica:

configurations.all {
resolutionStrategy {
force 'com.android.support:support-annotations:25.1.0'
}
}

Conclusão

A solução mais sensata é remover as dependências internas mais desatualizadas do projeto (usando exclude). 
A desvantagem de forçar uma versão de uma biblioteca no projeto (usando force) é que se alguma biblioteca for atualizada e sua dependência interna também for atualizada ela continuará a utilizar a versão fixada no force mesmo assim. Por exemplo, se biblioteca de Suporte v7 for atualizada e a biblioteca Suporte de Anotações utilizada internamente também for atualizada, ela continuará usando a versão fixada no force, podendo causar outros problemas como ausência de funcionalidades que garantem o bom funcionamento da V7.
E, consequentemente, excluindo a dependência mais desatualizada garantimos que a dependência será atualizada automaticamente caso a biblioteca atualize.


Considerações finais

Documentação oficial

Na documentação do Gradle há uma parte falando sobre listar as depedências, utilizar plugin de relatório, excluir dependências e resolução de conflitos.

Contatos

Podem me encontrar pelo e-mail orafaaraujo@gmail.com, pelo skype orafaaraujo, e pelo slack/medium/twitter @orafaaraujo. 
Feedbacks são bem-vindos!

Comunidade Android

Participe do maior forum sobre Android no Brasil no slack Android Dev BR
Site: http://www.androiddevbr.org | Convite: http://slack.androiddevbr.org