Tipos en Kotlin: lo esencial para Android
En Android, Kotlin se usa para modelar datos (por ejemplo, lo que llega de una API), transformar listas para mostrarlas en pantalla y escribir lógica segura (sin errores por nulos). Para eso necesitas dominar el sistema de tipos.
Tipos básicos y conversión
Kotlin tiene tipos como Int, Long, Double, Boolean y String. A diferencia de otros lenguajes, Kotlin no hace conversiones numéricas implícitas: si necesitas convertir, lo haces explícitamente.
val edad: Int = 28
val puntos: Long = edad.toLong()
val precioTexto = "19.99"
val precio: Double = precioTexto.toDouble()Inferencia de tipos y legibilidad
Kotlin suele inferir el tipo, pero en Android conviene declarar tipos cuando ayuda a documentar el modelo o evitar ambigüedades.
val nombre = "Ana" // String inferido
val stock: Int = 10 // explícito por claridadInmutabilidad: val por defecto
Una buena práctica clave es preferir val (inmutable) y usar var solo cuando sea necesario. Esto reduce errores al actualizar estado en pantallas y facilita razonar sobre el flujo de datos.
val impuesto = 0.21
var total = 0.0
total += 10.0Null safety: manejo seguro de nulos (imprescindible en Android)
En Android es común recibir valores nulos (campos opcionales de API, extras de Intent, argumentos de navegación). Kotlin separa tipos no nulos (String) de tipos anulables (String?).
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Operadores principales
?.llamada segura: ejecuta solo si no es nulo.?:Elvis: valor por defecto si es nulo.!!aserción no nula: evita usarlo salvo casos muy controlados.
val nickname: String? = null
val longitud = nickname?.length // Int?
val longitudSegura = nickname?.length ?: 0 // IntGuía práctica: normalizar datos opcionales
Supón que recibes un nombre opcional y quieres mostrarlo en UI sin romper la app.
- Define el tipo anulable.
- Normaliza con
trim()si existe. - Aplica un valor por defecto con
?:.
fun nombreParaMostrar(nombre: String?): String {
val limpio = nombre?.trim()
return if (limpio.isNullOrEmpty()) "Invitado" else limpio
}Funciones: reutilización y claridad
En Android, las funciones te ayudan a separar lógica de transformación (por ejemplo, preparar listas para un RecyclerView) de la capa de UI. Prioriza funciones pequeñas, puras (sin efectos secundarios) y con nombres descriptivos.
Parámetros con valores por defecto y nombrados
fun precioConImpuesto(precio: Double, iva: Double = 0.21): Double {
return precio * (1 + iva)
}
val total = precioConImpuesto(precio = 100.0)Funciones de extensión (muy útiles)
Permiten “añadir” funciones a tipos existentes sin herencia. Útiles para formateo y validaciones.
fun String?.orDash(): String = if (this.isNullOrBlank()) "-" else this
val ciudad: String? = null
val texto = ciudad.orDash() // "-"Ejercicio corto 1: transformar una lista de precios
Objetivo: dado un listado de precios (algunos nulos), obtener una lista final con impuesto aplicado y redondeo a 2 decimales, ignorando nulos.
fun Double.round2(): Double = kotlin.math.round(this * 100) / 100
fun preciosFinales(precios: List, iva: Double = 0.21): List {
return precios
.filterNotNull()
.map { (it * (1 + iva)).round2() }
}
val entrada = listOf(10.0, null, 25.5)
val salida = preciosFinales(entrada) // [12.1, 30.86] Clases, propiedades y encapsulación
Las clases modelan entidades que luego se reutilizan en pantallas (por ejemplo, un Usuario para un perfil o un Producto para un catálogo). En Kotlin, las propiedades se declaran en el constructor primario, y puedes controlar mutabilidad con val/var.
Modelo base: Usuario
class Usuario(
val id: String,
val nombre: String,
val email: String?
) {
fun iniciales(): String {
val partes = nombre.trim().split(" ").filter { it.isNotBlank() }
val first = partes.getOrNull(0)?.firstOrNull()?.uppercaseChar()
val second = partes.getOrNull(1)?.firstOrNull()?.uppercaseChar()
return listOfNotNull(first, second).joinToString("")
}
}Buenas prácticas: usa val para que el modelo sea inmutable; si necesitas “actualizar” algo, crea una nueva instancia (especialmente útil con estados de UI).
Data classes: modelos ideales para UI y estado
Las data class generan automáticamente equals, hashCode, toString y copy. Son perfectas para representar estado de pantalla y elementos de listas.
Modelo: Producto
data class Producto(
val id: String,
val nombre: String,
val precio: Double,
val stock: Int,
val descuento: Double? = null
)Guía práctica: actualizar inmutablemente con copy
Si un producto recibe un descuento, no mutas el objeto: creas uno nuevo.
fun aplicarDescuento(p: Producto, descuento: Double): Producto {
return p.copy(descuento = descuento)
}Ejercicio corto 2: modelar y derivar datos para UI
Objetivo: crear una función que convierta una lista de Producto en textos listos para mostrar (por ejemplo, en una lista), manejando descuento opcional.
fun Producto.precioFinal(): Double {
val d = descuento ?: 0.0
return (precio * (1 - d)).let { kotlin.math.round(it * 100) / 100 }
}
fun productosParaLista(productos: List): List<String> {
return productos.map { p ->
val stockTexto = if (p.stock > 0) "Stock: ${p.stock}" else "Sin stock"
"${p.nombre} - ${p.precioFinal()} (${stockTexto})"
}
} Colecciones: listas y transformaciones (base de la UI)
En Android, gran parte del trabajo es transformar colecciones: filtrar, ordenar, agrupar y mapear para mostrar en pantalla. Prioriza colecciones inmutables (List, Map) y evita mutarlas dentro de funciones.
Operaciones más usadas
map: transforma cada elemento.filter: conserva los que cumplen una condición.sortedBy/sortedByDescending: ordena.groupBy: agrupa por clave.associateBy: crea un mapa por id.distinctBy: elimina duplicados por clave.
val productos = listOf(
Producto("p1", "Teclado", 25.0, 10),
Producto("p2", "Mouse", 15.0, 0, descuento = 0.10),
Producto("p3", "Monitor", 199.99, 3)
)
val disponibles = productos.filter { it.stock > 0 }
val ordenados = disponibles.sortedBy { it.precioFinal() }
val porId = productos.associateBy { it.id }Ejercicio corto 3: pipeline de transformación para catálogo
Objetivo: a partir de una lista de productos, obtener los nombres en mayúsculas de los 2 más baratos disponibles (stock > 0), considerando descuento.
fun top2BaratosDisponibles(productos: List<Producto>): List<String> {
return productos
.asSequence()
.filter { it.stock > 0 }
.sortedBy { it.precioFinal() }
.take(2)
.map { it.nombre.uppercase() }
.toList()
}asSequence() es útil cuando encadenas muchas operaciones y quieres evitar listas intermedias.
Lambdas: funciones como valores (eventos y callbacks)
Las lambdas son funciones anónimas. En Android aparecen en listeners, callbacks y transformaciones de colecciones.
val duplicar: (Int) -> Int = { it * 2 }
val resultado = duplicar(21) // 42Lambdas con múltiples parámetros
val formatear: (String, Double) -> String = { nombre, precio ->
"$nombre: $precio"
}Ejercicio corto 4: callback para seleccionar un producto
Objetivo: simular una acción de selección (como al tocar un item en una lista) usando una lambda.
fun seleccionarProducto(productos: List<Producto>, id: String, onSeleccion: (Producto) -> Unit) {
val encontrado = productos.firstOrNull { it.id == id } ?: return
onSeleccion(encontrado)
}
seleccionarProducto(productos, "p3") { p ->
val texto = "Seleccionado: ${p.nombre} (${p.precioFinal()})"
// En una pantalla Android, este texto podría ir a un TextView o estado de UI
}Scope functions: let, run, apply, also, with
Las scope functions ayudan a escribir código más expresivo al operar sobre un objeto dentro de un bloque. En Android se usan mucho para: encadenar transformaciones, manejar nulos, configurar objetos y evitar variables temporales.
| Función | Receiver | Retorna | Uso típico |
|---|---|---|---|
let | it | resultado del bloque | transformar y null-safety |
run | this | resultado del bloque | computar con un objeto |
apply | this | el objeto | configurar (builder-like) |
also | it | el objeto | side-effects (logs, métricas) |
with | this | resultado del bloque | operar sobre un objeto externo |
let para nulos: patrón recomendado
val email: String? = "ana@correo.com"
val dominio: String? = email?.let { it.substringAfter("@") }apply para configurar sin perder el objeto
Útil al construir modelos o configurar objetos (sin entrar en detalles de UI).
val p = Producto(
id = "p10",
nombre = "Auriculares",
precio = 49.99,
stock = 5
).copy().also {
// side-effect controlado (por ejemplo, registrar evento)
}run para calcular un valor a partir de un objeto
fun etiquetaStock(p: Producto): String = p.run {
if (stock > 0) "Disponible" else "Agotado"
}Ejercicio corto 5: pipeline seguro con let y takeIf
Objetivo: dado un Usuario con email opcional, obtener un email “válido para mostrar” solo si contiene @; si no, devolver null (para que la UI decida ocultarlo).
fun emailMostrable(u: Usuario): String? {
return u.email
?.trim()
?.takeIf { it.contains("@") }
}
val u1 = Usuario("1", "Ana Pérez", " ana@correo.com ")
val u2 = Usuario("2", "Luis", "sin-arroba")
val e1 = emailMostrable(u1) // "ana@correo.com"
val e2 = emailMostrable(u2) // nullBuenas prácticas aplicadas: inmutabilidad y nulos en modelos reutilizables
Reglas prácticas para tus entidades (Usuario, Producto)
- Prefiere
data classpara modelos de UI/estado. - Usa
valen propiedades; evitavaren entidades compartidas. - Representa opcionales con
?y normaliza en funciones puras. - Evita
!!; usa?.,?:,takeIf,requireNotNullsolo cuando sea un contrato claro. - Centraliza transformaciones: por ejemplo,
precioFinal(),emailMostrable(),nombreParaMostrar().
Mini práctica integradora: preparar datos para una pantalla
Objetivo: con una lista de productos, construir un “modelo de item” listo para UI (texto principal y secundario), sin nulos peligrosos y sin mutar la lista original.
data class ProductoItemUi(
val id: String,
val titulo: String,
val subtitulo: String
)
fun aItemsUi(productos: List<Producto>): List<ProductoItemUi> {
return productos.map { p ->
val precio = p.precioFinal()
val subtitulo = buildString {
append("Precio: ")
append(precio)
append(" | ")
append(if (p.stock > 0) "Stock: ${p.stock}" else "Sin stock")
}
ProductoItemUi(
id = p.id,
titulo = p.nombre,
subtitulo = subtitulo
)
}
}