1. Introducción
Dart es el lenguaje de programación para Flutter, el kit de herramientas de IU de Google diseñado para crear aplicaciones atractivas compiladas de forma nativa que funcionen en dispositivos móviles, la Web y apps de escritorio a partir de una base de código única.
En este codelab, te brindamos una introducción a Dart con un enfoque en las características que los desarrolladores de Java podrían no esperar. Puedes escribir funciones de Dart en 1 minuto, secuencias de comandos en 5 minutos y apps en 10 minutos.
Qué aprenderás
- Cómo crear constructores
- Cómo especificar los parámetros de maneras diferentes
- Cuándo y cómo crear métodos get y métodos set
- Cómo Dart controla la privacidad
- Cómo crear fábricas
- Cómo funciona la programación funcional en Dart
- Otros conceptos principales de Dart
Requisitos
Para completar este codelab, solo necesitas un navegador.
Escribirás y ejecutarás todos los ejemplos en DartPad, una herramienta interactiva basada en el navegador que te permite jugar con las características del lenguaje y las bibliotecas principales de Dart. Si prefieres, puedes usar un IDE, como WebStorm, IntelliJ con el complemento de Dart o Visual Studio Code con la extensión del código Dart.
¿Qué te gustaría aprender de este codelab?
2. Cómo crear una clase simple de Dart
Comenzarás compilando una clase simple de Dart con la misma funcionalidad que la clase Bicycle
del instructivo de Java. La clase Bicycle
contiene algunas variables de instancia privada con métodos get y métodos set. Un método main()
crea una instancia de Bicycle
y la imprime en la consola.
Cómo iniciar DartPad
En este codelab, se brinda una instancia nueva de DartPad para cada conjunto de ejercicios. Con el siguiente vínculo, se abre una instancia nueva, que contiene un ejemplo predeterminado de "Hello". Puedes continuar usando el mismo DartPad en todo el codelab, pero si haces clic en Restablecer, DartPad te llevará nuevamente al ejemplo predeterminado, y perderás tu trabajo.
Cómo definir una clase Bicycle
Arriba de la función
main()
, agrega una clase Bicycle
con tres variables de instancia. También, quita el contenido de main()
, como se muestra en el siguiente fragmento de código:
class Bicycle {
int cadence;
int speed;
int gear;
}
void main() {
}
Observaciones
- Con este ejemplo, el analizador de Dart genera un error que te informa que las variables deben inicializarse porque no son anulables. Deberás corregir eso en la siguiente sección.
- El método principal de Dart se llama
main()
. Si necesitas acceder a los argumentos de la línea de comandos, puedes agregarlos:main(List<String> args)
. - El método
main()
está en el nivel superior. En Dart, puedes definir el código fuera de las clases. Las variables, las funciones, los métodos get y métodos set pueden permanecer fuera de las clases. - El ejemplo original de Java declara las variables de instancia privada con la etiqueta
private
, que Dart no usa. Más adelante, en la sección Cómo agregar una variable de solo lectura, obtendrás más información sobre la privacidad. - Ni
main()
niBicycle
se declaran comopublic
, porque todos los identificadores son públicos de forma predeterminada. Dart no tiene palabras clave parapublic
,private
niprotected
. - Por convención, Dart usa una sangría de dos caracteres en lugar de cuatro. No tienes que preocuparte por las convenciones de espacios en blanco de Dart, gracias a una herramienta práctica llamada formato dart. Como se afirma en las convenciones del código Dart ( Dart en vigencia), las reglas oficiales de Dart para controlar espacios en blanco son cualquier formato de dart que produzca.
Cómo definir un constructor Bicycle
Agrega el siguiente constructor a la clase
Bicycle
:
Bicycle(this.cadence, this.speed, this.gear);
Observaciones
- Este constructor no tiene cuerpo, que es válido en Dart.
- Si olvidas el punto y coma (
;
) al final de un constructor sin cuerpo, DartPad muestra el siguiente error: "Debe proporcionarse el cuerpo de la función". - Un acceso directo práctico es usar
this
en la lista de parámetros de un constructor para asignar valores a variables de instancia. - El código anterior es equivalente a lo siguiente, que usa una lista de inicializador:
Bicycle(int cadence, int speed, int gear)
: this.cadence = cadence,
this.speed = speed,
this.gear = gear;
Cómo darle formato al código
Vuelve a darle formato al código Dart en cualquier momento haciendo clic en Formato en la parte superior de la IU de DartPad. Volver a cambiar el formato es particularmente útil cuando pegas el código en DartPad y la justificación está desactivada.
Haz clic en Formato.
Cómo crear un instancia Bicycle e imprimirla
Agrega el siguiente código a la función
main()
:
void main() {
var bike = new Bicycle(2, 0, 1);
print(bike);
}
Quita la palabra clave opcional
new
:
var bike = Bicycle(2, 0, 1);
Observación
- La palabra clave
new
se volvió opcional en Dart 2. - Si sabes que el valor de una variable no cambiará, puedes usar
final
en lugar devar
. - La función
print()
acepta cualquier objeto (no solo strings). Lo convierte en unString
con el métodotoString()
del objeto.
Cómo ejecutar el ejemplo
Para ejecutar el ejemplo, haz clic en Ejecutar en la parte superior de la ventana de DartPad. Si el botón Ejecutar no está habilitado, consulta la sección Problemas más adelante en esta página.
Deberías ver el siguiente resultado:
Instance of 'Bicycle'
Observación
- No deberían aparecer errores ni advertencias, lo que indica que el tipo de inferencia funciona y que el analizador deduce que la sentencia que comienza con
var bike =
define una instancia Bicycle.
Cómo mejorar el resultado
Si bien es correcto el resultado "Instancia de 'Bicycle'", no es muy informativo. Todas las clases de Dart tienen un método toString()
que puedes anular para brindar un resultado más útil.
Agrega el siguiente método
toString()
en cualquier lugar de la clase Bicycle
:
@override
String toString() => 'Bicycle: $speed mph';
Observaciones
- La anotación
@override
le indica al analizador que anularás un miembro de manera intencional. El analizador genera un error si no puedes realizar la anulación de forma correcta. - Dart admite comillas simples o dobles cuando se especifican strings.
- Usa la interpolación de strings para colocar el valor de una expresión dentro de un literal de string:
${expression}
. Si la expresión es un identificador, puedes omitir las llaves:$variableName
. - Acorta las funciones o métodos de una línea con la notación de fechas gruesas (
=>
).
Cómo ejecutar el ejemplo
Haz clic en Ejecutar.
Deberías ver el siguiente resultado:
Bicycle: 0 mph
¿Tienes problemas? Verifica tu código.
Cómo agregar una variable de solo lectura
El ejemplo original de Java define el elemento speed
como una variable de solo lectura: lo declara como privado y solo brinda un método get. Luego, proporcionarás la misma funcionalidad en Dart.
Abre
bicycle.dart
en DartPad (o continúa usando tu copia).
Para marcar un identificador de Dart como privado para su biblioteca, comienza su nombre con un guion bajo (_
). Puedes convertir speed
a solo lectura si cambias su nombre y agregas un método get.
Cómo convertir speed en una variable de instancia privada de solo lectura
En el constructor
Bicycle
, quita el parámetro speed
:
Bicycle(this.cadence, this.gear);
En
main()
, quita el segundo parámetro (speed
) de la llamada al constructor Bicycle
:
var bike = Bicycle(2, 1);
Cambia los casos restantes de
speed
a _speed
. (Dos lugares)
Inicializa
_speed
en 0:
int _speed = 0;
Agrega el siguiente método get a la clase
Bicycle
:
int get speed => _speed;
Observaciones
- Cada variable (incluso si es un número) debe inicializarse o declararse anulable agregando
?
a la declaración de tipo. - El compilador de Dart aplica, de manera forzosa, la privacidad de la biblioteca para cualquier identificador con el prefijo de guion bajo. En general, la privacidad de la biblioteca significa que el identificador solo es visible dentro del archivo (no solo la clase) en el que se define el identificador.
- De forma predeterminada, Dart brinda métodos get y métodos set implícitos para todas las variables de instancia pública. No necesitas definir tus propios métodos get o métodos set, a menos que desees aplicar, de manera forzosa, variables de solo lectura o solo escritura, calcular o verificar un valor, o actualizar un valor en otro lugar.
- El ejemplo original de Java brindó métodos get y métodos set para los elementos
cadence
ygear
. El ejemplo de Dart no necesita métodos get ni métodos set explícitos para estos elementos, por lo que solo usa variables de instancia. - Puedes comenzar con un campo simple, como
bike.cadence
, y volver a darle formato más adelante para usar métodos get y métodos set. La API continúa siendo la misma. En otras palabras, ir de un campo a un método get y un método set no implica un cambio rotundo en Dart.
Cómo terminar de implementar speed como una variable de instancia de solo lectura
Agrega los siguientes métodos a la clase
Bicycle
:
void applyBrake(int decrement) {
_speed -= decrement;
}
void speedUp(int increment) {
_speed += increment;
}
El ejemplo definitivo de Dart es similar al ejemplo original de Java, pero es más compacto con 23 líneas en lugar de 40:
class Bicycle {
int cadence;
int _speed = 0;
int get speed => _speed;
int gear;
Bicycle(this.cadence, this.gear);
void applyBrake(int decrement) {
_speed -= decrement;
}
void speedUp(int increment) {
_speed += increment;
}
@override
String toString() => 'Bicycle: $_speed mph';
}
void main() {
var bike = Bicycle(2, 1);
print(bike);
}
¿Tienes problemas? Verifica tu código.
3. Cómo usar parámetros opcionales (en lugar de sobrecarga)
En el siguiente ejercicio, se define una clase Rectangle
, otro ejemplo del instructivo de Java.
En el código Java, se muestran constructores de sobrecarga, una práctica común en Java en la que los constructores tienen el mismo nombre, pero la cantidad o el tipo de parámetros son diferentes. Dart no admite constructores de sobrecarga y controla esta situación de manera diferente, como se explicará en esta sección.
Abre el ejemplo de Rectangle en DartPad.
Cómo agregar un constructor Rectangle
Agrega un solo constructor vacío que reemplace los cuatro constructores en el ejemplo de Java:
Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});
Este constructor usa parámetros opcionales con nombre.
Observaciones
this.origin
,this.width
ythis.height
usan abreviaturas para asignar variables de instancia a la declaración de un constructor.this.origin
,this.width
ythis.height
son parámetros opcionales con nombre. Los parámetros con nombre se encierran entre llaves ({}
).- La sintaxis
this.origin = const Point(0, 0)
especifica un valor predeterminado dePoint(0,0)
para la variable de instanciaorigin
. El valor predeterminado especificado debe ser una constante de tiempo de compilación. Este constructor proporciona valores predeterminados para las tres variables de instancias.
Cómo mejorar el resultado
Agrega la siguiente función
toString()
a la clase Rectangle
.
@override
String toString() =>
'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
Cómo usar el constructor
Reemplaza
main()
por el siguiente código para verificar que puedes crear instancias de Rectangle
con solo los parámetros que necesitas:
main() {
print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
print(Rectangle(origin: const Point(10, 10)));
print(Rectangle(width: 200));
print(Rectangle());
}
Observación
- El constructor de Dart para
Rectangle
es una línea de código, en comparación con las 16 líneas de código para constructores equivalentes en la versión de Java.
Cómo ejecutar el ejemplo
Deberías ver el siguiente resultado:
Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0
¿Tienes problemas? Verifica tu código.
4. Cómo crear una fábrica
Las fábricas, un patrón de diseño de uso frecuente en Java, ofrecen varias ventajas en comparación con la creación de instancias de objetos directos, como ocultar detalles de la creación de instancias, brindar la capacidad de mostrar un subtipo del tipo de fábrica que se muestra y, de manera opcional, mostrar un objeto existente en lugar de un objeto nuevo.
En este paso, se explican dos maneras de implementar una fábrica de creación de forma:
- Opción 1: Crea una función de nivel superior.
- Opción 2: Crea un constructor factory.
Para este ejercicio, usarás el ejemplo de Shapes, que crea instancias de formas e imprime el área calculada:
import 'dart:math';
abstract class Shape {
num get area;
}
class Circle implements Shape {
final num radius;
Circle(this.radius);
num get area => pi * pow(radius, 2);
}
class Square implements Shape {
final num side;
Square(this.side);
num get area => pow(side, 2);
}
main() {
final circle = Circle(2);
final square = Square(2);
print(circle.area);
print(square.area);
}
Abre el ejemplo de Shapes en DartPad.
En el área de la consola, deberías ver las áreas calculadas de un círculo y un cuadrado:
12.566370614359172
4
Observaciones
- Dart admite clases abstractas.
- Puedes definir varias clases en un archivo.
dart:math
es una de las bibliotecas principales de Dart. Otras bibliotecas principales incluyendart:core
,dart:async
,dart:convert
ydart:collection
.- Por convención, las constantes de la biblioteca de Dart son
lowerCamelCase
(por ejemplo,pi
en lugar dePI)
). Si deseas comprender el razonamiento, consulta la sección SE PREFIERE usar lowerCamelCase para nombres constantes en la guía de estilo. - El siguiente código muestra dos métodos get que calculan un valor:
num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square
Opción 1: Crea una función de nivel superior
Para implementar factory como una función de nivel superior, agrega la siguiente función en el nivel más alto (fuera de cualquier clase):
Shape shapeFactory(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
Para invocar la función factory, reemplaza las dos primeras líneas en el método
main()
:
final circle = shapeFactory('circle');
final square = shapeFactory('square');
Cómo ejecutar el ejemplo
El resultado debería ser el mismo que antes.
Observaciones
- Si se llama a la función con cualquier string que no sea
'circle'
o'square'
, arrojará una excepción. - El SDK de Dart define clases para muchas excepciones comunes, o puedes implementar la clase
Exception
a fin de crear excepciones más específicas o (como en este ejemplo) puedes arrojar una string que describa el problema encontrado. - Cuando se encuentra una excepción, DartPad informa
Uncaught
. Para ver información más útil, une el código en una sentenciatry-catch
e imprime la excepción. Como ejercicio opcional, consulta este ejemplo de DartPad. - Para usar una comilla simple dentro de una string, evita la comilla incorporada con una barra diagonal (
'Can\'t create $type.'
) o especifica la string con comillas dobles ("Can't create $type."
).
¿Tienes problemas? Verifica tu código.
Opción 2: Crea un constructor factory
Usa la palabra clave factory
de Dart para crear un constructor factory.
Agrega un constructor factory a la clase abstracta
Shape
:
abstract class Shape {
factory Shape(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
num get area;
}
Reemplaza las dos primeras líneas de
main()
por el siguiente código para crear instancias de las formas:
final circle = Shape('circle');
final square = Shape('square');
Borra la función
shapeFactory()
que agregaste antes.
Observación
- El código en el constructor factory es idéntico al código que se usa en la función
shapeFactory()
.
¿Tienes problemas? Verifica tu código.
5. Cómo implementar una interfaz
El lenguaje Dart no incluye una palabra clave interface
porque todas las clases definen una interfaz.
Abre el ejemplo de Shapes en DartPad (o continúa usando tu copia).
Agrega una clase
CircleMock
que implemente la interfaz Circle
:
class CircleMock implements Circle {}
Deberías ver el error "Faltan implementaciones concretas", ya que
CircleMock
no hereda la implementación de Circle
; solo usa su interfaz. Para corregir este error, define las variables de instancia area
y radius
:
class CircleMock implements Circle {
num area = 0;
num radius = 0;
}
Observación
- Si bien la clase
CircleMock
no define ningún comportamiento, es válido para Dart: el analizador no genera errores. - La variable de instancia
area
deCircleMock
implementa el método getarea
deCircle
.
¿Tienes problemas? Verifica tu código.
6. Cómo usar Dart para la programación funcional
En la programación funcional, puedes realizar acciones como las siguientes:
- Pasar funciones como argumentos.
- Asignar una función a una variable.
- Deconstruir una función que toma varios argumentos en una secuencia de funciones de la que cada una toma un solo argumento (que también se denomina currificación).
- Crear una función sin nombre que se pueda usar como valor constante (que también se denomina expresión lambda; las expresiones lambda se agregaron a Java en la versión JDK 8).
Dart admite todas estas características. En Dart, incluso las funciones son objetos y tienen un tipo, Function
, lo que implica que las funciones pueden asignarse a variables o pasarse como argumentos a otras funciones. También, puedes llamar a una instancia de una clase de Dart como si fuera una función, como se muestra en este ejemplo.
En el siguiente ejemplo, se usa código imperativo (en lugar de código funcional):
String scream(int length) => "A${'a' * length}h!";
main() {
final values = [1, 2, 3, 5, 10, 50];
for (var length in values) {
print(scream(length));
}
}
Abre el ejemplo de Scream en DartPad.
El resultado debe verse de la siguiente manera:
Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!
Observación
- Cuando se utiliza la interpolación de strings, la string
${'a' * length}
se evalúa como "el carácter'a'
se repitelength
veces".
Cómo convertir el código imperativo en funcional
Quita el bucle
for() {...}
imperativo en main()
y reemplázalo por una sola línea de código que usa el método de encadenamiento:
values.map(scream).forEach(print);
Cómo ejecutar el ejemplo
El enfoque funcional imprime los mismos seis gritos que el ejemplo imperativo.
¿Tienes problemas? Verifica tu código.
Cómo usar más funciones Iterable
Las clases principales List
y Iterable
admiten fold()
, where()
, join()
y skip()
, entre otras. Además, Dart cuenta con compatibilidad integrada para mapas y conjuntos.
Reemplaza la línea
values.map()
en main()
por lo siguiente:
values.skip(1).take(3).map(scream).forEach(print);
Cómo ejecutar el ejemplo
El resultado debe verse de la siguiente manera:
Aaah!
Aaaah!
Aaaaaah!
Observaciones
skip(1)
omite el primer valor, 1, en el literal de la listavalues
.take(3)
obtiene los siguientes 3 valores, 2, 3 y 5, en el literal de la listavalues
.- Se omiten los valores restantes.
¿Tienes problemas? Verifica tu código.
7. ¡Felicitaciones!
Una vez que hayas completado este codelab, contarás con conocimiento sobre algunas diferencias entre Java y Dart. Dart es fácil de aprender y, además, sus bibliotecas principales y su conjunto amplio de paquetes disponibles aumentan tu productividad. Dart se ajusta bien a apps grandes. Cientos de ingenieros de Google usan Dart con el fin de escribir apps de servicio crítico que generan muchos ingresos para Google.
Próximos pasos
Un codelab de 20 minutos no es suficiente para mostrarte todas las diferencias entre Java y Dart. Por ejemplo, en este codelab, no se trató lo siguiente:
- async/await, que te permite escribir código asíncrono como si fuera sincronico. Consulta este ejemplo de DartPad, que anima el cálculo de los primeros cinco decimales de π.
- Métodos en cascadas, en donde todo es un compilador.
- Operadores que reconocen valores nulos
Si deseas ver las tecnologías de Dart en funcionamiento, prueba los codelabs de Flutter.
Más información
Podrás aprender mucho más sobre Dart con los siguientes artículos, recursos y sitios web.
Artículos
- Por qué Flutter usa Dart
- Anuncio de Dart 2: optimizado para el desarrollo del cliente
- Por qué cambié Java por Dart
Recursos
Sitios web