Saltar al contenido
K
Entorno y primer proyectoPaso a paso40 min de lectura · 20 min de máquina Intermedio

El módulo compartido y la prueba de humo

Al terminar: Dejar el módulo compartido con su saludo y su primera prueba, y ejecutarla en los tres destinos.

Una prueba de humo es la comprobación más pequeña posible de que todo el montaje funciona: enciendes la máquina y miras si sale humo. Aquí el montaje es el proyecto entero, y la prueba es un saludo («Hola desde Android…») que se escribe una sola vez, en el módulo compartido, y llega a cada plataforma. Si aparece, el código común está llegando a todas partes.

Al terminar, el módulo compartido tendrá sus destinos del curso (Android, iOS y la JVM), un saludo que dice en qué plataforma corre, y un test que se ejecuta en las tres. Y la app de Android lo mostrará en pantalla (en la lección siguiente, Ejecutar en Android, la ejecutas en el emulador). Aviso de tiempo: la primera vez que corra el test de iOS, tu Mac descarga el compilador de Kotlin/Native (unos 300 MB que ocupan casi 2 GB descomprimidos) y esa espera es larga; lo verás en el último paso.

Paso 1 · Tres cambios en el módulo compartido

Objetivo: que shared/build.gradle.kts declare Android, la JVM y solo dos targets de iOS, y que Sync Now termine sin rojo.

Abre shared/build.gradle.kts (en la vista Project, despliega la carpeta shared y haz doble clic en build.gradle.kts). No lo sustituyas entero: cada versión del asistente escribe el suyo, así que haces tres cambios pequeños sobre el que tienes y comparas con el bloque de referencia. Primero el bloque de referencia, para saber adónde vas; las instrucciones de cada cambio van justo debajo.

Antes de leerlo, dos recordatorios sobre cómo se lee.

Cómo queda el fichero cuando los tres cambios están hechos. No lo pegues: compáralo con el tuyo, línea a línea (cada una lleva su comentario). Si el tuyo trae algo de más, déjalo.

shared: cómo queda build.gradle.kts (para comparar)
plugins {
    // Los dos plugins del módulo: el de Kotlin Multiplatform y el que permite
    // el bloque `android {}` dentro de `kotlin {}` (un módulo Android que es librería).
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidKmpLibrary)
}
 
kotlin {
    // La JVM no es una plataforma de producto: está para que los tests corran
    // en segundos, sin emulador ni simulador de por medio.
    jvm()
 
    // `android {}` dentro de `kotlin {}` es el bloque del plugin KMP de AGP.
    // Sustituye a `androidLibrary {}`, obsoleto desde AGP 9.1.
    android {
        namespace = "com.sebastiannarvaez.pokedex.shared"
        // `libs.versions.androidCompileSdk` es el número de `[versions]` del catálogo,
        // pero como texto ("37"). `.get()` lo lee y `.toInt()` lo convierte en el
        // entero que pide `compileSdk`.
        compileSdk = libs.versions.androidCompileSdk.get().toInt()
        minSdk = libs.versions.androidMinSdk.get().toInt()
 
        // Habilita el source set de tests que corren en la máquina, no en un
        // dispositivo: es el equivalente de `test` en un módulo Android normal.
        withHostTest {}
    }
 
    // Sin iosX64: el simulador de los Mac con Intel se queda fuera. Es una
    // decisión, no un descuido: ya lo viste en Qué es KMP y lo comprobarás
    // en la lección de persistencia.
    listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "Shared"
            // Estático: Xcode solo tiene que enlazarlo (unirlo a la app al compilarla).
            // Un framework dinámico habría que incrustarlo (copiarlo dentro de la
            // app) y firmarlo en cada compilación.
            isStatic = true
        }
    }
 
    sourceSets {
        commonTest.dependencies {
            // El atajo `kotlin("test")` se explica justo debajo.
            implementation(kotlin("test"))
        }
    }
}

Ahora, los tres cambios sobre tu fichero:

Cambio 1 · El bloque de Android está obsoleto. Si el asistente escribió androidLibrary { } dentro de kotlin { }, ese bloque ya no es el recomendado: se sustituye por android { }. (AGP es el Android Gradle Plugin, el que compila las apps de Android.) Qué hacer: cambia la palabra androidLibrary por android, conserva dentro tu namespace, compileSdk y minSdk, y añade la línea withHostTest {} (activa los tests que corren en tu Mac, sobre la JVM, en vez de en un emulador). Si ya dice android { }, solo comprueba que lleva withHostTest {}.

