Lo primero que debemos entender es como funciona un app de Android en Kotlin, las buenas prácticas recomiendan seguir una arquitectura por capas donde la UI Layer muestra los datos y gestiona la interacción con el usuario, la Domain Layer contiene la lógica de negocio, y la Data Layer se encarga de obtener y ofrecer los datos. Si quieres profundizar más en el tema, puedes leer el siguiente articulo: Arquitectura de una App en Android con Kotlin
Parte 1: Construyendo tu Primera UI
1.1 Elementos básicos
🔗 [Text](https://developer.android.com/develop/ui/compose/text)
Muestra texto simple en la pantalla.
Text(text = "Hola, Compose!")
🔗 [Button](https://developer.android.com/develop/ui/compose/components/button)
Permitir al usuario realizar una acción al hacer clic.
Button(onClick = { /* Acción al hacer clic */ }) {
Text("Presioname")
}
🔗 [Image](https://developer.android.com/develop/ui/compose/graphics/images/loading)
Mostrar imágenes o iconos. Se usa para cualquier elemento visual no textual.
Image(
painter = painterResource(R.drawable.mi_icono),
contentDescription = "Icono"
)
🔗 [TextField](https://developer.android.com/develop/ui/compose/text/user-input?textfield=state-based)
Permitir al usuario introducir o editar texto (entrada de usuario).
var text by remember { mutableStateOf("") }
TextField(
value = text ,
onValueChange = { value -> text = value },
label = { Text("Introduce tu nombre") }
)
1.2 Organizando la pantalla
Estos componentes se usan para posicionar y alinear los elementos de contenido en la pantalla.
Column
Organizar los composables hijos verticalmente (uno encima del otro).
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Arriba")
Text("Medio")
Text("Abajo")
}
Row
Organizar los composables hijos horizontalmente (uno al lado del otro).
Row(verticalAlignment = Alignment.CenterVertically) {
Button(onClick = {}) { Text("A") }
Spacer(Modifier.width(8.dp)) // Espacio
Button(onClick = {}) { Text("B") }
}
Box
Apilar los composables hijos uno encima del otro (como un marco) o para definir un área.
Box(contentAlignment = Alignment.Center) {
Image(...)
Text("Overlay", color = Color.White)
}
🔗 Leer más aquí sobre los layouts
1.3 Personalizar elementos
Aquí entra en juego el Modifier, el cual se usa para decorar o agregar comportamiento a un composable (tamaño, relleno, fondo, clics, etc.).
Text("Clickeable", modifier = Modifier.padding(16.dp).clickable { /* ... */ }) // Agrega un padding de 16.dp y ademas esta escuchando cuando se haga clic para ejecutar cualquier código en `.clickable { /* ... */ }`,
1.4 Manejando el estado
El estado en una aplicación es cualquier valor que puede cambiar con el tiempo. Por ejemplo tienes un TextField con el titulo de libro, cada vez que el usuario escriba en ese TextField el estado cambia.
remember
El remember se usa para almacenar un objeto en la memoria de la composición (típicamente el estado) para que no se pierda en las recomposiciones.
val contador = remember { mutableStateOf(0) }
mutableStateOf
mutableStateOf crea un objeto de estado observable. Cuando su valor cambia, Jetpack Compose automáticamente "recompone" (redibuja) los composables que lo usan.
var nombre by remember { mutableStateOf("Invitado") }
Text(nombre) // Se redibuja si 'nombre' cambia
Parte 2: Separando Lógica con MVVM
2.1 ¿Qué es MVVM?
Es un patrón de diseño que se ha convertido en el estándar en el desarrollo de aplicaciones modernas de Android con Kotlin y Jetpack Compose, ya que promueve la separación de responsabilidades y el desarrollo reactivo.
MVVM divide la aplicación en tres capas interconectadas, donde el flujo de datos es principalmente unidireccional (aunque la View observa reactivamente al ViewModel).

2.2 ViewModel en acción
Un ViewModel es una clase que forma parte de las bibliotecas de Android Jetpack y está diseñada para almacenar y administrar datos relacionados con la interfaz de usuario (UI) de manera consciente del ciclo de vida.
- Contenedor de estado a nivel de pantalla o lógica de negocio: Un ViewModel expone el estado a la UI y encapsula la lógica de negocio relacionada.
- Cambios de configuración: Su principal ventaja es que guarda en caché el estado y lo mantiene a través de cambios de configuración (como rotaciones de pantalla).
- Separación de responsabilidades: Ayuda a separar la lógica de la UI (que reside en Activities o Fragments) de la lógica de datos y de negocio.
2.4 AppViewModelProvider
- Proporciona una fábrica centralizada (
Factory) para crear instancias de tus ViewModels. - Simplifica la inyección de dependencias en tus ViewModels. En lugar de tener que pasar manualmente las dependencias cada vez que creas un ViewModel en tu Activity o Fragment, puedes simplemente usar esta Factory.
- Asegura que TaskViewModel reciba la instancia correcta de TasksRepository al ser creado.
Parte 3: Navegación entre Pantallas
Activity
Un Activity representa una única pantalla con una interfaz de usuario (UI) específica con la que el usuario puede interactuar. Es un punto de entrada que el sistema operativo Android puede cargar y ejecutar en cualquier momento.

https://developer.android.com/guide/components/activities/activity-lifecycle
NavController
Este modelo se basa en la idea de tener una única Activity que aloja todas las "pantallas" (que ahora son funciones @Composable).
El NavController es el motor central de la navegación. Es responsable de gestionar la pila de atrás (back stack) de los composables y de cambiar de una pantalla a otra. Y el NavHost es el contenedor que define la región donde se mostrarán los diferentes composables de la aplicación.
📺 Navegación en Jetpack Compose con tipos seguros
Parte 4: Personalizando la Apariencia
MaterialTheme
En Jetpack Compose con la librería Material 3, todo el tema se define en torno a estos tres pilares dentro de la función MaterialTheme():
colorScheme(Esquema de Colores): Define todos los colores de la aplicación (primario, secundario, de superficie, de fondo, etc.) y soporta temas claros y oscuros.typography(Tipografía): Define los estilos de texto (fuentes, tamaños, pesos) para cada elemento de la UI (títulos, cuerpos, etiquetas).shapes(Formas): Define la apariencia de las esquinas (redondeadas, cuadradas) para los componentes grandes, medianos y pequeños.
colorScheme
El cambio más común es personalizar la paleta de colores. Esto generalmente se hace en un archivo Color.kt y Theme.kt dentro del paquete ui.theme.
// ui.theme/Theme.kt
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF1B6A1E),
onPrimary = Color.White,
secondary = Color(0xFF52634F),
background = Color.White
// ...
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF86D587),
onPrimary = Color.Black,
secondary = Color(0xFFBBCBB4),
background = Color(0xFF1C1B1F)
// ...
)
Utiliza la herramienta Material Theme Builder de Google. Simplemente ingresas tu color principal y la herramienta genera automáticamente todos los tonos y los esquemas de color (lightColorScheme y darkColorScheme) que puedes descargar y pegar en tu proyecto.
Typography
Crea una instancia de Typography y sobrescribe los estilos de texto necesarios (ej. displayLarge, titleMedium, bodyLarge).
Coloca tus archivos de fuente (.ttf, .otf) en la carpeta res/font.
// ui.theme/Type.kt
val MiTipografia = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default, // Puedes usar una fuente personalizada aquí
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
// ...
)
Aplicar el Tema
// ui.theme/Theme.kt
@Composable
fun MiAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = MiTipografia,
shapes = MaterialTheme.shapes,
content = content
)
}
Recursos y próximos pasos
Puedes ver una aplicación completa con todo lo anterior para que te puedas guiar: https://github.com/byandrev/habito