Saltar a contenido

AA5 · Ejercicios de rendimiento y publicación

Los ejercicios AA5.1, AA5.2, AA5.5 y AA5.6 se ejecutan de verdad: tu salida debe coincidir con el resultado esperado, obtenido ejecutando la solución modelo. Los AA5.3 (Gradle) y AA5.4 (Compose) no se pueden ejecutar aquí: compáralos con la solución modelo. El AA5.1 usa el runtime de Compose (androidx.compose.runtime). Los marcados con ⭐ son más difíciles.

Ejercicio AA5.1

Avisar solo cuando cambia. Escribe quedanPocas con derivedStateOf. Las unidades bajan de 9 a 0 (nueve cambios), pero quien lee quedanPocas solo debe enterarse dos veces: al empezar y cuando pasa a ser true.

import androidx.compose.runtime.*

class Almacen {
    var unidades by mutableStateOf(10)

    // true cuando quedan menos de 5 unidades. Debe avisar SOLO cuando cambie ese resultado.
    val quedanPocas: Boolean
        get() = TODO()
}

Para comprobarlo:

import androidx.compose.runtime.snapshots.Snapshot

fun main() = runBlocking {
    val almacen = Almacen()
    val avisos = mutableListOf<Boolean>()
    val job = launch(Dispatchers.Default) { snapshotFlow { almacen.quedanPocas }.collect { avisos += it } }
    delay(200)
    for (u in 9 downTo 0) {
        almacen.unidades = u
        Snapshot.sendApplyNotifications()
        delay(60)
    }
    job.cancel()
    println("avisos recibidos: $avisos")
}

Resultado esperado:

avisos recibidos: [false, true]
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.Snapshot
class Almacen {
    var unidades by mutableStateOf(10)
    val quedanPocas by derivedStateOf { unidades < 5 }
// ... (resto de la solución bloqueado)

Ejercicio AA5.2

El código de versión. Escribe versionCode: convierte mayor.menor.parche en mayor*10000 + menor*100 + parche y rechaza los formatos incorrectos.

// Convierte "2.13.7" en 21307 (mayor*10000 + menor*100 + parche).
// Si el texto no tiene tres números separados por puntos, lanza IllegalArgumentException.
fun versionCode(nombre: String): Int {
    // ...
}

Para comprobarlo:

fun main() {
    println(versionCode("2.13.7"))
    println(versionCode("0.1.0"))
    for (mala in listOf("1.2", "1.a.3")) {
        try { versionCode(mala) } catch (e: IllegalArgumentException) { println("«$mala» -> ${e.message}") }
    }
}

Resultado esperado:

21307
100
«1.2» -> formato esperado: mayor.menor.parche
«1.a.3» -> «a» no es un número
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

fun versionCode(nombre: String): Int {
    val partes = nombre.split(".").map { it.toIntOrNull() ?: throw IllegalArgumentException("«$it» no es un número") }
    require(partes.size == 3) { "formato esperado: mayor.menor.parche" }
    return partes[0] * 10_000 + partes[1] * 100 + partes[2]
}
// ... (resto de la solución bloqueado)

Ejercicio AA5.3

Escribe el bloque buildTypes { release { ... } } de build.gradle.kts para que la compilación release:

  • reduzca y ofusque el código con R8 y quite los recursos sin usar,
  • use el archivo de reglas por defecto proguard-android-optimize.txt y tu proguard-rules.pro,
  • se firme con la configuración release (signingConfigs),
  • no sea depurable.

Este ejercicio es de Gradle: no se puede ejecutar aquí y no tiene resultado esperado. Compara tu solución con la modelo.

Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

// Sin ejecutar: configuración de Gradle (Kotlin DSL)
buildTypes {
    release {
        isMinifyEnabled = true
        isShrinkResources = true
// ... (resto de la solución bloqueado)

Ejercicio AA5.4

Una lista con identidad. (Compose: no se puede ejecutar aquí.) Corrige ListaTareas para que cada fila se identifique por el id de la tarea y las de igual tipo reutilicen su estructura.

// Una lista de tareas (cada Tarea tiene id: Int y tipo: String). Hoy no usa key ni contentType,
// así que al insertar una tarea al principio Compose cree que todas han cambiado.
@Composable
fun ListaTareas(tareas: List<Tarea>) {
    LazyColumn {
        items(tareas) { tarea ->
            FilaTarea(tarea)
        }
    }
}
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

// Sin ejecutar: necesita el compilador de Compose
@Composable
fun ListaTareas(tareas: List<Tarea>) {
    LazyColumn {
        items(
// ... (resto de la solución bloqueado)

Ejercicio AA5.5

La versión siguiente. Escribe siguiente: según el tipo de cambio sube el parche, el menor (y el parche vuelve a 0) o el mayor (y los otros dos vuelven a 0).

data class Version(val mayor: Int, val menor: Int, val parche: Int) {
    override fun toString() = "$mayor.$menor.$parche"
}

enum class Cambio { PARCHE, MENOR, MAYOR }

// PARCHE: sube el parche. MENOR: sube el menor y pone el parche a 0.
// MAYOR: sube el mayor y pone menor y parche a 0.
fun siguiente(v: Version, cambio: Cambio): Version {
    // ...
}

Para comprobarlo:

fun main() {
    val v = Version(1, 4, 2)
    for (c in Cambio.entries) println("${c.name.padEnd(6)} -> ${siguiente(v, c)}")
}

Resultado esperado:

PARCHE -> 1.4.3
MENOR  -> 1.5.0
MAYOR  -> 2.0.0
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

data class Version(val mayor: Int, val menor: Int, val parche: Int) {
    override fun toString() = "$mayor.$menor.$parche"
}
enum class Cambio { PARCHE, MENOR, MAYOR }
fun siguiente(v: Version, cambio: Cambio): Version = when (cambio) {
// ... (resto de la solución bloqueado)

Ejercicio AA5.6

⭐ Lista de comprobación automática. Escribe problemas: devuelve los motivos por los que una compilación release no está lista para publicar, en el orden indicado.

data class ConfigRelease(
    val versionCode: Int,
    val ultimoPublicado: Int,
    val minificada: Boolean,
    val depurable: Boolean,
    val firmada: Boolean
)

// Devuelve la lista de problemas que impiden publicar (vacía si todo está bien):
//  - "versionCode no supera al publicado"  si versionCode <= ultimoPublicado
//  - "sin minificar"                       si no está minificada
//  - "es depurable"                        si es depurable
//  - "sin firmar"                          si no está firmada
fun problemas(c: ConfigRelease): List<String> {
    // ...
}

Para comprobarlo:

fun main() {
    val buena = ConfigRelease(10402, 10401, minificada = true, depurable = false, firmada = true)
    val mala = ConfigRelease(10401, 10401, minificada = false, depurable = true, firmada = false)
    println("buena: ${problemas(buena)}")
    println("mala:  ${problemas(mala)}")
}

Resultado esperado:

buena: []
mala:  [versionCode no supera al publicado, sin minificar, es depurable, sin firmar]
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

data class ConfigRelease(
    val versionCode: Int,
    val ultimoPublicado: Int,
    val minificada: Boolean,
    val depurable: Boolean,
// ... (resto de la solución bloqueado)