Si algún ejemplo de internet aún usa androidLibrary { }, haz el mismo cambio: la documentación va por detrás de las versiones.

Cambio 2 · Falta la JVM, que es donde vas a correr los tests. El asistente declara Android e iOS. Añadimos un target jvm() (un destino que compila para la máquina virtual de Java en tu Mac). No llega a ninguna tienda: existe para que los tests de la JVM terminen en un segundo, sin emulador ni simulador. Qué hacer: escribe jvm() como primera línea dentro de kotlin { }.

Cambio 3 · Sobra iosX64(). El asistente declara tres targets de iOS: iosArm64 (iPhone), iosSimulatorArm64 (simulador en un Mac con chip de Apple, M1 o posterior) e iosX64 (simulador en un Mac con procesador Intel). Quitamos el último. El motivo llega en la lección La base en cada plataforma: Room 3 no publica iosX64. Si lo dejas ahora, el proyecto compila hoy y se rompe cuando añadas la base de datos, y el error no dirá que sobra un target. Mejor quitarlo aquí y saber por qué. Qué hacer: borra iosX64() (o sácalo de la lista) y comprueba que quedan solo iosArm64() e iosSimulatorArm64(). Si no ves iosX64(), no hay nada que quitar. Si tu fichero escribe los targets sueltos (iosArm64(), iosSimulatorArm64()… cada uno con su bloque binaries.framework { }), borra también el bloque del iosX64(); dentro de binaries.framework { } deja baseName = "Shared" (es el nombre que Swift escribe en import Shared) e isStatic = true.

Si tu bloque plugins usa otros nombres de plugin (los alias que apuntaste del catálogo en la lección anterior), deja los tuyos.

Una aclaración sobre el bloque sourceSets, y una más sobre la carpeta de los tests:

  • kotlin("test") y el catálogo. En Marcador instalaste kotlin-test por el catálogo (libs.kotlin.test), con su versión escrita en un solo sitio. Aquí el asistente usa kotlin("test"), el atajo del plugin de Kotlin: es la misma librería, con la versión del compilador, y en un módulo multiplataforma es la forma habitual porque Gradle escoge solo la variante adecuada de cada destino (JVM, Android, iOS). Déjalo como está; si tu bloque ya trae implementation(libs.kotlin.test), también vale. Todas las librerías de verdad que añadas desde La primera dependencia sí pasan por el catálogo.
  • La carpeta de los tests de withHostTest {} se llama androidHostTest: shared/src/androidHostTest/kotlin. En un módulo Android corriente se llamaba test; aquí lleva «Host» (anfitrión) porque corren en tu Mac, no en un dispositivo. Ya existe: la creó el asistente. No pongas nada en ella: el test de esta lección vive en commonTest y corre también en ese destino.

Pulsa Sync Now otra vez. Debe terminar sin rojo; no compiles todavía.

Paso 2 · La prueba de humo

Objetivo: que shared tenga el saludo y su test, que la app de Android lo muestre y que compilen androidApp y los tests de las tres plataformas.

El asistente deja una clase que saluda (Greeting) y una Platform, y no siempre con la misma forma. No importa cuál sea: borra lo que haya y deja estos cuatro ficheros de Platform, uno por carpeta (más Greeting y su test). El resultado es el mismo para todos: Platform es una interfaz y la función expect se llama currentPlatform(). Antes de escribir, recuerda lo que viste en Qué es KMP y qué no: expect promete y actual cumple. En commonMain se declara la promesa (expect fun currentPlatform()) y cada plataforma escribe su cumplimiento (actual fun currentPlatform()) en su propia carpeta. Los cuatro ficheros de Platform son esa promesa más un cumplimiento por cada plataforma: commonMain, androidMain, iosMain y jvmMain (cada nombre es una carpeta de shared/src, un source set, como viste en Qué es KMP y qué no).

