Dart: O que é? Como começar a estudar? Para quê serve?

Dart: O que é? Como começar a estudar? Para quê serve?

Se você está atento às novidades do desenvolvimento de aplicativos, provavelmente já ouviu falar de Dart, especialmente em conjunto com o famoso Flutter.

Imagine criar aplicativos que funcionam em Android, iOS e Web, tudo a partir de um único código-base. Parece mágico, não é?

Bem, essa mágica tem um nome: Flutter, e no coração dessa tecnologia está o Dart.

Mas não se engane, Dart não é apenas um coadjuvante. Ele é a estrela que compila seu código para rodar em diversas plataformas.

Então, o que exatamente é Dart e por que ele é tão crucial? Vamos descobrir!

O que é Dart?

Dart é uma linguagem de programação desenvolvida pela Google, semelhante a Java, C++, e JavaScript.

Ela é "levemente tipada" e pode ser tanto interpretada quanto compilada. Dart foi criada com o objetivo de ser uma linguagem multiplataforma, permitindo que você escreva o código uma vez e o execute em diversas plataformas.

Recentemente, Dart tem sido amplamente utilizada no Flutter, como servidor web e, principalmente, para criar ferramentas para a própria linguagem, como o compilador e o analisador de código, todos escritos em Dart.

Dart foi projetada para ser fácil de aprender. Sua familiaridade com outras linguagens populares do mercado reduz a necessidade de aprender novas estruturas e sintaxes.

Além disso, é fácil iniciar com Dart, pois é possível testar a linguagem diretamente no navegador.

Essas são as declarações da Google. Agora, vou compartilhar minhas motivações:

  • Realmente é mais fácil começar a aprender se você já tem conhecimento em alguma outra linguagem de programação, principalmente alguma linguagem que siga bastante o paradigma de Orientação à Objetos;
  • A documentação é bem extensa, bem escrita e com várias ferramentas de busca;
  • A instalação é bem simples, podendo apenas baixar um arquivo .zip e depois adicionar uma variável de ambiente;
  • Não precisa de uma IDE específica para codar e aproveitar as funcionalidades (como o hot reload);
  • Você pode utilizar ItelliJ, AndroiStudio, xcode, Visual Studio Code e até mesmo NeoVim!;
  • Tem uma comunidade ativa e que produz diversas bibliotecas e extensões;
  • Tudo que a comunidade produz vai para um repositório chamado pub.dev.

E acho que o mais importante: é um código aberto (open source)! Bacana, né?!

Banner da Escola de Mobile: Matricula-se na escola de Mobile. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

A sintaxe do Dart

Vamos ver um exemplo de código Dart:

void main() {
  print('Hello World');
}

O código acima imprime no console a frase: "Hello World". Por ora é um código simples, mas é uma forma de mostrar como é fácil começar com Dart.

Vamos aprofundar um pouco mais, escrevendo uma função:

void main() {
  void sumNumbers() {
    print(10 + 10);
  }
  sumNumbers();
}

O código acima imprime no console o número 20. Também é um código simples, mas vemos algumas funcionalidades extras da linguagem.

Por exemplo: se você veio de outras linguagens, deu para perceber que para executar um código Dart, é necessário ter a função main().

Outro detalhe é na palavra void. Isso quer dizer que é importante dizer o tipo de retorno (se existir) de uma função.

Por fim, notamos também que a linguagem requer o uso de ; no final de cada linha executada.

Vamos ver um último exemplo:

void main() {
    var texto = "Um texto qualquer";
    String outroTexto = "que complementa outro texto.";

    minhaFuncao() {
        return texto + " " + outroTexto;
    }

    minhaFuncao();
}

Rodando o código acima, devemos ter o seguinte resultado no console:

Um texto qualquer que complementa outro texto.

Dart é uma linguagem "levemente tipada", o que significa que, similar ao JavaScript, permite declarar variáveis sem especificar o tipo de dado.

Também é possível omitir o tipo de retorno das funções. No entanto, uma diferença crucial é que, se o código tentar realizar uma operação com uma String em Dart, o programa lança uma exceção, enquanto no JavaScript, embora o comportamento seja incorreto, o programa continua funcionando.

Portanto, apesar de permitir omissões para facilitar a escrita de código, Dart é, na verdade, uma linguagem fortemente tipada.

Como instalar o Dart

Não vou passar um tutorial passo a passo de como instalar o Dart, até porque varia bastante de sistema operacional para sistema operacional, mas vou deixar aqui a documentação oficial para que você possa acompanhar o processo.

Confira o link do processo de instalação.

Como iniciar um projeto Dart

Depois de instalado, nós podemos iniciar um projeto Dart de várias maneiras. O que eu mais recomendo sempre fazer é utilizar comandos no terminal.

