Dart es un lenguaje de programación de código abierto desarrollado por Google. Dart es ideal para aplicaciones móviles y aplicaciones web. También puede ser usado en aplicaciones de escritorio, en aplicaciones de línea de comandos (‘command-line apps’), como lenguaje para escribir scripts o en aplicaciones server-side.
Para aplicaciones web, Dart Web incluye un compilador en tiempo de desarrollo (‘dartdevc’) y un compilador para producción (‘dart2js’). La herramienta ‘dart2js’ compila el código Dart en código JavaScript compacto, rápido y desplegable. Emplea técnicas como la eliminación de código muerto para generar código Javascript limpio y eficiente.
Para aplicaciones que se dirigen a dispositivos (móviles, de escritorio, servidores, etc..) Dart Native incluye una compilación Dart VM con compilación JIT (‘just-in-time’) y un compilador AOT (‘ahead-of-time’) para producir código máquina. Dart Native permite ejecutar código Dart compilado en código ARM nativo o X64 para aplicaciones móviles, de escritorio y de servidor.
Históricamente, los lenguajes de programación se han dividido en dos grupos: lenguajes estáticos (por ejemplo, Fortran o C, donde las variables se escriben estáticamente en tiempo de compilación), y lenguajes dinámicos (por ejemplo, Smalltalk o JavaScript, donde el tipo de una variable puede cambiar en tiempo de ejecución). Los lenguajes estáticos se compilaban normalmente para producir programas de código nativo de máquina (o código ensamblador) para el equipo destino, que en tiempo de ejecución eran ejecutados directamente por el hardware. Los lenguajes dinámicos eran ejecutados por un intérprete, sin producir código de lenguaje de máquina.
Por supuesto, las cosas eventualmente se volvieron mucho más complicadas. El concepto de una máquina virtual (VM) se hizo popular, la cual no es más que un intérprete avanzado que imita a un hardware de máquina en software. Una máquina virtual facilita la transferencia de un lenguaje a nuevas plataformas de hardware. En este caso, el lenguaje de entrada de una máquina virtual suele ser un lenguaje intermedio. Por ejemplo, un lenguaje de programación (como Java) se compila en un lenguaje intermedio (‘bytecode’) y luego se ejecuta en una VM (la JVM).
Además, ahora existen compiladores JIT (‘just-in-time’). Un compilador JIT trabaja la ejecución del programa, compilando sobre la marcha. Los compiladores originales que se ejecutan durante la creación del programa (antes del tiempo de ejecución) se denominan ahora compiladores AOT (‘ahead-of-time’).
En general, sólo los lenguajes estáticos son aptos para la compilación de AOT en código de máquina nativo porque los lenguajes de máquina normalmente necesitan saber el tipo de datos, y en los lenguajes dinámicos el tipo no se fija de antemano. En consecuencia, los lenguajes dinámicos suelen ser interpretados o compilados por JIT.
Cuando la compilación AOT se realiza durante el desarrollo, invariablemente resulta en ciclos de desarrollo mucho más lentos (el tiempo que transcurre entre el momento en que se realiza un cambio en un programa y el momento en que se puede ejecutar el programa para ver el resultado del cambio). Pero la compilación AOT da como resultado programas que pueden ejecutarse de forma más predecible y sin pausas para el análisis y la compilación en tiempo de ejecución. Los programas compilados por AOT también comienzan a ejecutarse más rápido (porque ya han sido compilados).
Por el contrario, la compilación JIT proporciona ciclos de desarrollo mucho más rápidos, pero puede dar lugar a una ejecución más lenta o jerárquica. En particular, los compiladores JIT tienen tiempos de inicio más lentos, porque cuando el programa comienza a ejecutarse, el compilador JIT tiene que hacer análisis y compilación antes de que el código pueda ser ejecutado.
Dart es uno de los pocos lenguajes que está bien adaptado para ser compilado tanto AOT como JIT. El soporte de ambos tipos de compilación proporciona ventajas significativas para Dart y (especialmente) Flutter.
La compilación JIT se utiliza durante el desarrollo, utilizando un compilador que es especialmente rápido lo que deriva en una de las características de Flutter, el “stateful hot reload”. Luego, cuando una aplicación está lista para su lanzamiento, se compila AOT. Consecuentemente, con la ayuda de herramientas y compiladores avanzados, Dart puede ofrecer lo mejor de ambos mundos: ciclos de desarrollo extremadamente rápidos y tiempos de ejecución y puesta en marcha rápidos.
Dart puede ser compilado eficientemente AOT o JIT, interpretado o transpilado a otros lenguajes como Javascript.
Todo lo que puede colocar en una variable es un objeto, y cada
objeto es una instancia de una clase. Incluso los números, las funciones
y null
son objetos. Todos los objetos heredan de la clase
Object
.
Aunque Dart está fuertemente tipado, las anotaciones de tipo son opcionales porque Dart puede inferir tipos.
Dart admite tipos genéricos, como List<int>
(una lista de enteros) o List<dynamic>
(una lista de
objetos de cualquier tipo).
Dart admite funciones de nivel superior o ‘top-level
functions’ (como main()
), así como funciones
vinculadas a una clase u objeto (métodos estáticos y de instancia,
respectivamente). También puede crear funciones dentro de funciones
(funciones anidadas o locales).
De manera similar, Dart admite variables de nivel superior, así como variables vinculadas a una clase u objeto (variables estáticas y de instancia). Las variables de instancia a veces se conocen como campos o propiedades.
A diferencia de Java, Dart no tiene las palabras clave
public
, protected
y private
. Si
un identificador comienza con un guión bajo (_), es privado a su
biblioteca.
Los identificadores pueden comenzar con una letra o un guión bajo (_), seguido de cualquier combinación de esos caracteres más dígitos.
Dart tiene expresiones (que tienen valores de tiempo de
ejecución) y declaraciones (que no). Por ejemplo, la expresión
condicional condicional ? expr1 : expr2
tiene un valor u
otro. Compare eso con una sentencia if-else, que no tiene valor. Una
declaración a menudo contiene una o más expresiones, pero una expresión
no puede contener directamente una declaración.
Las herramientas de Dart pueden reportar dos tipos de problemas: warnings y errors. Las advertencias son solo indicaciones de que su código podría no funcionar, pero no impiden que su programa se ejecute. Los errores pueden ser de compilación o de ejecución. Un error en tiempo de compilación evita que el código se ejecute; un error en tiempo de ejecución hace que se genere una excepción mientras se ejecuta el código.
// Define a function.
int aNumber) {
printInteger('The number is $aNumber.'); // Print to console.
print(}
// This is where the app starts executing.
{
main() var number = 42; // Declare and initialize a variable.
// Call a function.
printInteger(number); }
Para ejecutar código en línea de comandos se necesita la VM de Dart
que se incluye con el SDK de Dart. Una vez instalado, añadimos el
PATH a las variables del sistema para ejecutar el comando
dart
directamente en la consola.
void main() {
'Hello, World!');
print(}
dart helloworld.dart
Más información:
Dart admite comentarios de una sola línea (//
),
comentarios de varias líneas (/* */
) y comentarios de
documentación.
Los comentarios de una sola línea o de varias líneas funcionan igual que en Java.
void main() {
// Comentarios de una sola línea
'Welcome to my Llama farm!');
print(
/*
Comentarios de varias líneas
larry.feed();
larry.exercise();
larry.clean();
*/
}
Los comentarios de documentación pueden ser de una línea o de varias
líneas y empiezan por ///
o /**
. Dentro de un
comentario de documentación, el compilador ignora todo el texto a menos
que esté entre corchetes. Usando corchetes, puede referirse a clases,
métodos, campos, variables de nivel superior, funciones y parámetros.
Los nombres entre paréntesis se resuelven en el ámbito léxico del
elemento del programa documentado. Para generar la documentación, se usa
la herramienta dartdoc
/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
/// Feeds your llama [Food].
///
/// The typical llama eats one bale of hay per week.
void feed(Food food) {
// ...
}
/** Exercises your llama with an [activity] for
[timeLimit] minutes. **/
void exercise(Activity activity, int timeLimit) {
// ...
}
}
// En la documentación generada, [Food] se convierte en un enlace a los documentos API para la clase 'Food'.
Las variables almacenan referencias a objetos. La declaración de tipo es opcional ya que Dart puede inferir el tipo de variable. Dart tiene un tipado fuerte lo que significa que una variable de un tipo no puede almacenar referencias a objetos de otro tipo:
var name = 'John'; // El compilador infiere el tipo 'String'
String lastname = "Doe" // Declaración de tipo 'String'
// lastname = 40; // Error: A value of type 'dart.core::int' can't be assigned to a variable of type 'dart.core::String'.
var age = 40; // El compilador infiere el tipo 'int'
.runtimeType); // => int print(age
Si una variable no está restringida a un tipo, podemos indicar que es
de tipo Object
o dynamic
. En este caso la
variable puede almacenar distintos tipos:
dynamic name = 'Bob';
.runtimeType); // => String
print(name
= 45;
name .runtimeType); // => int print(name
Las variables sin inicializar tienen un valor inicial
null
. Incluso las variables con tipos numéricos son
inicialmente nulas, porque los números, como todo lo demás en Dart, son
objetos.
final
y const
Para declarar variables
finales cuyo valor no va a cambiar, se utilizan las palabras clave
final
o const
en lugar de la palabra clave
var
. Una variable final
solo se puede asignar
una vez; una variable const
es una constante en tiempo de
compilación. Una constante en tiempo de compilación o
compile-time constant es una constante cuyo
valor es conocido en tiempo de compilación.
Las variables const
son implícitamente finales. Una
variable final de nivel superior o una variable final de clase se
inicializa la primera vez que se usa.
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
= 'Alice'; // Error: a final variable can only be set once. name
Las variables de instancia pueden ser final
pero no
const
. Las variables finales de instancia deben
inicializarse antes de que se inicie el cuerpo del constructor: en la
declaración de la variable, mediante un parámetro del constructor o en
la lista de inicializadores del constructor.
Utilice const
para las variables que desea que sean
constantes en tiempo de compilación (compile-time
constants). Si la variable const
está en el
nivel de clase, márquela como static const
.
Cuando se declare la variable como const
, hay que
establecer el valor en tiempo de compilación. Por tanto se necesita un
número o cadena literal, otra variable const
o el resultado
de una operación aritmética en números constantes:
const bar = 1000000;
const double atm = 1.01325 * bar;
La palabra clave const
no es solo para declarar
variables constantes. También puede usarlo para crear valores
constantes, así como para declarar constructores que crean valores
constantes. Cualquier variable puede tener un valor constante.
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
Dart tiene soporte especial para los siguientes tipos:
Se puede inicializar un objeto de cualquiera de estos tipos
especiales utilizando un literal. Por ejemplo,
'esto es una cadena'
es un literal de cadena, y
true
es un literal booleano.
Debido a que cada variable en Dart se refiere a un objeto, esto es,
una instancia de una clase, usualmente puede usar constructores para
inicializar variables. Algunos de los tipos incorporados tienen sus
propios constructores. Por ejemplo, el constructor Map()
sirve para crear un mapa.
Dart tiene dos tipos para representar tipos numéricos. Ambos tipos son objetos.
int
- Valores enteros no mayores a 64 bits, dependiendo de
la plataforma. En Dart VM puede representar valores entre -2^63 y 2^63 -
1. Dado que Dart se puede compilar a Javascript, en Javascript el
intervalo de valores está entre -2^53 hasta 2^53 - 1.var x = 1;
var hex = 0xDEADBEEF;
double
- Números de punto flotante de 64 bits (precisión
doble), según lo especificado por el estándar IEEE 754.var y = 1.1;
var exponents = 1.42e5;
Tanto el tipo int
como el tipo double
son
subtipos de la clase num
. Esta clase incluye operaciones
aritméticas como suma, resta, etc… y métodos como abs()
,
ceil()
, floor()
, etc…
Si la clase num
y sus subtipos incluyendo las
operaciones no son suficientes, la librería dart:math
tiene una amplia variedad de tipos y métodos.
Se puede realizar la conversión entre tipos:
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
Los números literales son constantes en tiempo de compilación. Muchas expresiones aritméticas también son constantes en tiempo de compilación, siempre que sus operandos sean constantes en tiempo de compilación que evalúen los números.
const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
Una cadena
en Dart es una secuencia de unidades de código UTF-16. Para crear una
cadena se pueden utilizar comillas simples o comillas dobles. El
operador +
permite concatenar cadenas:
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
var s5 = 'The + operator ' + 'works, as well.'; // => 'The operator + works, as well.'
var s6 = 'String '
'concatenation'
" works even over line breaks."; //=> 'String concatenation works even over line breaks'
var s7 = '''
You can create
multi-line strings like this one.
''';
var s8 = """This is also a
multi-line string.""";
En Dart (al igual que en Kotlin) se puede emplear expresiones y
variables directamente en una cadena con la forma
${expresión}
. Si la expresión es un identificador, se
pueden omitir las llaves {}
. Esto se denomina
interpolación de cadena o **_‘template expressión’**_
en Kotlin.
Para obtener la cadena correspondiente a un objeto, Dart llama al
método toString()
del objeto.
var s = 'string interpolation';
var s1 = 'Dart has $s.'; // => 'Dart has string interpolation.'
var s2 = 'Dart has ${s.toUpperCase()}.' // => 'Dart has STRING INTERPOLATION'
Nota: El operador ==
comprueba si dos
objetos son equivalentes. Dos cadenas son equivalentes si contienen la
misma secuencia de unidades de código.
Las cadenas literales son constantes de tiempo de compilación, mientras que para que una interpolación de cadena sea una constante de tiempo de compilación las expresiones también tienen que ser constantes en tiempo de compilación.
// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';
Para representar valores
booleanos, Dart tiene un tipo llamado bool
. Solo dos
objetos tienen el tipo bool
: los literales booleanos
true
y false
, que son constantes de tiempo de
compilación.
Quizás la colección más común en casi todos los lenguajes de programación es el array, o grupo ordenado de objetos. En Dart, los arrays son objetos de tipo List.
var list = [1, 2, 3];
Es importante destacar que en el ejemplo anterior el compilador
infiere la lista como List<int>
. Por tanto, cualquier
intento de añadir objetos que no sean int
el compilador
lanzará un error.
Las listas, al igual que en la mayoría de lenguajes, utilizan la
indexación basada en cero, donde 0 es el índice del
primer elemento y list.length - 1
es el índice del último
elemento.
Para crear una lista que sea una constante de tiempo de compilación,
hay que agregar const
antes del literal de lista:
var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.
En Dart, un conjunto es una colección desordenada de elementos únicos. El soporte de Dart para conjuntos se proporciona mediante literales de conjunto y el tipo Set.
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Para crear un conjunto vacío, se utiliza {}
precedido
por un argumento de tipo, o se asigna {}
a una variable de
tipo Set
:
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
Podemos añadir elementos a un conjunto existente con los métodos
add()
o addAll()
y obtener el tamaño del
conjunto con .length
;
var elements = <String>{};
.add('fluorine');
elements.addAll(halogens);
elementsassert(elements.length == 5);
Para crear un conjunto que sea una constante en tiempo de
compilación, hay que agregar const
antes del literal del
conjunto:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.
En general, un mapa es
un objeto que asocia claves y valores. Tanto las claves como los valores
pueden ser de cualquier tipo de objeto. Cada clave aparece solo una vez,
pero puede usar el mismo valor varias veces. El soporte de Dart para
mapas es proporcionado por literales de mapas y el tipo
Map
:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
// Using constructor
var gifts = Map();
'first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
gifts[
var nobleGases = Map();
2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon'; nobleGases[
Destacar que, al igual que con los arrays, el compilador infiere los
tipos. En el primer ejemplo, el compilador infiere el tipo como
Map<String, String>
. Añadir otro tipo al mapa genera
un error.
var gifts = {'first': 'partridge'};
'fourth'] = 'calling birds'; // Add a key-value pair
gifts[
'first']); // => partridge
print(gifts[
'fifth']); // => null
print(gifts[
.length); // => 2 print(gifts
Para crear un mapa que sea una constante en tiempo de compilación,
hay que agregar const
antes del literal del mapa:
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; // Uncommenting this causes an error.
Cuando se usan los operadores, se crea una expresión.
++
a+ b
a = b
a == b
a ? a : b
c is T a
Al igual que en otros lenguajes, hay operadores con mayor prioridad que otros. Para evitar problemas y facilitar la legibilidad, es recomendable usar paréntesis:
// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...
// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder (modulo)
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart también admite operadores de incremento y decremento tanto de prefijo como de sufijo.
++var | var = var + 1 (expression value is var + 1) |
var++ | var = var + 1 (expression value is var) |
–var | var = var – 1 (expression value is var – 1) |
var– | var = var – 1 (expression value is var) |
var a, b;
= 0;
a = ++a; // Increment a before b gets its value.
b assert(a == b); // 1 == 1
= 0;
a = a++; // Increment a AFTER b gets its value.
b assert(a != b); // 1 != 0
= 0;
a = --a; // Decrement a before b gets its value.
b assert(a == b); // -1 == -1
= 0;
a = a--; // Decrement a AFTER b gets its value.
b assert(a != b); // -1 != 0
assert(2 == 2); // equal
assert(2 != 3); // not equal
assert(3 > 2); // greater than
assert(2 < 3); // less than
assert(3 >= 3); // greater than or equal to
assert(2 <= 3); // less than or equal to
Para probar si dos objetos representan la misma cosa, use el operador
==
. En el caso poco frecuente de necesitar saber si dos
objetos son exactamente el mismo objeto, se usa la función
identical()
.
Los operadores as
, is
y is!
son útiles para verificar tipos en tiempo de ejecución.
if (emp is Person) {
// Type check
.firstName = 'Bob';
emp}
as Person).firstName = 'Bob'; // Si 'emp' no es de tipo 'Person' se lanzará una excepción (emp
Para asignar un valor se usa el operador =
. Para asignar
un valor a una variable sólo si ésta es nula, se usa el operador
??=
.
// Assign value to a
= value;
a // Assign value to b if b is null; otherwise, b stays the same
??= value; b
El símbolo y su expresión equivalente:
+=
: a = a + b
-=
: a = a - b
*=
: a = a * b
/=
: a = a / b
~/=
: a = a ~/ b
%=
: a = a % b
<<=
: a = a << b
=>>
: a = a >> b
&=
: a = a & b
^=
: a = a ^ b
|=
: a = a | b
var a = 2; // Assign using =
*= 3; // Assign and multiply: a = a * 3
a assert(a == 6);
!exp
: inverts the following expression (changes
false to true, and vice versa)
||
: logical OR
&&
: logical AND
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
Dart tiene dos operadores que permiten evaluar de forma concisa
expresiones que de otro modo podrían requerir sentencias tipo
if-else
:
condición ? expr1 : expr2
- Si la condición es
verdadera, evalúa expr1 (y devuelve su valor); de lo contrario, evalúa y
devuelve el valor de expr2.
expr1 ?? expr2
- Si expr1 no es nulo, devuelve su
valor; de lo contrario, evalúa y devuelve el valor de expr2.
var visibility = isPublic ? 'public' : 'private';
var b = a ?? 45; // si 'a' no es nulo se usa su valor o en caso contrario se usa 45 como valor para 'b'
La notación en cascada (..) permite realizar una secuencia de operaciones en el mismo objeto. Además de las llamadas de función, también puede acceder a los campos en ese mismo objeto. Esto a menudo ahorra el paso de crear una variable temporal y permite escribir código más fluido y legible.
'#confirm') // Get an object.
uerySelector(..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
// equivalent
var button = querySelector('#confirm');
.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!')); button
La notación en cascada funciona cuando la función devuelve el objeto
sobre el cual se aplica la siguiente función. Por ejemplo, la función
write()
de la clase StringBuffer
no devuelve
el objeto sino que devuelve void
, con lo que el siguiente
ejemplo produce un error:
var sb = StringBuffer();
.write('foo')
sb..write('bar'); // Error: method 'write' isn't defined for 'void'.
if (isRaining()) {
.bringRainCoat();
you} else if (isSnowing()) {
.wearJacket();
you} else {
.putTopDown();
car}
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
.write('!');
message}
Si el objeto sobre el que está iterando es ‘Iterable’, se puede usar
el método forEach()
. Usar forEach()
es una
buena opción si no necesita conocer el contador de iteración actual:
.forEach((candidate) => candidate.interview()); candidates
Clases iterables como List
y Map
son
compatibles con la forma for-in
:
var collection = [0, 1, 2];
for (var x in collection) {
// 0 1 2
print(x); }
while (!isDone()) {
doSomething();}
do {
printLine();} while (!atEndOfPage());
Se usa break
para detener el bucle:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();}
Se usa continue
para saltarse la siguiente iteración del
bucle:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
.interview();
candidate}
En Dart los bloques switch
comparan enteros, cadenas y
constantes en tiempo de compilación usando ==
. Los objetos
que se comparan tiene que ser de la misma clase. Las enumeraciones
también funcionan con los bloques switch
.
var command = 'OPEN';
switch (command) {
case 'CLOSED': // Dart soporte las cláusulas vacías.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();break;
case 'PENDING':
executePending();break;
case 'APPROVED':
executeApproved();break;
case 'DENIED':
executeDenied();break;
case 'OPEN':
executeOpen();break;
default:
// cláusula 'default'
executeUnknown(); }
Si se omite el break
se produce un error:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();// ERROR: Missing break
case 'CLOSED':
executeClosed();break;
}
Se usa assert
para interrumpir la ejecución normal si
una condición booleana es falsa. Cuando la condición es falsa la
afirmación falla y se lanza la excepción AssertionError
.
Estas declaraciones son ignoradas en producción.
// Make sure the variable has a non-null value.
assert(text != null);
// Make sure the value is less than 100.
assert(number < 100);
// Make sure this is an https URL.
assert(urlString.startsWith('https'));
// Mensaje en un 'assert'
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
Dart es un verdadero lenguaje orientado a objetos, por lo que incluso las funciones son objetos y tienen un tipo, el tipo Function. Esto significa que las funciones pueden asignarse a variables o pasarse como argumentos a otras funciones. También puede llamar a una instancia de una clase como si fuera una función.
bool isEven(int number) {
return number % 2 == 0;
}
// Aunque se recomienda anotar el tipo en APIs públicas, se puede omitir
int number) {
isEven(return number % 2 == 0;
}
Para las funciones que contienen una sola expresión, puede usar una
sintaxis abreviada usando la notación =>
también llamada
‘arrow syntax’. La sintaxis => expr
es una
forma abreviada de { return expr; }
bool isEven(int number) => number % 2 == 0;
Una función puede tener dos tipos de parámetros: requeridos y
opcionales. Los parámetros requeridos se enumeran primero,
seguidos de cualquier parámetro opcional. Los parámetros opcionales
nombrados también se pueden marcar como @required
.
Los parámetros opcionales pueden ser posicionales o nombrados, pero no ambos a la vez.
Al definir una función que tenga parámetros con nombre se usa la
forma {param1, param2,…}
para especificar los parámetros
nombrados:
// Named parameters
void enableFlags({bool bold, bool hidden}) {
"bold: $bold - hidden: $hidden");
print(}
Al llamar a una función, se especifican los parámetros con nombre
usando paramName: valor
:
// => bold: null - hidden: null
enableFlags(); : true); // => bold: true - hidden: null
enableFlags(bold: true, hidden: true); // => bold: true - hidden: true
enableFlags(bold: true, bold: false); // bold: false - hidden: true enableFlags(hidden
Para indicar que un parámetro es obligatorio usamos la anotación
@required
en el parámetro. Al usar la anotación el
analizador de código del compilador pueden comprobar si no se suministra
el argumento en la construcción y lanzar un aviso.
const Scrollbar({Key key, @required Widget child})
Para indicar que uno o varios parámetros son posicionales se usan los
corchetes []
:
String say(String from, String msg, [String device]) {
"from: $from - msg: $msg - device: $device");.
print(}
String hello(String from, [String device, bool status]) {
"from: $from - device: $device - status: $status");.
print(}
// Calling function
'John', 'hi'); // => from: John - msg: hi - device: null
say('John', 'hi', 'phone'); // => from: John - msg: hi - device: phone
say(
'John'); // => from: John - device: null - status: null
hello('John', 'phone'); // => from: John - device: phone - status: null
hello('John', 'phone', true); // => from: John - device: phone - status: true hello(
Para definir valores por defecto en parámetros con nombre y
posicionales se usa =
. Los valores por defecto deben ser
constantes en tiempo de compilación. Si no se proporciona un valor por
defecto, el valor predeterminado es null
:
// Named parameters
void enableFlags({bool bold = false, bool hidden = false}) {
//...
}
// => bold: false - hidden: false
enableFlags(); : true); // => bold: true - hidden: false
enableFlags(bold: true, hidden: true); // => bold: true - hidden: true
enableFlags(bold: true, bold: false); // bold: false - hidden: true
enableFlags(hidden
// Positional parameters
String say(String from, String msg, [String device = 'carrier pigeon']) {
// ...
}
'John', 'hi'); // => from: John - msg: hi - device: carrier pigeon
say('John', 'hi', 'phone'); // => from: John - msg: hi - device: phone say(
main()
Cada aplicación debe tener una función main()
de nivel
superior, que sirve como punto de entrada a la aplicación. La función
main()
devuelve void
y tiene un parámetro
opcional List<String>
para los argumentos. Se puede
usar la biblioteca ‘args’
para definir y analizar argumentos de línea de comandos.
// This is where the app starts executing.
{
main() var number = 42; // Declare and initialize a variable.
// Call a function.
printInteger(number); }
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
Las funciones se pueden pasar como parámetro a otra función:
void printElement(int element) {
print(element);}
var list = [1, 2, 3];
// Pass printElement as a parameter.
.forEach(printElement); list
También se puede asignar una función a una variable:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
Las funciones tienen un nombre que permite invocar la función pero también se pueden crear funciones sin nombre llamadas funciones anónimas o, a veces, lambda o closure. Se puede asignar una función anónima a una variable para, por ejemplo, agregarla o eliminarla de una colección.
Una función anónima es similar a una función con nombre. Pueden tener cero o más parámetros separados por comas y anotaciones de tipo opcionales entre paréntesis:
, …]]) {
([[Type] param1[
codeBlock;};
El siguiente ejemplo define una función anónima con un parámetro sin
tipo llamado item
. La función, invocada para cada elemento
de la lista, imprime una cadena que incluye el valor en el índice
especificado.
var list = ['apples', 'bananas', 'oranges'];
.forEach((item) {
list'${list.indexOf(item)}: $item');
print(});
Si la función contiene solo una declaración, se puede acortar usando
la notación de flecha =>
. El código anterior puede
escribirse como:
.forEach(
list=> print('${list.indexOf(item)}: $item')); (item)
Dart es un lenguaje de ámbito léxico, lo que significa que el alcance de las variables se determina de forma estática, simplemente por el diseño del código. Puede “seguir las llaves hacia afuera” para ver si una variable está dentro del alcance. Las funciones más interiores puede hacer uso de las variables de nivel superior.
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
Un closure es un objeto de función que tiene acceso a variables en su ámbito léxico, incluso cuando la función se utiliza fuera de su ámbito original.
En el siguiente ejemplo, la función makeAdder()
captura
la variable addBy
. Dondequiera que se utilice la función,
‘recuerda’ el valor de addBy
.
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
Las top-level functions, los métodos estáticos y/o los métodos de instancia se pueden comparar entre sí:
void foo() {} // A top-level function
class A {
static void bar() {} // A static method
void baz() {} // An instance method
}
void main() {
var x;
// Comparing top-level functions.
= foo;
x assert(foo == x);
// Comparing static methods.
= A.bar;
x assert(A.bar == x);
// Comparing instance methods.
var v = A(); // Instance #1 of A
var w = A(); // Instance #2 of A
var y = w;
= w.baz;
x
// These closures refer to the same instance (#2),
// so they're equal.
assert(y.baz == x);
// These closures refer to different instances,
// so they're unequal.
assert(v.baz != w.baz);
}
Todas las funciones devuelven un valor. Si no se especifica ningún
valor de retorno, la instrucción devuelve null
, que se
adjunta implícitamente al cuerpo de la función.
{}
foo()
assert(foo() == null);
Las excepciones son errores que indican que sucedió algo inesperado. Si la excepción no se detecta, el ‘isolate’ que generó la excepción se suspende y, por lo general, el ‘isolate’ y su programa se terminan.
A diferencia de Java, todas las excepciones en Dart son ‘unchecked exceptions’. Los métodos no declaran las excepciones que pueden lanzar y no se requiere capturar ninguna excepción.
Dart provee los tipos Exception
y Error
así como otros subtipos. Además, se pueden definir excepciones propias o
personalizadas.
Sin embargo, en Dart se puede lanzar cualquier objeto que no sea nulo
como una excepción, no solo los objetos Exception
y
Error
. No obstante, es recomendable utilizar estas clases,
alguno de sus subtipos o nuestras propias excepciones.
throw FormatException('Expected at least 1 section'); // throws an exception
throw 'Out of llamas!'; // thow an arbitrary object
Debido a que lanzar una excepción es una expresión, se puede lanzar
excepciones en sentencias =>
, así como en cualquier otro
lugar que permita el uso de expresiones:
void distanceTo(Point other) => throw UnimplementedError();
Al capturar una excepción se impide que la excepción se propague, a menos que se vuelva a lanzar la excepción. Capturar una excepción da la oportunidad de manejarla:
try {
breedMoreLlamas();} on OutOfLlamasException {
buyMoreLlamas();}
Para manejar código que puede lanzar más de un tipo de excepción, se puede especificar múltiples cláusulas de captura. La primera cláusula de captura que coincida con el tipo de objeto lanzado maneja la excepción. Si la cláusula de captura no especifica un tipo, esa cláusula puede manejar cualquier tipo de objeto que sea lanzado:
try {
breedMoreLlamas();} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();} on Exception catch (e) {
// Anything else that is an exception
'Unknown exception: $e');
print(} catch (e) {
// No specified type, handles all
'Something really unknown: $e');
print(}
Como muestra el código anterior, se puede usar on
,
catch
o ambos. Se utiliza on
cuando se
necesite especificar el tipo de excepción. Se utiliza catch
cuando el manejador de excepciones necesite el objeto de excepción.
Se puede especificar uno o dos parámetros para catch()
.
El primer parámetro es la excepción que fue lanzada, y el segundo
parámetro es la traza de la pila, que es un objeto StackTrace
.
try {
// ···
} on Exception catch (e) {
'Exception details:\n $e');
print(} catch (e, s) {
'Exception details:\n $e');
print('Stack trace:\n $s');
print(}
Para manejar parcialmente una excepción para luego ser relanzada para
que se propague, se usa la palabra clave rethrow
:
void misbehave() {
try {
dynamic foo = true;
++); // Runtime error
print(foo} catch (e) {
'misbehave() partially handled ${e.runtimeType}.');
print(rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();} catch (e) {
'main() finished handling ${e.runtimeType}.');
print(}
}
Para asegurarse de que se ejecute código independientemente de si se
lanza o no una excepción, se usa la cláusula finally
. Si
ninguna cláusula catch
coincide con la excepción, la
excepción se propaga después de que se ejecute la cláusula
finally
. Si una cláusula catch
coincide y
captura la excepción, se ejecuta esta cláusula y después se ejecuta la
cláusula finally
:
try {
breedMoreLlamas();} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();}
try {
breedMoreLlamas();} catch (e) {
'Error: $e'); // Handle the exception first.
print(} finally {
// Then clean up.
cleanLlamaStalls(); }
Dart es un lenguaje orientado a objetos con clases y herencia de clases.
Los objetos tienen miembros que consisten en funciones y datos
(métodos y variables de instancia, respectivamente). Cuando se llama a
un método, se invoca en un objeto: el método tiene acceso a las
funciones y datos de ese objeto. Se usa la notación punto
(.)
para referirse a una variable de instancia o un miembro
de un objeto.
var p = Point(2, 2);
// Set the value of the instance variable y.
.y = 3;
p
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
= p.distanceTo(Point(4, 4)); num distance
Se puede utilizar ?.
en lugar de solo .
para evitar una excepción cuando el operando más a la izquierda sea
nulo:
// If p is non-null, set its y value to 4.
?.y = 4; p
Puede crear un objeto utilizando un constructor. Los nombres de los
constructores pueden ser ‘ClassName’ o
‘ClassName.identifier’. La palabra clave new
es
opcional en Dart v2.0 y posteriores:
import 'dart:math';
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
var p1 = new Point(2, 2); // 'new' es opcional
var p2 = new Point.fromJson({'x': 1, 'y': 2}); // 'new' es opcional
Algunas clases proporcionan constructores constantes. Para crear una
constante de tiempo de compilación utilizando un constructor constante,
coloque la palabra clave const
antes del nombre del
constructor. La construcción de dos constantes idénticas en tiempo de
compilación da como resultado una única instancia canónica:
import 'dart:math';
var p = const Point(2, 2);
var a = const Point(1, 1);
var b = const Point(1, 1);
assert(identical(a, b)); // They are the same instance!
Para obtener el tipo de un objeto en tiempo de ejecución, puede usar
la propiedad runtimeType
de la clase Object
,
que devuelve un objeto Type:
var a = 45;
var b = Point(1, 1);
'The type of a is ${a.runtimeType}'); // => The type of a is int
print('The type of a is ${b.runtimeType}'); // => The type of a is Point<int> print(
Las variables declaradas dentro de una clase son variables de instancia. Todas las variables de instancia sin inicializar tienen el valor nulo.
class Point {
// Declare instance variable x, initially null.
num x; // Declare y, initially null.
num y; = 0; // Declare z, initially 0.
num z }
Todas las variables de instancia generan un método
getter
implícito. Las variables de instancia no final
también generan un método setter
implícito.
Si se inicializa una variable de instancia donde se declara (en lugar de en un constructor o método), el valor se establece cuando se crea la instancia, que es antes de que se ejecute el constructor y su lista de inicializadores.
class Point {
num x;
num y;}
void main() {
var point = Point();
.x = 4; // Use the setter method for x.
pointassert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
Un constructor se declara creando una función con el mismo nombre que la clase. La forma más común de constructor es el constructor que sirve para crear una nueva instancia de una clase. Dada su utilidad y lo común de su uso Dart tiene una forma compacta de constructor en la cual los argumentos del constructor se asignan a las variables de instancia:
class Point {
, y;
num x
, num y) {
Point(num xthis.x = x; // 'this' se refiere a la instancia actual
this.y = y;
}
}
class Point {
, y;
num x
// Forma compacta
this.x, this.y); // los argumentos 'x' e 'y' se asignan a las variables de instancia con el mismo nombre
Point(}
Si no se declara un constructor, se proporciona un constructor por defecto. El constructor por defecto no tiene argumentos e invoca al constructor sin argumentos de la superclase.
Las subclases no heredan constructores de su superclase. Una subclase que declara que no hay constructores tiene solo el constructor por defecto (sin argumento y sin nombre).
Los constructores con nombre se emplean para implementar múltiples constructores para una clase o para proporcionar mayor claridad.
Como ya se ha comentado, los constructores no se heredan, lo que significa que el constructor con nombre de una superclase no es heredado por una subclase. En caso de que sea necesario que una subclase se cree con un constructor con nombre definido en la superclase, debe implementar ese constructor en la subclase.
class Point {
, y;
num x
this.x, this.y);
Point(
// Named constructor
.origin() {
Point= 0;
x = 0;
y }
}
Se puede inicializar las variables de instancia antes de que se ejecute el cuerpo del constructor. Los inicializadores se separan con comas:
class Point {
var x, y;
// Initializer list sets instance variables before
// the constructor body runs.
.fromJson(Map<String, num> json)
Point: x = json['x'],
= json['y'] {
y 'In Point.fromJson(): ($x, $y)');
print(}
.raw(var x1, var y2) : x = x1, y = y2 { // initializer list
Point'In Point.raw(): ($x, $y)');
print(}
}
void main() {
= Point.raw(10, 15); // => In Point.raw(): (10, 15)
Point point }
Las listas de inicialización también son útiles al configurar variables finales:
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
, y)
Point(x: x = x,
= y,
y = sqrt(x * x + y * y);
distanceFromOrigin }
{
main() var p = new Point(2, 3);
.distanceFromOrigin);
print(p}
Durante el desarrollo, se puede validar entradas utilizando
assert
en la lista de inicializadores:
.withAssert(this.x, this.y) : assert(x >= 0) {
Point'In Point.withAssert(): ($x, $y)');
print(}
Por defecto, un constructor en una subclase llama al constructor sin nombre de la superclase, sin argumentos. El constructor de la superclase es llamado al principio del cuerpo del constructor. Si también se está utilizando una lista de inicializadores, se ejecuta antes de que se llame a la superclase. En resumen, el orden de ejecución es el siguiente:
Si la superclase no tiene un constructor sin nombre y sin argumentos,
se debe llamar manualmente a uno de los constructores en la superclase.
Para llamar a un constructor de la superclase se usa :
justo antes del cuerpo del constructor si lo hay.
class Person {
String firstName;
.fromJson(Map data) {
Person'in Person');
print(}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
.fromJson(Map data) : super.fromJson(data) {
Employee'in Employee');
print(}
}
{
main() var emp = new Employee.fromJson({});
// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
.firstName = 'Bob';
emp}
as Person).firstName = 'Bob';
(emp }
Debido a que los argumentos del constructor de la superclase se
evalúan antes de invocar al constructor, un argumento puede ser una
expresión como una llamada a la función. Sin embargo, los argumentos del
constructor de la superclase no tienen acceso a this
. Por
ejemplo, los argumentos pueden llamar a métodos estáticos pero no a
métodos de instancia.
class Employee extends Person {
: super.fromJson(getDefaultData()); // 'getDefaultData() es un método estático
Employee() // ···
}
A veces, el único propósito de un constructor es redirigir la llamada
a otro constructor de la misma clase. El cuerpo del constructor que
redirige estará vacío con la llamada al constructor apareciendo después
de dos puntos (:
)
class Point {
, y;
num x
// The main constructor for this class.
this.x, this.y);
Point(
// Delegates to the main constructor.
.alongXAxis(num x) : this(x, 0);
Point}
Si una clase instancia objetos que nunca cambian, se puede hacer que
estos objetos sean constantes en tiempo de compilación. Para hacer esto,
se define un constructor con la palabra clave const
y todas
las variables de instancia se definen como finales:
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
Se usa la palabra clave factory
cuando se implementa un
constructor que no siempre crea una nueva instancia de su clase. Por
ejemplo, un constructor puede devolver una instancia de una caché o
puede devolver una instancia de un subtipo. Estos constructores se
invocan como un constructor normal.
Los métodos son funciones que proveen de comportamiento a los objetos.
Los métodos de instancia pueden acceder a las variables de instancia
y a this
:
import 'dart:math';
class Point {
, y;
num x
this.x, this.y);
Point(
{
num distanceTo(Point other) var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
Los ‘getters’ y ‘setters’ son métodos especiales
que proporcionan acceso de lectura y escritura a las propiedades de un
objeto. Cada variable de instancia tiene un ‘getter’ implícito,
más un ‘setter’ si corresponde. Puede crear propiedades
adicionales implementando ‘getters’ y ‘setters’,
usando las palabras clave get
y set
:
class Rectangle {
, top, width, height;
num left
this.left, this.top, this.width, this.height);
Rectangle(
// Define two calculated properties: right and bottom.
get right => left + width;
num set right(num value) => left = value - width;
get bottom => top + height;
num set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
.right = 12;
rectassert(rect.left == -8);
}
Los métodos de instancia, los ‘getters’ y los ‘setters’ pueden ser abstractos, definiendo una interfaz pero dejando su implementación en otras clases. Los métodos abstractos solo pueden declararse en clases abstractas.
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
Para definir una clase abstracta se utiliza el modificador
abstract
. Una clase abstracta es una clase que no puede ser
instanciada. Las clases abstractas son útiles para definir interfaces, a
menudo con alguna implementación. Las clases abstractas normalmente
tienen métodos abstractos.
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
A diferencia de Java o Kotlin, en Dart no existe el concepto de interfaz como entidad. Cada clase define implícitamente una interfaz que contiene todos los miembros de instancia de la clase y de las interfaces que implementa. Si desea crear una clase A que soporte la API de la clase B sin heredar la implementación de B, la clase A debería implementar la interfaz B.
Una clase implementa una o más interfaces al declararlas en una
cláusula implements
y luego proporcionar las API requeridas
por las interfaces.
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
this._name);
Person(
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
'Kathy')));
print(greetBob(Person(
print(greetBob(Impostor()));}
Una clase puede implementar múltiples interfaces separadas por comas:
class Point implements Comparable, Location {...}
Se utiliza la palabra clave extends
para crear una
subclase y la palabra clave super
para referirse a la
superclase:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();}
// ···
}
Las subclases puede sobrescribir métodos de instancia,
‘getters’ y ‘setters’. Se utiliza la anotación
@override
para indicar al compilador que un método está
sobrescribiendo un método de la superclase.
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
Los tipos enumerados, a menudo llamados enumeraciones o enums, son un tipo especial de clase que se utiliza para representar un número fijo de valores constantes.
Para declarar una enumeración se utiliza la palabra clave
enum
:
enum Color { red, green, blue }
Cada valor en una enumeración tiene un índice, que devuelve la posición de dicho valor dentro de la enumeración, teniendo en cuenta que las enumeraciones, al igual que los arrays, empiezan en 0:
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
Para obtener una lista con todos los valores de una enumeración, se
usa la constante values
:
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
Las enumeraciones se puede emplear en bloques switch
. En
caso de que no haya una cláusula case
para cada valor de la
enumeración se lanza un aviso:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
'Red as roses!');
print(break;
case Color.green:
'Green as grass!');
print(break;
default: // Without this, you see a WARNING.
// 'Color.blue'
print(aColor); }
Las clases enumeradas tienen los siguientes límites:
Se usa la palabra clave static
para implementar
variables y métodos en toda la clase.
Las variables estáticas (variables de clase) son útiles para constantes y estados de toda la clase. Las variables estáticas no se inicializan hasta que no se utilizan.
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
Los métodos estáticos (métodos de clase) no funcionan en una
instancia y, por lo tanto, no tienen acceso a this
. Los
métodos estáticos se pueden utilizar como constantes en tiempo de
compilación.
import 'dart:math';
class Point {
, y;
num xthis.x, this.y);
Point(
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);}
Nota: Es recomendable utilizar funciones de nivel superior en lugar de métodos estáticos para utilidades y funcionalidades comunes que son ampliamente utilizadas.
Las directivas import
y library
permiten
crear código modular y reutilizable. Las bibliotecas no sólo
proporcionan APIs, sino que son una unidad de privacidad: los
identificadores que comienzan con un guión bajo (_
) sólo
son visibles dentro de la biblioteca. Cada aplicación en Dart es una
biblioteca, incluso si no usa la directiva library
. Las
bibliotecas pueden distribuirse usando packages
.
Se utiliza la palabra clave import
para especificar cómo
se usa un espacio de nombres de una biblioteca en el alcance de otra
biblioteca, es decir, como importar un biblioteca para ser utilizada en
otra biblioteca:
import 'dart:html';
import 'dart:math';
El único argumento necesario para importar una biblioteca es una URI
que especifique la biblioteca. Para las bibliotecas incorporadas en el
núcleo de Dart, la URI tiene la forma dart:
. Para otras
bibliotecas o bibliotecas de terceros, puede utilizar una ruta de
sistema de archivos o la forma package:
. La forma
package:
especifica las bibliotecas proporcionadas por un
gestor de paquetes como la herramienta ‘pub’:
import 'package:test/test.dart';
Si se importan dos bibliotecas que tienen identificadores en conflicto, se puede especificar un prefijo para una o ambas bibliotecas y así eliminar la ambigüedad:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
= Element();
Element element1
// Uses Element from lib2.
.Element element2 = lib2.Element(); lib2
Si desea utilizar solo una parte de una biblioteca, puede importar selectivamente la biblioteca:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
La carga diferida (también llamada ‘lazy loading’) permite que una aplicación cargue una biblioteca bajo demanda, cuando y donde sea necesario. He aquí algunos casos en los que puede utilizar la carga diferida:
Para indicar que una biblioteca se cargará de forma diferida se
utiliza la palabra clave deferred as
. Cuando la biblioteca
sea necesaria, se invocará llamando al método
loadLibrary()
. En el ejemplo se emplea await
para pausar la ejecución hasta que la biblioteca se cargue:
import 'package:greetings/hello.dart' deferred as hello;
Future greet() async {
await hello.loadLibrary();
.printGreeting();
hello}
Tenga en cuenta lo siguiente cuando utilice la carga diferida:
loadLibrary()
varias veces en una
biblioteca sin problemas. La biblioteca se cargará una sola vez.loadLibrary()
en el espacio
de nombres cuando se utiliza deferred as
. La función
loadLibrary()
devuelve un Future.El conjunto de bibliotecas de Dart tienen muchas funciones que devuelven tipos como Future o Stream. Estas funciones son asíncronas: regresan después de configurar una operación que puede llevar mucho tiempo (como I/O), sin esperar a que esa operación se complete.
Dart proporciona las palabras clave async
y
await
para dar soporte a la programación asíncrona,
permitiendo escribir código asíncrono que se parece al código
síncrono.
Existen dos formas de manejar el tipo ‘Future’:
async
y await
dart:async
Una función asíncrona es una función cuyo cuerpo está marcado con el
modificador async
. Una función declarada como asíncrona
retorna un tipo ‘Future’.
// Función síncrona
String lookUpVersion() => '1.0.0';
// Función asíncrona que retorna un Future<String>
Future<String> lookUpVersion() async => '1.0.0';
// Función asíncrona que retorna un Future<void> o Future
Future hello() async => print("Hello");
Aunque una función async
puede realizar operaciones que
requieren mucho tiempo, no espera a que se realicen. En su lugar, la
función asíncrona se ejecuta sólo hasta que encuentra su primera
expresión await
. Luego devuelve un objeto
‘Future’, reanudando la ejecución sólo después de que se
complete la expresión await
.
Para manejar los errores se utiliza una sentencia
try-catch
:
try {
= await lookUpVersion();
version } catch (e) {
// React to inability to look up the version
}
Si se obtiene un error en tiempo de compilación al usar
await
, hay que comprobar que await
esté en una
función asíncrona. Es por esto que para usar await
en la
función main()
esta se tiene que marcar como
async
:
Future main() async {
checkVersion();'In main: version is ${await lookUpVersion()}');
print(}
Ejemplo de uso de async
y await
:
import 'dart:async';
Future<void> printDailyNewsDigest() async {
var newsDigest = await gatherNewsReports();
print(newsDigest);}
{
main()
printDailyNewsDigest();
printWinningLotteryNumbers();
printWeatherForecast();
printBaseballScore();}
{
printWinningLotteryNumbers() 'Winning lotto numbers: [23, 63, 87, 26, 2]');
print(}
{
printWeatherForecast() "Tomorrow's forecast: 70F, sunny.");
print(}
{
printBaseballScore() 'Baseball score: Red Sox 10, Yankees 0');
print(}
const news = '<gathered news goes here>';
const oneSecond = Duration(seconds: 1);
// Imagine that this function is more complex and slow. :)
Future<String> gatherNewsReports() =>
Future.delayed(oneSecond, () => news);
La mayoría de los ordenadores, incluso en plataformas móviles, tienen CPUs multinúcleo. Para aprovechar todos estos núcleos, los desarrolladores utilizan tradicionalmente hilos de memoria compartida que se ejecutan simultáneamente. Sin embargo, la concurrencia de estados compartidos es propensa a errores y puede conducir a código complicado.
En lugar de hilos, todo el código en Dart corre dentro de **_isolates**_. Cada entorno aislado tiene su propia pila de memoria, lo que al no ser compartida se garantiza que no se pueda acceder ni modificar el estado.
Esta obra está bajo una licencia de
Creative Commons Reconocimiento-Compartir Igual 4.0
Internacional.