Introdução ao Dart para desenvolvedores Java

Dart é a linguagem de programação do Flutter, o kit de ferramentas de IU do Google para criar apps nativos incríveis para computador, dispositivos móveis e Web com uma única base do código.

Este codelab apresenta o Dart com foco em recursos que os desenvolvedores Java não esperam. É possível gravar funções do Dart em um minuto, scripts em 5 minutos e apps em 10 minutos.

O que você aprenderá

  • Instruções para criar construtores
  • Maneiras diferentes de especificar parâmetros
  • Quando e como criar getters e setters
  • Como o Dart cuida da privacidade
  • Instruções para criar fábricas
  • Como é a programação funcional no Dart
  • Outros conceitos centrais do Dart

Pré-requisitos

Para concluir este codelab, você só precisa de um navegador.

Todos os exemplos são gravados e executados no DartPad, uma ferramenta interativa com base em navegador que permite brincar com recursos da linguagem Dart e bibliotecas principais. Se preferir, você pode usar um ambiente de desenvolvimento integrado, como o WebStorm, o IntelliJ com o plug-in Dart ou o Visual Studio Code com a extensão Dart Code.

O que você quer aprender neste codelab?

Sou iniciante no assunto e quero ter uma boa visão geral. Conheço um pouco sobre esse assunto, mas quero uma recapitulação. Estou procurando um exemplo de código para usar no meu projeto. Estou procurando uma explicação de algo específico.

Para começar, você criará uma classe Dart simples com a mesma funcionalidade da classe Bicycle do tutorial sobre Java. A classe Bicycle contém algumas variáveis de instância privadas com getters e setters. Um método main() instancia uma Bicycle e a imprime no console.

99c813a1913dcc42.png c97a12197358d545.png

Iniciar o DartPad

Este codelab fornece uma nova instância do DartPad para cada conjunto de exercícios. O link abaixo abre uma nova instância, que contém um exemplo "Hello" padrão. Você pode continuar usando o mesmo DartPad em todo o codelab, mas, se clicar em Redefinir, o DartPad voltará ao exemplo padrão e seu trabalho será perdido.

b2f84ff91b0e1396.png Abra o DartPad.

Definir uma classe Bicycle

b2f84ff91b0e1396.png Acima da função main(), adicione uma classe Bicycle com três variáveis de instância. Além disso, remova o conteúdo de main(), conforme mostrado no snippet de código a seguir:

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

cf1e10b838bf60ee.png Observações

  • O método principal do Dart é chamado de main(). Se você precisar de acesso a argumentos da linha de comando, poderá adicioná-los: main(List<String> args).
  • O método main() fica no nível superior. No Dart, é possível definir o código fora das classes. Variáveis, funções, getters e setters podem ficar fora das classes.
  • O exemplo Java original declara variáveis de instância privadas usando a tag private, que o Dart não usa. Você aprenderá mais sobre privacidade posteriormente, em "Adicionar uma variável somente leitura".
  • main() e Bicycle não são declarados como public porque, por padrão, todos os identificadores são públicos. O Dart não tem palavras-chave para public, private ou protected.
  • Neste exemplo, o analisador do Dart produz um erro informando que as variáveis precisam ser inicializadas porque não são anuláveis. Isso será corrigido na próxima seção.
  • Por convenção, o Dart usa o recuo de dois caracteres, em vez de quatro. Não é preciso se preocupar com as convenções de espaço em branco do Dart, graças a uma ferramenta útil chamada dartfmt. Como dizem as convenções de código do Dart (Effective Dart, link em inglês), "as regras oficiais de processamento de espaços em branco para Dart são qualquer coisa que seja produzida por dartfmt".

Definir um construtor da classe Bicycle

b2f84ff91b0e1396.png Adicione o seguinte construtor à classe Bicycle:

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

cf1e10b838bf60ee.png Observações

  • Esse construtor não tem corpo, o que é válido no Dart.
  • Se você esquecer o ponto e vírgula (;) no final de um construtor sem corpo, o DartPad exibirá o seguinte erro: "Um corpo de função precisa ser fornecido".
  • O uso de this na lista de parâmetros de um construtor é um atalho útil para atribuir valores a variáveis de instância.
  • O código acima é equivalente ao seguinte:
Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

Formatar o código

Reformate o código do Dart a qualquer momento clicando em Formatar na parte superior da IU do DartPad. A reformatação é particularmente útil quando você cola o código no DartPad e a justificativa está desativada.

