Aprendiendo Flutter: Estructuras de datos y Control de flujo en Dart

Si desarrollas aplicaciones móviles o multiplataforma, seguro has escuchado, o incluso trabajado, con frameworks como Flutter, y por ende, conoces Dart, su lenguaje nativo. En este hilo de publicaciones voy a documentar todo mi aprendizaje sobre Dart y Flutter con el objetivo de desarrollar mi propia aplicación y, a la vez, brindar una guía de estudio para cualquiera que esté interesado en aprender sobre este fascinante rubro.
En la publicación anterior, tuvimos una introducción a Dart, explorando los tipos de datos y variables que existen en su ecosistema. Hoy vamos a profundizar en este tema y daremos un vistazo a las Estructuras de Datos y de Control de Flujo en Dart.
Estas publicaciones van dirigidas a personas con una base de programación. Si todo esto es nuevo para ti, te recomiendo aprender primero los fundamentos básicos sobre lógica de programación antes de tocar tu primer lenguaje. Y si ya tienes conocimientos y te gustaría aprender tu primer lenguaje, o eres un desarrollador que quiere aprender Dart para adentrarse en el mundo del desarrollo de aplicaciones multiplataforma, te recomiendo leer mi publicación sobre Dart y su sintaxis básica.
En programación, comprender cómo organizar y manipular la información, así como dirigir el flujo de ejecución de nuestro código, es algo fundamental. En Dart, al igual que en muchos otros lenguajes, contamos con distintas manera para lograr esto.
Estructuras de Datos
Las estructuras de datos son formas de almacenar datos de forma organizada, permitiendo la gestión eficiente de estos. Cada tipo de estructura nos va a permitir conseguir diferentes resultados, y el uso correcto de las mismas puede impactar significativamente en el rendimiento de nuestra aplicación.
Basándonos en la documentación oficial del lenguaje, Dart ofrece tres tipos de colecciones fundamentales: Listas, Sets y Mapas, junto con elementos de colección que permiten su construcción dinámica. Vamos a profundizar un poco más en cada una:
Listas
Una Lista, también llamada Array, es seguramente el conjunto ordenado de datos más común que hay en los lenguajes de programación. Piensa en una lista como una fila ordenada de elementos, donde cada uno tiene una posición específica, su índice (Siempre empezando desde el cero). Son de las colecciones más versátiles y normalmente se usan para agrupar elementos del mismo tipo, aunque pueden ser de diferente tipo si no se especifica uno.
Cabe destacar que las listas:
- Son mutables, es decir, puedes añadir, eliminar o modificar elementos después de haberla creado.
- Son ordenas y los elementos mantienen el orden en el que fueron añadidos, pudiendo acceder a estos a través de su posición númerica.
- Permiten duplicados, así que en una lista puede haber el mismo valor múltiples veces.
// Lista de números enteros
List<int> edades = [25, 30, 22, 30];
print('Edades: $edades');
// Salida: Edades: [25, 30, 22, 30]
// Acceder a elementos
print('Primera edad: ${edades[0]}');
// Salida: Primera edad: 25
Al igual que las variables, en caso de no ser definido, Dart va a inferir automáticamente el tipo de dato permitido en una lista basándose en los elementos que se le agregan inicialmente.
var numerosEnteros = [10, 20, 30];
// numerosEnteros.add(5.5);
// Esto va a devolver el error: El tipo 'double' no puede ser asignado al tipo 'int'
// Dart infiere el tipo List<int>
Sets
En Dart, un Set es una colección no ordenada de elementos únicos. En un set el orden es indiferente, pero sí se asegura de que no existan valores duplicados. Si un elemento ya existente se repite, simplemente lo va a ignorar.
// Set de nombres
Set<String> nombres = {'Ana', 'Juan', 'Pedro', 'Ana'};
print('Nombres: $nombres');
// Salida: Nombres: {Ana, Juan, Pedro}
// Si intentas añadir un duplicado, no va a cambiar el Set
nombres.add('Juan');
print('Nombres después de añadir duplicado: $nombres');
// Salida: Nombres después de añadir duplicado: {Ana, Juan, Pedro}
También se pueden inicializar sets vacíos, incluso sin definir su tipo, pero como buena práctica, es recomendado hacerlo.
// Crear un Set vacío
Set<int> numerosPrimos = {};
numerosPrimos.add(2);
numerosPrimos.add(3);
print('Números primos: $numerosPrimos');
// Salida: Números primos: {2, 3}
Mapas
Un Mapa es una colección de pares clave-valor. Cada clave dentro de un par tiene un valor asociado, y tanto las claves como los valores pueden ser de cualquier tipo. Piensa en un diccionario donde cada palabra tiene una definición (Aquellos que han trabajado con Python, ya estarán familiarizados con esto). Las claves deben ser únicas, pero múltiples claves pueden apuntar al mismo valor.
- Al igual que un set, los mapas, generalmente, son colecciones no-ordenadas,
- Como las colecciones vistas anteriormente, también son mutables.
- Y por último, cada clave debe ser diferente; si añades una clave existente, su valor será sobrescrito.
// Mapa de capitales de países
Map<String, String> capitales = {
'España': 'Madrid',
'Francia': 'París',
'Italia': 'Roma',
};
print('Capitales: $capitales');
// Salida: Capitales: {España: Madrid, Francia: París, Italia: Roma}
// Acceder a un valor por su clave
print('Capital de Francia: ${capitales['Francia']}');
// Salida: Capital de Francia: París
// Modificar un valor existente
capitales['España'] = 'Barcelona';
print('Capital de España modificada: ${capitales['España']}');
// Salida: Capital de España modificada: Barcelona
Ya hemos visto las estructuras de datos y las herramientas que nos ofrece Dart para trabajar con ellas. Si quisieras profundizar más sobre este punto, te recomiendo leer la documentación oficial de Dart sobre colecciones en su página web.
Control de Flujo
Cuando desarrollamos un programa, nos importar tener control sobre las acciones que se ejecutan; es decir, la capacidad de dirigir el flujo de ejecución de una aplicación. El control de flujo es precisamente eso: la habilidad de decidir qué bloques de código se ejecutan basándose en ciertas condiciones y decisiones, y cuántas veces deben repetirse.
La mayoría de los lenguajes de programación nos ofrecen distintas herramientas para conseguir este control, y Dart no es la excepción. Ahora, vamos a hablar sobre las sentencias de control que encontraremos con mayor frecuencia en este campo, tanto en Dart como en otros lenguajes: las Condicionales y Bucles.
Declaraciones condicionales
Estas declaraciones nos permiten indicarle al programa que ejecute o no un bloque de código, basándose en si una condición es verdadera (true) o falsa (false). Las más estandarizadas en los lenguajes de programación son las sentencias If y else, else If y Switch.
If y Else
En una sentencia if, el bloque de código que se encuentre dentro de esta se ejecutará siempre y cuando la condición establecida retorne un valor verdadero. Si la condición retorna un valor falso, el programa continuará al siguiente bloque. Sin embargo, si quisiéramos ejecutar una instrucción específica cuando esto suceda, podemos indicarlo usando la sentencia else
//Ejemplo básico de un If y else
int edad = 20;
if (edad >= 18) {
print('La persona es mayor de edad.');
} else {
print('La persona es menor de edad.');
}
Else if
Cuando necesites evaluar múltiples condiciones en secuencia, te resultará más útil encadenar varios if y else usando la sentencia else if. En una sentencia else if se evalúan distintas condiciones en secuencia para ejecutar la primera que retorne un valor verdadero y luego salir de esa estructura condicional. En caso de no cumplirse ninguna de las condiciones else if, se puede indicar una respuesta predeterminada finalizando con un else.
// Ejemplo de if-else y else if
int puntuacion = 85;
if (puntuacion >= 90) {
print('Calificación: A (Excelente)');
} else if (puntuacion >= 80) {
print('Calificación: B (Muy Bien)');
} else if (puntuacion >= 70) {
print('Calificación: C (Bien)');
} else {
print('Calificación: D (Necesita mejorar)');
}
Switch
Si practicas un poco lo que hemos visto hasta ahora, te habrás fijado que trabajar únicamente con if o con else if puede llegar a ser poco práctico si se requiere evaluar una cadena larga de condiciones. Para ese tipo de casos, es más limpio y eficiente usar las sentencias Switch.
En Dart, esta herramienta es más versátil que en otros lenguajes, implementando las sentencias switch y los patrones switch.
- Una sentencia switch evalúa un valor contra una serie de casos. Cuando el valor coincide con el patrón de un caso, el código dentro de ese caso se ejecuta.
- Las sentencias switch no requieren un break para finalizar la estructura condicional porque Dart lo hace automáticamente cuando un caso coincide, pero si dejamos un caso vacío (Fall-through), pasará a evaluar el que le siga. Usaremos la sentencia default cuando ninguno de los casos coincida.
String comando = 'ABRIR';
switch (comando) {
case 'ABRIR':
print('Abriendo archivo...');
// Cuando un case queda vacío, pasa automáticamente al siguiente
case 'GUARDAR':
case 'SALIR':
print('Guardando cambios y saliendo...');
case 'ELIMINAR':
print('Eliminando elemento...');
default: // Se ejecuta si ningún caso anterior coincide
print('Comando desconocido.');
}
// Salida si comando es 'ABRIR': Abriendo archivo...
// Salida si comando es 'GUARDAR': Guardando cambios y saliendo...
- Las expresiones switch producen un valor, a diferencia de las sentencias switch que ejecutan un bloque de código. Son ideales para asignar un valor a una variable o para usarlas directamente en otras expresiones.
- En vez de usar llaves, se utiliza la sintaxis
=>
, no llevan la palabra clavecase
y lo más importante, es que tienen que ser exhaustivas. Las expresiones switch deben abarcar todos los casos posibles para devolver un valor, y si no se pueden cubrir todos, se tiene que agregar la sentencia_
para asignar un valor predeterminado.
String nivelEstudiante = 'intermedio';
String mensaje = switch (nivelEstudiante) {
'principiante' => '¡Bienvenido al curso básico!',
'intermedio' => 'Estás progresando muy bien.',
'avanzado' => 'Eres un experto en el tema.',
_ => 'Nivel no reconocido.', // El '_' asegura la exhaustividad
};
print(mensaje); // Salida: Estás progresando muy bien.
Declaraciones de Bucle
Los bucles permiten ejecutar un bloque de código repetidamente, lo cual es ideal para ejecutar tareas repetitivas o procesar colecciones de datos. En Dart, podemos utilizar bucles for, while y do-while:
- Uno de los más conocidos y el más común de todos. El for es ideal cuando necesitas iterar sobre los elementos de una colección o cuando eres consciente de cuántas veces necesitas debes repetir la acción.
// Imprime números del 0 al 4
for (int i = 0; i < 5; i++) {
print('Iteración número: $i');
}
- Para iterar cómodamente sobre los elementos de una colección, podemos usar la variante for-in
List<String> frutas = ['manzana', 'banana', 'cereza'];
for (String fruta in frutas) {
print('Me gusta la $fruta.');
}
Set<int> numeros = {10, 20, 30};
for (int num in numeros) {
print('Número en el set: $num');
}
- El bucle while ejecuta un bloque de código mientras una condición sea verdadera. Es importante aclarar que La condición se evalúa antes de cada iteración. Si la condición es falsa desde el principio, el bloque de código nunca se ejecutará.
- Contruye la lógica de tu bucle para asegurarte que, eventualmente, la condición se vuelva falsa, porque si siempre se mantiene en verdadero, caerá en un loop infinito que puede causar que el programa deje de responder.
// Imprime números del 0 al 2
int contador = 0;
while (contador < 3) {
print('Contador: $contador');
contador++;
}
- El bucle do-while es similar a while, pero garantiza que el bloque de código se ejecute al menos una vez, ya que la condición se evalúa después de la primera iteración.
int i = 0;
do {
print('Esta se ejecuta al menos una vez. i = $i');
i++;
} while (i < 0);
Palabras clave en los bucles
En los bucles, también podemos usar palabras clave como el break, además de la palabra clave continue. Estas expresiones nos permitirán tener un mejor control sobre el flujo de los bucles que estamos trabajando.
-
El break actuará de la misma forma que en un estructura switch, indicando que termine el bucle y pase al siguiente bloque de código.
-
El continue, en cambio, se utiliza para saltar la iteración actual de un bucle y pasar a la siguiente iteración. El código restante dentro del bloque del bucle para esa iteración es ignorado.
//Palabra clave break
for (int i = 0; i < 10; i++) {
if (i == 5) {
print('Se encontró el número 5, saliendo del bucle.');
break; // Sale del bucle for
}
print('Procesando: $i');
}
print('Fin del programa.');
//Palabra clave continue
for (int i = 0; i < 5; i++) {
if (i % 2 != 0) { // Si i es impar
print('Saltando número impar: $i');
continue; // Pasa a la siguiente iteración
}
print('Procesando número par: $i');
}
print('Bucle completado.');
Y con esto finalizamos por hoy. Espero que este post te esté siendo de gran utilidad para aprender sobre este lenguaje.
En la siguiente publicación, abarcaremos las funciones en Dart para así dar paso a empezar a trabajar directamente en un entorno de flutter.
Mientras tanto, si quieres profundizar en cualquiera de las estructuras o conceptos que abarcamos hoy, te invito a consultar la documentación oficial de Dart, donde podrás documentarte a profundidad.
¡Nos vemos!