Introducción a Dart para desarrolladores de Java

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

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?

Desconozco el tema y me gustaría obtener una buena descripción general. Tengo algunos conocimientos sobre este tema, pero me gustaría repasarlos. Estoy buscando código de ejemplo para usar en mi proyecto. Estoy buscando una explicación sobre un tema específico.

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.

99c813a1913dcc42.png c97a12197358d545.png

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.

b2f84ff91b0e1396.png Abrir DartPad

Cómo definir una clase Bicycle

b2f84ff91b0e1396.png 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() {
}

cf1e10b838bf60ee.png 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() ni Bicycle se declaran como public, porque todos los identificadores son públicos de forma predeterminada. Dart no tiene palabras clave para public, private ni protected.
  • 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

b2f84ff91b0e1396.png Agrega el siguiente constructor a la clase Bicycle:

Bicycle(this.cadence, this.speed, this.gear);

cf1e10b838bf60ee.png 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.

b2f84ff91b0e1396.png Haz clic en Formato.

Cómo crear un instancia Bicycle e imprimirla

b2f84ff91b0e1396.png Agrega el siguiente código a la función main():

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

b2f84ff91b0e1396.png Quita la palabra clave opcional new:

var bike = Bicycle(2, 0, 1);

cf1e10b838bf60ee.png 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 de var.
  • La función print() acepta cualquier objeto (no solo strings). Lo convierte en un String con el método toString() del objeto.

Cómo ejecutar el ejemplo

b2f84ff91b0e1396.png 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'

cf1e10b838bf60ee.png 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.

b2f84ff91b0e1396.png Agrega el siguiente método toString() en cualquier lugar de la clase Bicycle:

@override
String toString() => 'Bicycle: $speed mph';

cf1e10b838bf60ee.png 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

b2f84ff91b0e1396.png 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.

b2f84ff91b0e1396.png 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

b2f84ff91b0e1396.png En el constructor Bicycle, quita el parámetro speed:

Bicycle(this.cadence, this.gear);

b2f84ff91b0e1396.png En main(), quita el segundo parámetro (speed) de la llamada al constructor Bicycle:

var bike = Bicycle(2, 1);

b2f84ff91b0e1396.png Cambia los casos restantes de speed a _speed. (Dos lugares)

b2f84ff91b0e1396.png Inicializa _speed en 0:

int _speed = 0;

b2f84ff91b0e1396.png Agrega el siguiente método get a la clase Bicycle:

int get speed => _speed;

cf1e10b838bf60ee.png 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 y gear. 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

b2f84ff91b0e1396.png 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.

b2f84ff91b0e1396.png Abre el ejemplo de Rectangle en DartPad.

Cómo agregar un constructor Rectangle

b2f84ff91b0e1396.png 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.

cf1e10b838bf60ee.png Observaciones

  • this.origin, this.width y this.height usan abreviaturas para asignar variables de instancia a la declaración de un constructor.
  • this.origin, this.width y this.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 de Point(0,0) para la variable de instancia origin. 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

b2f84ff91b0e1396.png 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

b2f84ff91b0e1396.png 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());
}

cf1e10b838bf60ee.png 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);
}

b2f84ff91b0e1396.png 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

cf1e10b838bf60ee.png 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 incluyen dart:core, dart:async, dart:convert y dart:collection.
  • Por convención, las constantes de la biblioteca de Dart son lowerCamelCase (por ejemplo, pi en lugar de PI)). 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

b2f84ff91b0e1396.png 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.';
}

b2f84ff91b0e1396.png 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.

cf1e10b838bf60ee.png 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 sentencia try-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.

b2f84ff91b0e1396.png 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;
}

b2f84ff91b0e1396.png 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');

b2f84ff91b0e1396.png Borra la función shapeFactory() que agregaste antes.

cf1e10b838bf60ee.png 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.

b2f84ff91b0e1396.png Abre el ejemplo de Shapes en DartPad (o continúa usando tu copia).

b2f84ff91b0e1396.png Agrega una clase CircleMock que implemente la interfaz Circle:

class CircleMock implements Circle {}

b2f84ff91b0e1396.png 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;
}

cf1e10b838bf60ee.png 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 de CircleMock implementa el método get area de Circle.

¿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));
  }
}

b2f84ff91b0e1396.png Abre el ejemplo de Scream en DartPad.

El resultado debe verse de la siguiente manera:

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

cf1e10b838bf60ee.png Observación

  • Cuando se utiliza la interpolación de strings, la string ${'a' * length} se evalúa como "el carácter 'a' se repite length veces".

Cómo convertir el código imperativo en funcional

b2f84ff91b0e1396.png 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.

b2f84ff91b0e1396.png 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!

cf1e10b838bf60ee.png Observaciones

  • skip(1) omite el primer valor, 1, en el literal de la lista values.
  • take(3) obtiene los siguientes 3 valores, 2, 3 y 5, en el literal de la lista values.
  • 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:

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

Recursos

Sitios web