Cómo se crea un fichero en este curso. Para cada fichero de abajo:

  1. En la vista Project, clic derecho sobre la carpeta shared → New → File.
  2. Escribe la ruta completa desde src/, por ejemplo src/commonMain/kotlin/com/sebastiannarvaez/pokedex/Platform.kt. Las carpetas que falten se crean solas.
  3. Pega el contenido: Cmd+A para seleccionar todo lo que hubiera y Cmd+V. Si el fichero ya existía (el asistente crea algunos con estos nombres), ábrelo, selecciona todo y pega encima.

Plan B, desde la Terminal de Android Studio (View → Tool Windows → Terminal, abierta en la raíz del proyecto): mkdir -p crea una carpeta con todas las intermedias y touch crea un fichero vacío, y el fichero aparece en el panel unos segundos después. Por ejemplo, para el de la JVM:

mkdir -p shared/src/jvmMain/kotlin/com/sebastiannarvaez/pokedex
touch shared/src/jvmMain/kotlin/com/sebastiannarvaez/pokedex/Platform.jvm.kt

Si tu asistente dejó en shared ficheros con otros nombres (por ejemplo Platform.android.kt con getPlatform(), o un Greeting distinto), sustituye su contenido por los de abajo o bórralos (clic derecho sobre el fichero → Delete; Android Studio puede mostrar un diálogo Safe Delete que avisa de dónde se usa: confirma). Al final solo debe haber una función expect, currentPlatform(), con su actual en cada plataforma.

Mira también shared/src/androidHostTest y shared/src/iosTest: ya existen, las creó el asistente. No les añadas nada, pero si traen un test que usa getPlatform() o el Greeting antiguo, bórralo (ese código ya no existirá). Lo mismo con cualquier otro fichero .kt de androidApp (por ejemplo un App.kt) que use lo que has borrado: bórralo o quita la línea. iosApp no se toca.

shared/src/
commonMain/kotlin/…/Platform.ktla promesa (expect)
commonMain/kotlin/…/Greeting.ktel saludo
androidMain/kotlin/…/Platform.android.ktcumple para Android
iosMain/kotlin/…/Platform.ios.ktcumple para iOS
jvmMain/kotlin/…/Platform.jvm.ktcumple para la JVM (carpeta nueva)
commonTest/kotlin/…/GreetingTest.ktun test, tres ejecuciones
androidApp/src/main/kotlin/…/android/
MainActivity.ktpinta el saludo
Lo que dejas en este paso. Son rutas del disco, dentro de ~/Pokedex-KMP.

Primero la promesa y el cumplimiento de cada plataforma:

shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/Platform.kt
package com.sebastiannarvaez.pokedex
 
/**
 * El nombre y la version del sistema donde corre este codigo.
 *
 * Es una interfaz y no una `expect class` a proposito: JetBrains desaconseja
 * las clases `expect` donde basta una interfaz, porque una interfaz se puede
 * falsear en los tests y una `expect class` no.
 */
interface Platform {
    val name: String
}
 
/** Cada plataforma dice quien es. Lo unico que hace falta declarar dos veces. */
expect fun currentPlatform(): Platform
shared/src/androidMain/kotlin/com/sebastiannarvaez/pokedex/Platform.android.kt
package com.sebastiannarvaez.pokedex
 
import android.os.Build
 
private class AndroidPlatform : Platform {
    override val name: String = "Android ${Build.VERSION.SDK_INT}"
}
 
actual fun currentPlatform(): Platform = AndroidPlatform()
shared/src/iosMain/kotlin/com/sebastiannarvaez/pokedex/Platform.ios.kt
package com.sebastiannarvaez.pokedex
 
import platform.UIKit.UIDevice
 
private class IosPlatform : Platform {
    override val name: String =
        UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
 
actual fun currentPlatform(): Platform = IosPlatform()
shared/src/jvmMain/kotlin/com/sebastiannarvaez/pokedex/Platform.jvm.kt
package com.sebastiannarvaez.pokedex
 
private class JvmPlatform : Platform {
    override val name: String = "JVM ${System.getProperty("java.version")}"
}
 
actual fun currentPlatform(): Platform = JvmPlatform()

Qué hace cada cumplimiento, en una línea:

