Cómo depurar errores de resolución de dependencias

Cuando agregues una dependencia, es posible que encuentres problemas con las dependencias que requiere la dependencia original y conflictos entre diferentes versiones de la dependencia. A continuación, te mostramos cómo analizar tu gráfico de dependencia y solucionar los problemas habituales que surgen.

Para obtener orientación sobre cómo corregir errores de resolución de dependencias que involucran una lógica de compilación personalizada, consulta Estrategias de resolución de dependencias personalizadas.

Cómo ver las dependencias de un módulo

Algunas dependencias directas pueden tener dependencias propias, que se denominan dependencias transitivas. A fin de que no tengas que declarar manualmente cada dependencia transitiva, Gradle las reúne y agrega de forma automática. El complemento de Android para Gradle proporciona una tarea en la que se muestra una lista de las dependencias que Gradle selecciona para un módulo específico.

Para cada módulo, el informe también agrupa las dependencias que se basan en la variante de compilación, el conjunto de fuentes de prueba y la ruta de clase. A continuación, se muestra un informe de ejemplo sobre la ruta de clase de tiempo de ejecución de su variante de compilación de depuración y la ruta de clase de compilación de su conjunto de fuentes de pruebas instrumentadas del módulo de una app.

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 ejecutar la tarea, haz lo siguiente:

  1. Selecciona View > Tool Windows > Gradle (o haz clic en Gradle en la barra de la ventana de herramientas).
  2. Expande AppName > Tasks > android y haz doble clic en androidDependencies. Una vez que Gradle ejecute la tarea, se debería abrir la ventana Run para mostrar el resultado.

Si quieres obtener más información para administrar dependencias en Gradle, consulta los Conceptos básicos de la administración de dependencias en la Guía del usuario de Gradle.

Cómo excluir dependencias transitivas

A medida que crece el alcance de una app, esta puede contener varias dependencias, incluidas las dependencias directas y las transitivas (bibliotecas de las que dependen las bibliotecas importadas de tu app). Para excluir dependencias transitivas que ya no necesitas, puedes usar la palabra clave exclude como se indica a continuación:

Kotlin

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

Groovy

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

Cómo excluir dependencias transitivas de las configuraciones de pruebas

Si necesitas excluir dependencias transitivas específicas de tus pruebas, es posible que el ejemplo de código que se muestra más arriba no funcione como se espera porque una configuración de prueba (p. ej., androidTestImplementation) extiende la configuración implementation del módulo. Es decir, siempre contiene dependencias implementation cuando Gradle determina la configuración.

Entonces, si quieres excluir dependencias transitivas de tus pruebas, deberás hacerlo en el tiempo de ejecución, como se muestra a continuación:

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'
}

Nota: De igual manera, puedes usar la palabra clave exclude en el bloque de dependencias como se muestra en el ejemplo de código original de la sección Cómo excluir dependencias transitivas a fin de omitir las que son específicas de la configuración de pruebas y que no se incluyen en otros parámetros de configuración.

Cómo corregir errores de selección de dependencias

Cuando agregas varias dependencias al proyecto de tu app, es posible que se genere un conflicto entre las dependencias directas y transitivas. El complemento de Android para Gradle intenta resolver esos conflictos de manera fluida, pero es posible que algunos generen errores de tiempo de ejecución o compilación.

Para investigar qué dependencias generan estos errores, inspecciona el árbol de dependencias de tu app y busca las dependencias que aparezcan más de una vez o tengan versiones conflictivas.

Si no puedes identificar fácilmente la dependencia duplicada, intenta usar la IU de Android Studio para buscar las dependencias que incluyan la clase duplicada de la siguiente manera:

  1. Selecciona Navigate > Class en la barra de menú.
  2. En el diálogo de búsqueda emergente, asegúrate de que la casilla junto a Include non-project items esté marcada.
  3. Escribe el nombre de la clase que aparece en el error de compilación.
  4. Inspecciona los resultados de las dependencias que incluyen esa clase.

En las siguientes secciones, se describen diferentes tipos de errores de selección de dependencias que podrías experimentar y cómo corregirlos.

Cómo corregir errores de clases duplicadas

Si una clase aparece más de una vez en la ruta de clase de tiempo de ejecución, es posible que se produzca un error similar al siguiente:

Program type already present com.example.MyClass

Generalmente, este error sucede debido a una de las siguientes circunstancias:

  • Una dependencia binaria contiene una biblioteca que tu app también incluye como dependencia directa. Por ejemplo, tu app declara que depende directamente de la Biblioteca A y de la Biblioteca B, pero la Biblioteca A ya incluye la Biblioteca B en su objeto binario.
    • Para resolver este problema, quita la Biblioteca B como dependencia directa.
  • Tu app tiene una dependencia binaria local y una dependencia binaria remota en la misma biblioteca.
    • Para resolver este problema, quita una de las dependencias binarias.

Cómo corregir conflictos entre rutas de clase

Cuando Gradle determina la ruta de clase de la compilación, primero especifica la ruta de clase de tiempo de ejecución y usa el resultado para establecer qué versiones de las dependencias deberían agregarse a la ruta de clase de la compilación. En otras palabras, la ruta de clase del tiempo de ejecución determina los números de versión que se requieren para dependencias idénticas en las rutas de clase descendientes.

El classpath de tiempo de ejecución de tu app también determina los números de versión que Gradle{}require a fin de hacer coincidir las dependencias en el classpath de tiempo de ejecución para el APK{}de prueba de tu app. La jerarquía de los classpaths se describe en la figura 1.

Figura 1: Los números de versión de las dependencias que aparecen en varias rutas de clase deben coincidir según esta jerarquía

Es posible que se produzca un conflicto en el que diferentes versiones de la misma dependencia aparecen en varias rutas de clase cuando, por ejemplo, tu app incluye una versión de una dependencia que usa la configuración de dependencia implementation y un módulo de biblioteca incluye una versión diferente de la dependencia que usa la configuración runtimeOnly.

Cuando determina las dependencias en las rutas de clase de tiempo de ejecución y compilación, el complemento de Gradle para Android 3.3.0 o una versión posterior intenta corregir automáticamente conflictos entre determinadas versiones descendentes. Por ejemplo, si la ruta de clase de tiempo de ejecución incluye una biblioteca A versión 2.0 y la de compilación incluye una biblioteca A versión 1.0, el complemento actualizará la dependencia automáticamente en la ruta de clase de compilación a la versión 2.0 de la biblioteca A para evitar errores.

Sin embargo, si la ruta de clase de tiempo de ejecución incluye la versión 1.0 de la Biblioteca A y la ruta de clase de la compilación incluye la versión 2.0 de la Biblioteca A, el complemento no pasará la dependencia en la ruta de clase de compilación a la versión 1.0 de la Biblioteca A, y aun así se producirá un error similar al siguiente:

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 solucionar este problema, realiza una de las siguientes acciones:

  • Incluye la versión deseada de la dependencia como una dependencia api en tu módulo de biblioteca. Es decir, solo tu módulo de biblioteca declarará la dependencia, pero el módulo de la app también tendrá acceso a su API de manera transitiva.
  • Como alternativa, puedes declarar la dependencia en ambos módulos, pero deberías asegurarte de que cada uno use la misma versión de la dependencia. Considera configurar propiedades en todo el proyecto a fin de garantizar que las versiones de cada dependencia mantengan la coherencia en el proyecto.