b2f84ff91b0e1396.png Clique em Formatar.

Instanciar e imprimir uma instância da classe Bicycle

b2f84ff91b0e1396.png Adicione o seguinte código à função main():

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

b2f84ff91b0e1396.png Remova a palavra-chave new opcional:

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

cf1e10b838bf60ee.png Observação

  • A palavra-chave new passou a ser opcional no Dart 2.
  • Se você souber que o valor de uma variável não será alterado, poderá usar final em vez de var.

Executar o exemplo

b2f84ff91b0e1396.png Execute o exemplo clicando em Executar na parte superior da janela do DartPad. Se Executar não estiver ativado, consulte a seção Problemas mais adiante nesta página.

Você verá esta resposta:

Instance of 'Bicycle'

cf1e10b838bf60ee.png Observação

  • Nenhum erro ou aviso pode ser exibido para indicar que a inferência de tipo está funcionando e que o analisador infere que a instrução que começa com var bike = define uma instância de Bicycle.

Melhorar a resposta

Embora a resposta "Instância de Bicycle'" esteja correta, ela não é muito informativa. Todas as classes do Dart têm um método toString() que pode ser substituído para fornecer uma resposta mais útil.

b2f84ff91b0e1396.png Adicione o seguinte método toString() em qualquer lugar na classe Bicycle:

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

cf1e10b838bf60ee.png Observações

  • A anotação @override informa ao analisador que você está substituindo um membro intencionalmente. O analisador gerará um erro se você não executar corretamente a substituição.
  • O Dart aceita aspas simples ou duplas ao especificar strings.
  • Use a interpolação de string para colocar o valor de uma expressão dentro de uma literal de string: ${expression}. Se a expressão for um identificador, ignore as chaves: $variableName.
  • Encurte funções ou métodos de uma linha usando a notação de seta dupla (=>).

Executar o exemplo

b2f84ff91b0e1396.png Clique em Executar.

Você verá esta resposta:

Bicycle: 0 mph

Problemas?Verifique seu código.

Adicionar uma variável somente leitura

O exemplo Java original define speed como uma variável somente leitura. Ele a declara como privada e fornece apenas um getter. Depois, você fornecerá a mesma funcionalidade no Dart.

b2f84ff91b0e1396.png Abra bicycle.dart no DartPad (ou continue usando sua cópia).

Para marcar um identificador do Dart como privado na biblioteca, comece o nome com um sublinhado (_). É possível converter speed em somente leitura mudando o nome e adicionando um getter.

Transformar a velocidade em uma variável de instância privada somente leitura

b2f84ff91b0e1396.png No construtor Bicycle, remova o parâmetro speed:

Bicycle(this.cadence, this.gear);

b2f84ff91b0e1396.png Em main(), remova o segundo parâmetro (speed) da chamada para o construtor Bicycle:

var bike = Bicycle(2, 1);

b2f84ff91b0e1396.png Mude as ocorrências restantes de speed para _speed. (Dois lugares)

b2f84ff91b0e1396.png Inicialize _speed como 0:

int _speed = 0;

b2f84ff91b0e1396.png Adicione o seguinte getter à classe Bicycle:

int get speed => _speed;

cf1e10b838bf60ee.png Observações

  • Cada variável, mesmo que seja um número, precisa ser inicializada ou declarada como nulo, adicionando ? à declaração de tipo.
  • O compilador do Dart aplica a privacidade da biblioteca para qualquer identificador com um sublinhado como prefixo. Privacidade da biblioteca geralmente significa que o identificador só é visível dentro do arquivo (não apenas na classe) em que ele é definido.
  • Por padrão, o Dart fornece getters e setters implícitos para todas as variáveis de instância públicas. Não é necessário definir seus próprios getters ou setters, a menos que você queira aplicar variáveis somente leitura ou somente gravação, calcular ou verificar um valor ou atualizar um valor em outro lugar.
  • O exemplo de Java original forneceu getters e setters para cadence e gear. Como o exemplo de Dart não precisa de getters e setters explícitos, ele usa apenas variáveis de instância.
  • Você pode começar com um campo simples, como bike.cadence, e depois refatorar para usar getters e setters. A API permanece igual. Em outras palavras, ir de um campo para um getter e setter não é uma alteração interruptiva no Dart.

Concluir a implementação da velocidade como uma variável de instância somente leitura

b2f84ff91b0e1396.png Adicione os seguintes métodos à classe Bicycle:

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