  • Android: Build.VERSION.SDK_INT es el número de versión de Android del móvil o del emulador (su «nivel de API»; 35 es Android 15). La clase es private: solo la ve este fichero, y fuera se usa a través de currentPlatform().
  • iOS: UIDevice es la clase de Apple que describe el dispositivo, y se llama desde Kotlin tal cual. systemName() es una función, con paréntesis, y systemVersion una propiedad, sin ellos: así las expone Kotlin/Native.
  • JVM: System.getProperty("java.version") pregunta a Java por su propia versión, y por eso en el saludo sale la de tu Mac.

jvmMain aún no existe como carpeta: la forma de crear ficheros de arriba la crea al crear el fichero. Después, Greeting deja de construir una Platform y llama a la función, y el test comprueba la forma del saludo:

shared/src/commonMain/kotlin/com/sebastiannarvaez/pokedex/Greeting.kt
package com.sebastiannarvaez.pokedex
 
/**
 * La prueba de humo del proyecto: si esta frase aparece en las dos pantallas,
 * el codigo compartido esta llegando a Android y a iOS.
 */
class Greeting {
    private val platform = currentPlatform()
 
    fun greet(): String = "Hola desde ${platform.name}"
}
shared/src/commonTest/kotlin/com/sebastiannarvaez/pokedex/GreetingTest.kt
package com.sebastiannarvaez.pokedex
 
import kotlin.test.Test
import kotlin.test.assertTrue
 
class GreetingTest {
 
    @Test
    fun elSaludoNombraLaPlataforma() {
        // El mismo test corre en la JVM, en los tests de Android que corren en tu Mac
        // y en el simulador de iOS, y en cada uno el nombre es distinto. Por eso se comprueba la forma, no el texto.
        val saludo = Greeting().greet()
        assertTrue(saludo.startsWith("Hola desde "), "saludo inesperado: $saludo")
        assertTrue(saludo.length > "Hola desde ".length, "falta el nombre de la plataforma")
    }
}

El test hace dos comprobaciones: que el saludo empieza por «Hola desde » y que detrás hay algo, el nombre de la plataforma. Si tu asistente dejó un test de ejemplo propio en commonTest, bórralo para que tus cuentas coincidan con las de la lección.

Por qué se comprueba la forma y no el texto: el saludo cambia con cada destino. Si imprimieras Greeting().greet() en cada uno, saldría algo así (los números dependen de tu simulador, tu emulador y tu Java):

Hola desde iOS 26.2        (el simulador de iPhone)
Hola desde Android 35      (la app en un emulador con Android 15)
Hola desde Android 0       (los tests de Android en tu Mac: no hay un Android debajo)
Hola desde JVM 17.0.12     (los tests en la JVM de tu Mac)

expect y actual son pareja: en el margen izquierdo del editor, junto a los números de línea, el IDE suele poner un icono en cada expect y en cada actual que lleva a la pareja. Por qué una interfaz y no una expect class es la lección expect/actual frente a interfaces. De momento quédate con que el proyecto arranca ya con la forma buena.

Falta que la app de Android muestre el saludo. La pantalla está hecha con Compose, que viste en Compose: primeros pasos.

Antes de pegar nada, comprueba el namespace de androidApp. Todo lo que sigue (la ejecución en el emulador y las rutas de las lecciones siguientes) cuenta con que MainActivity vive en el paquete que acaba en .android. Abre androidApp/build.gradle.kts y compara solo el namespace con el de abajo (el resto puede traer bloques de más, como compileOptions o packaging: déjalos):

androidApp: cómo queda build.gradle.kts (para comparar)
plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.composeCompiler)
}
 
android {
    // El paquete donde vive el código de la app; acaba en `.android`.
    namespace = "com.sebastiannarvaez.pokedex.android"
    compileSdk = libs.versions.androidCompileSdk.get().toInt()
 
    defaultConfig {
        // La identidad de la app en el móvil; es el paquete que escribiste en el formulario
        // del paso 2 de la lección anterior.
        applicationId = "com.sebastiannarvaez.pokedex"
        minSdk = libs.versions.androidMinSdk.get().toInt()
        targetSdk = libs.versions.androidTargetSdk.get().toInt()
        versionCode = 1
        versionName = "0.1.0"
    }
 
    buildFeatures {
        compose = true
    }
}
 
dependencies {
    // El módulo compartido: lo que escribes en `shared` lo usa esta app.
    implementation(projects.shared)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.compose.material3)
}

