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é?!
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:
- .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
.
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ávelcontador
do tipo inteiro e inicializamos com o valor 1.while (contador < 5) { ... }
: Este é o loopwhile
. Ele vai continuar executando o bloco de código dentro das chaves{...}
enquanto a condiçãocontador <= 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çãocontador < 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ávelcontador
do tipo inteiro e inicializamos com o valor 6.do { ... } while (contador <= 5);
: Este é o loopdo while
. Ele executa o bloco de código dentro das chaves{...}
pelo menos uma vez, e então verifica a condiçãocontador <= 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
chamadaaccount
; - 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:
- Construtores no Dart: conhecendo tipos de construtores e como usá-los
- Entrada e saída de dados no terminal do Dart
- Qual a diferença entre Static, Const e Final no Dart
Além de artigos, também temos Alura:
- Os 5 métodos da lista do Dart
- Entendendo Input e Output no Dart
- Usando o teclado para entrada e saída de dados com Dart
- Const x Final no Dart
- Null Safety no Dart
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!