É uma garantia de que o processo será igual independente da IDE que você esteja utilizando.

Outro detalhe é que, para este exemplo, utilizarei o Visual Studio Code com os seguintes plugins e versões:

  • Visual Studio Code na versão 1.89.1;
  • Plugin Dart na versão 3.88.1 (dartcode.org).

Atenção! Eu já estou assumindo que você conhece comandos básicos do terminal, como ls, cd, mkdir e também como funciona a estrutura básica de arquivos no seu computador (como identificar a pasta que você está e a árvore de arquivos).

No meu terminal, vou navegar para a minha pasta de documentos, onde tenho uma outra pasta de estudos. Dentro dela vou rodar o seguinte comando no terminal:

dart create meu_app

Bom, dentro do meu terminal apareceu a seguinte mensagem:

❯ dart create meu_app
Creating meu_app using template console...

  .gitignore
  analysis_options.yaml
  CHANGELOG.md
  pubspec.yaml
  README.md
  bin/meu_app.dart
  lib/meu_app.dart
  test/meu_app_test.dart

Running pub get...                     1.7s
  Resolving dependencies...
  Changed 48 dependencies!
  3 packages have newer versions incompatible with dependency constraints.
  Try `dart pub outdated` for more information.

Created project meu_app in meu_app! In order to get started, run the following commands:

  cd meu_app
  dart run

Observe que esse comando gerou várias pastas e arquivos para nós. Agora podemos ir no Visual Studio Code.

Abra o seu VSCode e em seguida abra a pasta meu_app que nós criamos através do comando dart create.

Você deve ter uma estrutura de arquivos parecida com esta:

A imagem exibe uma captura de tela de um editor de código com um painel de explorador de arquivos à esquerda. O explorador de arquivos mostra uma lista de arquivos e pastas, indicando que provavelmente faz parte de um ambiente de desenvolvimento de software. O projeto destacado tem o nome “MEU_APP”, sugerindo que o usuário pode estar trabalhando em um aplicativo ou projeto com esse nome. Dentro do diretório “MEU_APP”, há várias outras pastas e arquivos visíveis: “bin”, “lib”, “test”, “.dart_tool”, “.idea”, “.gitignore”, “analysis_options.yaml”, “CHANGELOG.md”, “pubspec.lock”, “pubspec.yaml” e “README.md”. Esses itens sugerem que este é provavelmente um projeto na linguagem de programação Dart, conforme indicado pela presença de arquivos específicos do Dart, como “.dart_tool”, e pelo uso de arquivos “.yaml”, que são frequentemente usados para configuração em projetos Dart. Além disso, há um ícone semelhante a um frasco de laboratório ao lado da pasta chamada ‘test’, o que geralmente denota scripts ou estruturas de teste em projetos de desenvolvimento.
- .dart_tool
- bin
- lib
- test
.gitignore
analysis_opgions.yaml
CHANGELOG.md
pubspec.lock
pubspec.yaml
README.md

Podemos fechar nosso terminal e agora vamos entender um pouco de como funciona a estrutura de arquivos do Dart.

Vamos ignorar a pasta .dart_tool e vamos direto para a pasta bin. Dentro da pasta bin é onde encontramos o arquivo principal para rodar nossa aplicação.

É onde encontramos o arquivo com o mesmo nome do projeto, mas o ideal é que aqui tenha apenas um arquivo como main.dart.

A próxima pasta é a pasta lib. É na pasta lib onde criamos as outras partes importantes para a nossa aplicação como, por exemplo, os módulos, modelos, controllers e serviços que formam toda a nossa aplicação.

A pasta test é autoexplicativa. É onde criamos todos os testes da nossa aplicação.

Dos arquivos externos, apenas dois são importantes para nós em um nível básico:

  • pubspec.yaml;
  • README.md.

O arquivo pubspec.yaml é onde dizemos para o Dart onde encontrar recursos como imagens, fontes e módulos externos.

Também é nesse arquivo que dizemos quais bibliotecas do pub.dev vamos utilizar.

Por fim, o arquivo README.md é onde escrevemos a página inicial que ficará no GitHub.

É importante que esse arquivo tenha uma boa descrição sobre o que é o projeto, como iniciar, quais dependências ele utiliza, entre outras informações importantes.

Você pode conferir nosso tutorial sobre como escrever um README neste link.

Agora você já tem tudo necessário para começar a escrever códigos em Dart.

Tipos de dados

Como Dart é uma linguagem fortemente tipada, é importante conhecermos os tipos básicos:

