Depurar erros de resolução de dependências

Ao adicionar uma dependência, você pode encontrar problemas com dependências exigidas pela dependência original, além de conflitos entre diferentes versões. Veja como analisar seu gráfico de dependências e corrigir problemas comuns que surgem.

Para conferir orientações sobre como corrigir erros de resolução de dependências que envolvem lógica de build personalizada, consulte Estratégias de resolução de dependências personalizadas.

Visualizar dependências do módulo

Algumas dependências diretas podem ter as próprias dependências. Essas próprias dependências são denominadas dependências transitivas. Em vez de exigir que você declare manualmente cada dependência transitiva, o Gradle coleta e adiciona automaticamente essas dependências para você. O plug-in do Android para Gradle disponibiliza uma tarefa que mostra uma lista das dependências que o Gradle resolve para determinado módulo.

Para cada módulo, o relatório também agrupa as dependências com base na variante de build, no conjunto de origem de teste e no caminho de classe. Confira abaixo um exemplo de relatório do caminho de classe da execução de um módulo de app referente à variante de build de depuração e a um caminho de classe da compilação do conjunto de origem de teste instrumentado.

debugRuntimeClasspath - Dependencies for runtime/packaging
+--- :mylibrary (variant: debug)
+--- com.google.android.material:material:1.0.0@aar
+--- androidx.appcompat:appcompat:1.0.2@aar
+--- androidx.constraintlayout:constraintlayout:1.1.3@aar
+--- androidx.fragment:fragment:1.0.0@aar
+--- androidx.vectordrawable:vectordrawable-animated:1.0.0@aar
+--- androidx.recyclerview:recyclerview:1.0.0@aar
+--- androidx.legacy:legacy-support-core-ui:1.0.0@aar
...

debugAndroidTest
debugAndroidTestCompileClasspath - Dependencies for compilation
+--- androidx.test.ext:junit:1.1.0@aar
+--- androidx.test.espresso:espresso-core:3.1.1@aar
+--- androidx.test:runner:1.1.1@aar
+--- junit:junit:4.12@jar
...

Para executar a tarefa, faça o seguinte:

  1. Selecione View > Tool Windows > Gradle (ou clique em Gradle na barra de janelas de ferramentas).
  2. Abra AppName > Tasks > android e clique duas vezes em androidDependencies. Depois que o Gradle executa a tarefa, a janela Run é aberta para mostrar a saída.

Para saber mais sobre como gerenciar dependências no Gradle, consulte Conceitos básicos sobre gerenciamento de dependências (link em inglês) no guia do usuário do Gradle.

Excluir dependências transitivas

À medida que um app cresce em escopo, ele pode conter uma série de dependências, incluindo dependências diretas e transitivas (bibliotecas do seu app que dependem de bibliotecas importadas). Para excluir as dependências transitivas que você não precisa mais, é possível usar a palavra-chave exclude, conforme mostrado abaixo.

Kotlin

dependencies {
    implementation("some-library") {
        exclude(group = "com.example.imgtools", module = "native")
    }
}

Groovy

dependencies {
    implementation('some-library') {
        exclude group: 'com.example.imgtools', module: 'native'
    }
}

Excluir dependências transitivas das configurações de teste

Se você precisar excluir determinadas dependências transitivas dos seus testes, o exemplo de código mostrado acima talvez não funcione conforme o esperado. Isso acontece porque uma configuração de teste (por exemplo, androidTestImplementation) estende a configuração implementation do módulo. Ou seja, sempre há dependências implementation quando o Gradle resolve a configuração.

Assim, para excluir dependências transitivas dos seus testes, é preciso fazer isso no tempo de execução, conforme mostrado abaixo.

Kotlin

android.testVariants.all {
    compileConfiguration.exclude(group = "com.jakewharton.threetenabp", module = "threetenabp")
    runtimeConfiguration.exclude(group = "com.jakewharton.threetenabp", module = "threetenabp")
}

Groovy

android.testVariants.all { variant ->
    variant.getCompileConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
    variant.getRuntimeConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
}

Observação: ainda é possível usar a palavra-chave exclude no bloco de dependências, conforme mostrado no exemplo de código original na seção Excluir dependências transitivas para omitir dependências transitivas que são específicas à configuração de teste e não estão incluídas em outras configurações.

