Tema 1A. Kotlin para Android — Repaso acelerado

  • Bloque: B1 — Fundamentos: Kotlin, Compose y entorno Android
  • Duración aproximada: 4 horas
  • RA1 — Aplica tecnologías de desarrollo para dispositivos móviles evaluando sus características y capacidades.
Código Criterio
RA1-b Se han identificado las tecnologías de desarrollo de aplicaciones para dispositivos móviles.
RA1-f Se ha analizado la estructura de aplicaciones existentes para dispositivos móviles identificando las clases utilizadas.
RA1-g Se han realizado modificaciones sobre aplicaciones existentes.

Introducción#

Kotlin es el lenguaje oficial de Android desde 2017 y el único recomendado por Google para el desarrollo de aplicaciones modernas con Jetpack Compose. Esta sección asume que ya conoces los fundamentos de programación orientada a objetos, idealmente en Java, y se centra en los aspectos de Kotlin que son distintos, más potentes o especialmente relevantes para el desarrollo Android.

¿Por qué Kotlin y no Java? Kotlin elimina gran parte del código repetitivo (boilerplate), incorpora seguridad frente a nulos en el sistema de tipos, y su integración con Compose es nativa. Kotlin es 100 % interoperable con Java, lo que significa que puedes usar cualquier librería Java desde Kotlin sin adaptadores.

Si quieres profundizar en alguno de los conceptos presentados aquí, consulta el Anexo B1-A1 — Kotlin Referencia Completa, donde cada apartado está desarrollado con mayor detalle y más ejemplos.


1. Variables y tipos básicos#

En Kotlin no se declaran tipos primitivos: todo es un objeto. El compilador infiere el tipo automáticamente en la mayoría de los casos.

1// val → inmutable (equivalente a final en Java). Preferir siempre que sea posible.
2val nombre: String = "AppFlix"
3val version = 1              // El compilador infiere Int
4
5// var → mutable. Usar solo cuando el valor deba cambiar.
6var contador = 0
7contador = contador + 1      // ✅ permitido
8// nombre = "Otra app"       // ❌ no compila: val no se puede reasignar

Regla práctica: Declara siempre con val. Cambia a var solo cuando el compilador te lo exija o la lógica lo requiera. Esto produce código más predecible y seguro.


2. Null safety#

El error más frecuente en Java es el NullPointerException. Kotlin lo previene en tiempo de compilación: por defecto, ninguna variable puede ser null.

 1// Por defecto, ningún tipo acepta null
 2var titulo: String = "Inception"
 3// titulo = null  // ❌ no compila
 4
 5// Para permitir null, se añade ? al tipo
 6var descripcion: String? = null
 7descripcion = "Un thriller de ciencia ficción"
 8
 9// Acceso seguro con ?. → no falla si es null, devuelve null
10val longitud = descripcion?.length   // Int? — puede ser null
11
12// Operador Elvis ?: → valor por defecto si la expresión es null
13val longitudSegura = descripcion?.length ?: 0   // Int — nunca null
14
15// Acceso no seguro con !! → lanza excepción si es null (evitar siempre que sea posible)
16val longitudForzada = descripcion!!.length   // ⚠️ usar solo si estás 100% seguro

El operador ?. es especialmente útil al encadenar llamadas. Si algún eslabón de la cadena es null, la expresión completa devuelve null sin lanzar excepción:

1data class Director(val nombre: String?)
2data class Pelicula(val titulo: String, val director: Director?)
3
4val pelicula: Pelicula? = Pelicula("Interstellar", Director("Christopher Nolan"))
5
6// Acceso encadenado: si algún elemento es null, el resultado es null
7val nombreDirector = pelicula?.director?.nombre ?: "Desconocido"
8println(nombreDirector)  // Christopher Nolan

3. Funciones#

Kotlin permite declarar funciones de nivel superior (fuera de cualquier clase), como en Python. Esto es muy habitual en Compose.

 1// Función con tipo de retorno explícito
 2fun saludar(nombre: String): String {
 3    return "Hola, $nombre"
 4}
 5
 6// Versión compacta con expresión (el return es implícito)
 7fun saludar(nombre: String): String = "Hola, $nombre"
 8
 9// Parámetros con valores por defecto
10fun crearTitulo(texto: String, mayusculas: Boolean = false): String {
11    return if (mayusculas) texto.uppercase() else texto
12}
13
14crearTitulo("appflix")           // "appflix"
15crearTitulo("appflix", true)     // "APPFLIX"
16
17// Argumentos con nombre (mejoran la legibilidad, muy usados en Compose)
18crearTitulo(texto = "appflix", mayusculas = true)

Lambdas y funciones de orden superior#

