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 reasignarRegla práctica: Declara siempre con
val. Cambia avarsolo 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% seguroEl 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 Nolan3. 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 } // 15En 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.04.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 referenciaLas
data classson 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 classson 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.0companion 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_db5. 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 5Pista: Los errores involucran: inmutabilidad, null safety, inicialización de propiedades, tipo de retorno y mutabilidad de colecciones.