Corrigir erros de resolução de dependência

Quando você adiciona várias dependências ao projeto do seu app, essas dependências diretas e transitivas podem entrar em conflito. O Plug-in do Android para Gradle tenta resolver esses conflitos, mas alguns deles podem levar a erros de tempo de execução ou compilação.

Para ajudar a investigar quais dependências contribuem com erros, examine a árvore de dependências do app e procure dependências que aparecem mais de uma vez ou versões conflitantes.

Se não for possível identificar facilmente a dependência duplicada, tente usar a IU do Android Studio para procurar dependências que incluem a classe duplicada da seguinte forma:

  1. Selecione Navigate > Class na barra de menus.
  2. Na caixa de diálogo pop-up de pesquisa, confira se a caixa ao lado de Include non-project items está marcada.
  3. Digite o nome da classe que aparece no erro de build.
  4. Examine os resultados das dependências que incluem a classe.

As seções a seguir descrevem os diferentes tipos de erro de resolução de dependência que você pode encontrar e como corrigi-los.

Corrigir erros de classe duplicados

Se uma classe aparecer mais de uma vez no caminho de classe de execução, você vai encontrar um erro parecido com este:

Program type already present com.example.MyClass

Normalmente, esse erro ocorre devido a uma das seguintes circunstâncias:

  • Uma dependência binária inclui uma biblioteca que o app também inclui como uma dependência direta. Por exemplo, o método declara uma dependência direta de Biblioteca A e Biblioteca B, mas a Biblioteca A já inclui a Biblioteca B no binário dela.
    • Para resolver esse problema, remova a Biblioteca B como dependência direta.
  • O app tem uma dependência binária local e uma dependência binária remota na mesma biblioteca.
    • Para resolver esse problema, remova uma das dependências binárias.

Corrigir conflitos entre caminhos de classe

Quando o Gradle resolve o caminho de classe de compilação, ele primeiro resolve o de execução e usa o resultado para determinar quais versões das dependências precisam ser adicionadas ao de compilação. Em outras palavras, o caminho de classe de execução determina os números de versão necessários para dependências idênticas nos caminhos de classe downstream.

O caminho de classe de execução do app também determina os números da versão que o Gradle exige para corresponder dependências no caminho de classe de execução para o APK de teste do app. A hierarquia dos caminhos de classe está descrita na figura 1.

Figura 1. Números da versão das dependências que aparecem em vários caminhos de classe precisam corresponder a esta hierarquia.

Um conflito em que versões diferentes da mesma dependência aparecem em vários caminhos de classe ocorre quando, por exemplo, o app inclui uma versão de uma dependência usando a configuração de dependência implementation, e um módulo de biblioteca inclui uma versão diferente da dependência usando a configuração runtimeOnly.

Ao resolver dependências nos caminhos de classe de execução e compilação, o Plug-in do Android para Gradle 3.3.0 ou versões mais recentes tentam corrigir automaticamente alguns conflitos de versão downstream. Por exemplo, se o caminho de classe de execução incluir a Biblioteca A versão 2.0 e o de compilação incluir a versão 1.0, o plug-in vai evitar erros atualizando automaticamente a dependência do caminho de classe de compilação para a Biblioteca A 2.0.

No entanto, se o caminho de classe de execução incluir a Biblioteca A versão 1.0 e o de compilação incluir a Biblioteca A versão 2.0, o plug-in não vai fazer downgrade da dependência do caminho de classe de compilação para a Biblioteca A versão 1.0, e isso vai gerar um erro semelhante a este:

Conflict with dependency 'com.example.library:some-lib:2.0' in project 'my-library'.
Resolved versions for runtime classpath (1.0) and compile classpath (2.0) differ.

Para resolver esse problema, execute uma destas ações:

  • Inclua a versão desejada da dependência como uma dependência api ao seu módulo de biblioteca. Ou seja, somente seu módulo de biblioteca declara a dependência, mas o módulo do app também terá acesso transitivo à API.
  • Como alternativa, é possível declarar a dependência em ambos os módulos, mas cada módulo tem que usar a mesma versão da dependência. Considere configurar propriedades em todo o projeto para garantir que as versões de cada dependência permaneçam consistentes em todo o projeto.