Una lambda es una función anónima que puede pasarse como argumento. Son fundamentales en Kotlin y en Compose.

 1// Sintaxis: { parámetros -> cuerpo }
 2val duplicar: (Int) -> Int = { numero -> numero * 2 }
 3println(duplicar(5))  // 10
 4
 5// Cuando hay un solo parámetro, se puede usar 'it' como nombre implícito
 6val duplicar2: (Int) -> Int = { it * 2 }
 7
 8// Función de orden superior: recibe una función como parámetro
 9fun aplicar(valor: Int, operacion: (Int) -> Int): Int = operacion(valor)
10
11aplicar(5) { it * 2 }      // 10  — sintaxis lambda trailing (fuera de paréntesis)
12aplicar(5) { it + 10 }     // 15

En Compose encontrarás constantemente el patrón onClick: () -> Unit, que es precisamente una función lambda que no recibe parámetros ni devuelve nada.


4. Clases esenciales para Android#

4.1 Clases regulares#

 1// Constructor primario en la propia cabecera de la clase
 2class Pelicula(
 3    val titulo: String,
 4    val anyo: Int,
 5    var puntuacion: Double = 0.0   // parámetro con valor por defecto
 6) {
 7    // Bloque init: se ejecuta al crear una instancia
 8    init {
 9        require(anyo > 1888) { "El año debe ser posterior a 1888" }
10    }
11
12    fun descripcion(): String = "$titulo ($anyo) — ★ $puntuacion"
13}
14
15val pelicula = Pelicula("Dune", 2021, 8.0)
16println(pelicula.descripcion())  // Dune (2021) — ★ 8.0

4.2 Data class#

Las data class están diseñadas para modelar datos. El compilador genera automáticamente equals(), hashCode(), toString() y copy().

 1data class Pelicula(
 2    val id: Int,
 3    val titulo: String,
 4    val puntuacion: Double
 5)
 6
 7val p1 = Pelicula(1, "Dune", 8.0)
 8val p2 = p1.copy(puntuacion = 9.0)   // copia con un campo modificado
 9
10println(p1)          // Pelicula(id=1, titulo=Dune, puntuacion=8.0)
11println(p1 == p2)    // false — compara por valor, no por referencia

Las data class son el tipo de clase más usado para representar los modelos de datos en la arquitectura MVVM: películas, usuarios, productos, etc.

4.3 Sealed class#

Una sealed class define una jerarquía cerrada: el compilador conoce en tiempo de compilación todas sus subclases. Esto permite usar when sin else y sin perder ningún caso.

 1// Representa los posibles estados de la UI
 2sealed class EstadoUI {
 3    data object Cargando : EstadoUI()
 4    data class Exito(val peliculas: List<Pelicula>) : EstadoUI()
 5    data class Error(val mensaje: String) : EstadoUI()
 6}
 7
 8fun mostrarEstado(estado: EstadoUI) {
 9    when (estado) {
10        is EstadoUI.Cargando -> println("Cargando...")
11        is EstadoUI.Exito   -> println("${estado.peliculas.size} películas cargadas")
12        is EstadoUI.Error   -> println("Error: ${estado.mensaje}")
13        // No necesita 'else' — el compilador sabe que no hay más casos
14    }
15}

Las sealed class son la herramienta perfecta para representar el estado de la UI en arquitectura MVVM, como verás en el Bloque 2.

4.4 Object y companion object#

object crea un singleton: una clase con una única instancia gestionada por Kotlin.

1// Singleton: una sola instancia en toda la app
2object ConfiguracionApp {
3    const val VERSION = "1.0"
4    const val NOMBRE = "AppFlix"
5
6    fun mostrarInfo() = println("$NOMBRE v$VERSION")
7}
8
9ConfiguracionApp.mostrarInfo()   // AppFlix v1.0

companion object es el equivalente a los miembros static de Java, dentro de una clase:

 1class ConexionBD private constructor() {
 2    companion object {
 3        // Constante accesible sin instanciar la clase
 4        const val NOMBRE_BD = "appflix_db"
 5
 6        // Factory method — patrón habitual para crear instancias controladas
 7        fun crear(): ConexionBD = ConexionBD()
 8    }
 9}
10
11val bd = ConexionBD.crear()
12println(ConexionBD.NOMBRE_BD)   // appflix_db

5. Colecciones y operaciones funcionales#

Kotlin distingue entre colecciones inmutables (no se pueden modificar) y mutables.

1// Inmutables — uso preferido
2val peliculas: List<String>     = listOf("Dune", "Inception", "Interstellar")
3val generos: Map<String, Int>   = mapOf("Acción" to 5, "Drama" to 3)
4val directores: Set<String>     = setOf("Nolan", "Villeneuve")
5
6// Mutables — solo cuando necesitas añadir, eliminar o modificar
7val favoritas: MutableList<String> = mutableListOf("Dune")
8favoritas.add("Interstellar")