Dart, assim como outras linguagens, consegue trabalhar com diferentes tipos de dados. É importante conhecer as palavras-chave que definem esses tipos de dados. Veja os tipos mais comuns:

  • int;
    • int é utilizado para designar números inteiros. Ex. 1, 10, -100;
  • double;
    • double é utilizado para definir números com pontos flutuantes (com casas decimais). Ex. 3.14, -2.7;
  • String;
    • String é utilizado para definir conteúdos textuais. Pode-se utilizar aspas simples ou duplas. Ex. "Matheus", 'Uma String';
  • bool;
    • bool é utilizado para definir valores booleanos como verdadeiro ou falso. Ex. true, false;
  • List;
    • List é utilizado para definir uma lista ordenada de qualquer tipo. Ex. ["Abóbora", "Cenoura"], [10, 5, 32];
  • Set;
    • Set é utilizado para definir um conjunto não ordenado de qualquer tipo. Ex. {"Abóbora", "Cenoura"};
  • Map;
    • Map é utilizado para definir objetos que possuem chave e valor. Ex. {"chave": "valor", "chave2": "outro valor"};

Também é importante conhecer tipos especiais:

  • null;
    • null representa nada, vazio, ausência de valor;
  • dynamic;
    • dynamic representa qualquer coisa. É utilizado exatamente quando não sabemos o tipo de dado que precisamos guardar;

Exemplo de código mostrando como declarar cada tipo falado acima:

void main() {
  int inteiro = 10;
  double float = 2.5;
  String texto = "Um texto";
  bool sim = true;
  List lista = ["Abóbora", "Cenoura"];
  Set conjunto = {"Abóbora", "Cenoura"};
  Map objeto = {"chave": "valor", "chave2": "valor2"};

  print(inteiro);
  print(float);
  print(texto);
  print(sim);
  print(lista);
  print(conjunto);
  print(objeto);
}

Para rodar o código, você pode clicar na opção "Run" que fica em cima da função main() e ver o resultado diretamente no Debug console do Visual Studio Code.

Outra opção é utilizar o terminal, rodando o comando dart run.

Resposta no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
10
2.5
Um texto
true
[Abóbora, Cenoura]
{Abóbora, Cenoura}
{chave: valor, chave2: valor2}

Nós também podemos tipar listas, conjuntos e objetos:

  List<String> lista = ["Abóbora", "Cenoura"];
  Set<String> conjunto = {"Abóbora", "Cenoura"};
  Map<String, String> objeto = {"chave": "valor", "chave2": "valor2"};

É sempre muito importante dizer para o Dart que tipo de informação que estamos trabalhando.

Na lista dizemos que estamos trabalhando com uma lista de String, um conjunto de String, e um objeto que tem uma chave no formato String e um valor também no formato String.

O que acontece se a gente alterar para um tipo que não corresponde ao conteúdo? Por exemplo:

  List<bool> lista = ["Abóbora", "Cenoura"];

No próprio VSCode aparecerá uma mensagem de erro dizendo que tipo String não pode ser atribuído para valores bool.

Uma captura de tela mostrando código na linguagem de programação Dart dentro de um IDE com destaque de sintaxe. O código inclui declarações de variáveis de diferentes tipos, como inteiros, doubles, strings e booleanos, além de coleções como listas e conjuntos contendo elementos de string. Duas linhas estão marcadas com indicadores de erro, sugerindo incompatibilidades de tipos nas atribuições.

Mas agora vem uma parte muito interessante do Dart! Veja o exemplo a seguir:

List<dynamic> lista = ["Abóbora", 4, true];

O código acima não dá erro. O Dart permite criar listas que possuem valores diferentes e isso não é algo exclusivo para listas, visto que os conjuntos e objetos também podem trabalhar com dynamic.

E não precisamos declarar variáveis como dynamic. Podemos utilizar a palavra-chave var (como no JavaScript).

  var umValor = bool;

Veja um exemplo muito comum:

Map<String, dynamic> objeto = {
    "chave": {"chave": "valor"},
    "outra chave": bool
  };

Não foque muito nos valores, mas foque na estrutura de Map<String, dynamic>. Essa estrutura é super usada quando trabalhamos com banco de dados ou informações da internet.

Normalmente nós transformamos os dados em objetos para trabalhar dentro do Dart. Vamos entender melhor quando entrarmos em classes.

Funções

Funções também são muito importantes de serem aprendidas e, conhecer a sua estrutura, é essencial para criar aplicações dinâmicas.

Nós já vimos um exemplo de funções no começo do artigo, mas vamos aprofundar um pouco mais, entendendo como funciona a sua estrutura e como podemos fazer funções mais robustas e complexas.

Veja um exemplo de criação de uma função:

void main() {
  int valor1 = 10;
  int valor2 = 5;

  void calcular() {
    print(valor1 + valor2);
  }

  calcular();
}

