Introducción a Flutter
Flutter es un framework de desarrollo de aplicaciones móviles creado por Google, utiliza el motor de …
leer másEn el mundo del desarrollo de aplicaciones móviles, Flutter se ha destacado como uno de los frameworks más innovadores y eficientes. Sin embargo, el verdadero héroe detrás de su éxito es el lenguaje de programación que lo impulsa: Dart. Anteriormente hemos visto los conceptos básicos tras los Widgets de Flutter y un poco de su historia, ahora nos sumergiremos en sus detalles, explorando sus orígenes, características y cómo se utiliza en el contexto de Flutter para crear aplicaciones móviles, web y pronto videojuegos.
Dart, un lenguaje de programación moderno y escalable, fue desarrollado por Google y presentado al mundo en 2011. Surgió como una alternativa a JavaScript, con el objetivo de abordar algunas de las limitaciones que los desarrolladores enfrentaban al utilizarlo, especialmente en términos de estructura y rendimiento. Dart se diseñó para ser tanto familiar como fácil de aprender para los programadores con experiencia en otros lenguajes orientados a objetos como C++ o Java, ofreciendo una sintaxis clara y concisa.
Desde su concepción, Dart fue ideado para ser un lenguaje versátil, adecuado para el desarrollo tanto en el cliente como en el servidor. Su naturaleza compilada a código máquina lo hace especialmente potente para aplicaciones móviles, donde el rendimiento y la fluidez son críticos. Además, Dart se optimizó para trabajar de manera eficiente en la web, permitiendo a los desarrolladores compilar aplicaciones de alto rendimiento que pueden ejecutarse en cualquier navegador moderno.
Características como el compilado Just-In-Time (JIT) para un desarrollo rápido y el compilado Ahead-Of-Time (AOT) para un rendimiento óptimo en tiempo de ejecución, determinaron la elección de Dart como la opción ideal para el framework. Dart facilita la creación de aplicaciones de alta calidad con animaciones suaves y una interfaz de usuario receptiva, elementos clave en la experiencia del usuario móvil.
Estamos hablando de un lenguaje de programación orientado a objetos, con una sintaxis similar a la de Java o C++. En
Dart, como en muchos otros lenguajes de programación, las variables son un componente básico. Puedes declarar variables
de diferentes tipos de datos, incluyendo: int
, double
, String
, bool
, List
, Map
, Set
y muchos otros.
int age = 25;
double height = 1.75;
String name = 'Dart';
bool isDeveloper = true;
List<String> hobbies = ['programming', 'reading', 'running'];
Map<String, String> languages = {'es': 'Spanish', 'en': 'English'};
Set<String> skills = {'Dart', 'Flutter', 'Java', 'C++'};
Para ejecutar código Dart no necesitamos un IDE completo aunque existan plugins y herramientas para los más comunes, puede utilizarse como lenguaje de scripting y ejecutarse directamente desde la línea de comandos con tenerlo instalado en nuestro sistema. Incluso podemos ejecutar su código fuente directamente desde el navegador web antes que nada si sólo queremos probarlo.
Es de tipado estático, lo que significa que el tipo de una variable se define en el momento de su declaración y no puede cambiar. Esto ayuda a detectar errores en tiempo de compilación y mejora el rendimiento del código. Sin embargo, Dart también ofrece cierta flexibilidad con el tipado dinámico cuando es necesario.
dynamic
si necesitas una variable que pueda cambiar de tipo.
dynamic dynamicVariable = 'Hola Dart';
void main(){
dynamicVariable = 100; // Permite cambiar el tipo
print(dynamicVariable); // Salida: 100
}
Aun siendo un lenguaje de tipado estático, el compilador es capaz de inferir el tipo de una variable basándose en el
valor que se le asigna al momento de su declaración, permitiendo el uso de var
. Esto significa que, aunque no
especifiques explícitamente el tipo de una variable al usar var
, el compilador determina y fija el tipo de esa variable
basándose en el valor inicial que recibe.
var name = 'Dart'; // Dart infiere que 'name' es de tipo String
name = 123; // Esto resultaría en un error porque 'name' es un String
De todas maneras es recomendable no abusar del uso de var
y especificar el tipo de las variables siempre que sea posible
ya que esto facilita la lectura del código y ayuda a detectar errores. En su documentación tienes más información sobre
el tipado en Dart.
El uso de constantes es una práctica común en la programación, y Dart no es una excepción. Éstas, como en cualquier otro
lenguaje no pueden cambiar durante la ejecución del programa. Puedes declarar constantes usando la palabra clave const
o
final
. Suele utilizarse final
cuando necesites una variable que solo se asigne una vez y pueda ser calculada en tiempo
de ejecución. En cambio, usa const
para valores que nunca cambian y pueden ser determinados en tiempo de compilación,
asegurando que todas las instancias de ese valor en el programa sean idénticas y no mutables.
const String name = 'Dart';
final DateTime now = DateTime.now();
Las funciones en Dart son bloques de código que realizan una tarea específica y pueden ser reutilizadas en diferentes partes de tu programa. Puedes definir funciones con o sin parámetros y especificar el tipo de retorno. Si no especificas el tipo de retorno, el tipo de retorno predeterminado es dynamic.
// Función sin parámetros y sin retorno
void sayHello() {
print('Hello Dart!');
}
// Función con parámetros y sin retorno
void sayHelloTo(String name) {
print('Hello $name!');
}
// Función con parámetros y con retorno
String sayHelloTo(String name) {
return 'Hello $name!';
}
Dart ofrece la posibilidad de definir funciones anónimas, que son funciones sin nombre y que pueden ser útiles en varias situaciones, como para ser asignadas a variables o pasadas como parámetros a otras funciones. Son un recurso poderoso y flexible, se utilizan comúnmente en situaciones donde se requieren funciones breves o de uso único, como en los callbacks.
// Función anónima asignada a una variable
var sayHello = () {
print('Hello Dart!');
};
// Función anónima pasada como parámetro
void sayHelloTo(String name, Function callback) {
callback(name);
}
// Llamada a la función 'sayHelloTo' pasando una función anónima como parámetro
sayHelloTo('Dart', (name) {
print('Hello $name!');
});
// Función anónima con parámetros y retorno
var numbersList = [1, 2, 3, 4, 5];
var squares = numbersList.map((number) {
return number * number;
});
// Printing the squares of the numbers
print("Squares of the numbers: $squares");
Utilizar return
o un callback cambia la forma en que se devuelven los valores de una función. Cuando se utiliza
return
, estás devolviendo un valor directamente desde una función al punto en el que se llamó a esa función. Esto es útil cuando
deseas obtener un resultado inmediato y sincrónico de una función.
int sum(int a, int b) {
return a + b;
}
void main() {
var result = sum(5, 3);
print("La suma es: $result");
}
Un mecanismo de callback implica pasar una función como argumento a otra función para que sea ejecutada en un momento posterior o en respuesta a un evento específico. Esto es especialmente útil en situaciones asincrónicas o cuando deseas realizar ciertas acciones después de que se complete una operación. Aquí hay un ejemplo:
void performOperation(int a, int b, void Function(int) callback) {
int result = a + b;
// Simula una operación asincrónica, como una solicitud de red o un temporizador.
Future.delayed(Duration(seconds: 2), () {
callback(result);
});
}
void main() {
performOperation(5, 3, (result) {
print("La suma es: $result");
});
}
En este caso, la respuesta nos llegaría dos segundos después de haber ejecutado la función. Como podemos observar, la
diferencia a la hora de obtener el resultado de cada función es que con return
obtenemos el valor de la función de
forma inmediata, mientras que con un callback obtenemos el valor de la función en un momento concreto. De esta forma
podemos realizar operaciones asincrónicas y obtener el resultado cuando esté disponible, pero no es la única. En Dart
también podemos utilizar async
y await
, además de Future
para realizar operaciones asíncronas.
Existen varios mecanismos en Dart para realizar operaciones asíncronas, los más utilizados además de async
/await
son
los Future
. Un Future
es un objeto que representa un valor potencial o un error que estará disponible en algún
momento en el futuro. Son utilizados para realizar operaciones asíncronas, como solicitudes de red o consultas a una
base de datos, y obtener el resultado cuando estuviera disponible.
// Define una función asíncrona que toma dos números y devuelve un resultado tras 2 segundos.
Future<int> performOperation(int a, int b) async {
int result = a + b;
// Simula una operación asincrónica, como una solicitud de red o un temporizador.
await Future.delayed(Duration(seconds: 2));
return result;
}
void main() async {
print("Inicio del programa");
// Llamada a performOperation y espera a que se complete.
int result = await performOperation(5, 3);
print("El resultado es: $result");
print("Final del programa");
}
Es común que las aplicaciones móviles necesiten realizar operaciones asincrónicas, como solicitudes de red o consultas a
una base de datos. En estos casos, es recomendable utilizar async
y await
para realizar estas operaciones de forma
asincrónica. También podemos utilizar Future
para solicitar datos sin conocer de antemano el tiempo que tardará en
estar disponible.
La combinación de cada una de las técnicas mencionadas es decisión última del desarrollador y dependerá de las necesidades y contexto de cada aplicación. En la documentación de Dart tienes más información sobre la asincronía que te recomiendo visitar para profundizar en el tema.
Dart acepta todo tipo de estructuras de control como cualquier otro lenguaje de programación,
incluyendo if
, else
, switch
, for
, while
, etc. Como estamos en una serie de artículos sobre Flutter, vamos a
dar por supuesto que ya se conocen las estructuras de control básicas de cualquier lenguaje de programación.
Mencionaremos algunos de los más utilizados y específicos de Dart, aun a pesar de que también puedan existir en otros
lenguajes.
El operador ternario es una forma abreviada de la estructura if-else
y se utiliza para asignar un valor a una variable
o para realizar una operación en función de una condición.
// Asigna el valor 'Dart' a la variable 'name' si 'isDart' es true, de lo contrario asigna 'Flutter'.
String name = isDart ? 'Dart' : 'Flutter';
El operador de cascada (..) es una forma abreviada de llamar a varios métodos en un objeto. Esto es especialmente útil cuando necesitas realizar varias operaciones de una vez, como agregar varios elementos a una lista o establecer algunas de las propiedades de un widget.
// Crea una lista y agrega varios elementos a la vez.
var numbersList = []
..add(1)
..add(2)
..add(3)
..add(4)
..add(5);
// Crea un widget y establece varias propiedades a la vez.
var widget = Container()
..color = Colors.blue
..width = 100
..height = 100;
El operador de propagación es una forma abreviada de agregar los elementos de una lista a otra lista. De esta manera no es necesario recorrer la lista para agregar cada elemento a la nueva lista.
var numbersList = [1, 2, 3, 4, 5];
var newNumbersList = [0, ...numbersList];
El operador null-aware
es una forma abreviada de verificar si una variable es nula y asignar un valor predeterminado
en caso de que lo sea.
String? predefinedName = 'Alice';
// Asigna el valor 'Alice' a la variable 'name' si 'predefinedName' no es nulo, como este caso, de lo contrario asigna 'Bob'.
String name = predefinedName ?? 'Bob';
El operador de null-aware
de acceso condicional es una forma abreviada de acceder a una propiedad de un objeto si ese
objeto no es nulo. De serlo no se accede a la propiedad.
User? user = User();
// Accede a la propiedad 'name' del objeto 'user' si 'user' no es nulo.
String name = user?.name ?? 'Dart';
Con las clases y objetos sucede más de lo mismo, Dart es un lenguaje de programación orientado a objetos y por tanto
dispone de ellos como cualquier otro lenguaje de este tipo. Para crear una clase en Dart, se utiliza la palabra clave
class
, seguida del nombre de la clase y su definición. Veamos un ejemplo de una clase llamada Person que tiene atributos
y métodos básicos:
class Person {
String name;
int age;
Person(this.name, this.age);
void introduceYourself() {
print('Hola, me llamo $name y tengo $age años.');
}
}
void main() {
var person = Person('Alice', 30);
person.introduceYourself(); // Salida: Hola, me llamo Alice y tengo 30 años.
}
Puedes recibir parámetros opcionales utilizando llaves {}
en la definición del constructor para crear un constructor
más flexible que permite enviar bajo demanda los valores que en él se definen.
class Person {
String name;
int age;
String? hairColor; // Parámetro opcional
Person(this.name, this.age, {this.hairColor});
void introduceYourself() {
if (hairColor != null) {
print('Hola, mi nombre es $name, tengo $age años y mi pelo es de color $hairColor.');
} else {
print('Hola, mi nombre es $name, tengo $age años.');
}
}
}
void main() {
var person1 = Person('Bob', 30);
var person2 = Person('Alice', 25, hairColor: 'Azul');
person1.introduceYourself(); // Salida: Hola, mi nombre es Bob y tengo 30 años.
person2.introduceYourself(); // Salida: Hola, mi nombre es Alice, tengo 25 años y mi pelo es de color Marrón.
}
La evolución de este lenguaje ha sido muy pronunciada desde su creación y ha ido incorporando nuevas características y mejoras con el paso del tiempo. Una de las más importantes es la Null Safety, que se introdujo en la versión 2.12 de Dart y que es una de las principales razones por las que Flutter ha decidido migrar a la versión 2.12 de Dart. Permite a los desarrolladores evitar errores de tiempo de ejecución relacionados con el valor null.
String? name = null;
print(name.length); // Error: La propiedad 'length' no puede ser accedida de forma incondicional porque el receptor puede ser 'null'.
En el ejemplo anterior, la variable name es de tipo String?, lo que significa que puede ser String o null. Si la variable name es null, el programa fallará en tiempo de ejecución porque no se puede acceder a la propiedad length de un valor null. Para evitar este error, podemos utilizar el operador de null-aware de acceso condicional ?. para acceder a la propiedad length solo si la variable name no es null.
String? name = null;
print(name?.length); // Sin error
En la documentación de Dart tienes más información sobre la Null Safety que te recomiendo visitar para profundizar en el tema.
Hemos visto los conceptos básicos de Dart, los tipos de datos que existen, las funciones y lo fundamental para entender el lenguaje. Te invito a poner en práctica lo aprendido en este artículo con un pequeño ejercicio creando una sencilla aplicación para gestionar tareas por consola de comandos que permita crear, listar y eliminar. Una vez terminado puedes compararlo con este ejemplo de lo que podría ser el código fuente final dónde podrás identificar muchos de los conceptos aquí explicados.
import 'dart:io';
class Task {
String description;
bool isCompleted;
Task(this.description, this.isCompleted);
// Método para convertir una tarea a una cadena de texto que se puede guardar en un archivo.
String toFileString() {
return '$description|$isCompleted';
}
// Método estático para crear una tarea desde una cadena de texto recuperada de un archivo.
static Task fromFileString(String line) {
final parts = line.split('|');
final description = parts[0];
final isCompleted = parts[1] == 'true';
return Task(description, isCompleted);
}
}
class TaskList {
List<Task> tasks = [];
void addTask(String description) {
final task = Task(description, false);
tasks.add(task);
}
void completeTask(int index) {
if (index >= 0 && index < tasks.length) {
tasks[index].isCompleted = true;
}
}
void removeTask(int index) {
if (index >= 0 && index < tasks.length) {
tasks.removeAt(index);
}
}
void displayTasks() {
print("Tasks:");
for (var i = 0; i < tasks.length; i++) {
final task = tasks[i];
final status = task.isCompleted ? "Completed" : "Pending";
print("$i. ${task.description} ($status)");
}
}
// Método para guardar la lista de tareas en un archivo.
void saveToFile(String filename) {
final file = File(filename);
final lines = tasks.map((task) => task.toFileString()).toList();
file.writeAsStringSync(lines.join('\n'));
}
// Método para cargar la lista de tareas desde un archivo.
void loadFromFile(String filename) {
final file = File(filename);
if (file.existsSync()) {
final lines = file.readAsLinesSync();
tasks = lines.map((line) => Task.fromFileString(line)).toList();
}
}
}
void main() {
final taskList = TaskList();
final filename = 'tasks.txt';
// Cargar tareas desde el archivo (si existe).
taskList.loadFromFile(filename);
while (true) {
print("Elige una acción:");
print("1. Añadir tarea");
print("2. Mostrar tareas");
print("3. Completar tarea");
print("4. Eliminar tarea");
print("5. Guardar y Salir");
var choice = int.tryParse(stdin.readLineSync() ?? '');
switch (choice) {
case 1:
print("Introduce la descripción de la tarea:");
var description = stdin.readLineSync() ?? '';
taskList.addTask(description);
break;
case 2:
taskList.displayTasks();
break;
case 3:
taskList.displayTasks();
print("Introduce el número de tarea para marcarla como `completada`:");
var index = int.tryParse(stdin.readLineSync() ?? '');
taskList.completeTask(index ?? -1);
break;
case 4:
taskList.displayTasks();
print("Introduce el número de tarea a eliminar:");
var index = int.tryParse(stdin.readLineSync() ?? '');
taskList.removeTask(index ?? -1);
break;
case 5:
// Guardar tareas en el archivo y salir.
taskList.saveToFile(filename);
exit(0);
break;
default:
print("Elección inválida. Inténtalo de nuevo.");
}
}
}
En este artículo, hemos explorado las bases de Dart, el lenguaje de programación que impulsa el desarrollo de aplicaciones en Flutter. Hemos cubierto conceptos esenciales, como tipos de datos, funciones, clases y objetos, así como características un poco más avanzadas, incluyendo la programación asincrónica.
Hemos demostrado cómo crear programas interactivos en Dart, como una lista de tareas con capacidad de agregar, mostrar, completar y eliminar tareas, y cómo persistir los datos en archivos para hacer que las tareas sean accesibles a lo largo del tiempo. Para el próximo continuaremos indagando en Flutter y empezaremos a ver algunos ejemplos.
¡Happy Coding!
Quizá te puedan interesar
Flutter es un framework de desarrollo de aplicaciones móviles creado por Google, utiliza el motor de …
leer másEn el capítulo anterior hemos visto los orígenes del framework Flutter, quién lo creó, un poco sobre …
leer másEn este caso veremos cómo podemos aprovechar el auto relleno de Google Admob para publicitar …
leer másDe concepto a realidad