
Requerimiento: Aplicación de Gestión de Tareas
La aplicación debe permitir la gestión integral de tareas, incorporando las siguientes funcionalidades y características:
1. Listado de tareas
Mostrar un listado general de todas las tareas.
Posibilidad de filtrar por:
Nombre
Estatus
Prioridad
Categoría
Posibilidad de ordenar por:
Nombre
Categoría
Fecha de entrega
Progreso
Prioridad
2. Propiedades de cada tarea
Cada tarea debe incluir:
Título
Descripción alusiva
Prioridad (alta, media, baja, u otra escala definida)
Categoría asignada
Fecha de entrega prevista (opcionalmente obligatoria)
3. Objetivos dentro de cada tarea
Cada tarea puede contener cero o más objetivos, que representan su progreso.
Cada objetivo debe contar con:
Título
Descripción
Indicador de estado (Completado / No completado)
4. Acciones permitidas
Agregar nuevas tareas y objetivos.
Modificar todas las propiedades de una tarea u objetivo.
Eliminar tareas (al hacerlo, también se eliminan todos sus objetivos asociados).
Eliminar objetivos de manera independiente, sin restricciones.
5. Representación visual
En el listado, cada tarea debe representarse visualmente de acuerdo con:
Su nivel de prioridad
La cercanía de su fecha de entrega
6. Interfaz de usuario
Diseño con fondo blanco y elementos en tonos negros.
No debe depender del tema oscuro o claro del dispositivo.
Requerimientos de trabajo
Para desarrollar la aplicación es necesario contar con las siguientes herramientas:
Android Studio: entorno de desarrollo oficial para Android.
Creación del proyecto en Android Studio
Para comenzar con el desarrollo de aplicaciones, se deben seguir estos pasos en Android Studio:
Crear un nuevo proyecto con la plantilla Empty Activity (vista) utilizando XML para la interfaz gráfica.
Asignar un nombre al proyecto.
Definir el nombre del paquete con la convención:
com.[desarrollador].[nombre_de_la_aplicacion]
Seleccionar la versión de Android SDK a utilizar.
Configurar la versión de Gradle correspondiente al proyecto.
¿Qué es Room y para qué se utiliza?
Room es una librería oficial de Android que forma parte de Android Jetpack.
Se utiliza para trabajar con bases de datos locales dentro de una aplicación, ya que ofrece una forma más sencilla, segura y eficiente de interactuar con SQLite, evitando el manejo manual de consultas SQL complejas.
Beneficios principales de Room
Permite crear, leer, actualizar y eliminar datos (CRUD) de manera estructurada.
Facilita la definición de entidades (tablas) y relaciones entre ellas.
Garantiza seguridad en el acceso a la base de datos mediante validaciones en tiempo de compilación.
Se integra con LiveData y Flow, lo que permite observar los datos en tiempo real y actualizar la interfaz automáticamente cuando cambian.
Soporta consultas personalizadas usando anotaciones (@Query).
¿Qué podemos hacer con Room?
Con Room es posible:
Guardar información localmente en el dispositivo, incluso sin conexión a internet.
Consultar datos de forma optimizada y reactiva.
Mantener la persistencia de tareas, usuarios, configuraciones u otros elementos de la aplicación.
Definir relaciones entre tablas (uno a muchos, muchos a muchos).
Migrar la base de datos cuando se agregan o cambian campos.
Componentes XML en Android
En Android, la interfaz gráfica (UI) de una aplicación se define normalmente con archivos XML.
Dentro de estos archivos se colocan los componentes de la interfaz (también llamados views o widgets), que representan los elementos visibles con los que el usuario interactúa.
Cada componente tiene:
Un nombre (ejemplo: TextView, Button, ImageView).
Una serie de propiedades (atributos en XML) que permiten personalizar su apariencia y comportamiento.
Ejemplos de componentes comunes:
TextView
Se utiliza para mostrar texto en pantalla.
Propiedades más comunes:
android:text="Hola Mundo" → Texto a mostrar.
android:textSize="16sp" → Tamaño de letra.
android:textColor="#000000" → Color del texto.
android:gravity="center" → Alineación interna.
EditText
Caja de texto que permite al usuario escribir.
Propiedades comunes:
android:hint="Escribe aquí..." → Texto de ayuda.
android:inputType="text" → Tipo de entrada (texto, número, contraseña, email, etc.).
android:maxLength="50" → Número máximo de caracteres.
Button
Botón interactivo que ejecuta una acción.
Propiedades comunes:
android:text="Aceptar" → Texto del botón.
android:enabled="true/false" → Habilitar o deshabilitar.
android:onClick="nombreMetodo" → Método que se ejecuta al pulsar.
ImageView
Sirve para mostrar imágenes.
Propiedades comunes:
android:src="@drawable/imagen" → Imagen a mostrar.
android:scaleType="centerCrop" → Ajuste de la imagen.
android:contentDescription="Descripción de la imagen" → Texto alternativo para accesibilidad.
CheckBox
Casilla de verificación que el usuario puede marcar o desmarcar.
Propiedades comunes:
android:text="Aceptar términos" → Texto junto a la casilla.
android:checked="true/false" → Estado inicial.
RadioButton (usado en grupo con RadioGroup)
Permite seleccionar una opción entre varias.
Propiedades comunes:
android:text="Opción 1"
android:checked="true/false"
LinearLayout, ConstraintLayout, RelativeLayout, FrameLayout
Son contenedores (layouts) que organizan cómo se acomodan los componentes dentro de la pantalla.
Propiedades comunes:
android:orientation="vertical/horizontal" (en LinearLayout).
app:layout_constraintTop_toTopOf="parent" (en ConstraintLayout).
Propiedades generales (aplican a casi todos los componentes):
android:id="@+id/miComponente" → Identificador único.
android:layout_width="match_parent / wrap_content / valor dp" → Ancho.
android:layout_height="match_parent / wrap_content / valor dp" → Alto.
android:padding="8dp" → Espacio interno.
android:margin="8dp" → Espacio externo.
android:background="#FFFFFF" → Fondo.
android:visibility="visible / invisible / gone" → Visibilidad.
¿Qué es un Adapter en un RecyclerView?
En Android, un RecyclerView es un componente que permite mostrar listas o colecciones de elementos de manera eficiente, incluso si son muy grandes.
Para poder mostrar esos datos en pantalla, necesita un Adapter.
El Adapter es la clase que:
Recibe los datos (por ejemplo, una lista de tareas).
Crea las vistas necesarias (los elementos visuales que se van a mostrar).
Asigna los datos a cada vista (ejemplo: mostrar el título de la tarea en un TextView).
En otras palabras, el Adapter es el puente entre los datos y la interfaz gráfica.
Funciones principales del Adapter:
Indicar cuántos elementos hay en la lista (getItemCount).
Crear un nuevo ViewHolder cuando se necesite (onCreateViewHolder).
Llenar el ViewHolder con los datos correctos (onBindViewHolder).
¿Qué es un ViewHolder?
El ViewHolder es una clase que se utiliza dentro del Adapter y representa la vista de un solo elemento de la lista.
En lugar de estar creando y buscando vistas cada vez que se muestra un elemento, el ViewHolder guarda referencias a los componentes (ejemplo: TextView, ImageView) para que el RecyclerView pueda reciclar las vistas y mejorar el rendimiento.
Ejemplo práctico:
Imagina que tienes un RecyclerView con una lista de tareas.
El Adapter se encarga de recorrer tu lista de tareas y decirle al RecyclerView:
"Esta tarea tiene el título Hacer la compra, ponlo en un TextView".
"Esta otra tiene el título Ir al gimnasio".
El ViewHolder es el que guarda las referencias de los TextView y ImageView de cada ítem para no estar buscándolos cada vez con findViewById, lo cual sería ineficiente.
Consideraciones al trabajar con un Adapter en RecyclerView
Siempre manejar los estados completos
Si cambias un atributo de la vista (ejemplo: color de fondo, visibilidad, texto, etc.), asegúrate de manejar también el caso contrario con un else.
De lo contrario, al reciclar la vista, puede quedar con valores de un ítem anterior.
if (objetivoModelo.completado) {
binding.titulo.paintFlags =
binding.titulo.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
binding.descripcion.paintFlags =
binding.descripcion.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
binding.titulo.paintFlags =
binding.titulo.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
binding.descripcion.paintFlags =
binding.descripcion.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
Evita dejar valores “colgados”
Atributos como visibility, checked, enabled, etc. deben siempre reiniciarse según el estado actual de los datos.
Ejemplo con CheckBox:
binding.completado.isChecked = objetivo.completada
Nunca modifiques la vista sin actualizar los datos
Si haces un cambio directo en la vista (ejemplo: marcar un CheckBox o cambiar color al hacer clic), actualiza también el modelo de datos.
Así aseguras que cuando se vuelva a dibujar, los valores sigan siendo correctos.
Ten cuidado con listeners dentro del ViewHolder
Los listeners (ej. setOnClickListener, addTextChangedListener) pueden quedarse “pegados” si no se limpian o reinicializan.
Ejemplo correcto:
binding.titulo.apply {
setOnCheckedChangeListener(null)
isChecked = objetivoModelo.completado
setOnCheckedChangeListener { _, _ ->
objetivoCompletado(objetivoModelo.tareaId, objetivoModelo.id)
}
}
Usar DiffUtil cuando sea posible
Si actualizas la lista con notifyDataSetChanged(), se vuelve a redibujar todo, lo cual puede ser poco eficiente.
Con DiffUtil, solo se actualizan los ítems que realmente cambiaron, reduciendo errores visuales y mejorando rendimiento.
Separar la lógica de UI y datos
El Adapter solo debe encargarse de mostrar los datos, no de procesarlos.
Mantén la lógica de negocio en el ViewModel o en otro lugar, y pasa al Adapter solo lo que necesita pintar.
¿Qué es DiffUtil?
DiffUtil es una clase de la librería de Android que sirve para comparar dos listas y calcular de manera eficiente las diferencias entre ellas.
En lugar de redibujar toda la lista (con notifyDataSetChanged()), DiffUtil determina exactamente qué elementos:
Se agregaron
Se eliminaron
Se modificaron
Se reordenaron
Y actualiza solo esos ítems en el RecyclerView.
¿Para qué se utiliza?
Se usa principalmente en los Adapters de RecyclerView para:
Actualizar la lista de manera eficiente
Evita redibujar todos los ítems.
Aumenta el rendimiento, sobre todo con listas largas.
Mejorar la experiencia de usuario
Las animaciones de inserción, eliminación o actualización se aplican automáticamente.
El cambio es más fluido y natural.
Evitar bugs visuales por reciclaje de vistas
Como solo se actualizan los ítems que cambiaron, se reduce el riesgo de estados incorrectos (como el fondo verde que mencionabas antes).
Uso de clases intermedias en lugar de entidades de la base de datos en un Adapter
En el desarrollo de aplicaciones Android con Kotlin, al trabajar con un RecyclerView y Room, es común que surja la duda de si debemos pasar directamente las entidades de la base de datos al Adapter o si es mejor usar una clase intermedia (también llamada DTO, Data Transfer Object o modelo de presentación).
Razones para usar una clase intermedia
Separación de responsabilidades
Las entidades de Room representan cómo se almacena la información en la base de datos.
El Adapter se encarga de mostrar los datos en la interfaz.
Al usar una clase intermedia, mantenemos la lógica de almacenamiento separada de la lógica de presentación, evitando acoplamientos innecesarios.
Flexibilidad para la UI
La clase intermedia puede contener campos calculados o combinados, como:
Cantidad de objetivos completados dentro de una tarea.
Texto formateado o concatenado.
Estados de visibilidad o color de fondo según reglas de negocio.
Esto permite que el Adapter tenga todos los datos listos para renderizar la UI, sin modificar la entidad original.
Evitar efectos secundarios no deseados
Las entidades están directamente conectadas a la base de datos. Si el Adapter modifica un campo por error, podríamos alterar datos persistentes sin querer.
Con la clase intermedia, cualquier cambio en la UI no afecta directamente la base de datos hasta que lo decidamos explícitamente.
Facilita pruebas y mantenimiento
Separar la capa de presentación de la de datos hace que el código sea más fácil de testear y mantener.
Podemos modificar la forma en que se muestran los datos sin tocar la base de datos, y viceversa.
Compatibilidad con DiffUtil y listas dinámicas
La clase intermedia puede implementar fácilmente métodos como equals() y hashCode() necesarios para que DiffUtil funcione correctamente, sin depender de la estructura interna de la entidad.
Actividad, ViewModel y Ciclo de Vida en Android
1. Actividad (Activity)
Una Actividad es uno de los componentes principales de una aplicación Android.
Representa una pantalla con la que el usuario puede interactuar.
Cada actividad se asocia normalmente a un layout XML, donde se definen los componentes visuales.
Puede contener lógica de interfaz, recibir eventos de usuario y lanzar otras actividades.
Ejemplo de uso:
Pantalla de inicio de sesión.
Lista de tareas.
Formulario de registro.
2. ViewModel
El ViewModel es una clase que forma parte de la arquitectura MVVM (Model-View-ViewModel).
Su función principal es gestionar y preparar los datos para la interfaz de usuario, separando la lógica de negocio de la UI.
Ventajas de usar ViewModel:
Permite que los datos sobrevivan a cambios de configuración, como rotación de pantalla.
Evita que la Activity o Fragment tenga que manejar directamente los datos, manteniendo el código más limpio.
Se puede combinar con LiveData o Flow para actualizar automáticamente la UI cuando los datos cambian.
3. Ciclo de vida (Lifecycle)
El ciclo de vida de una Activity o Fragment describe los estados por los que pasa desde que se crea hasta que se destruye.
Conocerlo es fundamental para manejar recursos correctamente, evitar fugas de memoria y actualizar la UI de forma segura.
Flujos (Flows) y Migraciones en Android Room
1. Flujos (Flows) en Android
En Kotlin, un Flow es una forma de manejar datos que llegan de manera asíncrona y reactiva.
Es parte de Kotlin Coroutines, y permite emitir múltiples valores a lo largo del tiempo.
Se utiliza mucho en Android para observar cambios en la base de datos o en la UI sin bloquear el hilo principal.
Ventajas de usar Flow:
Permite recibir actualizaciones automáticas cuando los datos cambian.
Es asíncrono, evitando que la UI se congele.
Se puede combinar con operadores de transformación (map, filter, collect) para manipular datos de manera funcional.
@Query( """ SELECT * FROM tareas""")
fun obtieneTareasModelo(): Flow<List<TareaModelo>>
Cada vez que se inserte, modifique o elimine una tarea en la base de datos, el Flow emitirá automáticamente la nueva lista, y la UI puede actualizarse en tiempo real usando collect o asLiveData().
2. Migraciones en Room
Cuando cambias la estructura de la base de datos (por ejemplo, agregando una columna, una tabla o un índice), necesitas manejar Migraciones para no perder los datos existentes.
Qué hacen las migraciones:
Indican a Room cómo pasar de una versión de la base de datos a otra.
Permiten agregar, modificar o eliminar tablas y columnas de manera segura.
Evitan que la aplicación falle por cambios en el esquema de la base de datos.
Diferencia entre un Flow y un listado normal en Android
En Android, cuando trabajamos con datos de una base de datos o de cualquier fuente, podemos manejarlos de distintas formas: como listados normales o como flujos (Flow).
1. Listado normal
Es simplemente una colección de datos estática, como List<T> o MutableList<T>.
Se obtiene una sola vez al hacer la consulta.
Si los datos cambian después, la lista no se actualiza automáticamente, y tendrías que volver a consultar para reflejar los cambios.
Ventajas:
Sencillo de usar y entender.
Útil cuando los datos no cambian frecuentemente.
Desventajas:
No se actualiza automáticamente si cambian los datos.
Necesita refrescarse manualmente, lo que puede generar código repetitivo y más errores.
2. Flow (flujo de datos)
Representa un flujo de datos asíncrono y reactivo.
Cada vez que los datos cambian (ej. inserción, actualización o eliminación en Room), el Flow emite automáticamente la nueva lista.
Permite observar cambios en tiempo real y actualizar la UI de forma reactiva.
Ventajas:
Actualización automática de la UI cuando los datos cambian.
Se integra con Kotlin Coroutines, evitando bloquear el hilo principal.
Funciona muy bien con LiveData y patrones de arquitectura como MVVM.
Desventajas:
Requiere entender programación reactiva y coroutines.
Un poco más complejo de implementar que una lista estática.
Iniciar nuevas actividades y pasar datos entre ellas
En Android, una Activity representa una pantalla de la aplicación. Para moverse de una pantalla a otra, se crean intents que permiten iniciar nuevas actividades y, si es necesario, pasarles información.
1. Iniciar una nueva Activity
Para iniciar una nueva actividad se utiliza la clase Intent.
val intent = Intent(this@ActividadPrincipal, ActividadAgregar::class.java)
startActivity(intent)
2. Pasar datos entre Activities
Podemos enviar datos usando extras dentro del intent.
val intent = Intent(this@ActividadDetalles, ActividadAgregar::class.java)
intent.putExtra(ActividadAgregar.TAREA_ID, tareaId)
startActivity(intent)
Efecto Ripple en componentes y CardView
En Android, el efecto ripple es un efecto visual que se aplica a los componentes cuando el usuario interactúa con ellos (toca o presiona). Es parte del Material Design y sirve para dar retroalimentación visual, indicando que la acción fue registrada.
Cerrar el teclado al salir de un TextInputEditText (Clear Focus)
En Android, cuando un TextInputEditText mantiene el foco, el teclado virtual (soft keyboard) suele quedarse abierto.
Para evitar esto al salir del campo de texto, se puede quitar el foco y ocultar el teclado.
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
currentFocus?.let { view ->
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
binding.tituloObjetivo.clearFocus()
binding.descripcionObjetivo.clearFocus()
}
return super.dispatchTouchEvent(ev)
}
android:windowSoftInputMode="adjustPan"
En Android, cuando aparece el teclado virtual (soft keyboard), puede afectar cómo se muestra la interfaz de usuario.
El atributo android:windowSoftInputMode en el AndroidManifest.xml permite definir cómo se ajusta la pantalla cuando se abre el teclado.
1. adjustPan
Indica que la ventana no se redimensiona, sino que se desplaza (“pan”) hacia arriba para que el campo de texto que tiene el foco quede visible.
La vista se mueve, pero no cambia su tamaño.
Útil cuando tienes un layout que no quieres que se comprima o redimensione, como un formulario simple.
Formatear el código en Android Studio (Ctrl + Alt + L)
En Android Studio, mantener el código bien organizado y legible es fundamental, sobre todo cuando trabajas en proyectos grandes.
1. Atajo para formatear código
Windows / Linux: Ctrl + Alt + L
Mac: Cmd + Option + L
Al presionar este atajo:
Se organiza la indentación de todo el archivo.
Se alinean correctamente llaves, paréntesis y sangrías.
Se aplican espacios y saltos de línea según las convenciones de estilo de Kotlin/Java.
Opcionalmente, puede reordenar imports si así está configurado.
2. Beneficios de formatear el código
Mejora la legibilidad: más fácil de entender para ti y otros desarrolladores.
Evita errores visuales: ayuda a identificar bloques de código mal cerrados o mal indentados.
Uniformidad: sigue las convenciones de estilo del proyecto.
Preparación para revisión de código o commits: hace que las diferencias sean más claras en Git.
APK y App Bundle para distribución en Google Play Store
Cuando desarrollas una aplicación Android y quieres publicarla en Google Play Store, necesitas generar un archivo empaquetado que contenga tu app. Existen dos formatos principales: APK y App Bundle (AAB).
1. APK (Android Package)
Es el formato clásico de instalación de apps en Android.
Contiene todos los recursos, código y configuraciones de la aplicación en un solo archivo.
Puede ser instalado directamente en un dispositivo Android.
Limitaciones al publicar en Play Store:
Google Play prefiere App Bundles porque permiten optimizar la descarga y el tamaño de la app para cada dispositivo.
Si subes un APK grande, todos los usuarios recibirán todos los recursos, aunque no los necesiten.
2. App Bundle (AAB)
Formato recomendado por Google Play.
Contiene todas las variantes de tu app (diferentes densidades de pantalla, arquitecturas de CPU, idiomas, etc.).
Google Play genera automáticamente un APK optimizado para cada dispositivo, reduciendo el tamaño de descarga.
Ventajas del App Bundle:
Menor tamaño de descarga → los usuarios solo reciben lo necesario.
Distribución más eficiente → recursos específicos según el dispositivo.
Compatibilidad con funciones avanzadas de Play Store, como Dynamic Delivery (entregar módulos bajo demanda).
Cómo generar un App Bundle en Android Studio:
Abrir el proyecto en Android Studio.
Ir a Build → Generate Signed Bundle / APK.
Seleccionar Android App Bundle.
Elegir un KeyStore y firmar la app.
Generar el archivo .aab listo para subir a Google Play.
Curso Android de Principiante a Avanzado: Aprende Kotlin desde cero
Este curso está diseñado para guiarte paso a paso en el desarrollo de aplicaciones móviles con Android y Kotlin, comenzando desde los fundamentos hasta llegar a un nivel avanzado, en el que podrás crear proyectos completos y listos para publicarse en la Google Play Store.
No importa si nunca has programado antes: partiremos desde lo más básico, explicando los conceptos de programación y del ecosistema Android de manera sencilla y práctica. A medida que avances, irás aplicando lo aprendido en proyectos reales que simulan escenarios de la vida profesional.
Durante el curso, trabajaremos con tecnologías y buenas prácticas utilizadas en la industria:
Kotlin: El lenguaje oficial de Android, moderno, seguro y conciso.
MVVM (Model-View-ViewModel): Un patrón de arquitectura que te permitirá organizar mejor tu código y crear aplicaciones escalables y mantenibles.
Room: La librería oficial de Android para manejar bases de datos locales de manera eficiente y segura.
Dagger Hilt: Una herramienta poderosa para la inyección de dependencias, que facilita la gestión de componentes y mejora la calidad del proyecto.
Estos proyectos no se quedarán solo en el ambiente de desarrollo: aprenderás también el proceso de preparar, compilar y publicar tus aplicaciones en Google Play Store, para que tengas la experiencia completa de un desarrollador profesional.
Al finalizar el curso tendrás:
El conocimiento necesario para desarrollar aplicaciones desde cero hasta un producto terminado.
La capacidad de implementar arquitecturas modernas y buenas prácticas.
Experiencia en el ciclo completo de creación, prueba y publicación de aplicaciones.
Tu propio portafolio de proyectos en la Play Store, que podrás mostrar en entrevistas de trabajo o a potenciales clientes.
Este curso no solo busca enseñarte a programar, sino formarte como un desarrollador Android listo para el mundo laboral, con proyectos prácticos, herramientas reales y la experiencia de llevar tus aplicaciones al mercado.