O código acima é uma aplicação que tem dois valores inteiros declarados, uma função sem retorno e seu corpo chama a função do Dart print para mostrar informações no console, somando os dois valores declarados anteriormente.

Para declarar uma função, primeiro precisamos dizer o tipo de retorno, que neste caso é void, depois o nome da função e por último o corpo da função.

Podemos pensar nesta estrutura:

retorno nome() {
  corpo;
}

No exemplo anterior, nós estávamos calculando a soma de duas variáveis fora da função, mas podemos deixar a função mais dinâmica passando valores como atributo. Veja o exemplo:

void main() {
  int valor1 = 10;
  int valor2 = 5;

  void calcular(a, b) {
    print(a + b);
  }

  calcular(valor1, valor2);
}

Neste caso, a função calcular recebe dois valores: a e b. A vantagem dessa prática, é que essa função não fica presa a apenas somar as variáveis valor1 e valor2, nós agora podemos passar quaisquer valores que a função imprimirá a soma deles.

Mas esse é um detalhe importante: qualquer valor. Isso quer dizer que podemos passar conteúdos textuais (Strings) ou booleanos, e isso não deveria ser possível.

Existe outra funcionalidade de funções, onde podemos tipar os valores que recebemos dentro da função. Veja o exemplo abaixo:

void main() {
  int valor1 = 10;
  int valor2 = 5;

  void calcular(int a, int b) {
    print(a + b);
  }

  calcular(valor1, valor2);
}

Neste exemplo, nós passamos dois valores para a função calcular e também passamos o tipo de dado que a função precisa receber, que neste caso serão números inteiros.

Assim, garantimos que a função vai trabalhar apenas com números inteiros, tornando nosso código mais seguro e robusto.

Escopo de código

Vou dar a seguinte situação: você está numa escola em uma sala de aula. Tudo que você precisa para estudar (mesa, lousa, etc) existe dentro da sua sala; outras salas também têm essas coisas.

Nessa escola também existe um pátio onde todas as pessoas vão se alimentar, brincar, conversar, etc. Guarde essa situação em sua memória.

Vamos ver agora um exemplo de código:

void main() {
  int valor1 = 10;
  int valor2 = 5;

  void calcular(int a, int b) {
    int soma = a + b;
  }

  calcular(valor1, valor2);

  print(soma);
}

Tentando rodar o código acima teremos um erro de compilação. O Dart vai dizer que a variável soma não existe dentro do contexto da função print().

Agora voltando para o contexto da escola. Podemos relacionar a função main como se fosse o pátio da escola, ou seja, todas as pessoas estudantes têm acesso.

Já a função calcular, é como se fosse uma sala de aula, ou seja, apenas estudantes daquela sala têm acesso.

Isso quer dizer que as variáveis valor1 e valor2 podem ser acessadas por toda a aplicação, mas a variável soma só pode ser acessada dentro da função calcular.

Nós chamamos essas limitações de escopo e é muito importante ter consciência dessa funcionalidade para evitar problemas como o que tivemos com o código acima, mas isso não quer dizer que queremos criar todas as variáveis globais (na função main)!

É algo que deve ser questionado com outras pessoas da equipe para ver a melhor forma de desenvolver o código.

Vamos ver algumas formas que podemos lidar com escopo:

Uma solução seria deixar a variável soma no escopo global da aplicação:

void main() {
  int valor1 = 10;
  int valor2 = 5;
  int soma = 0;

  void calcular(int a, int b) {
    soma = a + b;
  }

  calcular(valor1, valor2);

  print(soma);
}

Existem alguns problemas nessa solução. Por ora, a nossa aplicação é pequena e faz apenas esse cálculo, mas pode ser que em aplicações maiores, nós precisemos criar várias variáveis chamada soma em contextos diferentes.

Uma das coisas mais difíceis na vida de uma pessoa programadora é nomear coisas. Portanto, se pudermos evitar criar restrições para nós mesmos(as), melhor.

Uma outra possibilidade que nós temos é transformar a função de forma que ela retorne um valor.

Como estamos trabalhando com a soma de dois valores inteiros, então podemos dizer que a função vai retornar um valor inteiro.

Depois podemos guardar o resultado (ou retorno) em outra variável. Veja o exemplo:

void main() {
  int valor1 = 10;
  int valor2 = 5;
  int soma;

  int calcular(int a, int b) {
    int soma = a + b;

    return soma;
  }

  soma = calcular(valor1, valor2);

  print(soma);

}

Porém, veja que ainda estamos utilizando duas somas. Isso ainda pode ficar confuso. Então, vamos melhorar ainda mais fazendo o resultado da soma ser retornado diretamente. Além disso, podemos imprimir a função diretamente dentro da função de print():

void main() {
  int valor1 = 10;
  int valor2 = 5;

  int calcular(int a, int b) {
    return a + b;
  }

  print(calcular(valor1, valor2));

}

