Construir la lógica de una aplicación antes de diseñar su interfaz es una práctica fundamental en el desarrollo con Jetpack Compose. Aquí se aborda paso a paso cómo definir los modelos de datos, las operaciones básicas y una fuente de datos simulada para una To Do App, siguiendo principios de Clean Architecture y aprovechando herramientas propias de Kotlin como data classes, enums, flows y suspend functions.
¿Cómo se modela una tarea en Kotlin?
Antes de escribir cualquier pantalla, es necesario identificar qué representa una tarea dentro del dominio de la aplicación. Para ello se crea un paquete llamado Domain, donde vivirán los modelos del proyecto [01:00].
Una data class en Kotlin es el tipo de clase ideal para representar objetos cuyo propósito principal es almacenar datos. La clase Task se define con las siguientes propiedades:
- id: identificador único de tipo
String, generado con UUID.randomUUID().toString().
- title: título de la tarea, obligatorio.
- description: descripción opcional, declarada como nullable (
String?), ya que no siempre se necesita.
- isCompleted: booleano que indica si la tarea está completada, con valor por defecto
false.
- category: categoría opcional asociada a la tarea.
¿Qué papel juegan las categorías con enum?
Para clasificar las tareas se utiliza un enum class llamado Category [02:20]. Un enum permite enumerar un conjunto finito de valores posibles:
PERSONAL.
WORK.
SHOPPING.
OTHER.
Esta estructura garantiza que solo se asignen categorías válidas a cada tarea, evitando errores por valores inesperados.
¿Cómo se definen las operaciones CRUD con una interfaz?
El acrónimo CRUD representa las cuatro operaciones básicas sobre datos: crear, leer, actualizar y borrar. Siguiendo Clean Architecture, estas operaciones se declaran en una interfaz llamada TaskLocalDataSource [03:05]. Esto separa la abstracción de la implementación concreta.
Las operaciones definidas son:
addTask(task: Task): agrega una nueva tarea.
updateTask(task: Task): actualiza una tarea existente.
removeTask(task: Task): elimina una tarea específica.
deleteAllTasks(): borra todas las tareas.
getTaskById(id: String): obtiene una tarea por su identificador, retornando Task? porque podría no encontrarse.
Todas estas funciones se declaran como suspend functions [05:10], un concepto clave de las corrutinas en Kotlin que permite ejecutar operaciones asíncronas sin bloquear el hilo principal.
La interfaz también expone un taskFlow de tipo Flow<List<Task>>, que permite a la vista observar cambios en la lista de tareas de forma reactiva.
¿Qué es un Flow y por qué se usa aquí?
Un Flow es un tipo reactivo de Kotlin que emite valores a lo largo del tiempo [04:15]. Cuando una tarea se agrega, actualiza o elimina, el flow notifica automáticamente a los observadores. Se utiliza un MutableStateFlow inicializado con una lista vacía, lo que significa que la fuente de datos comienza sin tareas.
¿Cómo se implementa una fuente de datos falsa?
Dentro de un paquete Data se crea la clase FakeTaskLocalDataSource [03:55], que implementa la interfaz TaskLocalDataSource. Se denomina fake porque los datos residen únicamente en memoria RAM, sin persistencia en base de datos.
Cada operación manipula la lista interna del MutableStateFlow:
- Agregar: se extrae la lista actual, se convierte en
MutableList, se añade la nueva tarea y se actualiza el flow.
- Actualizar: se busca el índice de la tarea con
indexOfFirst comparando por id. Si el índice es distinto de -1, se reemplaza el elemento y se actualiza [06:00].
- Eliminar: se remueve la tarea de la lista y se actualiza el flow.
- Borrar todo: se reemplaza el valor del flow con una lista vacía.
- Buscar por id: se recorre la lista y se retorna la primera coincidencia, o
null si no existe.
Entre cada operación se simula un delay para emular la latencia que tendría una base de datos real como Room, que se abordaría en etapas posteriores.
¿Cómo se prueba la lógica desde la vista?
En el MainActivity se utiliza un LaunchedEffect con key true para recolectar el flow de tareas [08:30]. Cada vez que la lista cambia, se actualiza una variable de texto con remember { mutableStateOf("") } que muestra el contenido en un composable Text.
Un segundo LaunchedEffect inserta una tarea de prueba llamando a addTask con un objeto Task cuyo id se genera con UUID. Al ejecutar la aplicación, la tarea aparece en pantalla, confirmando que el flujo de datos funciona correctamente [09:20].
Para corregir problemas de superposición con la barra de menú, se aplica el innerPadding proporcionado por el Scaffold al modifier del texto [09:50], respetando así los márgenes del contenedor padre.
Con esta base funcional —modelos definidos, operaciones CRUD implementadas y un flow reactivo conectado a la interfaz— el siguiente paso natural es construir la experiencia visual que permita al usuario interactuar con sus tareas. ¿Qué elementos de UI añadirías primero?