O exemplo de Dart final é semelhante ao de Java original, mas é mais compacto em 23 linhas, em vez 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);
}

Problemas?Verifique seu código.

O próximo exercício define uma classe Rectangle, outro exemplo do tutorial sobre Java.

O código Java mostra a sobrecarga de construtores, uma prática comum em Java em que os construtores têm o mesmo nome, mas diferem no número ou no tipo de parâmetros. O Dart não é compatível com a sobrecarga de construtores e trata a situação de maneira diferente, como você verá nesta seção.

b2f84ff91b0e1396.png Abra o exemplo da classe Rectangle no DartPad.

Adicionar um construtor da classe Rectangle

b2f84ff91b0e1396.png Adicione um único construtor vazio que substitui os quatro construtores no exemplo de Java:

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

Esse construtor usa parâmetros nomeados opcionais.

cf1e10b838bf60ee.png Observações

  • this.origin, this.width e this.height usam a abreviação da atalho para atribuir variáveis de instância na declaração de um construtor.
  • this.origin, this.width e this.height são parâmetros nomeados opcionais. Os parâmetros nomeados ficam entre chaves ({}).
  • A sintaxe de this.origin = const Point(0, 0) especifica um valor padrão de Point(0,0) para a variável de instância origin. O padrão especificado precisa ser uma constante de tempo de compilação. Esse construtor fornece valores padrão para as três variáveis de instância.

Melhorar a resposta

b2f84ff91b0e1396.png Adicione a seguinte função toString() à classe Rectangle:

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

Usar o construtor

b2f84ff91b0e1396.png Substitua main() pelo seguinte código para confirmar se é possível instanciar Rectangle usando apenas os parâmetros necessários:

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 Observação

  • O construtor do Dart para Rectangle é uma linha de código, em comparação com as 16 linhas de código para construtores equivalentes na versão para Java.

Executar o exemplo

Você verá esta resposta:

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

Problemas?Verifique seu código.

As fábricas, um padrão de design usado com frequência em Java, têm diversas vantagens em relação à instanciação direta de objetos, como ocultar os detalhes da instanciação, permitir o retorno de um subtipo do tipo de retorno de fábrica e, opcionalmente, o retorno de um objeto existente em vez de um novo.

Essa etapa demonstra duas maneiras de implementar uma fábrica de criação de formas:

  • Opção 1: criar uma função de nível superior
  • Opção 2: criar um construtor de fábrica

Neste exercício, você usará o exemplo da classe Shapes, que instancia formas e imprime a á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 Abra o exemplo da classe Shapes no DartPad.

Na área do console, você verá as áreas calculadas de um círculo e um quadrado:

12.566370614359172
4

cf1e10b838bf60ee.png Observações

  • O Dart é compatível com classes abstratas.
  • É possível definir várias classes em um arquivo.
  • dart:math é uma das bibliotecas principais do Dart. Outras bibliotecas principais incluem dart:core, dart:async, dart:convert e dart:collection.
  • Por convenção, as constantes de biblioteca do Dart são lowerCamelCase (por exemplo, pi em vez de PI). Se você quiser saber mais sobre o motivo, consulte a diretriz de estilo PREFIRA lowerCamelCase para nomes de constante (link em inglês).
  • O código a seguir mostra dois getters que calculam um valor: num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square

Opção 1: criar uma função de nível superior

b2f84ff91b0e1396.png Implemente uma fábrica como uma função de nível superior adicionando a seguinte função no nível mais alto (fora de qualquer classe):

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

b2f84ff91b0e1396.png Invoque a função de fábrica substituindo as duas primeiras linhas no método main():

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

Executar o exemplo

A saída precisa parecer a mesma de antes.

cf1e10b838bf60ee.png Observações

  • Se a função for chamada com qualquer string diferente de 'circle' ou 'square', uma exceção será gerada.
  • O SDK do Dart define classes para muitas exceções comuns, é possível implementar a classe Exception para criar exceções mais específicas ou, como neste exemplo, é possível gerar uma string que descreve o problema encontrado.
  • Quando uma exceção é encontrada, o DartPad relata Uncaught. Para ver informações mais úteis, envolva o código em uma instrução try-catch e imprima a exceção. Como exercício opcional, confira este exemplo do DartPad.
  • Para usar aspas simples dentro de uma string, crie um escape para a aspa incorporada com uma barra ('Can\'t create $type.') ou especifique a string com aspas duplas ("Can't create $type.").

Problemas?Verifique seu código.

