Por que eu devo ler este artigo:

Durante o desenvolvimento de um software precisamos realizar diversas operações com dados, tais como percorrer Arrays, manipular Strings e, algumas vezes, converter um tipo em outro. Para isso é importante conhecer esse recurso tão fundamental da linguagem Dart.

Introdução

Dart é uma linguagem estaticamente tipada, o quer dizer que uma vez que atribuímos um valor a uma variável, valores de outros tipos não poderão ser armazenados por essa mesma variável. Também por esse motivo, Dart é uma linguagem Type Safe e por isso operações estranhas com variáveis, a exemplo de tentar somar caracteres e números, poderão ser alertados pelo compilador e corrigidos antes do programa falhar em tempo de execução.

Em Dart todos os tipos são objetos. Graças a essa característica temos acesso a um amplo conjunto de funções para processamento de dados, mesmo em objetos de tipos mais básicos, fornecidos pelo ambiente de execução do Dart. Esses tipos são:


A seguir, falamos um pouco sobre esses tipo, bem como sobre as suas principais funções.

Number

Dart oferece três tipos para armazenar valores numéricos. O primeiro deles é int, utilizado para o armazenamento de qualquer número inteiro, seja ele negativo ou positivo. O segundo é double, que é utilizado para o armazenamento de números de pontos flutuantes. Ambos, int e double, são subtipos de num. Ao declarar uma variável como num ela pode ser tanto um inteiro quanto um número de ponto flutuante:


num pi = 3;
pi = 3.14;

Contudo, ao declarar uma variável como um inteiro, ela não poderá receber um decimal. Vejamos:


int pi = 3;
pi = 3.14;

Error: A value of type 'double' can't be assigned to a variable of type 'int'.

Quando encontramos um número ou texto no código, como no exemplo acima, esse valor é chamado literal. Um literal é um valor que não precisa de avaliação por parte do compilador para ser atribuído a uma variável. Sabendo disso podemos concluir que de acordo com o literal utilizado, o compilador cria uma instância de num adequada a esse valor. Naturalmente, o mesmo também ocorrerá com o resultado de uma expressão.

Assim como o tipo num, int e double também fornecem diversos métodos e propriedades que podem ser utilizados para a transformação e checagem de dados. Eles também dispõem de capacidades para expressões utilizando os operadores + (adição), - (subtração), * (multiplicação), / (divisão) e outros. O exemplo a seguir demonstra algumas dessas expressões:


double pi = 3.14159265359;

int pi_arredondado = pi.floor();

print(pi_arredondado); // 3

Quando declaramos uma variável informando o seu tipo não é permitido o uso da palavra reservada var. Podemos, entretanto, utilizando final e const, se necessário.

No código acima inicializamos a variável pi do tipo double com o valor de pi contendo 11 casas decimais. Em seguida utilizamos o método floor() disponibilizado pelo tipo num (do qual o tipo double deriva) para arredondar o valor de pi para o número inteiro imediatamente menor que pi.

Outros métodos para operações matemáticas estão disponíveis na tabela a seguir:

Método Descrição
abs() Retorna o valor absoluto do número.
ceil() Retorna o último inteiro imediatamente superior.
ceilToDouble() Retorna o último número imediatamente superior com o tipo double.
clamp(num limiteInferior, num limiteSuperior) Se o número estiver dentro do limite, retorna o número. Se não, retorna o limite o qual ele extrapolou.
compareTo(num outro) Compara com outro número, retornando 1 quando forem diferentes e 0 quando forem iguais.
floor() Arredonda o número para o inteiro anterior.
floorToDouble() Arredonda o número para o número inteiro anterior no tipo double.
remainder(num outro) Retorna a sobra da divisão com outro número.
round() Arredonda o número para o inteiro mais próximo.
roundToDouble() Arredonda o número para o valor inteiro mais próximo no tipo double.
toDouble() Converte o número para Double.
toInt() Converte o número para Int.
toString() Converte o número em uma String.
toStringAsExponential([int digitos]) Converte para string com exponencial.
toStringAsFixed(int decimais) Converte para String contendo N casas decimais.
toStringAsPrecision(int digitos) Converte para String contendo N dígitos.
truncate() Retira as casas decimais, retornando um inteiro.
truncateToDouble() Retira as casas decimais, retornando um double.

Além dos métodos de transformação, também temos disponíveis diversas propriedades que permitem realizarmos checagens de valores, tais como isFinite (retorna true quando o número for finito), isInfinite (retorna true quando o número for infinito) e isNegative (retorna true quando o número for negativo):