Laços de repetição

Vão existir situações que queremos trabalhar com repetições como, por exemplo, percorrer por valores de uma lista.

Existem algumas maneiras de trabalhar com repetições, mas é importante saber que repetições precisam de uma condição de encerramento.

Vamos ver algumas formas que temos de trabalhar com repetição no Dart.

Laço com for

A função for é composta por 4 partes principais:

  • Uma função ou execução inicial;
  • Uma condição de encerramento;
  • Uma função ou execução no final de cada iteração;
  • O corpo do for (o que será executado a cada iteração).

Vamos ver o exemplo de um for em Dart:

void main() {
  for (var i = 0; i < 5; i++) {
    print(i);
  }
}

Na primeira parte, estamos declarando a variável i e inicializando ela com o valor 0. Depois estamos verificando se i é menor que 5.

Em seguida, fazemos a impressão da variável i. Depois executamos i++ que vai simplesmente adicionar 1 à variável i. Depois voltamos para a condição e continuamos nesse ciclo até que a condição i < 5 se torne falsa.

Detalhe interessante sobre escopo: a variável i está sendo declarada dentro do for, portanto ela não pode ser acessada fora do escopo da função for.

Eu tinha falado sobre utilizar for para iterar sobre valores de uma lista, então vamos ver esse exemplo em ação:

void main() {
  List<String> listaDeCompras = ["Abóbora", "Arroz", "Sal", "Alho", "Pepino", "Alface"];

  for (var i = 0; i < listaDeCompras.length; i++) {
    print(listaDeCompras[i]);
  }
}

No exemplo acima, nós temos uma lista de compras do tipo String que contém os itens Abóbora, Arroz, Sal, Alho, Pepino e Alface.

Dentro do for nós vamos comparar o valor de i com o tamanho da lista. Para cada iteração do for, nós estamos imprimindo o item da lista listaDeCompras na posição que se encontra i.

Dentro do console temos o seguinte resultado:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
Abóbora
Arroz
Sal
Alho
Pepino
Alface

Cuidado que a primeira posição de listas é sempre a posição 0.

Laços com while

O Dart também tem outra forma de laço de repetição com while. A estrutura é um pouco diferente que o for, mas ainda segue o princípio de que uma condição de término precisa existir.

Vamos ver um exemplo de código:

void main() {
  int contador = 0;
  while (contador < 5) {
    print(contador);
    contador++;
  }
}
  • int contador = 0;: Declaramos uma variável contador do tipo inteiro e inicializamos com o valor 1.
  • while (contador < 5) { ... }: Este é o loop while. Ele vai continuar executando o bloco de código dentro das chaves {...} enquanto a condição contador <= 5 for verdadeira.
    • print(contador);: Imprime o valor atual do contador no console.
    • contador++;: Incrementa o valor do contador em 1 a cada iteração. Isso é importante para que o loop não continue infinitamente.
  • Quando o contador se torna 5, a condição contador < 5 não é mais verdadeira e o loop termina.

Este é um exemplo básico, mas demonstra como usar o loop while para executar um bloco de código repetidamente até que uma determinada condição se torne falsa.

Incrementando o while colocando nossa lista na receita:

void main() {
  List<String> listaDeCompras = ["Abóbora", "Arroz", "Sal", "Alho", "Pepino", "Alface"];

  int contador = 0;
  while (contador < listaDeCompras.length) {
    print(listaDeCompras[contador]);
    contador++;
  }
}

As estruturas de um for e um while são bem parecidas, mas as situações de uso são diferentes. Podemos utilizar for para percorrer coisas "com tamanho" ou com alguma contagem.

Listas tem um tamanho. Agora while continua executando até que algo diga que precisa parar.

Um exemplo seria: enquanto a tecla "w" for pressionada, mova o objeto para frente. A condição não é algo que podemos contar.

Do While

Existe uma versão mais complicada do while. O do while! Vamos ver um exemplo:

void main() {
  int contador = 6;

  do {
    print(contador);
    contador++;
  } while (contador <= 5);
}
  • int contador = 6;: Declaramos uma variável contador do tipo inteiro e inicializamos com o valor 6.
  • do { ... } while (contador <= 5);: Este é o loop do while. Ele executa o bloco de código dentro das chaves {...} pelo menos uma vez, e então verifica a condição contador <= 5.
    • print(contador);: Imprime o valor atual do contador na console (nesse caso, será impresso 6).
    • contador++;: Incrementa o valor do contador em 1.
  • Depois da primeira execução, a condição contador <= 5 se torna falsa (já que o contador é 7), e o loop termina.

Mesmo que a condição inicial (contador <= 5) seja falsa, o loop do while sempre garante que o bloco de código seja executado pelo menos uma vez.

