Resolução de dependência do Gradle

Os arquivos de build especificam as dependências diretas, mas cada uma delas pode exigir outras. Essas dependências transitivas aumentam rapidamente o gráfico de dependências geral, muitas vezes com versões conflitantes.

Quando as partes minor (novos recursos) ou patch (correções de bugs) mudam, a biblioteca ainda é compatível e tem menos probabilidade de afetar seu aplicativo.

Por exemplo, suponha que seu aplicativo dependa das bibliotecas A e B, que, por sua vez, dependem de diferentes versões da biblioteca C.

Seu app depende da biblioteca A e da biblioteca B, que, por sua vez, dependem de diferentes versões da biblioteca C. O Gradle escolhe a versão mais recente da biblioteca C.
Figura 1. Um conflito de versão transitivo. O Gradle é resolvido para a versão mais recente (por padrão).

Nesse caso, o Gradle escolhe a versão mais recente da biblioteca C por padrão, o que pode causar problemas de compilação ou de execução. Neste exemplo, a biblioteca C é resolvida para 2.1.1, mas observe que a biblioteca A solicitou a biblioteca C 1.0.3. A maior parte do número da versão mudou, indicando mudanças incompatíveis, como a remoção de funções ou tipos. Isso pode causar falhas nas chamadas feitas da biblioteca A.

O app pode ter dependências diretas que também são transitivas.

Seu app depende da biblioteca A e da biblioteca C. A biblioteca A depende de uma versão mais recente da biblioteca C. O Gradle escolhe a versão mais recente da biblioteca C.
Figura 2. Outro conflito de versão transitiva. Aqui, o Gradle resolve para a versão transitiva, e o aplicativo encontra essa versão mais recente.

Em um caso como esse, dependências transitivas mais recentes podem substituir a versão que você solicita diretamente no app.

O Gradle verifica todas as versões candidatas de todas as dependências no gráfico para determinar a versão mais recente de cada dependência. É possível usar tarefas básicas do Gradle e ferramentas mais avançadas para determinar quais versões de cada dependência o Gradle resolveu. Comparar as mudanças nesta resolução é fundamental para entender e reduzir os riscos do upgrade.

Por exemplo, é possível usar a tarefa dependencies do Gradle executando ./gradlew app:dependencies para mostrar uma árvore de todas as dependências usadas pelo módulo do app. Ao executar isso em um aplicativo que usa as bibliotecas, conforme mostrado na Figura 2, temos

1: releaseRuntimeClasspath - Runtime classpath of /release.
2: +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0
3: |    +--- ... (omitted for brevity) ...
4: +--- com.sample:library.a:1.2.3
5: |    +--- com.sample:library.c:2.1.1
6: |    |    \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
7: |    \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
8: +--- com.sample:library.c:1.4.1 -> 2.1.1 (*)

Essa parte do relatório mostra algumas das dependências resolvidas para a configuração releaseRuntimeClasspath.

Sempre que você encontrar -> no relatório de dependências, um solicitante (seu aplicativo ou outra biblioteca) vai usar uma versão dessa dependência que não é esperada. Em muitos casos, isso não causa problemas, já que a maioria das bibliotecas é programada para compatibilidade com versões anteriores. No entanto, algumas bibliotecas podem fazer mudanças incompatíveis, e esse relatório pode ajudar a determinar de onde vêm os novos problemas com o comportamento do aplicativo.

Mais detalhes sobre o uso dos relatórios de dependência do Gradle podem ser encontrados em Conferir e depurar dependências.

É possível especificar as versões solicitadas diretamente, em um catálogo de versões ou em uma lista de materiais (BOM, na sigla em inglês).

Resolução da especificação da versão direta

As versões das dependências especificadas se tornam candidatas à resolução de versão.

Por exemplo, para solicitar a versão 1.7.3 da biblioteca androidx.compose.ui:ui como uma dependência no app/build.gradle.kts:

dependencies {
    implementation("androidx.compose.ui:ui:1.7.3")
}

A versão 1.7.3 se torna uma versão candidata. O Gradle resolve a versão mais recente entre 1.7.3 e outras versões da mesma biblioteca solicitadas por dependências transitivas.

Resolução do catálogo de versões

Os catálogos de versões definem variáveis para acompanhar a versão das dependências usadas em todo o aplicativo. Se você usar uma variável do catálogo de versões, as dependências especificadas dessa variável serão adicionadas aos candidatos para resolução de versão. As variáveis não usadas no catálogo de versões são ignoradas.

Por exemplo, para especificar a versão 1.7.3 do androidx.compose.ui:ui como uma dependência no arquivo gradle/libs.versions.toml:

[versions]
ui = "1.7.3"

[libraries]
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }

Isso define uma variável com o nome libs.androidx.compose.ui para representar a biblioteca. Essa versão não é considerada um candidato, a menos que você use essa variável para especificar uma dependência.

Para solicitar a biblioteca e a versão dela no app/build.gradle.kts:

dependencies {
    implementation(libs.androidx.compose.ui)
}

O Gradle resolve da mesma forma que uma especificação direta.

Resolução da lista de materiais (BOM)

As versões de todas as bibliotecas que aparecem na BoM se tornam candidatas à resolução de versão. As bibliotecas só são usadas como dependências se especificadas como diretas ou indiretas. Outras bibliotecas no BOM são ignoradas.

As versões da BOM afetam suas dependências diretas, bem como todas as dependências transitivas que aparecem na BOM.

Por exemplo, especifique um BOM como uma dependência de plataforma no app/build.gradle.kts:

dependencies {
    implementation(platform("androidx.compose:compose-bom:2024.10.00"))
    implementation("androidx.compose.ui:ui")
}

As bibliotecas que você quer usar como dependências não exigem uma especificação de versão. A versão solicitada vem da BoM.

Você também pode usar um catálogo de versões para criar variáveis para a BoM e bibliotecas. Omita os números de versão no catálogo de versões para bibliotecas que aparecem em uma dependência da BOM.

Por exemplo, o catálogo de versões contém a BoM e o número da versão, mas não especifica uma versão para as bibliotecas referenciadas na BoM:

[versions]
composeBom = "2024.10.00"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }

O app/build.gradle.kts faz referência à BoM e às bibliotecas usando as variáveis definidas no catálogo de versões:

dependencies {
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.compose.ui)
}

A versão da biblioteca especificada no BOM se torna um candidato para a resolução do Gradle. Além disso, todas as outras versões de biblioteca especificadas na BoM se tornam versões candidatas, mesmo que você as use diretamente como dependências.

Por exemplo, suponha que um BOM especifique versões para as bibliotecas A, B e C. O aplicativo quer usar a biblioteca A diretamente como uma dependência, assim como a biblioteca D. A biblioteca D usa a biblioteca B como dependência. Nada usa a biblioteca C.

Uma BoM inclui versões para as bibliotecas A, B e C. O aplicativo usa as bibliotecas A e D como dependências. A biblioteca D usa a biblioteca B como dependência. A biblioteca C não é usada de forma direta ou indireta neste app.
Figura 3. Cenário de BOM.

As bibliotecas A, B e D são dependências do aplicativo. A biblioteca C é ignorada. O Gradle usa as versões de A e B especificadas na BoM como candidatas, mesmo que você não especifique diretamente a biblioteca B como uma dependência.

Se a biblioteca D solicitar uma versão da biblioteca B inferior a 2.0.1, o Gradle vai resolver para 2.0.1. Se a biblioteca D solicitar uma versão mais recente da biblioteca B, o Gradle vai resolver para essa versão.