double euler = 0.5772156649;

print(euler.isFinite); // true

print(euler.isInfinite); // false

print(euler.isNegative); // false

String

Strings são cadeias de caracteres que podemos representar com aspas duplas ou aspas simples:


String nome_usuario = "Caio";

String sobrenome_usuario = 'Rolla';

print(nome_usuario); // Caio

print(sobrenome_usuario); // Rolla

Assim como os numbers, o objeto String também nos fornece diversos atributos e métodos para a verificação e transformação de cadeias de caracteres, tais como toUpperCase, que transforma os caracteres do texto em maiúsculas, toLowerCase, que transforma todos os caracteres em letras minúsculas, trim, que remove os espaços vazios do início e fim do texto, e muitos outros. Vejamos alguns exemplos:


String nome_usuario = "caio";

String sobrenome_usuario = ' ROLLA';

print(nome_usuario); // caio

print(sobrenome_usuario); // ROLLA

nome_usuario = nome_usuario.toUpperCase();

print(nome_usuario); // CAIO

sobrenome_usuario = sobrenome_usuario.trim();

print(sobrenome_usuario); // ROLLA

nome_usuario = nome_usuario.toLowerCase();

sobrenome_usuario = sobrenome_usuario.toLowerCase();

print(nome_usuario); // caio

print(sobrenome_usuario); // rolla

Podemos juntar duas strings através da concatenação ou através da interpolação. Com a segunda podemos, inclusive, transformar outros tipos de dados em String. Vejamos um exemplo que utiliza tanto concatenação quanto interpolação de Strings:


String nome = "Caio";

String sobrenome = "Rolla";

int idade = 22;

// Concatenação de duas Strings

String nome_completo = nome+" "+sobrenome;

//Interpolação com String e valor do tipo inteiro.

String mensagem = "O usuário $nome_completo possui $idade anos de idade.";

print(mensagem); // O usuário Caio Rolla possui 22 anos de idade.

Diferentemente do Java, por exemplo, que necessitamos utilizar o método equals para realizar a comparação de duas Strings, no Dart podemos compará-las utilizando o operador de igualdade ==:


String nome_usuario_1 = "Caio";

String nome_usuario_2 = "Caio";

if(nome_usuario_1 == nome_usuario_2) {

  print("Ambos os usuários possuem o mesmo nome.");

}

Boolean

Para representarmos valores booleanos no Dart utilizamos o tipo bool que pode ser representado por dois valores: true (verdadeiro) e false (falso). Quando utilizamos propriedades de checagem vistas anteriormente, como isFinite, isInfinite e isNegative por exemplo, o valor retornado por estas é do tipo bool.

O Dart fornece diversos operadores de comparação com resultados booleanos. Podemos, inclusive, checar por tipos durante a execução do software:

Operador Descrição
>= Maior ou igual
> Maior
<= Menor ou igual
< Menor
is É do mesmo tipo
is! Não é do mesmo tipo
== Igual
!= Diferente
&& E lógico (AND)
|| OU lógico (OR)

Vejamos um exemplo:


String papel = "ADMIN";

bool esta_logado = true;

String nome_usuario = "Caio";

if(papel == "ADMIN" && esta_logado) {

  print("O usuário $nome_usuario é um Admin e está logado.");

}

dynamic

No Dart existe tipo chamado dynamic, onde podemos atribuir valores de todos os outros tipos, e até mesmo modificar esses valores em tempo de execução. Vejamos:


dynamic nome = "Caio Rolla";

dynamic idade = 22;

print(nome); // Caio Rolla

print(idade); // 22

idade = "22 anos";

print(idade); // 22 anos

Function

Uma função é um trecho de código, um processo ou rotina, responsável pela execução de uma tarefa específica, podendo ser executado múltiplas vezes durante a execução do programa. No Dart, funções são um tipo de dado e podemos tratá-las como qualquer outro dado: podemos passá-las como parâmetro, atribuir a variáveis e mais.

Uma função pode receber parâmetros (dados necessários para sua execução) e retornar outros valores gerados a partir de um processamento qualquer. Entretanto, nenhum dos dois é obrigatório: uma função pode não precisar de parâmetros e não retornar nada. Por exemplo:


exibirMensagemDeErro(){

  print("Desculpa, encontramos um erro.");

}

No trecho de código anterior declaramos nossa função, mas esta não executará de imediato. Para que esta seja executada, precisamos explicitamente ordenar a execução:


exibirMensagemDeErro(); // Desculpa, encontramos um erro.

Funções também podem ter seu retorno tipado. Se ela não tiver retorno, podemos tipá-la como void:


void exibirMensagemDeErro(){

  print("Desculpa, encontramos um erro.");

}

Para recebermos parâmetros declararmos cada uma das variáveis que a função pode receber, e seus tipos, dentro dos parênteses:


void exibirNomeECargo(String nome, String cargo) {

  print("Nome: $nome ; Cargo: $cargo");

}
exibirNomeECargo("Caio", "Desenvolvedor"); // Nome: Caio ; Cargo: Desenvolvedor

No exemplo acima temos um exemplo de passagem de parâmetros obrigatórios, e posicionais (mudar a ordem de passagem dos parâmetros influencia a execução da função). Se um desses parâmetros não for passado na execução da função, um erro será gerado.

No Dart é possível definir parâmetros opcionais posicionais e nomeados, mas não ambos. Vejamos um exemplo de cada.

Parâmetro Opcional Posicional

Para definir parâmetros opcionais posicionais, devemos agrupar esses parâmetros entre colchetes.

Atenção: parâmetros opcionais posicionais precisam ser declarados após os parâmetros obrigatórios:


void exibirNomeECargo(String nome, [String cargo]) {

  if(cargo != null) {
    print("Nome: $nome ; Cargo: $cargo");
  } else {
    print("Nome: $nome ;");
  }
}

exibirNomeECargo("Caio", "Desenvolvedor"); // Nome: Caio ; Cargo: Desenvolvedor

exibirNomeECargo("Caio"); // Nome: Caio ;

Também temos a opção de declararmos valores padrão para os parâmetros opcionais:


void exibirNomeECargo(String nome, [String cargo = "Desconhecido"]) {

  print("Nome: $nome ; Cargo: $cargo");

}

exibirNomeECargo("Caio", "Desenvolvedor"); // Nome: Caio ; Cargo: Desenvolvedor

exibirNomeECargo("Caio"); // Nome: Caio ; Cargo: Desconhecido

Parâmetro Opcional Nomeado

A segunda forma de passarmos parâmetros opcionais para uma função se dá através da nomeação destes parâmetros. Nesse caso, no momento em que executamos a função, precisamos conhecer os nomes dos parâmetros que serão recebidos, não sua posição.

Da mesma forma que parâmetros opcionais posicionais, os parâmetros nomeados também precisam ser declarados após a declaração dos parâmetros obrigatórios. Dessa vez, entretanto, utilizamos chaves na declaração:


void exibirNomeECargo(String nome, {String cargo}) {

  if(cargo != null) {

    print("Nome: $nome ; Cargo: $cargo");

  } else {

    print("Nome: $nome ;");

  }

}

exibirNomeECargo("Caio", cargo: "Desenvolvedor"); // Nome: Caio ; Cargo: Desenvolvedor

exibirNomeECargo("Caio"); // Nome: Caio ;

Para retornar valores de uma função devemos utilizar a palavra reservada return, que retorna o valor gerado e interrompe a execução da função. Neste caso, podemos declarar o retorno da função como o tipo de dado que ela retornará:


String gerarMsgNomeECargo(String nome, String cargo) {

  return "Nome: $nome ; Cargo: $cargo";

}

print(gerarMsgNomeECargo("Caio", "Desenvolvedor")); // Nome: Caio ; Cargo: Desenvolvedor

No Dart, podemos tratar uma função como qualquer outro valor. Podemos, por exemplo, atribuir uma função a uma variável e passá-la como parâmetro para outra função, que executará essa variável:


Function printarErro = (String erro) {

  print("Encontramos um erro: $erro");

};

void checarIgualdade(String valor1, String valor2, Function callback) {

  if(valor1 != valor2){

    callback("Os valores são diferentes");

  }

}

checarIgualdade("Caio", "Caio", printarErro); // Não printa nada

checarIgualdade("Caio", "Rolla", printarErro); // Encontramos um erro: Os valores são diferentes

Funções que executam apenas um comando também podem ser escritas da seguinte forma:


exibirMsg(String msg) => print(msg);

exibirMsg("Essa é minha mensagem."); // Essa é minha mensagem.

List

Quando necessitamos trabalhar com arrays (estrutura de dados que armazena valores identificador a partir de um index), podemos contar com o objeto List. Uma lista pode ser criada de duas formas: através da criação de uma instância do List ou criando uma lista literal:


// Criando instância do List

var lista_usuarios = List(); // Também poderia ser new List()

// Usando o método add do objeto para adicionar novos valores a lista

lista_usuarios.add("Caio");

lista_usuarios.add("Aylan");

lista_usuarios.add("Estêvão");

// Lista literal

var lista_usuarios_literal = ["Caio", "Aylan", "Estêvão"];

Os exemplos anteriores possuem resultados idênticos:


print(lista_usuarios[0]); // Caio

print(lista_usuarios[1]); // Aylan

print(lista_usuarios[2]); // Estêvão

print(lista_usuarios_literal[0]); // Caio

print(lista_usuarios_literal[1]); // Aylan

print(lista_usuarios_literal[2]); // Estêvão

Perceba que utilizamos [número] após a variável para recuperar os valores. Esse número, que chamamos de index, é a posição dos valores na lista (que começa em 0).

Assim como outros tipos, também podemos realizar a tipagem de variáveis que recebam uma List. Mais do que isso: também podemos especificar o tipo dos valores que uma lista pode receber:


List lista_usuarios = List();

lista_usuarios.add("Caio");

lista_usuarios.add("Aylan");

lista_usuarios.add(10); // Gerará um erro de compilação
No Dart, um List é um genérico. Isto é: passamos o tipo do dado que ele armazenará como parâmetro.

O objeto List nos fornece diversos métodos e propriedades que facilitam a interação, manipulação e a análise de arrays. Podemos correr a lista utilizando o método forEach, modificar os dados da lista com o método map, filtrar com o método where, contar o número de valores com o atributo length e muito mais:


List lista_numeros = List();

lista_numeros.add(1);

lista_numeros.add(2);

lista_numeros.add(3);

lista_numeros.add(4);

lista_numeros.add(5);

lista_numeros.forEach((numero) {

  print("Número: $numero");

});


// Resultado

// Número: 1
// Número: 2
// Número: 3
// Número: 4
// Número: 5

No exemplo anterior utilizamos o método forEach, que iterará o array e executar a função de callback uma vez para cada valor. Podemos combinar diversos métodos para gerar diferentes resultados:


lista_numeros.map((numero) {

  return numero * 10;

}).toList().forEach((numero) {

  print("Número: $numero");

});


// Resultado

// Número: 10
// Número: 20
// Número: 30
// Número: 40
// Número: 50

Dessa vez, utilizamos o método map para modificar cada um dos valores da List e, ao final do processo, exibimos o valor de cada um dos números na tela com o método forEach. Podemos utilizar o atributo length para saber a quantidade de valores em uma List:


print("Quantidade de valores: ${lista_numeros.length}"); // Quantidade de valores: 5

Também é possível iterar um array, como o método forEach faz, utilizando estruturas mais familiares como o for, por exemplo:


for(int index = 0; index < lista_numeros.length; index++) {

  print("Linha ${lista_numeros[index]}");

}

Map

Um Map é uma estrutura similar à uma List. Entretanto, diferentemente da List onde cada valor tinha um index (número inteiro) correspondente, em um Map cada valor terá uma chave (que pode ser um objeto qualquer) correspondente. Assim como a List, o Map também pode ser criado através da criação de uma instância ou de forma literal.

Atenção: se criarmos um Map de forma literal, a chave só pode ser do tipo String.:


var nome_sobrenome = {

  'Caio': 'Rolla',

  'Aylan': 'Boscarino',

  'Estêvão': 'Dias',

};


print(nome_sobrenome['Caio']); // Rolla

print(nome_sobrenome['Aylan']); // Boscarino

print(nome_sobrenome['Estêvão']); // Dias

Se criarmos uma instância, entretanto, qualquer objeto pode ser utilizado como chave:


var chave_valor = Map();

chave_valor[10] = 'Valor 1';

chave_valor[true] = 'Valor 2';

chave_valor["Chave"] = 'Valor 3';

print(chave_valor[10]); // Valor 1

print(chave_valor[true]); // Valor 2

print(chave_valor["Chave"]); // Valor 3

Conclusão

No Dart tudo é um objeto, e isso abre incríveis possibilidades. Poderíamos, por exemplo, sobrescrever métodos e operadores (como o operador == de igualdade) de forma a adequar o comportamento destes de acordo com as necessidades de nossa aplicação. List e Map são essenciais de qualquer aplicação construída com o Dart, e seus métodos de modificação e filtragem podem transformar algoritmos complexos em algumas linhas de código.

Confira também