Opção 2: criar um construtor de fábrica

Use a palavra-chave factory do Dart para criar um construtor de fábrica.

b2f84ff91b0e1396.png Adicione um construtor de fábrica à classe abstrata 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 Substitua as duas primeiras linhas de main() pelo código a seguir para instanciar as formas:

  final circle = Shape('circle');
  final square = Shape('square');

b2f84ff91b0e1396.png Exclua a função shapeFactory() que você adicionou anteriormente.

cf1e10b838bf60ee.png Observação

  • O código no construtor da fábrica é idêntico ao usado na função shapeFactory().

Problemas?Verifique seu código.

A linguagem Dart não inclui uma palavra-chave interface porque cada classe define uma interface.

b2f84ff91b0e1396.png Abra o exemplo da classe Shapes no DartPad (ou continue usando a cópia).

b2f84ff91b0e1396.png Adicione uma classe CircleMock que implemente a interface Circle:

class CircleMock implements Circle {}

b2f84ff91b0e1396.png Você verá um erro "Implementações concretas ausentes" porque CircleMock não herda a implementação de Circle. Ele usa apenas a interface correspondente. Para corrigir esse erro, defina as variáveis de instância area e radius:

class CircleMock implements Circle {
  num area = 0;
  num radius = 0;
}

cf1e10b838bf60ee.png Observação

  • A classe CircleMock não define nenhum comportamento, mas é um Dart válido. O analisador não gera erros.
  • A variável de instância area de CircleMock implementa o getter de area de Circle.

Problemas?Verifique seu código.

Na programação funcional, é possível fazer o seguinte:

  • Transmitir funções como argumentos.
  • Atribuir uma função a uma variável.
  • Desconstruir uma função que usa vários argumentos em uma sequência de funções em que cada uma usa um único argumento (também chamado de currying).
  • Criar uma função sem nome que possa ser usada como um valor constante (também chamada de expressão lambda. As expressões lambda foram adicionadas ao Java no JDK 8).

O Dart é compatível com todos esses recursos. No Dart, até mesmo as funções são objetos e têm um tipo: Function. Isso significa que as funções podem ser atribuídas a variáveis ou transmitidas como argumentos para outras funções. Você também pode chamar uma instância de uma classe Dart como se ela fosse uma função, como neste exemplo.

O exemplo a seguir usa o código imperativo (estilo não 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 Abra o exemplo da classe Scream no DartPad.

A saída será semelhante a esta:

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

cf1e10b838bf60ee.png Observação

  • Ao usar a interpolação de strings, a string ${'a' * length} avalia como "o caractere 'a' foi repetido length vezes".

Converter o código imperativo em funcional

b2f84ff91b0e1396.png Remova o loop imperativo for() {...} em main() e substitua-o por uma única linha de código que use o encadeamento de métodos:

  values.map(scream).forEach(print);

Executar o exemplo

A abordagem funcional imprime as mesmas seis classes Scream do exemplo imperativo.

Problemas?Verifique seu código.

Usar mais recursos de iteração

As classes principais List e Iterable são compatíveis com fold(), where(), join(), skip() e muito mais. O Dart também tem suporte integrado para mapas e conjuntos.

b2f84ff91b0e1396.png Substitua a linha values.map() em main() pelo seguinte:

  values.skip(1).take(3).map(scream).forEach(print);

Executar o exemplo

A saída será semelhante a esta:

Aaah!
Aaaah!
Aaaaaah!

cf1e10b838bf60ee.png Observações

  • skip(1) ignora o primeiro valor, 1, no literal da lista de values.
  • take(3) recebe os próximos três valores (2, 3 e 5) no literal de lista de values.
  • Os valores restantes são ignorados.

Problemas?Verifique seu código.

Com este codelab, você aprendeu algumas diferenças entre Java e Dart. O Dart é fácil de aprender e, além disso, as bibliotecas principais e o rico conjunto de pacotes disponíveis aumentam sua produtividade. O Dart funciona bem para apps grandes. Centenas de engenheiros do Google usam o Dart para escrever apps essenciais que geram grande parte da receita do Google.

A seguir

Um codelab de 20 minutos não é suficiente para mostrar todas as diferenças entre Java e Dart. Por exemplo, este codelab não falou sobre:

Para ver as tecnologias do Dart em ação, confira os codelabs do Flutter.

Saiba mais

Aprenda sobre o Dart com os artigos, recursos e sites a seguir.

Artigos

Recursos

Websites