Si tu namespace ya acaba en .android y MainActivity.kt está en la carpeta …/pokedex/android, sigue. Si no, arréglalo en dos pasos:

  1. En namespace, escribe com.sebastiannarvaez.pokedex.android.
  2. Mueve MainActivity.kt a ese paquete: en la vista Project, clic derecho sobre la carpeta com/sebastiannarvaez/pokedex (dentro de androidApp/src/main/kotlin/) → New → Package, escribe solo android y confirma (o clic derecho sobre la carpeta kotlin y escribe el paquete completo); arrastra MainActivity al paquete nuevo y acepta Refactor en el aviso que sale. El manifiesto del proyecto la nombra como .MainActivity (relativa al namespace), así que, con las dos cosas hechas, la encuentra sin tocar nada más.

Abre MainActivity.kt (en la vista Project, dentro de androidApp/src/main/kotlin/, en la carpeta del paquete que acaba en .android) y sustituye todo su contenido por este. Es el único sitio donde se muestra entera: las lecciones siguientes dirán qué cambiarle.

androidApp/src/main/kotlin/com/sebastiannarvaez/pokedex/android/MainActivity.kt
package com.sebastiannarvaez.pokedex.android
 
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.sebastiannarvaez.pokedex.Greeting
 
class MainActivity : ComponentActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { padding ->
                    Box(
                        modifier = Modifier.fillMaxSize().padding(padding),
                        contentAlignment = Alignment.Center,
                    ) {
                        // El texto viene del modulo compartido: es la prueba de
                        // que Android esta consumiendo Kotlin comun.
                        Saludo(texto = Greeting().greet())
                    }
                }
            }
        }
    }
}
 
@Composable
private fun Saludo(texto: String, modifier: Modifier = Modifier) {
    Text(text = texto, style = MaterialTheme.typography.titleLarge, modifier = modifier)
}
 
// La vista previa recibe el texto ya hecho, no el objeto compartido: asi se
// puede pintar en el IDE sin ejecutar nada del modulo shared.
@Preview(showBackground = true)
@Composable
private fun SaludoPreview() {
    MaterialTheme { Saludo(texto = "Hola desde Android 35") }
}

Qué hace, en cuatro ideas: MainActivity es la puerta de entrada de la app (como en Marcador); setContent dibuja con Compose; el texto sale de Greeting().greet(), que es código del módulo shared; y Saludo es un pequeño composable que lo pinta. Cuatro detalles de la sintaxis:

  • Scaffold(…) { padding -> … } es una lambda que recibe padding: el hueco que el Scaffold reserva para barras del sistema, que el Box aplica con .padding(padding) para que el texto no quede debajo de ellas.
  • contentAlignment = Alignment.Center coloca lo que hay dentro del Box en el centro.
  • modifier: Modifier = Modifier es un parámetro con valor por defecto: quien llame a Saludo puede pasar un modificador, o nada.
  • style = MaterialTheme.typography.titleLarge pide el estilo de letra «título grande» del tema.

La vista previa pinta un texto de ejemplo fijo («Android 35»): en el emulador saldrá el número real del tuyo.

Un detalle del orden: en La Activity, super.onCreate(…) iba primero, y aquí enableEdgeToEdge() va antes. Lo que importa es que enableEdgeToEdge() vaya antes de setContent: solo configura la ventana para que la app se dibuje bajo las barras de estado y de navegación.

Para que MainActivity compile, el build.gradle.kts de androidApp tiene que depender del módulo compartido y de Compose (material3, ui-tooling-preview, activity-compose). El asistente ya las declara; si al compilar Gradle dice Unresolved reference, falta una dependencia. La palabra que sigue al error te dice cuál:

La palabra del errorQué falta
Greetingimplementation(projects.shared) en androidApp/build.gradle.kts
Scaffold, Text o MaterialThemeimplementation(libs.androidx.compose.material3)
Previewimplementation(libs.androidx.compose.ui.tooling.preview)

