Executando verificação de segurança...
1

Meu aprendizado em Dart! Introdução: parte 2!

Antes de começar a ler…

A ideia inicial era criar um artigo/tutorial, que contivesse os conhecimentos basico de Dart, e também algumas aplicações no final, mas, acabei percebendo que começou a ficar muito extenso, e por esse motivo decidi dividir em partes, essa primeira em até cinco partes ao longo da semana. Essa sendo a segunda parte. Originalmente com mais de 80000(oitenta mil) caracteres para esse texto...

E como eu sempre falo, leia a documentação oficial, se for possível para você!

Até o atual momento da publicação deste texto, o TabNews, não formata o código em Dart corretamente, peço de antemão desculpas por qualquer inconveniência causada pela formatação incorreta do código.

Requisitos

Ter lido a primeira parte do artigo/tutorial:

Meu aprendizado em Dart! Introdução: parte 1!

Variáveis ​​atrasadas

O Dart 2.12, adiciona o modificador late, que tem dois casos de uso:

  • Declarar uma variável não anulável que é iniciada após sua declaração.
  • Inicializando preguiçosamente uma variável.

Frequentemente, a análise de fluxo de controle do Dart pode detectar quando uma variável não anulável é definida como um valor não nulo antes de ser usada, mas às vezes a análise falha. Dois casos comuns são variáveis ​​de nível superior e variáveis ​​de instância: O Dart geralmente não consegue determinar se elas estão definidas, então não tenta.

Se você tem certeza de que uma variável foi definida antes de ser usada, mas Dart discorda, você pode corrigir o erro marcando a variável como late:

late String description;
 
void main() {
  description = 'Feijoada!';
  print(description);
}

Se você não conseguir inicializar uma variável late, ocorrerá um erro de tempo de execução quando a variável for usada.

Quando você marca uma variável como late, mas à inicializa em sua declaração, o inicializador é executado na primeira vez que a variável é usada. Essa inicialização preguiçosa é útil em alguns casos:

  • A variável pode não ser necessária e inicializá-la é caro.
  • Você está iniciando uma variável de instância e seu inicializador precisa acessar this.

No exemplo a seguir, se a variável de temperatura nunca foi usada, a função readThermometer() nunca será chamada:

    late String temperature = readThermometer();

Final e const

Se você nunca pretende alterar uma variável, use final ou const, em vez de var ou em adição a um tipo. Uma variável final pode ser definida apenas uma vez; uma variável const é uma constante de tempo de compilação. (As variáveis ​​const são implicitamente finais.)

Observação: as variáveis ​​de instância podem ser finais, mas não const.

Aqui está um exemplo de criação e configuração de uma variável final:

    final name = 'Bob'; // Without a type annotation
    final String nickname = 'Bobby';

Você não pode alterar o valor de uma variável final:

    name = 'Alice'; // Error: a final variable can only be set once.

Use const para variáveis ​​que você deseja que sejam constantes de tempo de compilação. Se a variável const estiver no nível de classe, marque-a como static const. Onde você declara a variável, defina o valor para uma constante de tempo de compilação, como um número ou string literal, uma variável const ou o resultado de uma operação aritmética em números constantes:

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

A palavra-chave const não serve apenas para declarar variáveis ​​constantes. Você também pode usá-lo para criar valores constantes, bem como para declarar construtores que criam valores constantes. Qualquer variável pode ter um valor constante.

var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

Você pode alterar o valor de uma variável não final e não constante, mesmo que ela tenha um valor const:

foo = [1, 2, 3]; // Was const []

Você não pode alterar o valor de uma variável const:

baz = [42]; // Error: Constant variables can't be assigned a value.

Você pode definir constantes que usam verificações de tipo e conversões (is and as), coleção if e operadores de dispersão (... e ...?):

const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: 'int'}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.

Tipos integrados

Números

  • int: Valores inteiros não maiores que 64 bits, dependendo da plataforma.

  • double: Números de ponto flutuante de 64 bits (precisão dupla), conforme especificado pelo padrão IEEE 754.

Strings

Uma string no Dart (objeto String) contém uma sequência de unidades de código UTF-16. Você pode usar aspas simples ou duplas para criar uma string.

Você pode colocar o valor de uma expressão dentro de uma string usando ${expressão}. Se a expressão for um identificador, você pode pular o {}. Para obter a string correspondente a um objeto, o Dart chama o método toString() do objeto.

var s = 'string interpolation';
 
assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, '
        'which is very handy.');
assert('That deserves all caps. '
        '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. '
        'STRING INTERPOLATION is very handy!');

Você pode concatenar strings usando string literals adjacentes ou com operador +:

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');
 
var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

Outra maneira de criar uma string de várias linhas: use aspas triplas com aspas simples ou duplas:

var s1 = '''
You can create
multi-line strings like this one.
''';
 
var s2 = """This is also a
multi-line string.""";

Booleans