Las operaciones funcionales permiten transformar colecciones de forma concisa:

 1val peliculas = listOf("Dune", "Inception", "Interstellar", "Dune: Parte 2")
 2
 3// filter → devuelve los elementos que cumplen la condición
 4val peliculasDune = peliculas.filter { it.startsWith("Dune") }
 5// ["Dune", "Dune: Parte 2"]
 6
 7// map → transforma cada elemento
 8val longitudes = peliculas.map { it.length }
 9// [4, 9, 13, 13]
10
11// Encadenamiento: filter + map
12val titulosLargos = peliculas
13    .filter { it.length > 5 }
14    .map { it.uppercase() }
15// ["INCEPTION", "INTERSTELLAR", "DUNE: PARTE 2"]
16
17// any / all / none → devuelven Boolean
18val hayDune = peliculas.any { it.contains("Dune") }   // true
19
20// sortedBy → ordena por criterio
21val ordenadas = peliculas.sortedBy { it.length }

6. Corrutinas — introducción imprescindible#

Las corrutinas son la solución de Kotlin para la programación asíncrona. En Android se usan para operaciones que no deben bloquear el hilo principal: llamadas de red, acceso a base de datos, lectura de archivos.

En esta sección introductoria solo se presentan los conceptos básicos. Se profundizará en corrutinas en el Bloque 2 (ViewModel) y el Bloque 3 (ROOM y Retrofit2).

 1import kotlinx.coroutines.*
 2
 3// suspend → marca una función que puede suspenderse sin bloquear el hilo
 4suspend fun cargarPeliculas(): List<String> {
 5    delay(1000)   // simula espera de red (no bloquea el hilo)
 6    return listOf("Dune", "Inception")
 7}
 8
 9// Las funciones suspend solo pueden llamarse desde una corrutina o desde otra función suspend
10fun main() = runBlocking {   // runBlocking solo para pruebas y main(); no en Android
11    val peliculas = cargarPeliculas()
12    peliculas.forEach { println(it) }
13}

Los tres conceptos clave que usarás en Android son:

Concepto Qué es Cuándo lo verás
suspend fun Función que puede pausarse sin bloquear el hilo ROOM, Retrofit2, cualquier operación asíncrona
viewModelScope Scope ligado al ciclo de vida del ViewModel Llamadas desde el ViewModel (Bloque 2)
Flow<T> Stream de datos reactivo ROOM devuelve Flow<List<T>> para observar cambios (Bloque 3)

7. Expresiones de control#

when como expresión#

when es más potente que switch de Java: puede usarse como expresión (devuelve un valor) y acepta condiciones arbitrarias.

 1val puntuacion = 8.5
 2
 3// when como expresión: devuelve un valor
 4val clasificacion = when {
 5    puntuacion >= 9.0 -> "Obra maestra"
 6    puntuacion >= 7.0 -> "Muy buena"
 7    puntuacion >= 5.0 -> "Regular"
 8    else              -> "Mala"
 9}
10println(clasificacion)   // Muy buena
11
12// when con tipo concreto
13val estado: EstadoUI = EstadoUI.Cargando
14val mensaje = when (estado) {
15    is EstadoUI.Cargando -> "Espera..."
16    is EstadoUI.Exito    -> "Listo"
17    is EstadoUI.Error    -> estado.mensaje
18}

String templates#

1val titulo = "Dune"
2val anyo = 2021
3println("La película $titulo se estrenó en $anyo")
4println("Título en mayúsculas: ${titulo.uppercase()}")

Resumen visual#

Kotlin para Android — Lo imprescindible
─────────────────────────────────────────────────────────
val / var         → preferir val (inmutabilidad)
String? / ?./ ?:  → null safety sin NullPointerException
data class        → modelos de datos (equals/copy automático)
sealed class      → jerarquías cerradas (estados de UI)
object            → singleton
companion object  → miembros estáticos
listOf / mapOf    → colecciones inmutables preferidas
filter/map/sortBy → operaciones funcionales sobre colecciones
suspend / Flow    → programación asíncrona (corrutinas)
─────────────────────────────────────────────────────────

Actividad de consolidación#

Dado el siguiente código con errores conceptuales, identifica cada uno, explica por qué es un error y propón la corrección:

 1// Código con errores — identifícalos y corrígelos
 2
 3var APP_NAME = "AppFlix"       // Error 1
 4
 5class Pelicula {
 6    var titulo = null          // Error 2
 7    var puntuacion: Double     // Error 3
 8}
 9
10fun buscar(lista: List<String>): String {
11    for (item in lista) {
12        if (item.contains("Dune")) return item
13    }
14    return null                // Error 4
15}
16
17val generos = listOf("Acción", "Drama")
18generos.add("Comedia")         // Error 5

Pista: Los errores involucran: inmutabilidad, null safety, inicialización de propiedades, tipo de retorno y mutabilidad de colecciones.


Referencias#

Calendar  Última modificación: viernes, 26 de junio de 2026