Isso é útil em situações onde você precisa executar um código pelo menos uma vez, e então verificar uma condição para continuar as iterações.

Classes

Dart é uma linguagem que segue bastante os paradigmas de Orientação à Objetos. Portanto, podemos criar representações de coisas utilizando classes. Vamos ver um exemplo de uma classe que representa uma conta bancária.

class Account {
  int id = 1;
  String cpf = "333.333.333-33";
  String name = "Matheus";
  double balance = 1000.00;
}

Criei uma classe conta que possui as seguintes propriedades e valores:

  • Um id que é um número inteiro e tem o valor 1;
  • Um cpf que é uma String e tem o valor "333.333.333-33";
  • Um nome que é uma String e tem o valor "Matheus";
  • Um balanço que é um valor de ponto flutuante (double) e tem o valor 1000.00;

Podemos acessar os valores desta conta através da criação de uma instância:

// meu_app.dart

import 'package:meu_app/account.dart';

void main() {
  Account account = Account();
  print(account.name);
}
  • Primeiro precisamos importar a classe Account dentro do arquivo principal meu_app.dart;
  • Dentro da função nós criamos uma instância do tipo Account chamada account;
  • Acessamos as propriedades chamando a instância + a propriedade.

Aqui entra um ponto importante com relação à estrutura de arquivos do Dart. Todas as classes novas, modelos e outras representações ou partes da aplicação, precisam ficar dentro da pasta lib.

Só que a conta que foi criada e instanciada, tem os seus valores fixos. O ideal é que uma classe seja um modelo e que outras instâncias tenham seus próprios valores.

Nós passamos novos valores para uma classe através do método construtor. No Dart nós podemos chamar esse construtor da seguinte maneira:

class Account {
  int id;
  String cpf;
  String name;
  double balance;

  Account(this.id, this.cpf, this.name, this.balance);
}

Agora quando vamos criar uma nova instância de Account, passamos os devidos valores que compõem essa nossa conta:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(1, "333.333.333-33", "Matheus", 1000);
  print(account.name);
}

Atributos nomeados

Em Dart, nós temos duas maneiras de passar informações para dentro de uma classe no momento da sua instância.

A forma comum que fizemos até agora, onde temos que colocar os valores na mesma ordem que aparecem no construtor, e a forma nomeada.

Utilizamos dentro do construtor o conjunto de chaves, e dentro dele, cada uma das propriedades que existem na classe:

class Account {
  int id;
  String cpf;
  String name;
  double balance;

  Account({required this.id, required this.cpf, required this.name, required this.balance});
}

Quando criamos uma instância, agora não importa mais a ordem dos parâmetros mas sim o seu nome. Veja como fica a criação de uma nova instância de Account:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", name: "Matheus", balance: 1000);
  print(account.name);
}

Detalhe importante é que quando usamos atributos nomeados, precisamos colocar a palavra-chave required, que significa "necessário". Dart, assim como outras linguagens, não gosta de valores nulos (o tipo especial null que falamos antes).

Dart e o Null safety

O Dart tem uma ferramenta muito incrível para lidar com valores nulos. Vamos dizer que a nossa classe Account pode ter a propriedade name vazia.

Pensando no mundo real, o mais importante para identificar uma pessoa é o CPF e nomes podem se repetir. Só que quando criamos uma instância, precisamos passar um valor para nome, mesmo que seja apenas uma String vazia.

Para sinalizar o Dart que alguma propriedade de uma classe pode não receber valores na sua instância, utilizamos o símbolo de interrogação (?).

Veja o exemplo a seguir:

class Account {
  int id;
  String cpf;
  String? name;
  double balance;

  Account({required this.id, required this.cpf, this.name, required this.balance});
}

Através do símbolo ? conseguimos dizer para o Dart que o valor da propriedade name pode ser nula. Isso quer dizer que quando vamos criar uma instância de Account, não precisamos passar o atributo name. Veja como fica a criação de uma instância de Account:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", balance: 1000);
  print(account.name);
}

Se a gente rodar o código agora, o valor de account.name é um valor nulo. Veja o resultado no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
null

Lógico que permitir valores nulos pode ser perigoso se não utilizado corretamente, mas uma parte muito interessante do Dart é a robustez de uma linguagem como Java e a maleabilidade e flexibilidade de uma linguagem como JavaScript.

Vamos ver rapidamente uma forma de nos proteger de valores nulos. Dentro do construtor podemos atribuir um valor padrão que será utilizado caso nenhum valor seja passado no processo de criação de uma instância.

Veja o exemplo passando o valor "Unknown" para a propriedade name:

class Account {
  int id;
  String cpf;
  String? name;
  double balance;
  Account({
    required this.id,
    required this.cpf,
    this.name = "Unknown",
    required this.balance,
  });

Quando executamos o código sem definir um valor para name, temos a seguinte mensagem no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
Unknown

Criando uma instância passando um nome:

Agora quando criamos uma instância passando um valor para a propriedade name:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", balance: 1000, name: "James");
  print(account.name);
}

Temos o seguinte resultado no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
James

Null Safety é uma ferramenta muito versátil do Dart, que permite que nosso código tenha uma grande maleabilidade, mas também é muito importante não utilizar em excesso ou sem muita necessidade, pois pode deixar nosso código "fraco".

Alterando propriedades

Bom, vimos até agora como criar instâncias, mas como podemos alterar valores depois que uma instância foi criada?

Podemos alterar valores das propriedades de uma instância simplesmente chamando o nome da instância (objeto) mais o nome da propriedade e depois atribuir um novo valor:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", balance: 1000, name: "James");
  print(account.balance);

  account.balance = 300;

  print(account.balance);
}

Executando o código acima temos o seguinte resultado no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
1000.0
300.0

Valores constantes

O Dart também tem uma forma de impedir que valores sejam modificados após a criação de uma instância. Podemos sinalizar que uma propriedade tem o seu valor imutável através da palavra-chave final:

class Account {
  final int id;
  final String cpf;
  final String? name;
  final double balance;
  Account({
    required this.id,
    required this.cpf,
    this.name = "Unknown",
    required this.balance,
  });

}

Podemos tentar alterar o valor da propriedade balance da seguinte maneira:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", balance: 1000, name: "James");
  print(account.balance);

  account.balance = 300;

  print(account.balance);
}

E isso acaba gerando um erro agora no código, dizendo que não podemos alterar o valor de propriedades constantes.

Detalhe que eu falei constantes mas utilizei a palavra-chave final. A palavra-chave const é utilizada em classes abstratas.

Propriedades privadas

O Dart também permite a criação de propriedades privadas. Utilizamos propriedades privadas para impedir que funções ou chamadas de código fora da classe, tenham acesso aos valores de uma propriedade de uma classe.

Veja como podemos transformar a propriedade balance em uma propriedade privada:

class Account {
  int id;
  String cpf;
  String? name;
  double _balance = 1000;
  Account({
    required this.id,
    required this.cpf,
    this.name = "Unknown",
  });

}

Um detalhe muito importante é que diferente de outras linguagens que existe a palavra-chave private, o Dart segue um padrão bem parecido com o JavaScript. Colocamos um underscore/underline (_) antes do nome da propriedade.

Porém, cuidado porque o underscore precisa estar junto com o nome da propriedade, sem espaços.

Agora tentando imprimir o valor de _balance:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", name: "James");
  print(account._balance);
}

O código acima vai dar um erro dizendo que não consegue encontrar a propriedade _balance. Isso porque agora ela está privada!

Métodos de uma classe

Além de propriedades, uma classe também pode ter métodos. Métodos de uma classe são funções que definem ações que um objeto pode ter.

Pensando em contas, uma conta pode realizar uma transferência. Essa é uma ação apenas dela.

Podemos criar o método de transferência para a classe Account da seguinte maneira:

class Account {
  int id;
  String cpf;
  String? name;
  double balance;

  Account({
    required this.id,
    required this.cpf,
    this.name = "Unknown",
    required this.balance,
  });

  void transfer(double amount) {
    balance = balance - amount;
  }

}

Agora podemos realizar uma transferência chamando a função transfer da seguinte maneira:

import 'package:meu_app/account.dart';

void main() {
  Account account = Account(id: 1, cpf: "333.333.333-33", balance: 1000, name: "James");
  print(account.balance);

  account.transfer(100);

  print(account.balance);
}

Executando o código acima temos o seguinte resultado no console:

❯ dart run
Building package executable... 
Built meu_app:meu_app.
1000.0
900.0

Instalando pacotes

O Dart tem um gerenciador de pacotes muito simples de utilizar. Novamente fazendo uma comparação com o npm do JavaScript, o pub.dev guarda todos os repositórios de bibliotecas criadas pela comunidade e até mesmo por equipes do Dart.

Para instalar pacotes nós temos duas opções principais:

Adicionando o pacote no pubspec

Lembra daquele arquivo pubspec.yaml? É nele que ficam as informações de como rodar e atualizar a aplicação, bem parecido com o arquivo package.json de projetos JavaScript.

name: meu_app
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo

environment:
  sdk: ^3.3.3

# Add regular dependencies here.
dependencies:
  # path: ^1.8.0

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0

O código acima é meu arquivo pubspec.yaml. Para adicionar o pacote http, podemos adicionar uma nova entrada na categoria de dependencies:

name: meu_app
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo

environment:
  sdk: ^3.3.3

# Add regular dependencies here.
dependencies:
  # path: ^1.8.0
  http: ^1.2.1

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0

Importante seguir os padrões de escrita de um documento YAML!

Logo em seguida, a IDE deve buscar automaticamente os pacotes novos adicionados. Caso isso não tenha acontecido, podemos forçar uma busca com o comando no terminal:

dart pub get
❯ dart pub get
Resolving dependencies... 
  _fe_analyzer_shared 67.0.0 (68.0.0 available)
  analyzer 6.4.1 (6.5.0 available)
  lints 3.0.0 (4.0.0 available)
Got dependencies!
3 packages have newer versions incompatible with dependency constraints.
Try `dart pub outdated` for more information.

Uma vantagem que temos de adicionar manualmente no arquivo pubspec.yaml é que temos controle total da versão que estamos baixando. Podemos também adicionar vários pacotes de uma só vez ao invés de instalar um por um pelo terminal.

Utilizando o terminal

Para instalar pelo terminal, podemos utilizar o seguinte comando:

dart pub add logger

Ele vai adicionar automaticamente dentro do arquivo pubspec.yaml uma entrada da biblioteca logger.

name: meu_app
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo

environment:
  sdk: ^3.3.3

# Add regular dependencies here.
dependencies:
  # path: ^1.8.0
  http: ^1.2.1
  logger: ^2.3.0

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0

Em teoria, rodando o comando no terminal deve acontecer automaticamente a instalação do pacote, mas caso não aconteça, você pode rodar o comando dart pub get em seguida.

Desvantagens do Dart

Infelizmente nem tudo são flores. Dart tem algumas desvantagens, assim como toda outra linguagem. Por exemplo:

Comunidade pequena

Dart ainda é uma linguagem relativamente nova comparada à outras linguagens como Java, Python ou JavaScript. Isso quer dizer que recursos, pacotes, suporte e ferramentas de aprendizado são mais escassos.

Menor adoção por empresas

O Dart ainda não é tão amplamente adotado por empresas quanto outras linguagens. Isso pode dificultar de encontrarmos oportunidades de trabalho em algumas áreas.

Falta de padronização de bibliotecas

Algumas bibliotecas de terceiros para Dart ainda podem estar em desenvolvimento ou não ter o mesmo nível de maturidade que em outras linguagens. Isso varia muito de quem está desenvolvendo a biblioteca.

Dependência da Google

O Dart é um projeto liderado pela Google, o que significa que algumas decisões de desenvolvimento podem estar alinhadas com os interesses da Google e não necessariamente com as necessidades da comunidade.

Possibilidade de mudanças na linguagem

Como o Dart é uma linguagem relativamente nova, ainda está em constante evolução e pode haver mudanças na sintaxe ou na API que podem afetar seus projetos no futuro. Isso aconteceu recentemente com a introdução do Material Design 3 para Flutter e também com o NullSafety que vimos aqui no artigo.

Menos integração com ferramentas existentes

A integração com ferramentas e frameworks de terceiros populares pode ser menos robusta em comparação com linguagens mais maduras.

Resumo das desvantagens

O maior problema no final das contas é a falta de maturidade, mas isso vai melhorando com o tempo e conforme mais empresas e pessoas desenvolvedoras vão adotando a linguagem.

Como aprender Dart

A própria Google tem uma seção específica para quem quer aprender Dart. Você pode acessar a partir deste link, mas os tutoriais estão todos em inglês e para quem está iniciando pode ser um grande susto.

O tutorial da Google é para quem já conhece lógica de programação e também já é uma pessoa programadora com alguma experiência em outras linguagens, não sendo uma boa opção para quem está iniciando no mundo do desenvolvimento.

E lógico, existe a opção de aprender com a gente na Alura. Nós temos uma formação focada em ensinar Dart desde os princípios mais básicos da linguagem. Você pode conferir a formação neste link.

Você também pode conferir os nossos artigos:

Além de artigos, também temos Alura:

Conclusão

Dart é uma linguagem relativamente nova com muito potencial. A qualidade da documentação é incrível, instalação e manutenção de pacotes é simples, o processo de instalar e começar com Dart é indolor e acima de tudo é a base para o framework Flutter.

Espero que este artigo tenha aumentado o seu interesse em aprender esta grande linguagem da Google, e espero encontrar você nos cursos aqui da Alura!

Até breve!

Matheus Alberto
Matheus Alberto

Formado em Sistemas de Informação na FIAP e em Design Gráfico na Escola Panamericana de Artes e Design. Trabalho como desenvolvedor e instrutor na Alura. Nas horas vagas sou artista/ilustrador.

Veja outros artigos sobre Mobile