Para representar valores booleanos, Dart tem um tipo chamado bool. Apenas dois objetos têm o tipo bool: Os literais booleanos true e false, que são constantes no tempo de compilação.

Lists/Arrays/Matriz

Talvez a coleção mais comum em quase todas as linguagens de programação seja a matriz, ou grupo ordenado de objetos. No Dart, arrays são objetos List, então a maioria das pessoas apenas os chama de listas.

Literais de lista Dart são indicados por uma lista separada por vírgulas de expressões ou valores, entre colchetes ([]).

var list = [1, 2, 3];
 
var list = [
  'Car',
  'Boat',
  'Plane',
];
 

As listas usam indexação baseada em zero, onde 0 é o índice do primeiro valor e list.length - 1 é o índice do último valor. Você pode obter o comprimento de uma lista usando a propriedade .length e acessar os valores de uma lista usando o operador subscrito ([]):

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
 
list[1] = 1;
assert(list[1] == 1);

Para criar uma lista que seja uma constante de tempo de compilação, adicione const antes da lista:

var constantList = const [1, 2, 3];
// constantList[1] = 1;
// This line will cause an error.

O Dart tem suporte ao spread operator(...), e ao spread operator com reconhecimento nulo(...?).

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
var list2 = [0, ...?list];
assert(list2.length == 1);

Sets/Conjuntos

Um conjunto no Dart é uma coleção não ordenada de itens exclusivos. O suporte Dart para conjuntos é fornecido por literais de conjunto e o tipo de conjunto.

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Para criar um conjunto vazio, use {} precedido por um argumento de tipo ou atribua {} a uma variável do tipo Set:

var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

Adicione itens a um conjunto existente usando os métodos add() ou addAll():

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

Use .length para obter o número de itens no conjunto:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

Para criar um conjunto que seja uma constante de tempo de compilação, adicione const antes do conjunto:

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // This line will cause an error.

Maps

Em geral, um mapa é um objeto que associa chaves e valores. Tanto as chaves quanto os valores podem ser qualquer tipo de objeto. Cada chave ocorre apenas uma vez, mas você pode usar o mesmo valor várias vezes. O suporte Dart para mapas é fornecido por literais de mapa e o tipo de mapa.

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};
 
var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

Você pode criar os mesmos objetos usando um construtor Map:

var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
 
var nobleGases = Map<int, String>();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

Recupere um valor de um mapa usando o operador subscrito ([]):

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

Use .length para obter o número de pares chave-valor no mapa:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
// Ele fica assim: var gifts = {'first': 'partridge', 'fourth': 'calling birds'};
assert(gifts.length == 2);

Para criar um mapa que seja uma constante de tempo de compilação, adicione const antes do mapa:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
 
// constantMap[2] = 'Helium'; 
// This line will cause an error.

Symbols

Um objeto Symbol representa um operador ou identificador declarado em um programa Dart. Talvez você nunca precise usar símbolos, mas eles são inestimáveis ​​para APIs que se referem a identificadores por nome, porque a minificação altera nomes de identificadores, mas não símbolos de identificadores.

Para obter o símbolo de um identificador, use um literal de símbolo, que é apenas # seguido pelo identificador:

#radix
#bar

Symbol literals são constantes no tempo de compilação.

Functions

Dart é uma linguagem orientada a objetos, então mesmo as funções são objetos e têm um tipo, Function. Isso significa que as funções podem ser atribuídas a variáveis ​​ou passadas como argumentos para outras funções. Você também pode chamar uma instância de uma classe Dart como se fosse uma função.

Aqui está um exemplo de implementação de uma função:

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Embora o Effective Dart recomende anotações de tipo para APIs públicas, a função ainda funcionará se você omitir os tipos:

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Para funções que contêm apenas uma expressão, você pode usar uma sintaxe abreviada:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

Parameters

Uma função pode ter qualquer número de parâmetros posicionais necessários. Estes podem ser seguidos por parâmetros nomeados ou por parâmetros posicionais opcionais (mas não ambos).

Observação: algumas APIs, principalmente os construtores de widget Flutter, usam apenas parâmetros nomeados, mesmo para parâmetros obrigatórios.

Você pode usar vírgulas à direita ao passar argumentos para uma função ou ao definir parâmetros de função.

Named parameters

Parâmetros nomeados são opcionais, a menos que sejam explicitamente marcados como obrigatórios.

Ao definir uma função, use {param1, param2, …} para especificar parâmetros nomeados. Se você não fornecer um valor padrão ou marcar um parâmetro nomeado como obrigatório, seus tipos devem ser anuláveis, pois seu valor padrão será nulo:

/// Sets the [bold] and [hidden] flags …
void enableFlags({bool? bold, bool? hidden}) {...}

Ao chamar uma função, você pode especificar argumentos nomeados usando paramName: value. Por exemplo:

enableFlags(bold: true, hidden: false);

Para definir um valor padrão para um parâmetro nomeado além de nulo, use = para especificar um valor padrão. O valor especificado deve ser uma constante de tempo de compilação. Por exemplo:

/// Sets the [bold] and [hidden] flags … 
void enableFlags({bool bold = false, bool hidden = false}) {...}
 
// bold will be true; hidden will be false.
enableFlags(bold: true);

Se, em vez disso, você quiser que um parâmetro nomeado seja obrigatório, exigindo que os chamadores forneçam um valor para o parâmetro, anote-os com obrigatório:

const Scrollbar({super.key, required Widget child});

Se alguém tentar criar uma barra de rolagem sem especificar o argumento do filho, o analisador relatará um problema.

Observação: um parâmetro marcado como obrigatório ainda pode ser anulável:

const Scrollbar({super.key, required Widget? child});

Você pode querer colocar argumentos posicionais primeiro, mas o Dart não exige isso. O Dart permite que argumentos nomeados sejam colocados em qualquer lugar na lista de argumentos quando for adequado à sua API:

repeat(times: 2, () {
  …
});

Parâmetros posicionais opcionais

Envolver um conjunto de parâmetros de função em [] os marca como parâmetros posicionais opcionais. Se você não fornecer um valor padrão, seus tipos devem ser anuláveis, pois seu valor padrão será nulo:

String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

Aqui está um exemplo de como chamar esta função sem o parâmetro opcional:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

E aqui está um exemplo de chamada desta função com o terceiro parâmetro:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

Para definir um valor padrão para um parâmetro posicional opcional além de nulo, use = para especificar um valor padrão. O valor especificado deve ser uma constante de tempo de compilação. Por exemplo:

String say(String from, String msg, [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

A função main()

Cada aplicativo deve ter uma função main() de nível superior, que serve como ponto de entrada para o aplicativo. A função main() retorna void e tem um parâmetro List<String> opcional para argumentos.

Aqui está uma função main() simples:

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

Esse será o nosso “Hello, World!”...

Aqui está um exemplo da função main() para um aplicativo de linha de comando que recebe argumentos:

// Execute o aplicativo assim: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);
 
  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

Você pode usar a biblioteca args para definir e analisar argumentos de linha de comando.

Functions as first-class objects

Você pode passar uma função como parâmetro para outra função. Por exemplo:

void printElement(int element) {
  print(element);
}
 
var list = [1, 2, 3];
 
// Pass printElement as a parameter.
list.forEach(printElement);

Você também pode atribuir uma função a uma variável, como:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

Este exemplo usa uma função anônima. Mais sobre eles na próxima seção.

Anonymous functions

A maioria das funções são nomeadas, como main() ou printElement(). Você também pode criar uma função sem nome chamada função anônima ou, às vezes, lambda ou closure. Você pode atribuir uma função anônima a uma variável para que, por exemplo, possa adicioná-la ou removê-la de uma coleção.

Uma função anônima é semelhante a uma função nomeada — zero ou mais parâmetros, separados por vírgulas e anotações de tipo opcionais, entre parênteses.

O bloco de código a seguir contém o corpo da função:

([[Type] param1[, …]]) {
  codeBlock;
};

O exemplo a seguir define uma função anônima com um parâmetro sem tipo, item, e o passa para a função map. A função, invocada para cada item da lista, converte cada string em letras maiúsculas. Em seguida, na função anônima passada para o forEach, cada string convertida é impressa junto com seu comprimento.

const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
  return item.toUpperCase();
}).forEach((item) {
  print('$item: ${item.length}');
});

Lexical scope

Dart é uma linguagem com escopo léxico, o que significa que o escopo das variáveis ​​é determinado estaticamente, simplesmente pelo layout do código. Você pode seguir as chaves para fora para ver se uma variável está no escopo.

Aqui está um exemplo de funções aninhadas com variáveis ​​em cada nível de escopo:

bool topLevel = true;
 
void main() {
  var insideMain = true;
 
  void myFunction() {
    var insideFunction = true;
 
    void nestedFunction() {
      var insideNestedFunction = true;
 
      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

Observe como nestedFunction() pode usar variáveis ​​de todos os níveis, até o nível superior.

Lexical closures

Um encerramento é um objeto de função que tem acesso a variáveis ​​em seu escopo léxico, mesmo quando a função é usada fora de seu escopo original.

As funções podem fechar sobre variáveis ​​definidas em escopos adjacentes. No exemplo a seguir, makeAdder() captura a variável addBy. Onde quer que a função seja retornada ela irá junto, ela se lembra de addBy.

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}
 
void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);
 
  // Create a function that adds 4.
  var add4 = makeAdder(4);
 
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Funções de teste para igualdade

Aqui está um exemplo de teste de funções de nível superior, métodos estáticos e métodos de instância para igualdade:

void foo() {} // A top-level function
 
class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}
 
void main() {
  Function x;
 
  // Comparing top-level functions.
  x = foo;
  assert(foo == x);
 
  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);
 
  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;
 
  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);
 
  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

Continua No Próximo Episódio...

Carregando publicação patrocinada...