implementation vs api

La diferencia importa sobre todo en módulos librería. implementation mantiene la dependencia encapsulada; api la expone hacia módulos consumidores.

dependencies {
    implementation(libs.coroutines.core)
    api(project(":core:model"))
}

La regla general es simple: usá implementation por defecto. Solo usá api cuando otro módulo realmente necesite ver ese tipo en su superficie pública.

Otras configurations

  • compileOnly: disponible al compilar, pero no empaquetada.
  • runtimeOnly: necesaria al ejecutar, no al compilar.
  • testImplementation: solo para unit tests.
  • androidTestImplementation: solo para tests instrumentados.
  • ksp o kapt: procesadores de código.

Idea importanteElegir mal una configuration no siempre rompe la app de inmediato, pero sí puede ensuciar la API de módulos, aumentar acoplamiento y afectar recompilaciones innecesarias.

Dependencias transitivas

Cuando una dependencia trae otras por detrás, aparecen las transitivas. Eso simplifica, pero a veces mete más de lo que querés.

implementation("com.squareup.retrofit2:retrofit:2.11.0") {
    exclude(group = "org.jetbrains", module = "annotations")
}

No conviene abusar de exclusiones, pero sí entender qué entra al classpath para no terminar con conflictos raros.

Criterios reales para elegir bien

  • Usá implementation salvo motivo concreto para exponer.
  • Reservá api para módulos que forman parte de una API compartida.
  • Mantené separadas las dependencias de test.
  • Evitá meter librerías enormes en módulos base si solo una feature las usa.

AtenciónUn anti-pattern común es poner casi todo en el módulo app “porque funciona”. Eso te deja sin límites claros y vuelve más difícil modularizar después.

Cierre

Entender configurations te da control sobre acoplamiento, recompilación y mantenimiento. En la próxima lección vemos cómo esas decisiones cambian cuando el proyecto crece y se organiza en múltiples módulos.