Una dependencia se añade así, en tres pasos (el ejemplo es material3):

  1. En el catálogo (gradle/libs.versions.toml), en [libraries], una línea con el nombre y las coordenadas de la librería: androidx-compose-material3 = { module = "androidx.compose.material3:material3" } (sin versión: la fija el BOM, el composeBom del catálogo, una lista de versiones de Compose que encajan entre sí; lo viste en La Activity). module = "grupo:artefacto" (el artefacto es el nombre de la librería dentro de su grupo) es la forma corta de la que escribiste en Marcador, { group = "…", name = "…" }: el grupo y el nombre unidos por dos puntos.
  2. En androidApp/build.gradle.kts, dentro de dependencies { }: implementation(libs.androidx.compose.material3). Los guiones del catálogo se vuelven puntos.
  3. Sync Now.

Y ahora compila. Abre la Terminal de Android Studio (View → Tool Windows → Terminal) y escribe:

./gradlew :androidApp:assembleDebug
BUILD SUCCESSFUL in 34s

La primera vez tarda más porque Gradle descarga lo que falta; el tiempo exacto varía de un ordenador a otro.

Los tests, que ya corren en las tres plataformas:

./gradlew :shared:jvmTest :shared:testAndroidHostTest :shared:iosSimulatorArm64Test
BUILD SUCCESSFUL in 8s

La primera vez que ejecutas iosSimulatorArm64Test tarda mucho más (varios minutos) y casi no da señales: Gradle descarga el compilador de Kotlin/Native (unos 300 MB que ocupan casi 2 GB descomprimidos) y lo deja en la carpeta .konan de tu carpeta personal. Se hace una sola vez y es normal; los 8 segundos de arriba son lo que tarda después. También hace falta Xcode instalado con un simulador, que dejaste listo antes.

Ese único test comprueba la forma del saludo, no el texto, porque el texto es distinto en cada plataforma. Es la primera vez que ves lo que hace especial a commonTest: un test, tres ejecuciones.

Las tareas salen de los targets. Fíjate en los nombres de la orden de arriba: jvmTest, testAndroidHostTest e iosSimulatorArm64Test son uno por cada destino, y existen porque el destino está declarado en shared/build.gradle.kts. Si quitaras iosSimulatorArm64() de la lista, esa tarea dejaría de existir y Gradle no diría «el target está desactivado», sino:

* What went wrong:
Selection failed
  Cannot locate tasks that match ':shared:iosSimulatorArm64Test' as task 'iosSimulatorArm64Test' not found in project ':shared'.

El mensaje habla de tareas y el problema es de targets. En KMP las tareas se generan a partir de los targets declarados: si quitas un target, desaparecen de golpe su tarea de compilación, la de enlazado, la de tests y la del framework. Es el mismo mecanismo que hace inofensivo quitar iosX64() hoy, y que dejarlo puesto rompería el proyecto en La base en cada plataforma: Gradle no encontraría la librería de base de datos para ese target. No hace falta que lo pruebes: guárdalo para cuando un mensaje así te salga sin haber tocado nada.

Punto de control

  • ./gradlew :androidApp:assembleDebug termina en BUILD SUCCESSFUL.
  • ./gradlew :shared:jvmTest :shared:testAndroidHostTest :shared:iosSimulatorArm64Test termina en BUILD SUCCESSFUL, sin ninguna línea con FAILED: el mismo test (GreetingTest) se ejecuta una vez en cada destino.
  • En shared/build.gradle.kts hay jvm(), android { … withHostTest {} } y solo dos targets de iOS.
  • En androidApp/build.gradle.kts, el namespace acaba en .android, y MainActivity está en ese paquete.
  • shared tiene los cuatro ficheros Platform, Greeting y GreetingTest.
  • No has intentado compilar iOS desde Xcode todavía.

Comprueba lo que entendiste

5 preguntas · necesitas 4 para completar la lección

  1. 1.Un ejemplo antiguo escribe androidLibrary { } dentro de kotlin { } en el módulo compartido. Con AGP 9.4, ¿qué haces?

  2. 2.¿Para qué sirve el target jvm() en un proyecto que solo publica apps de Android e iOS?

  3. 3.Quitas iosSimulatorArm64() de shared/build.gradle.kts y pides :shared:iosSimulatorArm64Test. ¿Qué pasa?

  4. 4.¿Por qué la guía quita iosX64() nada más generar el proyecto, mucho antes de tocar la base de datos?

  5. 5.¿Por qué GreetingTest comprueba la forma del saludo («Hola desde » seguido de algo) y no el texto completo?

Faltan 5 de 5