Você está na página 1de 632

DART

OCTOBER 1, 2020
2.10 RELEASE

Uma livre tradução e interpretação feita pelo autor


Livro traduzido e editado pelo autor
Nosso objetivo é levar conhecimento a todos

Reder Weyers
Copyright © 2021 Reder Weyers
Todos os direitos reservados
Sumário
LANGUAGE: EXEMPLO DA LINGUAGEM DART
Olá Mundo
Variáveis
Declarações De Fluxo De Controle
Funções
Comentários
Importações
Classes
Herança
Mixins
Interfaces E Classes Abstratas
Assíncrono
Exceções

LANGUAGE: EFFECTIVE DART: VISÃO GLOBAL


Os Guias
Como Ler Os Guias
Glossário

LANGUAGE: EFFECTIVE DART: ESTILO


Identificadores
USE tipos de nomes usando UpperCamelCase .
USE extensões de nome usando UpperCamelCase .
USE os nomes de bibliotecas, pacotes, diretórios e arquivos de origem usando
lowercase_with_underscores .
USE os prefixos de importação de nomes usando
lowercase_with_underscores .
USE os nomes de outros identificadores usando lowerCamelCase .
PREFIRA lowerCamelCase como o nome da constante.
USE letras maiúsculas em siglas e abreviações com mais de duas letras, como
palavras.
NÃO USE um sublinhado inicial para identificadores que não são privados.
NÃO USE letras de prefixo.
Encomenda
USE as importações “dart:” antes de outras importações.
USE as importações “pacote:” antes das importações relativas.
USE as especificações das exportações em uma seção separada após todas as
importações.
USE as seções em ordem alfabética.
Formatação
USE a formatação do seu código usando dartfmt .
CONSIDERE alterar seu código para torná-lo mais amigável ao formatador.
EVITAR linhas com mais de 80 caracteres.
USE chaves para todas as instruções de controle de fluxo.

LANGUAGE: EFFECTIVE DART: DOCUMENTAÇÃO


Comentários
USE comentários formatados como frases.
NÃO USE comentários de bloco para documentação.
DOC Comentários
USE comentários de documentos com /// para documentar membros e tipos.
PREFIRA escrever comentários de documentos para apis públicas.
CONSIDERE escrever um comentário de documento em nível de biblioteca.
CONSIDERE escrever comentários de documentos para apis privadas.
USE os comentários do documento com um resumo de uma única frase.
USE separar a primeira frase de um comentário de documento em seu próprio
parágrafo.
EVITAR redundância com o contexto circundante.
PREFIRA comentários de função ou método de início com verbos em terceira
pessoa.
PREFIRA os comentários da variável inicial, getter ou setter com frases
substantivas.
PREFIRA começar a biblioteca ou digite comentários com frases substantivas.
CONSIDERE incluindo amostras de código em comentários de documentos.
USE colchetes nos comentários do documento para se referir aos
identificadores dentro do escopo.
USE prosa para explicar parâmetros, valores de retorno e exceções.
USE comentários de documentos antes das anotações de metadados.
Markdown
EVITAR usar reduções excessivas.
EVITAR usar html para formatação.
PREFIRA cercas de backtick para blocos de código.
Escrita
PREFIRA Brevidade.
EVITAR Abreviações E Siglas, A Menos Que Sejam Óbvios.
PREFIRA Usar “este” Em Vez De “o” Para Se Referir À Instância De Um
Membro.

LANGUAGE: EFFECTIVE DART: USO


Bibliotecas
USE strings em diretivas part of .
NÃO USE importação de bibliotecas que estejam dentro do diretório src de
outro pacote.
USE caminhos relativos ao importar bibliotecas dentro do diretório lib do seu
próprio pacote.
Booleanos
USE ?? para converter null para um valor booleano.
Strings
USE strings adjacentes para concatenar literais de string.
PREFIRA usar interpolação para compor strings e valores.
EVITAR o uso de colchetes na interpolação quando não for necessário.
Coleções
USE literais de coleção quando possível.
NÃO USE .length para ver se uma coleção está vazia.
CONSIDERE o uso de métodos de ordem superior para transformar uma
sequência.
EVITAR usar Iterable.forEach() com um literal de função.
NÃO USE, List.from() a menos que pretenda alterar o tipo de resultado.
USE whereType() para filtrar uma coleção por tipo.
NÃO USE cast() quando uma operação próxima for suficiente.
EVITAR usar cast() .
Funções
USE uma declaração de função para vincular uma função a um nome.
NÃO USE um lambda quando um corte pode ser suficiente.
Parâmetros
USE = para separar um parâmetro nomeado de seu valor padrão.
NÃO USE um valor padrão explícito de null .
Variáveis
NÃO USE inicializar variáveis explicitamente para null .
EVITAR armazenar o que você pode calcular.
Membros
NÃO USE envolver um campo em um getter e setter desnecessariamente.
PREFIRA usar um campo final para fazer uma propriedade somente leitura.
CONSIDERE o uso de => para membros simples.
NÃO USE, this. exceto para redirecionar para um construtor nomeado ou para
evitar sombreamento.
USE inicializar os campos em sua declaração quando possível.
Construtores
USE formals de inicialização quando possível.
NÃO USE parâmetros para inicializar formals.
USE ; em vez de {} para corpos de construtor vazios.
NÃO USE new .
NÃO USE const redundantemente.
Manipulação De Erros
EVITAR capturas sem cláusulas on .
NÃO USE descartar erros de capturas sem cláusulas on.
USE lançar objetos que implementam apenas error para erros programáticos.
NÃO USE capturar explicitamente os tipos de error que o implementam.
USE rethrow para relançar uma exceção detectada.
Assincronia
PREFIRA async / await sobre o uso de futuros brutos.
NÃO USE async quando não tiver nenhum efeito útil.
CONSIDERE o uso de métodos de ordem superior para transformar um fluxo.
EVITAR usar o completer diretamente.
USE um teste para eliminar a ambiguidade de future<t> de um futureor<t>
cujo tipo de argumento poderia ser object .

LANGUAGE: EFFECTIVE DART: DESIGN


Nomes
USE os termos de forma consistente.
EVITAR abreviações.
PREFIRA colocar o substantivo mais descritivo por último.
CONSIDERE fazer o código parecer uma frase.
PREFIRA uma frase nominal para uma propriedade ou variável não booleana.
PREFIRA uma frase verbal não imperativa para uma propriedade ou variável
booleana.
CONSIDERE omitir o verbo para um parâmetro booleano nomeado.
PREFIRA um nome “positivo” para uma propriedade ou variável booleana.
PREFIRA uma frase verbal imperativa para uma função ou método cujo objetivo
principal seja um efeito colateral.
PREFIRA uma frase substantiva ou uma frase verbo não imperativa para uma
função ou método se devolver um valor é seu propósito principal.
CONSIDERE uma frase verbal imperativa para uma função ou método se quiser
chamar a atenção para o trabalho que ela executa.
EVITAR iniciar um nome de método com get .
PREFIRA nomear um método to_ _ _() se ele copiar o estado do objeto para
um novo objeto.
PREFIRA nomear um método as_ _ _() se ele retornar uma representação
diferente apoiada pelo objeto original.
EVITAR descrever os parâmetros no nome da função ou método.
USE as convenções mnemônicas existentes ao nomear parâmetros de tipo.
Bibliotecas
PREFEIRA fazer declarações privadas.
CONSIDERE declarando várias classes na mesma biblioteca.
Classes e Mixins
EVITAR definir uma classe abstrata de um membro quando uma função simples
for suficiente.
EVITAR definir uma classe que contém apenas membros estáticos.
EVITAR estender uma classe que não se destina a ser uma subclasse.
USE documentar sua classe suporta extensão.
EVITAR implementar uma classe que não tenha o objetivo de ser uma interface.
USE documentar sua classe suporta ser usada como interface.
USE mixin para definir um tipo de mixin.
EVITAR mixar em um tipo que não seja um mixin.
Construtores
CONSIDERE fazer seu construtor const se a classe o suportar.
Membros
PREFIRA criar campos e variáveis de nível superior final .
USE getters para operações que acessam conceitualmente as propriedades.
USE setters para operações que alteram propriedades conceitualmente.
NÃO USE um setter sem um getter correspondente.
EVITAR o retorno null dos membros cujo tipo de retorno é bool , double ,
int , ou num .
EVITAR retornar this de métodos apenas para habilitar uma interface fluente.
Tipos
PREFIRA anotar campos públicos e variáveis de nível superior se o tipo não for
óbvio.
CONSIDERE tipo anotando campos privados e variáveis de nível superior se o
tipo não for óbvio.
EVITAR o tipo de anotação de variáveis locais inicializadas.
EVITAR anotar tipos de parâmetros inferidos em expressões de função.
EVITAR argumentos de tipo redundante em invocações genéricas.
USE anotações quando o Dart inferir o tipo errado.
PREFIRA anotar com em dynamic vez de deixar a inferência falhar.
PREFIRA assinaturas em anotações de tipo de função.
NÃO USE especificar um tipo de retorno para um setter.
NÃO USE a sintaxe legada do typedef.
PREFIRA tipos de função inline sobre typedefs.
CONSIDERE usando sintaxe de tipo de função para parâmetros.
EVITAR o uso, a dynamic menos que queira desativar a verificação estática.
USE Future<void> como o tipo de retorno de membros assíncronos que não
produzem valores.
EVITAR usar FutureOr<T> como tipo de retorno.
Parâmetros
EVITAR parâmetros booleanos posicionais.
EVITAR parâmetros posicionais opcionais se o usuário desejar omitir
parâmetros anteriores.
EVITAR parâmetros obrigatórios que aceitam um valor especial “sem
argumento”.
USE parâmetros de início inclusivos e de término exclusivos para aceitar um
intervalo.
Igualdade
USE sobrepor hashcode se você substituir == .
USE o seu == operador obedecer às regras matemáticas de igualdade.
EVITAR definir igualdade personalizada para classes mutáveis.
NÃO USE verificar null em == operadores personalizados.

LANGUAGE: MÉTODOS DE EXTENSÃO


Visão Global
Usando Métodos De Extensão
Tipos estáticos e dinâmicos
Conflitos de api
Implementando Métodos De Extensão
Implementando Extensões Genéricas

LANGUAGE: NULL SAFETY: SEGURANÇA NULA DO SOM


Princípios De Segurança Nulos
Um Tour Pelo Recurso De Segurança Nula
Criação de variáveis
Usando variáveis e expressões
Compreender os tipos de lista, conjunto e mapa
Habilitando Segurança Nula

LANGUAGE: NULL SAFETY: MIGRANDO PARA SEGURANÇA


NULA
Espere Para Migrar
Mudar para uma versão beta 2.12
Verifique o status de dependência
Atualizar dependências
Migrar
Usando a ferramenta de migração
Migrando manualmente
Análise
Teste
Publique
Restrições SDK
Versão do pacote
Bem-Vindo Ao Null Safety

LANGUAGE: NULL SAFETY: COMPREENDENDO A


SEGURANÇA NULA
Anulabilidade No Sistema De Tipo
Tipos não anuláveis e anuláveis
Usando tipos anuláveis
Cabeçalho e rodapé
Garantindo Correção
Devoluções inválidas
Variáveis não inicializadas
Análise De Fluxo
Análise de acessibilidade
Nunca para código inacessível
Análise de atribuição definitiva
Tipo de promoção em verificações nulas
Avisos de código desnecessários
Trabalhando Com Tipos Anuláveis
Métodos nulos mais inteligentes
Operador de asserção nula
Variáveis atrasadas
Inicialização lenta
Variáveis finais tardias
Parâmetros nomeados obrigatórios
Trabalhando com campos anuláveis
Nulidade e genéricos
Mudanças Na Biblioteca Central
O operador de índice do mapa é anulável
Nenhum construtor de lista sem nome
Não é possível definir um comprimento maior em listas não anuláveis
Não é possível acessar Iterator.current antes ou depois da iteração
Resumo

LANGUAGE: NULL SAFETY: SEGURANÇA NULA NÃO SONORA


Segurança Nula Sã E Doentia
Migrando Incrementalmente
Testar Ou Executar Programas De Versão Mista

LANGUAGE: NULL SAFETY: PERGUNTAS FREQUENTES


Quais Alterações De Tempo De Execução, Devo Estar Ciente Para Usuários
De Código Migrado?
E Se Um Valor Estiver Apenas null Em Testes?
Como se @required Compara À Nova required Palavra-Chave?
Como Devo Migrar Campos Não Anuláveis Que Deveriam Ser final , Mas
Não São?
Como Devo Migrar Uma built_value Classe?
Como Devo Migrar Uma Fábrica Que Posso Retornar null ?
Como Devo Migrar Um assert(x != null) Que Agora Parece Desnecessário?
Como Devo Migrar Uma Verificação Nula De Tempo De Execução Que Agora
Mostra Como Desnecessária?
O Iterable.firstWhere Método Não Aceita Mais orElse: () => null .
Como Faço Para Lidar Com Atributos Que Possuem Setters?
Como Posso Sinalizar Que O Valor De Retorno De Um Mapa Não É Anulável?
Por Que O Tipo Genérico Em Minha Lista / Mapa É Anulável?
O Que Aconteceu Com O Construtor List Padrão?
Estou Usando package:ffi E Obtenho Uma Falha
Dart_CObject_kUnsupported Quando Migro. O Que Aconteceu?

LANGUAGE: EVOLUÇÃO DA LINGUAGEM DART


Mudanças Em Cada Versão
Dart 2.0
Dart 2.1
Dart 2.2
Dart 2.3
Dart 2.5
Dart 2.6
Dart 2.7
Dart 2.8
Dart 2.9
Dart 2.10
Controle De Versão De Idioma
Números da versão do idioma
Seleção de versão de idioma por biblioteca

LANGUAGE: ESPECIFICAÇÃO DA LINGUAGEM DART


Dart 2
Dart 1.x

LANGUAGE: UM TOUR PELA LINGUAGEM DART


Um Programa Básico De Dart
Conceitos Importantes
Palavras-Chave
Variáveis
Valor padrão
Final e constante
Tipos Integrados
Números
int
double
Strings
Booleanos
Listas
Conjuntos
Mapas
Grupos de runas e grafemas
Símbolos
Funções
Parâmetros
A função main()
Funciona como objetos de primeira classe
Funções anônimas
Âmbito lexical
Fechamentos lexicais
Testando funções de igualdade
Valores de retorno
Operadores
Operadores aritméticos
Operadores relacionais e de igualdade
Operadores de teste de tipo
Operadores de atribuição
Operadores lógicos
Operadores bit a bit e shift
Expressões condicionais
Notação em cascata (..)
Outras operadoras
Declarações De Fluxo De Controle
If e else
For loops
While e do-while
Break e continue
Switch e case
Assert
Exceções
Throw
Catch
Finally
Classes
Usando membros da classe
Usando construtores
Obter o tipo de um objeto
Variáveis de instância
Construtores
Métodos
Classes abstratas
Interfaces implícitas
Extensão de uma classe
Métodos de extensão
Tipos enumerados
Adicionando recursos a uma classe: mixins
Variáveis e métodos de classe
Genéricos
Por que usar genéricos?
Usando literais de coleção
Usando tipos parametrizados com construtores
Coleções genéricas e os tipos que contêm
Restringindo o tipo parametrizado
Usando métodos genéricos
Bibliotecas E Visibilidade
Usando bibliotecas
Implementando bibliotecas
Suporte para assincronia
Manipulação de futuros
Declaração de funções assíncronas
Manipulando streams
Geradores
Classes chamáveis
Isolados
• Programação assíncrona Dart: isola e loops de evento
• Dart: isolar a referência da API,
• Livro de receitas de
• Isolar aplicativo de amostra
Typedefs
Metadados
Comentários
Comentários de linha única
Comentários multilinhas
Comentários de documentação
Resumo

LANGUAGE: O SISTEMA DE TIPO DART


O Que É Solidez?
Os Benefícios Da Solidez
Dicas para passar na análise estática
Use tipos de retorno de som ao substituir métodos
Use tipos de parâmetros de som ao substituir métodos
Não use uma lista dinâmica como uma lista digitada
Verificações de tempo de execução
Inferência De Tipo
Inferência de campo e método
Inferência de campo estático
Inferência de variável local
Inferência de argumento de tipo
Substituindo Tipos
Atribuição de tipo simples
Métodos
Outros Recursos

CORE LIBRARIES: BIBLIOTECAS CENTRAIS


Bibliotecas Multiplataforma
Bibliotecas De Plataforma Nativa
Bibliotecas De Plataforma Web

CORE LIBRARIES: UM TOUR PELAS PRINCIPAIS BIBLIOTECAS


dart:core
dart:async
dart:math
dart:convert
dart:html
dart:io
Dart: Núcleo - Números, Coleções, Strings E Muito Mais
Imprimir para o console
Números
Strings e expressões regulares
Coleções
URIs
Datas e horas
Classes de utilidades
Exceções
NoSuchMethodError
ArgumentError
dart:async - Programação Assíncrona
Futuro
Corrente
Mais Informações
• Programação assíncrona: futuros, assíncronos, aguardar
• Futuros e tratamento de erros
• Programação assíncrona: fluxos
• Criação de fluxos no Dart
• Programação assíncrona Dart: isola e loops de evento
dart:math - matemática e aleatório
Trigonometria
Máximo e mínimo
Constantes matemáticas
Números aleatórios
Mais Informações
dart: convert - Decodificação E Codificação Json, Utf-8 E Mais
Decodificando e codificando JSON
Decodificando e codificando caracteres UTF-8
Outras funcionalidades
dart: html - Aplicativos Baseados Em Navegador
Manipulando o DOM
Usando recursos HTTP com HttpRequest
Envio e recebimento de dados em tempo real com WebSockets
Mais Informações
dart:io – I/O Para Servidores E Aplicativos De Linha De Comando
Arquivos e diretórios
Clientes e servidores HTTP
Mais Informações
Resumo

CORE LIBRARIES: ARTICLES: INTRODUÇÃO À BIBLIOTECA


DART:IO
O Dart VM E O Loop De Eventos
Acesso Ao Sistema De Arquivos
Interagindo Com Processos
Escrevendo Servidores Web
Solicitações De Recursos São Bem-Vindas

CORE LIBRARIES: ARTICLES: CRIAÇÃO DE FLUXOS NO DART


Transformando Um Fluxo Existente
Criando Um Stream Do Zero
Usando Um Streamcontroller
Esperando Por Uma Assinatura
Honrando O Estado De Pausa
Dicas Finais

PACKAGES: COMO USAR PACOTES


Criação De Um Pubspec
Obtendo pacotes
Importando Bibliotecas De Pacotes
Atualizando Uma Dependência
Mais Informações
Como
Referência
Comandos pub
Solução de problemas

PACKAGES: PACOTES COMUMENTE USADOS


Pacotes De Uso Geral
Pacotes Que Se Expandem Nas Bibliotecas Centrais Do Dart
Pacotes Especializados
Pacotes Flutter
Pacotes da web
Pacotes de linha de comando e servidor

PACKAGES: CRIAÇÃO DE PACOTES


O Que Torna Um Pacote De Biblioteca
Organizando Um Pacote De Biblioteca
Importando Arquivos De Biblioteca
Importação E Exportação Condicional De Arquivos De Biblioteca
Fornecimento De Arquivos Adicionais
Documentando Uma Biblioteca
Distribuir Uma Biblioteca De Código Aberto
Recursos

PACKAGES: PUBLICAÇÃO DE PACOTES


Publicar É Para Sempre
Preparando Para Publicar
Arquivos importantes
Vantagens de usar um editor verificado
Criação de um editor verificado
Publicando Seu Pacote
Realizando um ensaio
Publicação
Transferir um pacote para um editor verificado
Quais Arquivos São Publicados?
Uploaders
Publicação De Pré-Lançamentos
Marcando Pacotes Como Descontinuados
Recursos

PACKAGES: PACKAGE REFERENCE: DEPENDÊNCIAS DE


PACOTE
Visão Global
Fontes De Dependência
SDK
Pacotes hospedados
Pacotes Git
Pacotes de caminho
Restrições De Versão
Sintaxe de acento circunflexo
Sintaxe tradicional
Dependências Dev
Modificações De Dependência
Melhores Práticas
Use sintaxe circunflexa
Depende das últimas versões de pacotes estáveis
Teste sempre que atualizar as dependências do pacote

PACKAGES: PACKAGE REFERENCE: GLOSSÁRIO DE TERMOS


DO PACOTE
Pacote De Aplicativos
Dependência
Ponto De Entrada
Diretório De Ponto De Entrada
Dependência Imediata
Pacote De Biblioteca
Lockfile
Restrição SDK
Fonte
Cache Do Sistema
Dependência Transitiva
Uploader
Editora Verificada
Restrição De Versão

PACKAGES: PACKAGE REFERENCE: CONVENÇÕES DE


LAYOUT DE PACOTE
O Pubspec
Licença
README.md
CHANGELOG.md
Diretórios Públicos
Bibliotecas públicas
Ferramentas públicas
Bens Públicos
Arquivos De Implementação
Arquivos Da Web
Aplicativos De Linha De Comando
Testes E Benchmarks
Documentação
Exemplos
Ferramentas E Scripts Internos

PACKAGES: PACKAGE REFERENCE: CONFIGURANDO


VARIÁVEIS DE AMBIENTE PUB
PACKAGES: PACKAGE REFERENCE: O ARQUIVO PUBSPEC
Campos Suportados
Exemplo
Detalhes
Nome
Versão
Descrição
Autor / autores
Página inicial
Repositório
Rastreador de problemas
Documentação
Dependências
Executáveis
Publish_to
Restrições SDK

PACKAGES: PACKAGE REFERENCE: PUB DE SOLUÇÃO DE


PROBLEMAS
Obtendo Um Erro “403” Ao Publicar Um Pacote
Obtendo Um Erro “Unauthorizedaccess” Ao Publicar Um Pacote
A Compilação Do Pub Falha Com Erro HttpException
Falha Na Obtenção De Pub Por Trás De Um Firewall Corporativo
Localhost Inacessível Após O Login

PACKAGES: PACKAGE REFERENCE: EDITORES VERIFICADOS


Processo De Verificação
Criar Uma Conta De Editor Verificada

PACKAGES: PACKAGE REFERENCE: CONTROLE DE VERSÃO


DE PACOTE
Um Nome E Um Número
Resolvendo Dependências Compartilhadas
Bibliotecas não compartilhadas (a abordagem npm)
Bloqueio de versão (a abordagem de beco sem saída)
Restrições de versão (a abordagem Dart)
Versões Semânticas
Resolução De Restrições
Contexto De Restrição
Resolução De Restrições Para Dependências Exportadas
Lockfiles
Quando As Coisas Dão Errado
Você pode ter restrições disjuntas
Você pode ter intervalos que não contêm uma versão lançada
Você pode ter um gráfico instável
Resumo

DEVELOPMENT: CODELABS: DICAS DO DART


Interpolação De String
Exemplo de código
Operadores Nulos
Exemplo de código
Acesso Condicional À Propriedade
Exemplo de código
Literais De Coleção
Exemplo de código
Sintaxe De Seta
Exemplo de código
Cascades
Exemplo de código
Getters E Setters
Exemplo de código
Parâmetros Posicionais Opcionais
Exemplo de código
Parâmetros Nomeados Opcionais
Exemplo de código
Exceções
Exemplo de código
Usando this Em Um Construtor
Exemplo de código
Listas De Inicializadores
Exemplo de código
Construtores Nomeados
Exemplo de código
Construtores De Fábrica
Exemplo de código
Redirecionando Construtores
Exemplo de código
Construtores Const
Exemplo de código
DEVELOPMENT: CODELABS: COLEÇÕES ITERÁVEIS
O Que São Coleções?
O Que É Um Iterável?
Elementos De Leitura
Exemplo: usando um loop for-in
Exemplo: usando o primeiro e o último
Exemplo: usando firstWhere()
Exercício: Pratique escrever um predicado de teste
Verificando As Condições
Exemplo: usando any() e every()
Exercício: Verifique se um Iterable satisfaz uma condição
Filtrando
Exemplo: usando where()
Exemplo: usando takeWhile
Exercício: Filtrando elementos de uma lista
Mapeamento
Exemplo: usando o mapa para alterar os elementos
Exercício: Mapeando para um tipo diferente
Exercício: Juntando Tudo

DEVELOPMENT: CODELABS: PROGRAMAÇÃO ASSÍNCRONA:


FUTUROS, ASSÍNCRONOS, AGUARDAR
Por Que O Código Assíncrono É Importante
Exemplo: uso incorreto de uma função assíncrona
O Que É Futuro?
Incompleto
Concluído
Exemplo: Apresentando futuros
Exemplo: Concluindo com um erro
Trabalhando Com Futuros: Assíncrono E Aguardar
Fluxo de execução com assíncrono e espera
Exemplo: execução em funções assíncronas
Exercício: Pratique usando assíncrono e aguarde
Tratamento De Erros
Exemplo: assíncrono e esperar com try-catch
Exercício: Pratique o tratamento de erros
Exercício: Juntando Tudo
Qual É O Próximo?
DEVELOPMENT: INTEROPERABILITY: INTEROPERABILIDADE
C USANDO DART: FFI
Exemplos
Passo A Passo De Hello_World
Arquivos
Construindo e funcionando
Usando Dart: ffi
Agrupando E Carregando Bibliotecas C

DEVELOPMENT: INTEROPERABILITY: INTEROPERABILIDADE


DE JAVASCRIPT
DEVELOPMENT: USANDO JSON
Bibliotecas
Recursos De Vibração
Recursos De Aplicativos Da Web
Recursos de VM

DEVELOPMENT: PROGRAMAÇÃO ASSÍNCRONA: FLUXOS


Recebendo Eventos De Stream
Eventos De Erro
Trabalhando Com Streams
Dois Tipos De Streams
Streams de assinatura única
Transmitir streams
Métodos que processam um fluxo
Métodos Que Modificam Um Fluxo
A função transform()
Ler e decodificar um arquivo
O Método Listen()

SOBRE O AUTOR
Language: Exemplo da Linguagem Dart
As próximas linhas são apenas uma breve introdução ao idioma
Dart para pessoas que gostam de aprender pelo exemplo. Você
também pode querer verificar as diversidades da linguagem e
biblioteca Dart, visitando o Dart cheatsheet codelab.
Olá Mundo
Todo aplicativo tem uma função main (). Para exibir texto no console,
você pode usar a função print() de nível superior:
Variáveis
Mesmo em código Dart seguro para tipos, a maioria das variáveis
não precisam de tipos explícitos, graças à inferência de tipo:

Leia mais sobre variáveis no Dart, incluindo valores padrão, as


palavras-chave final - const e tipos estáticos.
Declarações De Fluxo De Controle
O Dart suporta as declarações de fluxo de controle usuais:

Leia mais sobre as declarações de fluxo de controle no Dart,


incluindo break e continue , switch e case e assert .
Funções
Recomendamos especificar os tipos de argumentos de cada função
e valor de retorno:

Uma sintaxe abreviada => (seta) é útil para funções que contêm
uma única instrução. Essa sintaxe é especialmente útil ao passar
funções anônimas como argumentos:

Além de mostrar uma função anônima (o argumento para where() ),


este código mostra que você pode usar uma função como um
argumento: a função print() de nível superior é um argumento para
forEach() .
Leia mais sobre funções no Dart, incluindo parâmetros opcionais,
valores de parâmetro padrão e escopo léxico.
Comentários
Os comentários do Dart geralmente começam com // .

Leia mais sobre os comentários no Dart, incluindo como funciona o


conjunto de ferramentas de documentação.
Importações
Para acessar APIs definidas em outras bibliotecas, use import .

Leia mais sobre bibliotecas e visibilidade em Dart, incluindo prefixos


de biblioteca, show e hide , e carregamento lento através da
palavra-chave deferred .
Classes
Aqui está um exemplo de uma classe com três propriedades, dois
construtores e um método. Uma das propriedades não pode ser
definida diretamente, portanto, é definida usando um método getter
(em vez de uma variável).

Você pode usar a classe Spacecraft assim:


Leia mais sobre classes no Dart, incluindo listas de inicializadores,
os opcionais new e const , construtores factory , getters, setters e
muito mais.
Herança
Dart tem herança única.

Leia mais sobre como estender classes, a anotação opcional


@override e muito mais.
Mixins
Mixins são uma forma de reutilizar código em múltiplas hierarquias
de classes. A seguinte classe pode atuar como um mixin:

Para adicionar os recursos de um mixin a uma classe, basta


estender a classe com o mixin.

agora tem o campo astronauts e o método describeCrew() .


PilotedCraft
Leia mais sobre mixins.
Interfaces E Classes Abstratas
Dart não tem palavra-chave interface . Em vez disso, todas as
classes definem implicitamente uma interface. Portanto, você pode
implementar qualquer classe.

Leia mais sobre interfaces implícitas.


Você pode criar uma classe abstrata para ser estendida (ou
implementada) por uma classe concreta. As classes abstratas
podem conter métodos abstratos (com corpos vazios).

Qualquer classe que estende Describable tem o método


describeWithEmphasis() , que chama a implementação do extensor de
describe() .
Leia mais sobre classes e métodos abstratos.
Assíncrono
Evite o inferno de callbacks e torne seu código muito mais legível
usando async e await .

O método acima é equivalente a:

Como mostra o próximo exemplo, async e await ajuda a tornar o


código assíncrono fácil de ler.

Você também pode usar o async* , que oferece uma maneira legal e
legível de criar fluxos (streams).
Leia mais sobre o suporte a assincronia, incluindo funções async ,
Future , Stream e o loop assíncrono ( await for ) .
Exceções
Para levantar uma exceção, use throw :

Para capturar uma exceção, use uma instrução try com on ou


catch (ou ambos):

Observe que o código acima é assíncrono; try funciona para código


síncrono e código em uma função async .
Leia mais sobre exceções, incluindo rastreamentos de pilha, rethrow
e a diferença entre Erro e Exceção.
Language: Effective Dart: Visão Global
Nos últimos anos, foram escritos muitos códigos Dart e aprendeu-se
muitos métodos eficazes e ineficazes com ele. As informações
abaixo servem para que você possa escrever um código
consistente, robusto e rápido. Existem dois tópicos:
Consciente. Quando se trata de coisas como formatação e
capitalização, os argumentos sobre qual é o melhor são
subjetivos e impossíveis de resolver. O que sabemos é que ser
consistente é objetivamente útil. Se duas partes do código
parecem diferentes, deve ser porque eles são diferentes em
algum sentido. Quando algum código se destaca e chama sua
atenção, deve ser por razões úteis.
Sucinto. O Dart foi projetado para torná-lo familiar, então ele
herda muitas das mesmas instruções e expressões que C,
Java, JavaScript e outras linguagens. Mas, Dart foi criado,
porque há muito espaço para melhorar a funcionalidade
dessas linguagens. Desde a interpolação de Strings até a
inicialização de formatos, para ajudá-lo a expressar sua
intenção de maneira mais simples e fácil. Se houver várias
maneiras de dizer algo, geralmente deve ser escolhida a forma
mais concisa. Isso não quer dizer que você precise codificar o
golfe para empilhar o programa inteiro em uma linha. O
objetivo é um código econômico, não um código denso.
O analisador Dart tem um Linter que pode ajudá-lo a escrever um
código bom e consistente. Se houver uma Regra de Linter que pode
ajudá-lo a seguir uma diretriz, a diretriz será vinculada a essa regra.
Aqui está um exemplo:
Regra de Linter: prefer_collection_literals
Para obter ajuda sobre como habilitar as regras do Linter, consulte a
documentação para personalizar a análise estática.
Os Guias
Dividimos o guia em várias páginas separadas para facilitar a
assimilação:
Guia de estilo - define as regras de layout e organização do
código, ou pelo menos as partes que o dartfmt não trata para
você. A guia de estilo também específica como identificadores
são formatados: camelCase , using_underscores etc.
Guia de documentação - contém tudo o que você precisa saber
sobre o conteúdo dos comentários. Comentários de
documentos e comentários de código comuns e comuns.
Guia de uso - ensina como fazer o melhor uso dos recursos da
linguagem Dart para implementar o comportamento. Se estiver
em uma declaração ou expressão, será abordado aqui.
Guia de design - este é o guia mais fácil, mas aquele com o
escopo mais amplo. Ele cobre como projetar APIs consistentes
e utilizáveis para bibliotecas. Se estiver em uma assinatura de
tipo ou declaração, isso vai por cima.
Como Ler Os Guias
Cada guia está dividida em várias partes. Essas seções contêm
uma lista de diretrizes. Cada diretriz começa com uma das
seguintes palavras:
USE, diretrizes que descrevem as práticas que sempre devem
ser seguidas. Quase nunca haverá uma razão válida para se
desviar deles.
NÃO USE , diretrizes são o contrário: coisas que quase nunca
são uma boa ideia. Felizmente, não temos tantos delas quanto
em outras línguas, porque temos menos bagagem histórica.
PREFIRA , estas diretrizes são práticas que você deve seguir.
No entanto, pode haver circunstâncias em que faça sentido
fazer o contrário. Apenas certifique-se de entender todas as
implicações de ignorar a diretriz ao fazer isso.
EVITAR , diretrizes são duplas para “preferir”: coisas que você
não deve fazer, mas para as quais pode haver boas razões em
raras ocasiões.
CONSIDERE , diretrizes são práticas que você pode ou não
querer seguir, dependendo das circunstâncias, precedentes e
de sua preferência.
Algumas diretrizes descrevem uma exceção onde a regra não se
aplica. Quando listadas, as exceções podem não ser completas –
talvez você ainda pode precise usar seu julgamento em outros
casos.
Parece que a polícia vai bater na sua porta se você não amarrar os
cadarços corretamente. As coisas não estão tão ruins. A maioria das
diretrizes aqui são de bom senso e somos todos pessoas razoáveis.
O objetivo, como sempre, é um código bom, legível e sustentável.
Glossário
Para manter as diretrizes resumidas, usamos alguns termos
abreviados para nos referirmos a diferentes construções do Dart.
Um libary member é um campo, getter, setter ou função de
nível superior. Basicamente, qualquer coisa no nível superior
que não seja um tipo.
Uma classe member é um construtor, campo, getter, setter,
função ou operador declarado dentro de uma classe. Os
membros da classe podem ser instâncias ou estáticos,
abstratos ou concretos.
Um member é um membro da biblioteca ou um membro da
classe.
Uma variable, quando usada geralmente, refere-se a variáveis
de nível superior, parâmetros e variáveis locais. Não incluem
campos estáticos ou de instância.
Um type é qualquer declaração de tipo nomeado: uma classe,
typedef ou enum.
Uma property é uma variável de nível superior, getter (dentro
de uma classe ou no nível superior, instância ou estática),
setter (mesmo) ou campo (instância ou estático). Praticamente
qualquer construção nomeada “semelhante a um campo”.
Language: Effective Dart: Estilo
A parte surpreendentemente importante de um bom código é o bom
estilo. Nomenclatura, ordenação e formatação consistentes ajudam
o código que é igual a ter a mesma aparência. Ele tira proveito do
poderoso hardware de correspondência de padrões na maioria de
nossos sistemas oculares. Se usarmos estilos consistentes no
ecossistema do Dart, será mais fácil para todos nós aprendermos e
contribuirmos com o código uns dos outros.
Identificadores
Os identificadores vêm em três tipos no Dart.
Os nomes UpperCamelCase colocam em maiúscula a
primeira letra de cada palavra, incluindo a primeira.
Os nomes lowerCamelCase colocam em maiúscula a primeira
letra de cada palavra, exceto a primeira que é sempre
minúscula, mesmo se for uma sigla.
lowercase_with_underscores usa apenas letras minúsculas,
mesmo para siglas, e separe as palavras com _.

USE tipos de nomes usando UpperCamelCase .


Regra do Linter : camel_case_types
Classes, tipos de enum, typedefs e parâmetros de tipo devem
colocar a primeira letra de cada palavra em maiúscula (incluindo a
primeira palavra) e não usar separadores.

Isso inclui até classes destinadas a serem usadas em anotações de


metadados.

Se o construtor da classe de anotação não aceita parâmetros,


convém criar uma constante lowerCamelCase separada para ele.
USE extensões de nome usando UpperCamelCase .
Regra de Linter: camel_case_extensions
Como os tipos, as extensões devem colocar a primeira letra de cada
palavra em maiúscula (incluindo a primeira palavra) e não usar
separadores.

USE os nomes de bibliotecas, pacotes, diretórios e


arquivos de origem usando
lowercase_with_underscores .
Regras do Linter: library_names, file_names
Alguns sistemas de arquivos não fazem distinção entre maiúsculas
e minúsculas, então muitos projetos exigem que os nomes dos
arquivos sejam todos minúsculos. Usar um caractere de separação
permite que os nomes ainda possam ser lidos nessa forma. O uso
de sublinhados como separador garante que o nome ainda seja um
identificador Dart válido, o que pode ser útil se o idioma
posteriormente oferecer suporte a importações simbólicas.
USE os prefixos de importação de nomes usando
lowercase_with_underscores .
Regra de Linter: library_prefixes

USE os nomes de outros identificadores usando


lowerCamelCase .
Regra Linter: non_constant_identifier_names
Os membros da classe, definições de nível superior, variáveis,
parâmetros e parâmetros nomeados devem colocar a primeira letra
de cada palavra em maiúscula, exceto a primeira palavra, e não
devem usar separadores.

PREFIRA lowerCamelCase como o nome da


constante.
Regra de Linter: constant_identifier_names
No novo código, use lowerCamelCase para variáveis constantes,
incluindo valores de enum.

Você pode usar SCREAMING_CAPS para consistência com o


código existente, como nos seguintes casos:
Ao adicionar código a um arquivo ou biblioteca que já usa
SCREAMING_CAPS .
Ao gerar código Dart que é paralelo ao código Java - por
exemplo, em tipos enumerados gerados a partir de protobufs.
USE letras maiúsculas em siglas e abreviações com
mais de duas letras, como palavras.
Siglas em maiúsculas podem ser difíceis de ler, e várias siglas
adjacentes podem levar a nomes ambíguos. Por exemplo, dado um
nome que começa com HTTPSFTP, não há como saber se ele se
refere a HTTPS FTP ou HTTP SFTP.
Para evitar isso, siglas e abreviações são capitalizadas como
palavras normais.
Excepção : siglas de duas letras como IO (entrada / saída) estão
totalmente capitalizadas: IO . Por outro lado, abreviaturas de duas
letras como ID (identificação) ainda são capitalizadas como
palavras regulares: Id .
NÃO USE um sublinhado inicial para identificadores
que não são privados.
O Dart usa um sublinhado inicial em um identificador para marcar
membros e declarações de nível superior como privadas. Isso treina
os usuários a associar um sublinhado à esquerda a um desses tipos
de declarações. Eles veem “_” e pensam “privado”.
Não há conceito de “privado” para variáveis locais, parâmetros ou
prefixos de biblioteca. Quando um deles tem um nome que começa
com um sublinhado, ele envia um sinal confuso para o leitor. Para
evitar isso, não use sublinhados à esquerda nesses nomes.
Exceção: Um parâmetro não utilizado pode ser nomeado _ , __ ,
___ , etc. Isso acontece em coisas como retornos de chamada,
onde um valor é passado, mas você não precisa usá-lo. Atribuir a
ele um nome que consiste apenas em sublinhados é a forma
idiomática de indicar que o valor não é usado.

NÃO USE letras de prefixo.


A notação húngara e outros esquemas surgiram na época do BCPL,
quando o compilador não fazia muito para ajudá-lo a entender seu
código. Como o Dart pode informar o tipo, escopo, mutabilidade e
outras propriedades de suas declarações, não há razão para
codificar essas propriedades em nomes de identificadores.
Encomenda
Para manter o preâmbulo de seu arquivo organizado, temos uma
ordem prescrita em que as diretivas devem aparecer. Cada “seção”
deve ser separada por uma linha em branco.
Uma única regra de Linter trata de todas as diretrizes de ordenação: directives_ordering.

USE as importações “dart:” antes de outras


importações.
Regra de Linter: directives_ordering

USE as importações “pacote:” antes das


importações relativas.
Regra de Linter: directives_ordering

USE as especificações das exportações em uma


seção separada após todas as importações.
Regra de Linter: directives_ordering
USE as seções em ordem alfabética.
Regra de Linter: directives_ordering
Formatação
Como muitas linguagens, o Dart ignora os espaços em branco. No
entanto, os humanos não. Ter um estilo de espaço em branco
consistente ajuda a garantir que os leitores humanos vejam o código
da mesma forma que o compilador.

USE a formatação do seu código usando dartfmt .


A formatação é um trabalho tedioso e consome muito tempo
durante a refatoração. Felizmente, você não precisa se preocupar
com isso. Nós fornecemos um formatador de código automatizado
sofisticado chamado dartfmt que faz isso para você. Temos alguma
documentação sobre as regras que se aplicam, mas as regras
oficiais de manipulação de espaços em branco para o Dart são tudo
o que o dartfmt produz.
As demais diretrizes de formatação são para as poucas coisas que
o dartfmt não pode corrigir para você.

CONSIDERE alterar seu código para torná-lo mais


amigável ao formatador.
O formatador faz o melhor que pode com qualquer código que você
lançar nele, mas não pode fazer milagres. Se o seu código tiver
identificadores particularmente longos, expressões profundamente
aninhadas, uma mistura de diferentes tipos de operadores etc., a
saída formatada ainda pode ser difícil de ler.
Quando isso acontecer, reorganize ou simplifique seu código.
Considere encurtar o nome de uma variável local ou colocar uma
expressão em uma nova variável local. Em outras palavras, faça os
mesmos tipos de modificações que faria se estivesse formatando o
código manualmente e tentando torná-lo mais legível. Pense no
dartfmt como uma parceria em que vocês trabalham juntos, às
vezes iterativamente, para produzir um código bonito.

EVITAR linhas com mais de 80 caracteres.


Regra de Linter: lines_longer_than_80_chars
Estudos de legibilidade mostram que longas linhas de texto são
mais difíceis de ler porque seu olho tem que viajar mais longe ao
mover para o início da próxima linha. É por isso que jornais e
revistas usam várias colunas de texto.
Se você realmente deseja linhas com mais de 80 caracteres, nossa
experiência é que seu código provavelmente é muito detalhado e
poderia ser um pouco mais compacto. O principal agressor
geralmente é VeryLongCamelCaseClassNames . Pergunte a si
mesmo: "Cada palavra nesse nome de tipo me diz algo crítico ou
evita uma colisão de nomes?" Se não, considere omiti-lo.
Observe que o dartfmt faz 99% disso para você, mas o último 1% é
você. Ele não divide literais de string longas para caber em 80
colunas, então você tem que fazer isso manualmente.
Exceção : quando um URI ou caminho de arquivo ocorre em um
comentário ou string (geralmente em uma importação ou
exportação), ele pode permanecer inteiro, mesmo que faça com que
a linha ultrapasse 80 caracteres. Isso torna mais fácil pesquisar um
caminho nos arquivos de origem.
Exceção : strings de várias linhas podem conter linhas com mais de
80 caracteres porque as novas linhas são significativas dentro da
string e dividir as linhas em outras menores pode alterar o
programa.

USE chaves para todas as instruções de controle de


fluxo.
Regra de Linter: curly_braces_in_flow_control_structures
Fazer isso evita o problema de pendurando mais.

Exceção : quando você tem uma instrução if sem cláusula else e


toda a instrução if cabe em uma linha, você pode omitir as chaves,
se preferir:
Se o corpo passar para a próxima linha, porém, use colchetes:
Language: Effective Dart: Documentação
É fácil pensar que seu código é óbvio hoje, sem perceber o quanto
você confia no contexto que já está em sua mente. Novatos em seu
código, e mesmo seu futuro esquecido não terão esse contexto.
Comentários concisos e precisos levam apenas alguns segundos
para ser escrito, mas podem economizar horas para uma pessoa.
Todos nós sabemos que o código deve ser auto documentado e
nem todos os comentários são úteis. Mas a realidade é que a
maioria de nós não escreve tantos comentários quanto deveria. É
como exercício: tecnicamente, você pode fazer muito, mas é muito
mais provável que esteja fazendo muito pouco. Tente intensificar.
Comentários
As dicas a seguir se aplicam a comentários que você não deseja
incluir na documentação gerada.

USE comentários formatados como frases.

Coloque a primeira palavra em maiúscula, a menos que seja um


identificador que diferencia maiúsculas de minúsculas. Termine com
um ponto final (ou “!” ou "?"). Isso é verdade para todos os
comentários: comentários de documentos, material embutido e até
TODOS. Mesmo que seja um fragmento de frase.

NÃO USE comentários de bloco para


documentação.

Você pode usar um comentário de bloco ( /* ... */ ) para comentar


temporariamente uma seção do código, mas para todos os outros
comentários devem usar // .
DOC Comentários
Os comentários de documentos são especialmente úteis porque o
dartdoc os analisa e gera lindas páginas de documentos a partir
deles. Um comentário de documento é qualquer comentário que
aparece antes de uma declaração e usa a sintaxe especial /// que o
dartdoc procura.

USE comentários de documentos com /// para


documentar membros e tipos.
Regra Linter: slash_for_doc_comments
Usar um comentário de documento em vez de um comentário
normal permite que o dartdoc o encontre e gere documentação
para ele.

Por razões históricas, o dartdoc suporta duas sintaxes de


comentários de documentos: /// (“estilo C#”) e /**...*/ (“estilo
JavaDoc”). Preferimos /// porque é mais compacto /** e */ adicione
duas linhas sem conteúdo a um comentário de documento de várias
linhas. A sintaxe /// também é mais fácil de ler em algumas
situações, como quando um comentário de documento contém uma
lista com marcadores que usa * para marcar itens da lista.
Se você tropeçar em um código que ainda usa o estilo JavaDoc,
considere limpá-lo.

PREFIRA escrever comentários de documentos para


apis públicas.
Regras do Linter: package_api_docs, public_member_api_docs
Você não precisa documentar cada biblioteca, variável de nível
superior, tipo e membro, mas deve documentar a maioria deles.
CONSIDERE escrever um comentário de documento
em nível de biblioteca.
Ao contrário de linguagens como Java, em que a classe é a única
unidade de organização do programa, no Dart, uma biblioteca é em
si uma entidade com a qual os usuários trabalham diretamente,
importam e pensam. Isso torna a diretiva library um ótimo lugar para
documentação que apresenta ao leitor os principais conceitos e
funcionalidades fornecidas. Considere incluir:
Um resumo de uma única frase sobre a finalidade da
biblioteca.
Explicações da terminologia usada em toda a biblioteca.
Alguns exemplos de código completos que mostram o uso da
API.
Links para as classes e funções mais importantes ou mais
comumente usadas.
Links para referências externas no domínio de interesse da
biblioteca.
Você documenta uma biblioteca colocando um comentário de
documento logo acima da diretiva library no início do arquivo. Se a
biblioteca não tiver uma diretiva library , você pode adicionar uma
apenas para pendurar o comentário do documento nela.

CONSIDERE escrever comentários de documentos


para apis privadas.
Os comentários do documento não são apenas para consumidores
externos da API pública de sua biblioteca. Eles também podem ser
úteis para entender os membros particulares que são chamados de
outras partes da biblioteca.

USE os comentários do documento com um resumo


de uma única frase.
Comece seu comentário de documento com uma breve descrição
centrada no usuário, terminando com um ponto. Um fragmento de
frase geralmente é suficiente. Forneça contexto suficiente para que
o leitor se oriente e decida se deve continuar lendo ou procurar em
outro lugar a solução para seu problema.
USE separar a primeira frase de um comentário de
documento em seu próprio parágrafo.
Adicione uma linha em branco após a primeira frase para dividi-la
em seu próprio parágrafo. Se mais de uma única frase de
explicação for útil, coloque o resto nos parágrafos posteriores.
Isso ajuda você a escrever uma primeira frase que resuma a
documentação. Além disso, ferramentas como o dartdoc usam o
primeiro parágrafo como um breve resumo em lugares como listas
de classes e membros.
EVITAR redundância com o contexto circundante.
O leitor do comentário do documento de uma classe pode ver
claramente o nome da classe, quais interfaces ela implementa etc.
Ao ler a documentação de um membro, a assinatura está bem ali, e
a classe envolvente é óbvia. Nada disso precisa ser explicitado no
comentário do documento. Em vez disso, concentre-se em explicar
o que o leitor ainda não sabe.

PREFIRA comentários de função ou método de


início com verbos em terceira pessoa.
O comentário do documento deve se concentrar no que o código
faz .
PREFIRA os comentários da variável inicial, getter
ou setter com frases substantivas.
O comentário do documento deve enfatizar o que é a propriedade.
Isso é verdadeiro até mesmo para getters que podem realizar
cálculos ou outras tarefas. O que interessa ao chamador é o
resultado desse trabalho, não o trabalho em si.

Evite ter um comentário doc no setter e no getter, pois o dartdoc


mostrará apenas um (o que está no getter).

PREFIRA começar a biblioteca ou digite comentários


com frases substantivas.
Os comentários do documento para as classes costumam ser a
documentação mais importante do programa. Eles descrevem as
invariantes do tipo, estabelecem a terminologia que ele usa e
fornecem contexto para os outros comentários de documentos para
os membros da classe. Um pequeno esforço extra aqui pode tornar
todos os outros membros mais simples de documentar.

CONSIDERE incluindo amostras de código em


comentários de documentos.
Os seres humanos são ótimos em generalizar a partir de exemplos,
portanto, até mesmo uma única amostra de código torna uma API
mais fácil de aprender.

USE colchetes nos comentários do documento para


se referir aos identificadores dentro do escopo.
Regra de Linter: comment_references
Se você colocar itens como variáveis, métodos ou nomes de tipos
entre colchetes, o dartdoc procura o nome e os links para os
documentos relevantes da API. Os parênteses são opcionais, mas
podem deixar isso mais claro quando você se refere a um método
ou construtor.

Para vincular a um membro de uma classe específica, use o nome


da classe e o nome do membro, separados por um ponto:

A sintaxe de ponto também pode ser usada para se referir a


construtores nomeados. Para o construtor sem nome, coloque
parênteses após o nome da classe:

USE prosa para explicar parâmetros, valores de


retorno e exceções.
Outras linguagens usam tags detalhadas e seções para descrever
quais são os parâmetros e retornos de um método.

A convenção no Dart é integrar isso na descrição do método e


destacar os parâmetros usando colchetes.

USE comentários de documentos antes das


anotações de metadados.
Markdown
Você tem permissão para usar a maior parte da formatação
markdown em seus comentários de documentos e o dartdoc irá
processá-la de acordo com o pacote markdown.
Já existem muitos guias por aí para apresentá-lo ao Markdown. Sua
popularidade universal é o motivo pelo qual o escolhemos. Aqui está
apenas um exemplo rápido para lhe dar uma ideia do que é
suportado:
/// This is a paragraph of regular text.
///
/// This sentence has *two* _emphasized_ words (italics) and **two**
/// __strong__ ones (bold).
///
/// A blank line creates a separate paragraph. It has some `inline code`
/// delimited using backticks.
///
/// * Unordered lists.
/// * Look like ASCII bullet lists.
/// * You can also use `-` or `+`.
///
/// 1. Numbered lists.
/// 2. Are, well, numbered.
/// 1. But the values don't matter.
///
/// * You can nest lists too.
/// * They must be indented at least 4 spaces.
/// * (Well, 5 including the space after `///`.)
///
/// Code blocks are fenced in triple backticks:
///
/// ```
/// this.code
/// .will
/// .retain(its, formatting);
/// ```
///
/// The code language (for syntax highlighting) defaults to Dart. You can
/// specify it by putting the name of the language after the opening backticks:
///
/// ```html
/// <h1>HTML is magical!</h1>
/// ```
///
/// Links can be:
///
/// * https://www.just-a-bare-url.com
/// * [with the URL inline](https://google.com)
/// * [or separated out][ref link]
///
/// [ref link]: https://google.com
///
/// # A Header
///
/// ## A subheader
///
/// ### A subsubheader
///
/// #### If you need this many levels of headers, you're doing it wrong

EVITAR usar reduções excessivas.


Em caso de dúvida, formate menos. A formatação existe para
iluminar seu conteúdo, não o substituir. Palavras são o que importa.

EVITAR usar html para formatação.


Pode ser útil usá-lo em casos raros para coisas como tabelas, mas
em quase todos os casos, se for muito complexo para expressar em
Markdown, é melhor não o expressar.

PREFIRA cercas de backtick para blocos de código.


O Markdown tem duas maneiras de indicar um bloco de código:
recuando o código quatro espaços em cada linha ou circundando-o
em um par de linhas de “cerca” com fundo triplo. A sintaxe anterior é
frágil quando usada dentro de coisas como listas Markdown onde o
recuo já é significativo ou quando o próprio bloco de código contém
código recuado.
A sintaxe de crase evita esses problemas de recuo, permite indicar
a linguagem do código e é consistente com o uso de crases para
código embutido.
Escrita
Pensamos em nós mesmos como programadores, mas a maioria
dos caracteres em um arquivo de origem é destinada principalmente
para leitura por humanos. Inglês é o idioma que codificamos para
modificar os cérebros de nossos colegas de trabalho. Como para
qualquer linguagem de programação, vale a pena se esforçar para
melhorar sua proficiência.
Esta seção lista algumas diretrizes para nossos documentos. Você
pode aprender mais sobre as práticas recomendadas para redação
técnica, em geral, em artigos como Estilo de redação técnica.

PREFIRA Brevidade.
Seja claro e preciso, mas também conciso.

EVITAR Abreviações E Siglas, A Menos Que Sejam


Óbvios.
Muitas pessoas não sabem o que significa “ie”, “eg” e “et al.”. Essa
sigla que você tem certeza de que todos em sua área conhecem
pode não ser tão amplamente conhecida quanto você pensa.

PREFIRA Usar “este” Em Vez De “o” Para Se Referir


À Instância De Um Membro.
Ao documentar um membro para uma classe, geralmente você
precisa consultar o objeto para o qual o membro está sendo
chamado. Usar “o” pode ser ambíguo.
Language: Effective Dart: Uso
Você pode usar essas diretrizes todos os dias nos corpos de seu
código Dart. Os usuários de sua biblioteca podem não ser capazes
de dizer que você internalizou as ideias aqui, mas os mantenedores
dela certamente o farão.
Bibliotecas
Essas diretrizes ajudam a compor seu programa a partir de vários
arquivos de maneira consistente e sustentável. Para manter essas
diretrizes breves, eles usam “importação” para cobrir as diretrizes
import e export . As diretrizes se aplicam igualmente a ambos.

USE strings em diretivas part of .


Muitos desenvolvedores do Dart evitam usar part inteiramente. Eles
acham mais fácil raciocinar sobre seu código quando cada biblioteca
é um único arquivo. Se você decidir usar part para dividir parte de
uma biblioteca em outro arquivo, o Dart exige que o outro arquivo
indique de qual biblioteca ele faz parte. Por motivos de legado, o
Dart permite que essa diretiva part of use o nome da biblioteca da
qual faz parte. Isso torna mais difícil para as ferramentas localizarem
fisicamente o arquivo da biblioteca principal e pode tornar ambígua
a biblioteca da qual a parte realmente faz parte.
A sintaxe moderna preferida é usar uma string URI que aponta
diretamente para o arquivo de biblioteca, assim como você usa em
outras diretivas. Se você tiver alguma biblioteca, que contém
my_library.dart :

Então, o arquivo de peça deve ser semelhante a:

E não:
NÃO USE importação de bibliotecas que estejam
dentro do diretório src de outro pacote.
Regra de Linter: implementação_imports
O diretório src em lib é especificado para conter bibliotecas
privadas para a própria implementação do pacote. A forma como os
mantenedores dos pacotes cria versões de seus pacotes leva esta
convenção em consideração. Eles são livres para fazer mudanças
radicais no código src sem que isso seja uma mudança significativa
no pacote.
Isso significa que se você importar a biblioteca privada de algum
outro pacote, um lançamento menor e teoricamente ininterrupto
desse pacote pode quebrar seu código.

USE caminhos relativos ao importar bibliotecas


dentro do diretório lib do seu próprio pacote.
Regras de Linter : Avoid_relative_lib_imports, prefer_relative_imports
Ao fazer referência a uma biblioteca dentro do diretório lib do seu
pacote de outra biblioteca no mesmo pacote, use um URI relativo,
não um package explícito: URI.
Por exemplo, digamos que sua estrutura de diretório seja assim:

Se api.dart quiser importar utils.dart , deve fazê-lo usando:

E não:

A parte “dentro do diretório lib do seu próprio pacote” é importante.


As bibliotecas dentro de lib podem importar outras bibliotecas
dentro de lib (ou em seus subdiretórios). Bibliotecas fora da lib
podem usar importações relativas para alcançar outras bibliotecas
fora da lib . Por exemplo, você pode ter uma biblioteca de utilitários
de teste sob test que outras bibliotecas em test de importação.
Mas você não pode “cruzar as correntes”. Uma biblioteca fora da lib
nunca deve usar uma importação relativa para acessar uma
biblioteca abaixo da lib , ou vice-versa. Fazer isso interromperá a
capacidade do Dart de dizer corretamente se dois URIs de biblioteca
se referem à mesma biblioteca, o que pode levar a tipos duplicados
inesperados.
Siga estas duas regras:
Um caminho de importação nunca deve conter /lib/ .
Uma biblioteca em lib nunca deve usar ../ para escapar do
diretório lib .
Booleanos
USE ?? para converter null para um valor
booleano.
Esta regra aplica-se quando uma expressão pode avaliar true , false
ou null , e você precisa passar o resultado para algo que não aceita
null . Um caso comum é o resultado de uma chamada de método
com reconhecimento de nulo sendo usada como uma condição:

Este código lança uma exceção se optionalThing para null . Para


corrigir isso, você precisa “converter” o valor null para true ou false .
Embora você possa fazer isso usando == , recomendamos usar ?? :

Ambas as operações produzem o mesmo resultado e fazem a coisa


certa, mas ?? são preferidos por três razões principais:
O operador ?? sinaliza claramente que o código tem algo a ver
com um valor null .
O == true parece um novo erro comum do programador em que
o operador de igualdade é redundante e pode ser removido.
Isso é verdade quando a expressão booleana à esquerda não
produz null , mas não quando pode.
O ?? false e ?? true mostra claramente qual valor será usado
quando a expressão for null . Com == true , você precisa pensar
através da lógica booleana para perceber que significa que
null é convertido em falso.
Strings
Aqui estão algumas práticas recomendadas para se ter em mente
ao compor strings no Dart.

USE strings adjacentes para concatenar literais de


string.
Regra de Linter: prefer_adjacent_string_concatenation
Se você tiver dois literais de string - não valores, mas a forma literal
real entre aspas - você não precisa usar + para concatená-los.
Assim como em C e C++, basta colocá-los próximos um do outro.
Esta é uma boa maneira de fazer uma única string longa que não
cabe em uma linha.

PREFIRA usar interpolação para compor strings e


valores.
Regra Linter: prefer_interpolation_to_compose_strings
Se você vem de outras linguagens, está acostumado a usar longas
cadeias de + para construir uma string de literais e outros valores.
Isso funciona no Dart, mas quase sempre é mais limpo e mais curto
usar a interpolação:
EVITAR o uso de colchetes na interpolação quando
não for necessário.
Regra de Linter: needed_brace_in_string_interps
Se você estiver interpolando um identificador simples não seguido
imediatamente por mais texto alfanumérico, o {} deve ser omitido.
Coleções
Fora da caixa, o Dart oferece suporte a quatro tipos de coleção:
listas, mapas, filas e conjuntos. As práticas recomendadas a seguir
se aplicam a coleções.

USE literais de coleção quando possível.


Regra de Linter: prefer_collection_literals
O Dart tem três tipos de coleção principais: Lista, Mapa e Conjunto.
Essas classes têm construtores sem nome como a maioria das
classes. Mas, como essas coleções são usadas com tanta
frequência, o Dart tem uma sintaxe integrada mais agradável para
criá-las:

Observe que esta diretriz não se aplica aos construtores nomeados


para essas classes. List.from() , Map.fromIterable() e amigos, todos têm
seus usos. Da mesma forma, se você está passando um tamanho
para List() para criar um tamanho que não pode ser aumentado, faz
sentido usá-lo.

NÃO USE .length para ver se uma coleção está


vazia.
Regras Linter: prefer_is_empty, prefer_is_not_empty
O contrato Iterable não exige que uma coleção conheça sua
duração ou seja capaz de fornecê-la em tempo constante. Chamar
.length apenas para ver se a coleção contém algo pode ser
dolorosamente lento.
Em vez disso, existem getters mais rápidos e legíveis: .isEmpty e
.isNotEmpty . Use aquele que não exige que você negue o resultado.
CONSIDERE o uso de métodos de ordem superior
para transformar uma sequência.
Se você tem uma coleção e deseja produzir uma nova coleção
modificada a partir dela, geralmente é mais curto e mais declarativo
de usar .map() , .where() e os outros métodos úteis em Iterable .
Usar aqueles em vez de um loop for obrigatório deixa claro que sua
intenção é produzir uma nova sequência e não produzir efeitos
colaterais.

Ao mesmo tempo, isso pode ser levado longe demais. Se você


estiver encadeando ou aninhando muitos métodos de ordem
superior, pode ser mais claro escrever um pedaço de código
imperativo.

EVITAR usar Iterable.forEach() com um literal de


função.
Regra de Linter: Avoid_function_literals_in_foreach_calls
As funções forEach() são amplamente utilizadas em JavaScript
porque o loop for-in embutido não faz o que você normalmente
deseja. No Dart, se você deseja iterar em uma sequência, a maneira
idiomática de fazer isso é usar um loop.
Observe que esta diretriz diz especificamente “literal de função”. Se
você deseja invocar alguma função já existente em cada elemento,
forEach() é adequado.

Observe também que é sempre bom usar Map.forEach() . Os mapas


não são iteráveis, portanto, está diretriz não se aplica.

NÃO USE, List.from() a menos que pretenda


alterar o tipo de resultado.
Dado um Iterable, existem duas maneiras óbvias de produzir uma
nova lista que contenha os mesmos elementos:

A diferença óbvia é que o primeiro é mais curto. A diferença


importante é que o primeiro preserva o argumento de tipo do objeto
original:
Se você deseja alterar o tipo, chamar List.from() é útil:

Mas se seu objetivo é apenas copiar o iterável e preservar seu tipo


original, ou se você não se importa com o tipo, use toList() .

USE whereType() para filtrar uma coleção por tipo.


Regra Linter: prefer_iterable_whereType
Digamos que você tenha uma lista contendo uma mistura de objetos
e deseja obter apenas os inteiros dela. Você pode usar where()
assim:

Isso é detalhado, mas, pior, retorna um iterável cujo tipo


provavelmente não é o que você deseja. No exemplo aqui, ele
retorna um Iterable<Object> , embora você provavelmente queira um
Iterable<int> , pois esse é o tipo para o qual você está filtrando.
Às vezes, você vê um código que “corrige” o erro acima,
adicionando cast() :
Isso é extenso e faz com que dois wrappers sejam criados, com
duas camadas de indireção e verificação de tempo de execução
redundante. Felizmente, a biblioteca central tem o método
whereType() para este caso de uso exato:

Usar whereType() é conciso, produz um Iterable do tipo desejado e


não tem níveis desnecessários de agrupamento.

NÃO USE cast() quando uma operação próxima for


suficiente.
Frequentemente, ao lidar com um iterável ou fluxo, você realiza
várias transformações nele. No final, você deseja produzir um objeto
com um determinado argumento de tipo. Em vez de adicionar uma
chamada para cast() , veja se uma das transformações existentes
pode mudar o tipo.
Se você já está chamando toList() , substitua isso por uma chamada
para List<T>.from() onde T é o tipo de lista resultante que você
deseja.

Se você estiver chamando map() , dê a ele um argumento de tipo


explícito para que ele produza um iterável do tipo desejado. A
inferência de tipo geralmente escolhe o tipo correto para você com
base na função a qual você passa para map() , mas às vezes você
precisa ser explícito.

EVITAR usar cast() .


Esta é a generalização mais branda da regra anterior. Às vezes, não
há nenhuma operação próxima que você possa usar para corrigir o
tipo de algum objeto. Mesmo assim, quando possível, evite usar
cast() para “alterar” o tipo de uma coleção.
Em vez disso, prefira qualquer uma destas opções:
Crie-o com o tipo certo. Altere o código onde a coleção é
criada pela primeira vez para que tenha o tipo certo.
Lance os elementos no acesso. Se você iterar
imediatamente sobre a coleção, lance cada elemento dentro da
iteração.
Transmita rapidamente usando List.from() . Se você
eventualmente acessar a maioria dos elementos da coleção e
não precisa que o objeto seja apoiado pelo objeto ativo
original, converta-o usando List.from() .
O método cast() retorna uma coleção preguiçosa que verifica o tipo
de elemento em cada operação. Se você realizar apenas algumas
operações em apenas alguns elementos, essa preguiça pode ser
boa. Mas, em muitos casos, a sobrecarga da validação preguiçosa e
do empacotamento supera os benefícios.
Aqui está um exemplo de criação com o tipo certo:
Aqui está lançando cada elemento no acesso:

Aqui está lançando rapidamente com List.from() :


Essas alternativas nem sempre funcionam, é claro, e às vezes cast()
é a resposta certa. Mas considere esse método um pouco arriscado
e indesejável - ele pode ser lento e falhar no tempo de execução se
você não tomar cuidado.
Funções
No Dart, até as funções são objetos. Aqui estão algumas práticas
recomendadas envolvendo funções.

USE uma declaração de função para vincular uma


função a um nome.
Regra de Linter: prefer_function_declarations_over_variables
As linguagens modernas perceberam o quão úteis são as funções e
encerramentos locais aninhados. É comum ter uma função definida
dentro de outra. Em muitos casos, essa função é usada como um
retorno de chamada imediatamente e não precisa de um nome.
Uma expressão de função é ótima para isso.
Mas, se você precisar dar um nome a ela, use uma instrução de
declaração de função em vez de vincular um lambda a uma variável.

NÃO USE um lambda quando um corte pode ser


suficiente.
Regra de Linter: needed_lambdas
Se você se referir a um método em um objeto, mas omitir os
parênteses, o Dart fornecerá um “corte” - um fechamento que usa os
mesmos parâmetros do método e o invoca quando você o chama.
Se você tiver uma função que invoca um método com os mesmos
argumentos que são passados a ela, não precisa envolver
manualmente a chamada em um lambda.
Parâmetros
USE = para separar um parâmetro nomeado de seu
valor padrão.
Regra Linter: prefer_equal_for_default_values
Por motivos de legado, o Dart permite : e = como separador de
valor padrão para parâmetros nomeados. Para consistência com
parâmetros posicionais opcionais, use = .

NÃO USE um valor padrão explícito de null .


Regra de Linter: Avoid_init_to_null
Se você tornar um parâmetro opcional, mas não atribuir a ele um
valor padrão, a linguagem usará implicitamente null como padrão,
portanto, não há necessidade de escrevê-lo.
Variáveis
As práticas recomendadas a seguir descrevem como usar da
melhor forma as variáveis no Dart.

NÃO USE inicializar variáveis explicitamente para


null .
Regra de Linter: Avoid_init_to_null
No Dart, uma variável ou campo que não é explicitamente
inicializado automaticamente é inicializado para null . Isso é
especificado de forma confiável pelo idioma. Não há conceito de
“memória não inicializada” no Dart. Adicionar = null é redundante e
desnecessário.

EVITAR armazenar o que você pode calcular.


Ao projetar uma classe, geralmente você deseja expor várias
visualizações no mesmo estado subjacente. Frequentemente, você
vê um código que calcula todas essas visualizações no construtor e
as armazena:

Este código tem duas coisas erradas. Primeiro, é provável que


esteja desperdiçando memória. A área e a circunferência,
estritamente falando, são esconderijos. Eles são cálculos
armazenados que podemos recalcular a partir de outros dados que
já temos. Eles estão trocando memória aumentada por uso reduzido
da CPU. Sabemos que temos um problema de desempenho que
merece essa compensação?
Pior, o código está errado. O problema com os caches é a
invalidação - como saber quando o cache está desatualizado e
precisa ser recalculado? Aqui, nunca fazemos, embora radius seja
mutável. Você pode atribuir um valor diferente e a área e a
circumference manterão seus valores anteriores, agora incorretos.
Para lidar corretamente com a invalidação de cache, precisamos
fazer o seguinte:
É uma quantidade enorme de código para escrever, manter, depurar
e ler. Em vez disso, sua primeira implementação deve ser:

Este código é mais curto, usa menos memória e é menos sujeito a


erros. Ele armazena a quantidade mínima de dados necessários
para representar o círculo. Não há campos para ficar fora de
sincronia porque há apenas uma única fonte de verdade.
Em alguns casos, você pode precisar armazenar em cache o
resultado de um cálculo lento, mas só faça isso depois de saber que
tem um problema de desempenho, faça isso com cuidado e deixe
um comentário explicando a otimização.
Membros
No Dart, os objetos têm membros que podem ser funções
(métodos) ou dados (variáveis de instância). As práticas
recomendadas a seguir se aplicam aos membros de um objeto.

NÃO USE envolver um campo em um getter e setter


desnecessariamente.
Regra de Linter: needed_getters_setters
Em Java e C#, é comum ocultar todos os campos atrás de getters e
setters (ou propriedades em C#), mesmo que a implementação
apenas encaminhe para o campo. Dessa forma, se você precisar
trabalhar mais nesses membros, poderá fazê-lo sem precisar tocar
nos callsites. Isso ocorre porque chamar um método getter é
diferente de acessar um campo em Java, e acessar uma
propriedade não é binário compatível com o acesso a um campo
bruto em C#.
O Dart não tem essa limitação. Os campos e getters / setters são
completamente indistinguíveis. Você pode expor um campo em uma
classe e depois envolvê-lo em um getter e setter sem ter que tocar
em nenhum código que use esse campo.

PREFIRA usar um campo final para fazer uma


propriedade somente leitura.
Regra de Linter: needed_getters
Se você tiver um campo que o código externo deveria ser capaz de
ver, mas não atribuir, uma solução simples que funciona em muitos
casos, deve simplesmente marcá-lo final .

Obviamente, se você precisar atribuir internamente ao campo fora


do construtor, pode precisar fazer o padrão “campo privado, getter
público”, mas não tente fazer isso até precisar.

CONSIDERE o uso de => para membros simples.


Regra de Linter: prefer_expression_function_bodies
Além de usar => para expressões de função, o Dart também
permite definir membros com ele. Esse estilo é adequado para
membros simples que apenas calculam e retornam um valor.

As pessoas que escrevem código parecem adorar => , mas é muito


fácil abusar dele e acabar com um código difícil de ler. Se sua
declaração tiver mais do que algumas linhas ou contiver expressões
profundamente aninhadas - cascatas e operadores condicionais são
criminosos comuns - faça um favor a si mesmo e a todos os que
lerem seu código e use um bloco de corpo e algumas instruções.
Você também pode usar => em membros que não retornam um
valor. Isso é idiomático quando um setter é pequeno e tem um getter
correspondente que usa => .

NÃO USE, this. exceto para redirecionar para um


construtor nomeado ou para evitar sombreamento.
Regra de Linter: desnecessário_este
JavaScript requer um this. explícito para se referir a membros no
objeto cujo método está sendo executado atualmente, mas Dart -
como C++, Java e C# - não tem essa limitação.
Existem apenas duas vezes que você precisa usar this. . Uma é
quando uma variável local com o mesmo nome obscurece o
membro que você deseja acessar:
A outra maneira de usar this. são ao redirecionar para um
construtor nomeado:
Observe que os parâmetros do construtor nunca sombreiam os
campos nas listas de inicialização do construtor:

Isso parece surpreendente, mas funciona como você deseja.


Felizmente, código como esse é relativamente raro graças à
“formals de inicialização”.

USE inicializar os campos em sua declaração


quando possível.
Se um campo não depende de nenhum parâmetro do construtor, ele
pode e deve ser inicializado em sua declaração. Leva menos código
e garante que você não se esqueça de inicializá-lo se a classe tiver
vários construtores.

Obviamente, se um campo depende dos parâmetros do construtor


ou é inicializado de maneira diferente por diferentes construtores,
então esta diretriz não se aplica.
Construtores
As práticas recomendadas a seguir se aplicam à declaração de
construtores para uma classe.

USE formals de inicialização quando possível.


Regra de Linter: prefer_initializing_formals
Muitos campos são inicializados diretamente de um parâmetro do
construtor, como:

Precisamos digitar x quatro vezes aqui para definir um campo.


Podemos fazer melhor:

A sintaxe this. antes de um parâmetro do construtor é chamada de


“formal de inicialização”. Você nem sempre pode tirar vantagem
disso. Às vezes, você deseja ter um parâmetro nomeado cujo nome
não corresponda ao nome do campo que você está inicializando.
Mas quando você pode usar formals de inicialização, você deve.

NÃO USE parâmetros para inicializar formals.


Regra de Linter: type_init_formals
Se um parâmetro do construtor estiver usando this. para inicializar
um campo, o tipo do parâmetro será considerado o mesmo tipo do
campo.
USE ; em vez de {} para corpos de construtor
vazios.
Regra de Linter: empty_constructor_bodies
No Dart, um construtor com um corpo vazio pode terminar com
apenas um ponto e vírgula. (Na verdade, é necessário para
construtores const.)

NÃO USE new .


Regra de Linter: desnecess_new
O Dart 2 torna a palavra-chave new opcional. Mesmo no Dart 1, seu
significado nunca foi claro porque os construtores de fábrica
significam que uma invocação new pode não retornar realmente um
novo objeto.
A linguagem ainda permite new para tornar a migração menos
penosa, mas considere-a obsoleta e remova-a do seu código.
NÃO USE const redundantemente.
Regra Linter: needed_const
Em contextos em que uma expressão deve ser constante, a
palavra-chave const é implícita, não precisa ser escrita e não
deveria. Esse contexto é qualquer expressão dentro de:
Um literal de coleção const .
Uma chamada de construtor const .
Uma anotação de metadados.
O inicializador para uma declaração de variável const .
Uma expressão switch case - a parte logo após case antes de:, não o
corpo do case .
(Os valores padrão não estão incluídos nesta lista porque as
versões futuras do DART podem suportar valores padrão não
constantes.)
Basicamente, em qualquer lugar onde seria um erro escrever new
e m vez de const , o Dart 2 permite que você omita o const .
Manipulação De Erros
O Dart usa exceções quando ocorre um erro em seu programa. As
práticas recomendadas a seguir se aplicam para capturar e lançar
exceções.

EVITAR capturas sem cláusulas on .


Regra de Linter: Avoid_catches_without_on_clauses
Uma cláusula catch sem o qualificador on captura qualquer coisa
lançada pelo código no bloco try . O tratamento de exceções do
Pokémon provavelmente não é o que você deseja. Seu código lida
corretamente com StackOverflowError ou OutOfMemoryError?
Se você passar incorretamente o argumento errado para um
método naquele bloco try , você deseja que o depurador aponte
para o erro ou prefere que o útil ArgumentError seja engolido?
Você deseja que as instruções assert () dentro desse código
desapareçam efetivamente, já que você está capturando os
AssertionErrors lançados?
A resposta provavelmente é “não”; nesse caso, você deve filtrar os
tipos que captura. Na maioria dos casos, você deve ter uma
cláusula on que o limite aos tipos de falhas de tempo de execução
que você conhece e está tratando corretamente.
Em casos raros, você pode desejar capturar qualquer erro de tempo
de execução. Isso geralmente ocorre em uma estrutura ou código
de baixo nível que tenta impedir que o código de aplicativo arbitrário
cause problemas. Mesmo aqui, geralmente é melhor capturar a
exceção do que todos os tipos. Exceção é a classe base para todos
os erros de tempo de execução e exclui erros que indicam bugs de
programação no código.

NÃO USE descartar erros de capturas sem


cláusulas on.
Se você realmente sente que precisa capturar tudo o que pode ser
lançado de uma região do código, faça algo com o que capturar.
Registre-o, exiba-o para o usuário ou jogue-o novamente, mas não
o descarte silenciosamente.
USE lançar objetos que implementam apenas error
para erros programáticos.
A classe Error é a classe base para erros programáticos. Quando
um objeto desse tipo ou uma de suas sub interfaces como
ArgumentError é lançado, significa que há um bug em seu código.
Quando sua API deseja relatar a um chamador que está sendo
usada incorretamente, lançar um erro envia esse sinal claramente.
Por outro lado, se a exceção for algum tipo de falha de tempo de
execução que não indica um bug no código, lançar um Erro é
enganoso. Em vez disso, lance uma das classes principais de
exceção ou algum outro tipo.

NÃO USE capturar explicitamente os tipos de error


que o implementam.
Regra de Linter: Avoid_catching_errors
Isso decorre do acima. Como um erro indica um bug em seu código,
ele deve desenrolar toda a pilha de chamadas, interromper o
programa e imprimir um rastreamento de pilha para que você possa
localizar e corrigir o bug.
A detecção de erros desses tipos interrompe o processo e máscara
o bug. Em vez de adicionar código de tratamento de erros para lidar
com essa exceção após o fato, volte e corrija o código que está
causando o seu lançamento.

USE rethrow para relançar uma exceção detectada.


Regra de Linter: use_rethrow_when_possible
Se você decidir relançar uma exceção, prefira usar a instrução
rethrow em vez de lançar o mesmo objeto de exceção usando throw .
Rethrow preserva o rastreamento de pilha original da exceção. Throw
por outro lado, redefine o rastreamento de pilha para a última
posição lançada.
Assincronia
O Dart possui vários recursos de linguagem para oferecer suporte à
programação assíncrona. As práticas recomendadas a seguir se
aplicam à codificação assíncrona.

PREFIRA async / await sobre o uso de futuros


brutos.
O código assíncrono é notoriamente difícil de ler e depurar, mesmo
quando se usa uma boa abstração como futuros. A sintaxe async /
await melhora a legibilidade e permite que você use todas as
estruturas de fluxo de controle do Dart em seu código assíncrono.
NÃO USE async quando não tiver nenhum efeito
útil.
É fácil adquirir o hábito de usar async em qualquer função que faça
qualquer coisa relacionada à assincronia. Mas, em alguns casos, é
estranho. Se você puder omitir o async sem alterar o
comportamento da função, faça isso.

Casos em que async é útil incluem:


Você está usando await . (Este é o mais óbvio.)
Você está retornando um erro de forma assíncrona. Async e
então throw é mais curto que return future.error(...) .
Você está retornando um valor e deseja envolvê-lo
implicitamente em um futuro. Async é mais curto do que
future.value(...) .

CONSIDERE o uso de métodos de ordem superior


para transformar um fluxo.
Isso é paralelo à sugestão acima sobre iteráveis. Streams suportam
muitos dos mesmos métodos e tratam coisas como transmitir erros,
fechar etc. corretamente.

EVITAR usar o completer diretamente.


Muitas pessoas novas na programação assíncrona desejam
escrever um código que produza um futuro. Os construtores no
Future não parecem se adequar às suas necessidades, então eles
eventualmente encontram a classe completer e a usam.

Completer é necessário para dois tipos de código de baixo nível:


novos primitivos assíncronos e interface com código assíncrono que
não usa futuros. A maioria dos outros códigos deve usar async /
await ou Future.then(), porque são mais claros e facilitam o
tratamento de erros.

USE um teste para eliminar a ambiguidade de


future<t> de um futureor<t> cujo tipo de
argumento poderia ser object .
Antes de fazer qualquer coisa útil com um FutureOr<T> , você
normalmente precisa fazer uma verificação is para ver se possui um
Future<T> ou um vazio T . Se o argumento de tipo for algum tipo
específico como em FutureOr<int> , não importa qual teste você usa,
is int ou is future<int> . Qualquer um dos dois funciona porque esses
dois tipos são separados.
No entanto, se o tipo de valor for Object ou um parâmetro de tipo
com o qual possivelmente poderia ser instanciado Object , os dois
ramos se sobrepõem. O próprio future<Object> implementa Object ,
então is Object ou is T onde T é algum parâmetro de tipo que pode
ser instanciado com Object retorna true mesmo quando o objeto é
um futuro. Em vez disso, teste explicitamente o caso Future :

No exemplo ruim, se você passar um Future<Object> , ele o tratará


incorretamente como um valor síncrono vazio.
Language: Effective Dart: Design
Aqui estão algumas diretrizes para escrever APIs consistentes e
utilizáveis para bibliotecas.
Nomes
Nomear é uma parte importante da escrita de código legível e
sustentável.
As práticas recomendadas a seguir podem ajudá-lo a atingir esse
objetivo.

USE os termos de forma consistente.


Use o mesmo nome para a mesma coisa em todo o código. Se já
existe um precedente fora de sua API que os usuários
provavelmente conheçam, siga esse precedente.

O objetivo é aproveitar o que o usuário já conhece. Isso inclui o


conhecimento do domínio do problema em si, as convenções das
bibliotecas principais e outras partes de sua própria API. Ao
construir sobre eles, você reduz a quantidade de novos
conhecimentos que eles precisam adquirir antes de se tornarem
produtivos.

EVITAR abreviações.
A menos que a abreviatura seja mais comum do que o termo não
abreviado, não abrevie. Se você abreviar, capitalize corretamente.
PREFIRA colocar o substantivo mais descritivo por
último.
A última palavra deve ser a mais descritiva do que a coisa é. Você
pode prefixá-lo com outras palavras, como adjetivos, para descrever
melhor a coisa.

CONSIDERE fazer o código parecer uma frase.


Na dúvida sobre como nomear, escreva algum código que use sua
API e tente lê-lo como uma frase.
É útil experimentar sua API e ver como ela “lê” quando usada no
código, mas você pode ir longe demais. Não é útil adicionar artigos
e outras classes gramaticais para forçar seus nomes a serem lidos
literalmente como uma frase gramaticalmente correta.

PREFIRA uma frase nominal para uma propriedade


ou variável não booleana.
O foco do leitor está no que a propriedade é. Se o usuário se
preocupa mais com como uma propriedade é determinada,
provavelmente deve ser um método com um nome de frase verbal.
PREFIRA uma frase verbal não imperativa para uma
propriedade ou variável booleana.
Os nomes booleanos são frequentemente usados como condições
no fluxo de controle, então você deseja um nome que leia bem ali.
Comparar:

Bons nomes tendem a começar com um dos poucos tipos de


verbos:
uma forma de “ser”: isEnabled , wasShown , willFire . Esses são, de
longe, os mais comuns.
um verbo auxiliar : hasElements , canClose , shouldConsume ,
mustSave .
um verbo ativo: ignoresInput , wroteFile . Eles são raros porque
geralmente são ambíguos. loggedResult é um nome incorreto
porque pode significar “se um resultado foi registrado ou não”
ou “o resultado que foi registrado”. Da mesma forma,
closingConnection pode ser “se a conexão está fechando” ou “a
conexão que está fechando”. Verbos ativos são permitidos
quando o nome só pode ser lido como um predicado.
O que separa todas essas frases verbais dos nomes dos métodos é
que elas não são imperativas. Um nome booleano nunca deve soar
como um comando para dizer ao objeto para fazer algo, porque
acessar uma propriedade não altera o objeto. (Se a propriedade não
modificar o objeto de uma forma significativa, deve ser um método.)
Exceção: As propriedades de entrada em componentes Angular às
vezes usam verbos imperativos para setters booleanos porque
esses setters são chamados em modelos, não a partir de outro
código Dart.

CONSIDERE omitir o verbo para um parâmetro


booleano nomeado.
Isso refina a regra anterior. Para parâmetros nomeados que são
booleanos, o nome geralmente é igualmente claro sem o verbo e o
código é lido melhor no site da chamada.

PREFIRA um nome “positivo” para uma propriedade


ou variável booleana.
A maioria dos nomes booleanos tem conceitualmente formas
"positivas" e "negativas", em que o primeiro parece ser o conceito
fundamental e o último é sua negação - "aberto" e "fechado",
"ativado" e “desativado” etc. Frequentemente, o último nome
literalmente tem um prefixo que nega o ex-: “visível” e “invisível”,
“ligado” e “desligado”, “zero” e “não-zero”.
Ao escolher qual dos dois casos que representa true - e, portanto,
qual o caso que dá nome ao imóvel - prefira o positivo ou mais
fundamental. Os membros booleanos costumam estar aninhados
em expressões lógicas, incluindo operadores de negação. Se sua
própria propriedade for lida como uma negação, será mais difícil
para o leitor realizar mentalmente a dupla negação e entender o que
o código significa.

Para algumas propriedades, não existe uma forma positiva óbvia. É


um documento que foi descarregado para o disco “salvo” ou
“inalterado”? Um documento que não foi liberado é “não salvo” ou
“alterado”? Em casos ambíguos, incline-se para a escolha que tem
menos probabilidade de ser negada pelos usuários ou tem o nome
mais curto.
Exceção: com algumas propriedades, a forma negativa é o que os
usuários mais precisam usar. Escolher o caso positivo os forçaria a
negar a propriedade em ! todos os lugares. Em vez disso, pode ser
melhor usar o caso negativo para essa propriedade.

PREFIRA uma frase verbal imperativa para uma


função ou método cujo objetivo principal seja um
efeito colateral.
Os membros que podem ser chamados podem retornar um
resultado ao chamador e realizar outro trabalho ou efeitos colaterais.
Em uma linguagem imperativa como o Dart, os membros são
frequentemente chamados principalmente por seus efeitos
colaterais: eles podem alterar o estado interno de um objeto,
produzir alguma saída ou falar com o mundo externo.
Esses tipos de membros devem ser nomeados usando uma frase
verbal imperativa que esclareça o trabalho que o membro realiza.

Dessa forma, uma invocação é lida como um comando para fazer


esse trabalho.

PREFIRA uma frase substantiva ou uma frase verbo


não imperativa para uma função ou método se
devolver um valor é seu propósito principal.
Outros membros que podem ser chamados têm poucos efeitos
colaterais, mas retornam um resultado útil ao chamador. Se o
membro não precisa de parâmetros para fazer isso, geralmente
deve ser um getter. Mas, às vezes, uma “propriedade” lógica precisa
de alguns parâmetros. Por exemplo, elementAt() retorna um dado de
uma coleção, mas precisa de um parâmetro para saber qual dado
retornar.
Isso significa que o membro é sintaticamente um método, mas
conceitualmente é uma propriedade e deve ser nomeado como tal
usando uma frase que descreve o que o membro retorna.

Esta diretriz é deliberadamente mais suave do que a anterior. Às


vezes, um método não tem efeitos colaterais, mas ainda é mais
simples de nomear com uma frase verbal como list.take() ou
string.split() .

CONSIDERE uma frase verbal imperativa para uma


função ou método se quiser chamar a atenção para
o trabalho que ela executa.
Quando um membro produz um resultado sem quaisquer efeitos
colaterais, geralmente deve ser um getter ou um método com um
nome sintagma substantivo descrevendo o resultado que retorna.
No entanto, às vezes o trabalho necessário para produzir esse
resultado é importante. Ele pode estar sujeito a falhas de tempo de
execução ou usar recursos pesados, como rede ou E/S de arquivo.
Em casos como este, em que você deseja que o chamador pense
sobre o trabalho que o membro está fazendo, dê ao membro um
nome de frase verbal que descreva esse trabalho.

Observe, porém, que esta orientação é mais suave do que as duas


anteriores. O trabalho que uma operação executa geralmente é um
detalhe de implementação que não é relevante para o chamador, e
os limites de desempenho e robustez mudam com o tempo. Na
maioria das vezes, nomeie seus membros com base no que eles
fazem pelo chamador, não em como o fazem.

EVITAR iniciar um nome de método com get .


Na maioria dos casos, o método deve ser um getter com get
removido do nome. Por exemplo, em vez de um método
denominado getBreakfastOrder() , defina um getter denominado
breakfastOrder .
Mesmo que o membro precise ser um método porque leva
argumentos ou não é adequado para um getter, você ainda deve
evitar get . Como as diretrizes anteriores afirmam:
Simplesmente elimine get e use um nome de frase nominal,
como breakfastOrder() se o chamador se preocupasse
principalmente com o valor que o método retorna.
Use um nome de expressão verbal, se os cuidados
chamadores sobre o trabalho que está sendo feito, mas
escolher um verbo que descreve mais precisamente o trabalho
que get , como create , download , fetch , calculate , request ,
aggregate etc.
PREFIRA nomear um método to_ _ _() se ele copiar
o estado do objeto para um novo objeto.
Regra Linter: use_to_and_as_if_applicable
Um método de conversão é aquele que retorna um novo objeto
contendo uma cópia de quase todo o estado do receptor, mas
geralmente em alguma forma ou representação diferente. As
bibliotecas centrais têm uma convenção de que esses métodos são
nomeados começando com to seguido pelo tipo de resultado.
Se você definir um método de conversão, é útil seguir essa
convenção.

PREFIRA nomear um método as_ _ _() se ele


retornar uma representação diferente apoiada pelo
objeto original.
Regra Linter: use_to_and_as_if_applicable
Os métodos de conversão são “instantâneos”. O objeto resultante
tem sua própria cópia do estado do objeto original. Existem outros
métodos semelhantes à conversão que retornam visualizações -
eles fornecem um novo objeto, mas esse objeto se refere ao
original. As alterações posteriores no objeto original são refletidas
na visualização.
A principal convenção de biblioteca a ser seguida é as_ _ _() .

EVITAR descrever os parâmetros no nome da


função ou método.
O usuário verá o argumento no site de chamada, portanto,
geralmente não ajuda na legibilidade também se referir a ele no
próprio nome.
No entanto, pode ser útil mencionar um parâmetro para separá-lo
de outros métodos com nomes semelhantes que assumem tipos
diferentes:

USE as convenções mnemônicas existentes ao


nomear parâmetros de tipo.
Os nomes de uma única letra não são exatamente esclarecedores,
mas quase todos os tipos genéricos os usam. Felizmente, eles os
usam principalmente de forma consistente e mnemônica. As
convenções são:
E para o tipo de elemento em uma coleção:

K e V para os tipos de chave e valor em uma coleção


associativa:

R para um tipo usado como o tipo de retorno de uma função


ou métodos de uma classe. Isso não é comum, mas às vezes
aparece em typedefs e em classes que implementam o
padrão de visitante:
Caso contrário, use T , S e U para genéricos que têm um
único parâmetro de tipo e onde o tipo circundante torna seu
significado óbvio. Existem várias letras aqui para permitir o
alinhamento sem sombrear o nome ao redor. Por exemplo:

Aqui, o método genérico then<S>() usa S para evitar obscurecer o T


on Future<T> .
Se nenhum dos casos acima for adequado, outro nome mnemônico
de uma letra ou um nome descritivo é adequado:

Na prática, as convenções existentes cobrem a maioria dos


parâmetros de tipo.
Bibliotecas
Um caractere de sublinhado inicial ( _ ) indica que um membro é
privado de sua biblioteca. Isso não é mera convenção, mas está
embutido na própria linguagem.

PREFEIRA fazer declarações privadas.


Uma declaração pública em uma biblioteca - de nível superior ou
em uma classe - é um sinal de que outras bibliotecas podem e
devem acessar esse membro. Também é um compromisso de a
parte da sua biblioteca apoiar isso e se comportar corretamente
quando isso acontecer.
Se não é o que pretende, acrescente _ e seja feliz. Interfaces
públicas estreitas são mais fáceis de manter e mais fáceis de
aprender para os usuários. Como um bom bônus, o analisador irá
informá-lo sobre declarações privadas, não utilizadas para que você
possa excluir o código morto. Ele não pode fazer isso se o membro
for público, pois não sabe se algum código fora de sua visualização
o está usando.

CONSIDERE declarando várias classes na mesma


biblioteca.
Algumas linguagens, como Java, vinculam a organização de
arquivos à organização de classes - cada arquivo pode definir
apenas uma única classe de nível superior. O Dart não tem essa
limitação. Bibliotecas são entidades distintas separadas de classes.
É perfeitamente normal que uma única biblioteca contenha várias
classes, variáveis de nível superior e funções se todas elas
pertencerem logicamente.
Colocar várias classes juntas em uma biblioteca pode permitir
alguns padrões úteis. Visto que a privacidade no Dart funciona no
nível da biblioteca, não no nível da classe, esta é uma maneira de
definir classes “amigas” como você faria em C++. Cada classe
declarada na mesma biblioteca pode acessar os membros privados
umas das outras, mas o código fora dessa biblioteca não pode.
Obviamente, esta diretriz não significa que você deve colocar todas
as suas classes em uma enorme biblioteca monolítica, apenas que
você tem permissão para colocar mais de uma classe em uma única
biblioteca.
Classes e Mixins
O Dart é uma linguagem “pura” orientada a objetos, em que todos
os objetos são instâncias de classes. Mas o Dart não exige que todo
o código seja definido dentro de uma classe - você pode definir
variáveis de nível superior, constantes e funções como faria em uma
linguagem procedural ou funcional.

EVITAR definir uma classe abstrata de um membro


quando uma função simples for suficiente.
Regra de Linter: one_member_abstracts
Ao contrário do Java, o Dart tem funções de primeira classe,
encerramentos e uma boa sintaxe leve para usá-los. Se tudo que
você precisa é algo como um retorno de chamada, basta usar uma
função. Se você estiver definindo uma classe e ela tiver apenas um
único membro abstrato com um nome sem sentido como call ou
invoke , há uma boa chance de você querer apenas uma função.

EVITAR definir uma classe que contém apenas


membros estáticos.
Regra Linter: Avoid_classes_with_only_static_members
Em Java e C#, cada definição deve estar dentro de uma classe,
então é comum ver “classes” que existem apenas como um lugar
para colocar membros estáticos. Outras classes são usadas como
namespaces - uma maneira de fornecer um prefixo compartilhado a
um grupo de membros para relacioná-los entre si ou evitar uma
colisão de nomes.
O Dart tem funções, variáveis e constantes de nível superior, então
você não precisa de uma classe apenas para definir algo. Se o que
você quer é um namespace, uma biblioteca é uma opção melhor.
Bibliotecas suportam prefixos de importação e combinadores de
mostrar / ocultar. Essas são ferramentas poderosas que permitem
ao consumidor de seu código lidar com colisões de nomes da
maneira que funciona melhor para eles.
Se uma função ou variável não estiver logicamente vinculada a uma
classe, coloque-a no nível superior. Se você estiver preocupado com
colisões de nomes, dê a ele um nome mais preciso ou mova-o para
uma biblioteca separada que pode ser importada com um prefixo.

No Dart idiomático, as classes definem tipos de objetos. Um tipo


que nunca é instanciado é um cheiro de código.
No entanto, esta não é uma regra rígida. Com constantes e tipos
semelhantes a enum, pode ser natural agrupá-los em uma classe.
EVITAR estender uma classe que não se destina a
ser uma subclasse.
Se um construtor for alterado de um construtor generativo para um
construtor de fábrica, qualquer construtor de subclasse chamando
esse construtor será interrompido. Além disso, se uma classe altera
quais de seus próprios métodos ela invoca this , isso pode quebrar
as subclasses que sobrescrevem esses métodos e esperam que
eles sejam chamados em certos pontos.
Ambos significam que uma classe precisa ser deliberada sobre se
deseja ou não permitir a subclasse. Isso pode ser comunicado em
um comentário de documento ou dando à classe um nome óbvio
como IterableBase . Se o autor da classe não fizer isso, é melhor
presumir que você não deve estender a classe. Caso contrário,
alterações posteriores podem quebrar seu código.

USE documentar sua classe suporta extensão.


Este é o corolário da regra acima. Se você quiser permitir
subclasses de sua classe, diga isso. Coloque o sufixo do nome da
classe Base ou mencione-o no comentário do documento da classe.

EVITAR implementar uma classe que não tenha o


objetivo de ser uma interface.
As interfaces implícitas são uma ferramenta poderosa no Dart para
evitar ter que repetir o contrato de uma classe quando pode ser
inferido trivialmente a partir das assinaturas de uma implementação
desse contrato.
Mas implementar a interface de uma classe é um acoplamento
muito forte a essa classe. Isso significa que virtualmente qualquer
alteração na classe cuja interface você está implementando
interromperá sua implementação. Por exemplo, adicionar um novo
membro a uma classe é geralmente uma mudança segura e
ininterrupta. Mas se você está implementando a interface dessa
classe, agora sua classe tem um erro estático porque não tem uma
implementação desse novo método.
Os mantenedores da biblioteca precisam ter a habilidade de evoluir
as classes existentes sem prejudicar os usuários. Se você tratar
cada classe como se expusesse uma interface que os usuários são
livres para implementar, então mudar essas classes se torna muito
difícil. Essa dificuldade, por sua vez, significa que as bibliotecas nas
quais você depende são mais lentas para crescer e se adaptar às
novas necessidades.
Para dar aos autores das classes que você usa mais margem de
manobra, EVITAR implementar interfaces implícitas, exceto para
classes que são claramente destinadas a serem implementadas.
Caso contrário, você pode introduzir um acoplamento que o autor
não pretende, e eles podem quebrar seu código sem perceber.

USE documentar sua classe suporta ser usada como


interface.
Se sua classe pode ser usada como uma interface, mencione isso
no comentário doc da classe.

USE mixin para definir um tipo de mixin.


Regra de Linter: prefer_mixin
O Dart originalmente não tinha uma sintaxe separada para declarar
uma classe destinada a ser misturada a outras classes. Em vez
disso, qualquer classe que atendesse a certas restrições (sem
construtor não padrão, sem superclasse etc.) poderia ser usada
como um mixin. Isso foi confuso porque o autor da classe pode não
ter pretendido que fosse misturado.
O Dart 2.1.0 adicionou uma palavra-chave mixin para declarar
explicitamente um mixin. Tipos criados usando que só podem ser
usados como mixins, e a linguagem também garante que seu mixin
permaneça dentro das restrições. Ao definir um novo tipo que você
pretende usar como mixin, use esta sintaxe.
Você ainda pode encontrar códigos mais antigos usando class para
definir mixins, mas a nova sintaxe é preferível.

EVITAR mixar em um tipo que não seja um mixin.


Regra de Linter: prefer_mixin
Para compatibilidade, o Dart ainda permite que você misture
classes que não são definidas usando mixin . No entanto, isso é
arriscado. Se o autor da classe não pretende que a classe seja
usada como um mixin, ele pode alterar a classe de uma forma que
quebre as restrições do mixin. Por exemplo, se eles adicionarem um
construtor, sua classe será interrompida.
Se a classe não tiver um comentário de documento ou um nome
óbvio como IterableMixin , assume que você não pode misturar a
classe se ela não for declarada usando mixin .
Construtores
Os construtores de Dart são criados declarando uma função com o
mesmo nome da classe e, opcionalmente, um identificador
adicional. Os últimos são chamados de construtores nomeados.

CONSIDERE fazer seu construtor const se a classe


o suportar.
Se você tiver uma classe em que todos os campos são finais e o
construtor não fizer nada além de inicializá-los, você poderá fazer
esse construtor const . Isso permite que os usuários criem instâncias
de sua classe em locais onde as constantes são necessárias -
dentro de outras constantes maiores, casos de troca, valores de
parâmetro padrão etc.
Se você não fizer isso explicitamente const , eles não serão capazes
de fazer isso.
Observe, no entanto, que um const construtor é um compromisso
em sua API pública. Se posteriormente você alterar o construtor
para não- const , isso interromperá os usuários que o estão
chamando em expressões constantes. Se você não quer se
comprometer com isso, não se comprometa const . Na prática, os
construtores const são mais úteis para tipos de classes de registro
de dados simples e imutáveis.
Membros
Um membro pertence a um objeto e pode ser métodos ou variáveis
de instância.

PREFIRA criar campos e variáveis de nível superior


final .
Regra de Linter: prefer_final_fields
O estado que não é mutável - que não muda com o tempo - é mais
fácil para os programadores raciocinarem. Classes e bibliotecas que
minimizam a quantidade de estado mutável com que trabalham
tendem a ser mais fáceis de manter.
Claro, muitas vezes é útil ter dados mutáveis. Mas, se você não
precisar, seu padrão deve ser criar campos e variáveis de nível
superior final quando possível.

USE getters para operações que acessam


conceitualmente as propriedades.
Decidir quando um membro deve ser um getter versus um método é
uma parte desafiadora, sutil, mas importante de um bom design de
API, daí esta diretriz muito longa. Algumas culturas de outras
línguas fogem de getters. Eles só os usam quando a operação é
quase exatamente como um campo - faz uma quantidade minúscula
de cálculo no estado que reside inteiramente no objeto. Qualquer
coisa mais complexa ou pesada do que isso vem () após o nome
para sinalizar "computação acontecendo aqui!" porque um nome
simples após um. significa “campo”.
Dart não é assim. No Dart, todos os nomes pontilhados são
invocações de membros que podem fazer cálculos. Os campos são
especiais - são getters cuja implementação é fornecida pela
linguagem. Em outras palavras, getters não são “campos
particularmente lentos” no Dart; campos são “getters particularmente
rápidos”.
Mesmo assim, escolher um getter em vez de um método envia um
sinal importante para o chamador. O sinal, grosso modo, é que a
operação é “semelhante a um campo”. A operação, pelo menos em
princípio, poderia ser implementada usando um campo, até onde o
chamador sabe. Isso implica:
A operação não aceita nenhum argumento e retorna um
resultado.
O chamador se preocupa principalmente com o resultado.
Se você quiser que o chamador se preocupe com a forma
como a operação produz seu resultado mais do que o
resultado que está sendo produzido, dê à operação um nome
de verbo que descreva o trabalho e transforme-o em um
método .
Isso não significa que a operação deve ser particularmente
rápida para ser um getter. IterableBase.length é O(n) , e está tudo
bem. É bom para um getter fazer cálculos significativos. Mas,
se fizer uma quantidade surpreendente de trabalho, convém
chamar a atenção para isso, tornando-o um método cujo nome
é um verbo que descreve o que ele faz.

A operação não tem efeitos colaterais visíveis ao usuário.


Acessar um campo real não altera o objeto ou qualquer outro
estado no programa. Ele não produz saída, grava arquivos etc.
Um getter também não deve fazer essas coisas .
A parte “visível ao usuário” é importante. É bom para getters
modificar o estado oculto ou produzir efeitos colaterais fora da
banda. Os getters podem calcular e armazenar seus resultados
preguiçosamente, gravar em um cache, registrar coisas etc.
Contanto que o chamador não se importe com o efeito
colateral, provavelmente está tudo bem .

A operação é idempotente. “Idempotente” é uma palavra


estranha que, neste contexto, basicamente significa que
chamar a operação várias vezes produz o mesmo resultado a
cada vez, a menos que algum estado seja explicitamente
modificado entre essas chamadas. (Obviamente, list.length
produz resultados diferentes se você adicionar um elemento à
lista entre as chamadas.)
“Mesmo resultado” aqui não significa que um getter deve
literalmente produzir um objeto idêntico em chamadas
sucessivas. Exigir isso forçaria muitos getters a ter um cache
frágil, o que nega todo o sentido de usar um getter. É comum, e
perfeitamente normal, um getter retornar um novo futuro ou
lista cada vez que você o chamar. O importante é que o futuro
termine com o mesmo valor e a lista contenha os mesmos
elementos .
Em outras palavras, o valor do resultado deve ser o mesmo
nos aspectos com os quais o chamador se preocupa.

O objeto resultante não expõe todo o estado do objeto


original. Um campo expõe apenas um pedaço de um objeto.
Se sua operação retornar um resultado que expõe todo o
estado do objeto original, é provavelmente melhor como um
método to_ _ _() ou as_ _ _() .
Se todos os itens acima descrevem sua operação, deve ser um
getter. Parece que poucos membros sobreviveriam a esse desafio,
mas surpreendentemente muitos sobrevivem. Muitas operações
apenas fazem alguns cálculos em algum estado e a maioria deles
pode e deve ser getters.

USE setters para operações que alteram


propriedades conceitualmente.
Regra Linter: use_setters_to_change_properties
Decidir entre um setter e um método é semelhante a decidir entre
um getter e um método. Em ambos os casos, a operação deve ser
do tipo “campo”.
Para um setter, "semelhante a campo" significa:
A operação leva um único argumento e não produz um valor
de resultado.
A operação altera algum estado do objeto.
A operação é idempotente. Chamar o mesmo setter duas
vezes com o mesmo valor não deve fazer nada na segunda
vez no que diz respeito ao chamador. Internamente, talvez
você tenha alguma invalidação de cache ou registro em
andamento. Isso é bom. Mas, da perspectiva do chamador,
parece que a segunda chamada não faz nada .

NÃO USE um setter sem um getter correspondente.


Regra Linter: Avoid_setters_without_getters
Os usuários pensam em getters e setters como propriedades
visíveis de um objeto. Uma propriedade de “caixa de depósito” que
pode ser gravada, mas não vista, é confusa e confunde sua intuição
sobre como as propriedades funcionam. Por exemplo, um setter
sem um getter significa que você pode usar = para modificá-lo, mas
não += .
Esta diretriz não significa que você deve adicionar um getter apenas
para permitir o setter que deseja adicionar. Os objetos geralmente
não devem expor mais estado do que o necessário. Se você tiver
alguma parte do estado de um objeto que pode ser modificada, mas
não exposta da mesma maneira, use um método.
Exceção: uma classe de componente Angular pode expor
configuradores que são chamados de um modelo para inicializar o
componente. Frequentemente, esses setters não se destinam a ser
chamados a partir do código Dart e não precisam de um getter
correspondente. (Se forem usados a partir do código Dart, devem
ter um getter.)

EVITAR o retorno null dos membros cujo tipo de


retorno é bool , double , int , ou num .
Regra de Linter: Avoid_returning_null
Embora todos os tipos sejam anuláveis no Dart, os usuários
presumem que esses tipos quase nunca contêm null , e os nomes
em minúsculas encorajam uma mentalidade “primitiva do Java”.
Pode ser ocasionalmente útil ter um tipo “primitivo anulável” em sua
API, por exemplo, para indicar a ausência de um valor para alguma
chave em um mapa, mas isso deve ser raro.
Se você tiver um membro desse tipo que pode retornar null ,
documente-o muito claramente, incluindo as condições em que null
será devolvido.

EVITAR retornar this de métodos apenas para


habilitar uma interface fluente.
Regra Linter: Avoid_returning_this
Cascatas de método são a melhor solução para encadear
chamadas de método.
Tipos
Ao escrever um tipo em seu programa, você restringe os tipos de
valores que fluem para diferentes partes de seu código. Os tipos
podem aparecer em dois tipos de lugares: anotações de tipo em
declarações e argumentos de tipo para invocações genéricas.
As anotações de tipo são o que você normalmente pensa quando
pensa em “tipos estáticos”. Você pode digitar anotar uma variável,
parâmetro, campo ou tipo de retorno. No exemplo a seguir, bool e
String são anotações de tipo. Eles desligam a estrutura declarativa
estática do código e não são “executados” em tempo de execução.

Uma invocação genérica é um literal de coleção, uma chamada


para o construtor de uma classe genérica ou uma invocação de um
método genérico. No próximo exemplo, num e int são argumentos
de tipo em invocações genéricas. Mesmo sendo tipos, eles são
entidades de primeira classe que são reificadas e passadas para a
invocação em tempo de execução.

Enfatizamos a parte da “invocação genérica” aqui, porque os


argumentos de tipo também podem aparecer nas anotações de tipo:

Aqui, int está um argumento de tipo, mas aparece dentro de uma


anotação de tipo, não em uma invocação genérica. Você geralmente
não precisa se preocupar com essa distinção, mas em alguns
lugares, temos orientações diferentes para quando um tipo é usado
em uma chamada genérica em oposição a uma anotação de tipo.
Na maioria dos lugares, o Dart permite que você omita uma
anotação de tipo e infere um tipo para você com base no contexto
próximo ou padroniza para o tipo dynamic . O fato de o Dart ter
inferência de tipo e um tipo dynamic leva a alguma confusão sobre o
que significa dizer que o código é “não tipado”. Isso significa que o
código é digitado dinamicamente ou que você não escreveu o tipo?
Para evitar essa confusão, evitamos dizer "não digitado" e, em vez
disso, usamos a seguinte terminologia:
Se o código for de tipo anotado, o tipo foi explicitamente escrito
no código .
Se o código for inferido, nenhuma anotação de tipo foi escrita e
o Dart descobriu o tipo sozinho. A inferência pode falhar, caso
em que as diretrizes não consideram isso inferido. Em alguns
lugares, a falha de inferência é um erro estático. Em outros, o
Dart usa dynamic como o tipo substituto.
Se o código for dinâmico, seu tipo estático será o tipo dynamic
especial. O código pode ser anotado explicitamente dynamic ou
pode ser inferido.
Em outras palavras, se algum código é anotado ou inferido é
ortogonal ao fato de ser dynamic ou a algum outro tipo.
A inferência é uma ferramenta poderosa para poupar você do
esforço de escrever e ler tipos que são óbvios ou desinteressantes.
A omissão de tipos em casos óbvios também chama a atenção do
leitor para tipos explícitos quando esses tipos são importantes, para
coisas como casts.
Os tipos explícitos também são uma parte importante do código
robusto e sustentável. Eles definem a forma estática de uma API.
Eles documentam e reforçam quais tipos de valores podem atingir
diferentes partes do programa.
As diretrizes aqui atingem o melhor equilíbrio que encontramos
entre brevidade e clareza, flexibilidade e segurança. Ao decidir quais
tipos escrever, você precisa responder a duas perguntas:
Quais tipos devo escrever porque acho melhor para eles serem
visíveis no código?
Que tipos devo escrever porque a inferência não pode fornecê-
los para mim?
Essas diretrizes ajudam você a responder à primeira pergunta:
PREFERIR tipo anotando campos públicos e variáveis de nível
superior se o tipo não for óbvio.
CONSIDERE tipo anotando campos privados e variáveis de
nível superior se o tipo não for óbvio.
EVITAR o tipo de anotação de variáveis locais inicializadas.
EVITAR anotar tipos de parâmetros inferidos em expressões
de função.
EVITAR argumentos de tipo redundante em invocações
genéricas.
Eles cobrem o segundo:
USE anotações quando o Dart inferir o tipo errado.
PREFERIR anotar com em dynamic vez de deixar a inferência
falhar.
As diretrizes restantes cobrem outras questões mais específicas
sobre os tipos.

PREFIRA anotar campos públicos e variáveis de


nível superior se o tipo não for óbvio.
Regra Linter: type_annotate_public_apis
As anotações de tipo são documentação importante sobre como
uma biblioteca deve ser usada. Eles formam limites entre regiões de
um programa para isolar a fonte de um erro de tipo. Considerar:

Aqui, não está claro o que id é.


Uma linha?
E o que é destination?
Uma string ou um objeto file ?
Este método é síncrono ou assíncrono?
Isso é mais claro:

Em alguns casos, porém, o tipo é tão óbvio que escrever é inútil:


“Óbvio” não é definido com precisão, mas esses são todos bons
candidatos:
Literais.
Invocações de construtor.
Referências a outras constantes digitadas explicitamente.
Expressões simples em números e strings.
Métodos de fábrica como int.parse() , Future.wait() etc., com os
quais se espera que os leitores estejam familiarizados.
Em caso de dúvida, adicione uma anotação de tipo. Mesmo quando
um tipo é óbvio, você ainda pode querer fazer anotações
explicitamente. Se o tipo inferido depende de valores ou
declarações de outras bibliotecas, você pode querer anotar o tipo
em sua declaração para que uma mudança nessa outra biblioteca
não mude silenciosamente o tipo de sua própria API sem você
perceber.

CONSIDERE tipo anotando campos privados e


variáveis de nível superior se o tipo não for óbvio.
Regra de Linter: prefer_typing_uninitialized_variables
Digite anotações em suas declarações públicas para ajudar os
usuários de seu código. Tipos em membros privados ajudam os
mantenedores. O escopo de uma declaração privada é menor e
aqueles que precisam saber o tipo dessa declaração também têm
maior probabilidade de estar familiarizados com o código ao redor.
Isso torna razoável apoiar-se mais fortemente na inferência e omitir
tipos para declarações privadas, razão pela qual está diretriz é mais
branda do que a anterior.
Se você acha que a expressão do inicializador - seja ela qual for - é
suficientemente clara, você pode omitir a anotação. Mas se você
acha que a anotação ajuda a tornar o código mais claro, adicione
um.

EVITAR o tipo de anotação de variáveis locais


inicializadas.
Regra de Linter: omit_local_variable_types
Variáveis locais, especialmente em código moderno onde as
funções tendem a ser pequenas, têm muito pouco escopo. A
omissão do tipo concentra a atenção do leitor no nome mais
importante da variável e em seu valor inicializado.

Se a variável local não tiver um inicializador, seu tipo não pode ser
inferido. Nesse caso, é uma boa ideia fazer anotações. Caso
contrário, você obtém dynamic e perde os benefícios da verificação
de tipo estático.

EVITAR anotar tipos de parâmetros inferidos em


expressões de função.
Regra de Linter: Avoid_types_on_closure_parameters
As funções anônimas são quase sempre passadas imediatamente
para um método que recebe um retorno de chamada de algum tipo.
(Se a função não for usada imediatamente, geralmente vale a pena
torná-la uma declaração nomeada.) Quando uma expressão de
função é criada em um contexto digitado, o Dart tenta inferir os tipos
de parâmetro da função com base no tipo esperado.
Por exemplo, quando você passa uma expressão de função para
Iterable.map() , o tipo de parâmetro de sua função é inferido com base
no tipo de retorno de chamada que map() espera:

Em casos raros, o contexto circundante não é preciso o suficiente


para fornecer um tipo para um ou mais dos parâmetros da função.
Nesses casos, pode ser necessário fazer anotações.

EVITAR argumentos de tipo redundante em


invocações genéricas.
Um argumento de tipo é redundante se a inferência preencher o
mesmo tipo. Se a invocação for o inicializador para uma variável
anotada por tipo ou um argumento para uma função, a inferência
geralmente preenche o tipo para você:

Aqui, a anotação de tipo na variável é usada para inferir o


argumento de tipo da chamada do construtor no inicializador.
Em outros contextos, não há informações suficientes para inferir o
tipo e, em seguida, você deve escrever o argumento de tipo:
Aqui, como a variável não tem anotação de tipo, não há contexto
suficiente para determinar que tipo de Set criar, portanto, o
argumento de tipo deve ser fornecido explicitamente.

USE anotações quando o Dart inferir o tipo errado.


Às vezes, Dart infere um tipo, mas não o tipo que você deseja. Por
exemplo, você pode querer que o tipo de uma variável seja um
supertipo do tipo do inicializador para que possa posteriormente
atribuir algum outro tipo de irmão à variável:

Aqui, se scores contém duplos, como [1.2] , então a atribuição a


highest falhará, pois, seu tipo inferido é int , não num . Nesses casos,
anotações explícitas fazem sentido.

PREFIRA anotar com em dynamic vez de deixar a


inferência falhar.
O Dart permite que você omita anotações de tipo em muitos lugares
e tentará inferir um tipo para você. Em alguns casos, se a inferência
falhar, ela o fornecerá silenciosamente dynamic . Se dynamic for o
tipo que você deseja, esta é tecnicamente a maneira mais concisa
de obtê-lo.
No entanto, não é a maneira mais clara. Um leitor casual do seu
código que vê uma anotação faltando não tem como saber se você
pretendia que fosse dynamic , esperava que a inferência
preenchesse algum outro tipo ou simplesmente se esqueceu de
escrever a anotação.
Quando dynamic é o tipo que você deseja, escrevê-lo explicitamente
torna sua intenção clara.

PREFIRA assinaturas em anotações de tipo de


função.
O identificador Function por si só, sem qualquer tipo de retorno ou
assinatura de parâmetro, refere-se ao tipo de Função especial. Esse
tipo é apenas um pouco mais útil do que usar dynamic . Se você for
fazer anotações, prefira um tipo de função completo que inclua os
parâmetros e o tipo de retorno da função.
Exceção: às vezes, você deseja um tipo que represente a união de
vários tipos de função diferentes. Por exemplo, você pode aceitar
uma função que leva um parâmetro ou uma função que leva dois.
Como não temos tipos de união, não há como digitar com precisão e
você normalmente teria que usar dynamic . Function é pelo menos um
pouco mais útil do que isso:
NÃO USE especificar um tipo de retorno para um
setter.
Regra de Linter: Avoid_return_types_on_setters
Os setters sempre retornam void em Dart. Escrever a palavra é
inútil.

NÃO USE a sintaxe legada do typedef.


Regra de Linter: prefer_generic_function_type_aliases
O Dart tem duas notações para definir um typedef nomeado para
um tipo de função. A sintaxe original é semelhante a:

Essa sintaxe tem alguns problemas:


Não há como atribuir um nome a um tipo de função genérico.
No exemplo acima, o typedef em si é genérico. Se você fizer
referência Comparison em seu código, sem um argumento de
tipo, você obterá implicitamente o tipo de função int
Function(dynamic, dynamic) , não int Function<T>(T, T) . Na prática,
isso não ocorre com frequência, mas é importante em alguns
casos extremos.
Um único identificador em um parâmetro é interpretado como o
nome do parâmetro, não seu tipo. Dado :

A maioria dos usuários espera que este seja um tipo de


função que recebe a num e retorna bool . Na verdade, é um tipo
de função que pega qualquer objeto ( dynamic ) e retorna bool .
O nome do parâmetro (que não é usado para nada exceto a
documentação no typedef) é “num”. Esta tem sido uma fonte
de erros de longa data no Dart.
A nova sintaxe é semelhante a esta:

Se quiser incluir o nome de um parâmetro, você também pode fazer


isso:

A nova sintaxe pode expressar qualquer coisa que a antiga sintaxe


pudesse expressar e muito mais, e não possui a característica
incorreta, propensa a erros, em que um único identificador é tratado
como o nome do parâmetro em vez de seu tipo. A mesma sintaxe
de tipo de função após = no typedef também é permitida em
qualquer lugar em que uma anotação de tipo possa aparecer,
dando-nos uma única maneira consistente de escrever tipos de
função em qualquer lugar em um programa.
A sintaxe de typedef antiga ainda é compatível para evitar a quebra
do código existente, mas está obsoleta.

PREFIRA tipos de função inline sobre typedefs.


Regra de Linter: Avoid_private_typedef_functions
No Dart 1, se você quisesse usar um tipo de função para um
campo, variável ou argumento de tipo genérico, primeiro tinha que
definir um typedef para ele. O Dart 2 suporta uma sintaxe de tipo de
função que pode ser usada em qualquer lugar em que uma
anotação de tipo seja permitida:
Ainda pode valer a pena definir um typedef se o tipo de função for
particularmente longo ou usado com frequência. Mas, na maioria
dos casos, os usuários desejam ver qual é o tipo de função
exatamente onde é usado, e a sintaxe do tipo de função fornece
essa clareza.

CONSIDERE usando sintaxe de tipo de função para


parâmetros.
Regra Linter: use_function_type_syntax_for_parameters
O Dart tem uma sintaxe especial ao definir um parâmetro cujo tipo é
uma função. Mais ou menos como em C, você envolve o nome do
parâmetro com o tipo de retorno da função e a assinatura do
parâmetro:

Antes do Dart 2 adicionar a sintaxe de tipo de função, essa era a


única maneira de dar a um parâmetro um tipo de função sem definir
um typedef. Agora que o Dart tem uma notação geral para tipos de
função, você também pode usá-lo para parâmetros digitados por
função:
A nova sintaxe é um pouco mais detalhada, mas é consistente com
outros locais onde você deve usar a nova sintaxe.

EVITAR o uso, a dynamic menos que queira


desativar a verificação estática.
Algumas operações funcionam com qualquer objeto possível. Por
exemplo, um método log() pode pegar qualquer objeto e chama
toString () nele. Dois tipos no Dart permitem todos os valores: Object e
dynamic . No entanto, eles transmitem coisas diferentes. Se você
simplesmente deseja declarar que permite todos os objetos, use
Object , como faria em Java ou C#.
O tipo dynamic não apenas aceita todos os objetos, mas também
permite todas as operações. Qualquer acesso de membro em um
valor do tipo dynamic é permitido em tempo de compilação, mas
pode falhar e lançar uma exceção em tempo de execução. Se você
deseja exatamente aquele despacho dinâmico arriscado, mas
flexível, dynamic é o tipo certo a ser usado.
Caso contrário, prefira usar Object . Confie em verificações is e
promoção de tipo para garantir que o tipo de tempo de execução do
valor oferece suporte ao membro que você deseja acessar antes de
acessá-lo.

A principal exceção a esta regra é ao trabalhar com APIs existentes


que usam dynamic , especialmente dentro de um tipo genérico. Por
exemplo, objetos JSON têm tipo Map<String, dynamic> e seu código
precisará aceitar esse mesmo tipo. Mesmo assim, ao usar um valor
de uma dessas APIs, geralmente é uma boa ideia lançá-lo para um
tipo mais preciso antes de acessar os membros.
USE Future<void> como o tipo de retorno de
membros assíncronos que não produzem valores.
Quando você tem uma função síncrona que não retorna um valor,
você a usa void como o tipo de retorno. O equivalente assíncrono
para um método que não produz um valor, mas que o chamador
pode precisar aguardar, Future<void> .
Você pode ver o código que usa future ou, Future<Null> em vez disso,
porque as versões anteriores do Dart não permitiam void como um
argumento de tipo. Agora que sim, você deve usá-lo. Fazer isso
corresponde mais diretamente a como você digitaria uma função
síncrona semelhante e oferece uma melhor verificação de erros
para chamadores e no corpo da função.
Para funções assíncronas que não retornam um valor útil e onde
nenhum chamador precisa aguardar o trabalho assíncrono ou lidar
com uma falha assíncrona, use um tipo de retorno de void .

EVITAR usar FutureOr<T> como tipo de retorno.


Se um método aceita um FutureOr<int> , é generoso no que aceita.
Os usuários podem chamar o método com qualquer um int ou
future<int> , então eles não precisam de envolver um int em Future
que você está indo para desembrulhar qualquer maneira.
Se você retornar um FutureOr<int> , os usuários precisarão verificar
se obtêm um int ou um Future<int> antes de fazerem algo útil. (Ou
eles vão apenas await o valor, efetivamente sempre tratando-o como
um Future .) Basta retornar a Future<int> , é mais limpo. É mais fácil
para os usuários entenderem que uma função é sempre assíncrona
ou sempre síncrona, mas uma função que pode ser é difícil de usar
corretamente.
A formulação mais precisa desta diretriz é usar apenas FutureOr<T>
em posições contravariantes. Os parâmetros são contravariantes e
os tipos de retorno são covariantes. Em tipos de função aninhados,
isso é invertido - se você tiver um parâmetro cujo tipo é uma função,
o tipo de retorno do retorno de chamada agora está na posição
contravariante e os parâmetros do retorno de chamada são
covariantes. Isso significa que não há problema em um tipo de
retorno de chamada retornar FutureOr<T> :
Parâmetros
No Dart, os parâmetros opcionais podem ser posicionais ou
nomeados, mas não ambos.

EVITAR parâmetros booleanos posicionais.


Regra Linter: Avoid_positional_boolean_parameters
Ao contrário de outros tipos, os booleanos geralmente são usados
na forma literal. Coisas como os números são geralmente envoltos
em constantes nomeadas, mas normalmente temos apenas passar
ao redor true e false diretamente. Isso pode tornar os callites
ilegíveis se não estiver claro o que o booleano representa:

Em vez disso, considere o uso de argumentos nomeados,


construtores nomeados ou constantes nomeados para esclarecer o
que a chamada está fazendo.

Observe que isso não se aplica a setters, onde o nome deixa claro o
que o valor representa:

EVITAR parâmetros posicionais opcionais se o


usuário desejar omitir parâmetros anteriores.
Os parâmetros posicionais opcionais devem ter uma progressão
lógica de forma que os parâmetros anteriores sejam passados com
mais frequência do que os posteriores. Os usuários quase nunca
deveriam precisar passar explicitamente por um “buraco” para omitir
um argumento posicional anterior para passar um depois. É melhor
você usar argumentos nomeados para isso.

EVITAR parâmetros obrigatórios que aceitam um


valor especial “sem argumento”.
Se o usuário está omitindo logicamente um parâmetro, prefira deixá-
lo realmente omiti-lo tornando o parâmetro opcional em vez de
forçá-lo a passar null , uma string vazia ou algum outro valor
especial que significa “não passou”.
Omitir o parâmetro é mais conciso e ajuda a evitar bugs em que um
valor sentinela como null é passado acidentalmente quando o
usuário pensa que está fornecendo um valor real.
USE parâmetros de início inclusivos e de término
exclusivos para aceitar um intervalo.
Se você estiver definindo um método ou função que permite ao
usuário selecionar um intervalo de elementos ou itens de alguma
sequência indexada por inteiro, pegue um índice inicial, que se
refere ao primeiro item e um índice final (provavelmente opcional)
que é um maior que o índice do último item.
Isso é consistente com as bibliotecas principais que fazem a mesma
coisa.

É particularmente importante ser consistente aqui porque esses


parâmetros geralmente não têm nomes. Se sua API tiver um
comprimento em vez de um ponto final, a diferença não será visível
no callite.
Igualdade
Implementar o comportamento de igualdade personalizado para
uma classe pode ser complicado. Os usuários têm uma profunda
intuição sobre como funciona a igualdade de que seus objetos
precisam para corresponder, e tipos de coleção como tabelas hash
têm contratos sutis que eles esperam que os elementos sigam.
USE sobrepor hashcode se você substituir == .
Regra de Linter: hash_and_equals
A implementação do código hash padrão fornece um hash de
identidade - dois objetos geralmente só têm o mesmo código hash
se forem exatamente o mesmo objeto. Da mesma forma, o
comportamento padrão para == é identidade.
Se você está substituindo == , isso implica que você pode ter objetos
diferentes que são considerados “iguais” por sua classe. Quaisquer
dois objetos iguais devem ter o mesmo código hash. Caso
contrário, os mapas e outras coleções baseadas em hash não
reconhecerão que os dois objetos são equivalentes.

USE o seu == operador obedecer às regras


matemáticas de igualdade.
Uma relação de equivalência deve ser:
Reflexivo: a == a deve sempre retornar true .
Simétrico: a == b deve retornar o mesmo que b == a .
Transitivo: se a == b e b == cambos retornarem true , a == c
também deveriam.
Os usuários e o código que usa == esperam que todas essas leis
sejam seguidas. Se sua classe não consegue obedecer a essas
regras, então esse == não é o nome certo para a operação que
você está tentando expressar.

EVITAR definir igualdade personalizada para classes


mutáveis.
Regra Linter: Avoid_equals_and_hash_code_on_mutable_classes
Quando você define == , você também tem que definir hashCode .
Ambos devem levar em consideração os campos do objeto. Se
esses campos mudarem, isso significa que o código hash do objeto
pode mudar.
A maioria das coleções baseadas em hash não prevê isso - eles
assumem que o código hash de um objeto será o mesmo para
sempre e pode se comportar de maneira imprevisível se isso não for
verdade.
NÃO USE verificar null em == operadores
personalizados.
Regra Linter: Avoid_null_checks_in_equality_operators
O idioma especifica que essa verificação é feita automaticamente e seu
== método é chamado apenas se o lado direito não for null .
Language: Métodos de Extensão
Os métodos de extensão, introduzidos no Dart 2.7, são uma forma de
adicionar funcionalidade às bibliotecas existentes. Você pode usar
métodos de extensão mesmo sem saber. Por exemplo, quando você usa o
autocompletar de código em um IDE, ele sugere métodos de extensão
junto com métodos regulares.
Visão Global
Quando você está usando a API de outra pessoa ou quando implementa
uma biblioteca amplamente usada, geralmente é impraticável ou
impossível alterar a API. Mas você ainda pode querer adicionar alguma
funcionalidade.
Por exemplo, considere o seguinte código que analisa uma String em um
inteiro:

Pode ser bom - mais curto e fácil de usar com ferramentas - ter essa
funcionalidade String ativada:

Para habilitar esse código, você pode importar uma biblioteca que contém
uma extensão da classe String :

As extensões podem definir não apenas métodos, mas também outros


membros, como getter, setters e operadores. Além disso, as extensões
têm nomes, o que pode ser útil se surgir um conflito de API. Veja como
você pode implementar o método de extensão parseInt() , usando uma
extensão (nomeada NumberParsing ) que opera em strings :

A próxima seção descreve como usar métodos de extensão. Depois


disso, estão as seções sobre a implementação de métodos de extensão.
Usando Métodos De Extensão
Como todo código Dart, os métodos de extensão estão em bibliotecas.
Você já viu como usar um método de extensão - basta importar a
biblioteca em que ele está e usá-lo como um método comum:

Isso é tudo que você geralmente precisa saber para usar métodos de
extensão. Ao escrever seu código, você também pode precisar saber
como os métodos de extensão dependem de tipos estáticos (em oposição
a dynamic ) e como resolver conflitos de API.

Tipos estáticos e dinâmicos


Você não pode invocar métodos de extensão em variáveis do tipo
dynamic . Por exemplo, o código a seguir resulta em uma exceção de
tempo de execução:

Os métodos de extensão fazer o trabalho com a inferência de tipos de


Dart. O código a seguir é adequado porque a variável v foi inferida como
tendo um tipo String :

O
motivo de isso não funcionar no dynamic é que os métodos de extensão
são resolvidos em relação ao tipo estático do receptor. Como os métodos
de extensão são resolvidos estaticamente, eles são tão rápidos quanto
chamar uma função estática.

Conflitos de api
Se um membro de extensão entrar em conflito com uma interface ou com
outro membro de extensão, você terá algumas opções.
Uma opção é alterar a forma como você importa a extensão conflitante,
usando show ou hide para limitar a API exposta:

Outra opção é aplicar a extensão explicitamente, o que resulta em um


código que parece ser uma classe wrapper:

Se as duas extensões tiverem o mesmo nome, talvez seja necessário


importar usando um prefixo:
Como mostra o exemplo, você pode invocar métodos de extensão
implicitamente, mesmo se importar usando um prefixo. A única vez em
que você precisa usar o prefixo é para evitar um conflito de nome ao
invocar uma extensão explicitamente.
Implementando Métodos De Extensão
Use a seguinte sintaxe para criar uma extensão:

Por exemplo, veja como você pode implementar uma extensão na classe
String :

Para criar uma extensão local que seja visível apenas na biblioteca onde
está declarada, omita o nome da extensão ou dê a ela um nome que
comece com um sublinhado ( _ ).
Os membros da extensão podem ser métodos, getters, setters,
operadores. As extensões também podem ter campos estáticos e
métodos auxiliares estáticos.
Implementando Extensões Genéricas
As extensões podem ter parâmetros de tipo genérico. Por exemplo, aqui
está um código que estende o tipo integrado List<T> com um getter, um
operador e um método:

O
tipo T é vinculado com base no tipo estático da lista em que os métodos
são chamados.
Language: Null Safety: Segurança Nula do
Som
Segurança nula de som - atualmente em beta - está chegando à
linguagem Dart!
Quando você opta por segurança nula, os tipos em seu código não
podem ser nulos por padrão, o que significa que os valores não podem
ser nulos a menos que você diga que podem ser. Com segurança nula,
seus erros de desreferência nula de tempo de execução se transformam
em erros de análise de tempo de edição.
Você pode tentar segurança nula em seu ambiente de desenvolvimento
normal, configurando seu projeto para usar um SDK de visualização de
tecnologia. Ou você pode praticar o uso de segurança nula no aplicativo
da web DartPad com segurança nula, mostrado na captura de tela a
seguir.
Princípios De Segurança Nulos
O suporte de segurança nula do Dart é baseado nos seguintes três
princípios básicos de design:
Não anulável por padrão. A menos que você diga explicitamente
ao Dart que uma variável pode ser nula, ela é considerada não
anulável. Esse padrão foi escolhido depois que a pesquisa descobriu
que não nulo era de longe a escolha mais comum em APIs.
Incrementalmente adotável. Você escolhe o que migrar para a
segurança nula e quando. Você pode migrar de forma incremental,
combinando código seguro para nulo e código seguro não nulo no
mesmo projeto. Fornecemos ferramentas para ajudá-lo com a
migração.
Totalmente correto. A segurança nula do Dart é boa, o que permite
otimizações do compilador. Se o sistema de tipos determina que
algo não é nulo, então esse algo nunca pode ser nulo. Depois de
migrar todo o seu projeto e suas dependências para segurança nula,
você obtém todos os benefícios da integridade - não apenas menos
bugs, mas binários menores e execução mais rápida.
Um Tour Pelo Recurso De Segurança Nula
Novos operadores e palavras-chave relacionadas com a segurança nula
incluem ? , ! e late . Se você usou Kotlin, TypeScript ou C#, a sintaxe para
segurança nula pode parecer familiar. Isso é intencional: a linguagem Dart
pretende não surpreender.

Criação de variáveis
Ao criar uma variável, você pode usar ? e late para informar o Dart da
nulidade da variável.
Aqui estão alguns exemplos de declaração de variáveis não anuláveis
(assumindo que você optou pela segurança nula):

Se a variável pode ter o valor null , adicione ? à sua declaração de tipo:

Se você sabe que uma variável não anulável será inicializada com um
valor não nulo antes de ser usada, mas o analisador Dart não concorda,
insira late antes do tipo da variável:

A palavra-chave late tem dois efeitos:


O analisador não exige que você inicialize imediatamente uma
variável late com um valor não nulo.
O tempo de execução inicializa lentamente a variável late . Por
exemplo, se uma variável de instância não anulável deve ser
calculada, adicionar o modificador late atrasa o cálculo até o
primeiro uso da variável de instância.

Usando variáveis e expressões


Com segurança nula, o analisador Dart gera erros quando encontra um valor anulável
onde um valor não nulo é necessário. Isso não é tão ruim quanto parece: o analisador
pode frequentemente reconhecer quando uma variável ou expressão dentro de uma
função tem um tipo anulável, mas não pode ter um valor nulo.

Ao usar uma variável ou expressão anulável, certifique-se de manipular valores nulos.


Por exemplo, você pode usar uma instrução if , o operador ?? ou o operador ?. para
lidar com possíveis valores nulos.
Aqui está um exemplo de uso do operador ?? para evitar definir uma variável não
anulável como nula:

Este é um código semelhante, mas com uma instrução if que verifica se há nulo:

Se você tiver certeza de que uma expressão com um tipo anulável não é
nula, você pode adicionar ! para fazer o Dart tratá-la como não anulável:

Se você precisar alterar o tipo de uma variável anulável - além do que o


operador ! pode fazer - você pode usar o operador typecast (as) . O
exemplo a seguir usa as para converter um num ? em int :
Depois de optar pela segurança nula, você não pode usar o operador de
acesso de membro (.) se o operando puder ser nulo. Em vez disso, você
pode usar a versão com reconhecimento de nulo desse operador ( ?. ):

Compreender os tipos de lista, conjunto e mapa


Listas, conjuntos e mapas são tipos de coleção comumente usados em
programas Dart, então você precisa saber como eles interagem com
segurança nula. Aqui estão alguns exemplos de como o código Dart usa
esses tipos de coleção:
Widgets de layout oscilantes, como Column muitas vezes têm uma
propriedade children que é List de objetos Widget .
O exemplo Veggie Seasons usa um Set de VeggieCategory para
armazenar as preferências alimentares do usuário.
O exemplo GitHub Dataviz tem um método fromJson() que cria um
objeto a partir de dados JSON fornecidos em a Map<String,
dynamic> .
Listar e definir tipos
Ao declarar o tipo de uma lista ou conjunto, pense no que pode ser nulo.
A tabela a seguir mostra as possibilidades de uma lista de strings se você
optar pela segurança nula.
A lista pode Um item (string) pode
Tipo ser nula? ser nulo? Descrição
List<String> Não Não Uma lista não nula que contém
strings não nulas
List<String>? sim Não Uma lista que pode ser nula e que
contém strings não nulas
List<String?> Não sim Uma lista não nula que contém
strings que podem ser nulas
List<String?>? sim sim Uma lista que pode ser nula e que
contém strings que podem ser nulas

Quando um literal cria uma lista ou conjunto, em vez de um tipo como na


tabela acima, você normalmente vê uma anotação de tipo no literal. Por
exemplo, aqui está o código que você pode usar para criar uma variável
( nameList ) do tipo List<String?> e uma variável ( nameSet ) do tipo
Set<String?> :
Tipos de mapa
Os tipos de mapa se comportam principalmente como você espera, com
uma exceção: o valor retornado de uma pesquisa pode ser nulo. Nulo
é o valor de uma chave que não está presente no mapa.
Como exemplo, observe o código a seguir. Quais você acha que são o
valor e o tipo uhOh ?

A resposta é que uhOh é nula e tem tipo int? .


Como listas e conjuntos, os mapas podem ter vários tipos:
O mapa pode Um item (int) pode
Tipo ser nulo? ser nulo?
Map<String, int> Não Não*
Map<String, int>? sim Não*
Map<String, int?> Não sim
Map<String, int?>? sim sim

* Mesmo quando todos os valores int no mapa não são nulos, quando
você usa uma chave inválida para fazer uma pesquisa no mapa, o valor
retornado é nulo.
Como as pesquisas de mapa podem retornar nulo, você não pode atribuí-
las a variáveis não anuláveis:

Uma solução alternativa é alterar o tipo da variável para ser anulável:

Outra maneira de corrigir o problema - se você tiver certeza de que a


pesquisa foi bem-sucedida - é adicionar ! :
Uma abordagem mais segura é usar o valor de pesquisa apenas se não
for nulo. Você pode testar seu valor usando uma instrução if ou o
operador ?? . Aqui está um exemplo de como usar o valor 0 se a
pesquisa retornar um valor nulo:
Habilitando Segurança Nula
A segurança nula é atualmente um recurso beta. Recomendamos exigir e
usar o lançamento de canal beta mais recente do Dart ou Flutter SDK.
Para encontrar os lançamentos mais recentes, consulte a seção do canal
beta do arquivo do Dart SDK ou a seção do canal beta do arquivo do
Flutter SDK.
Defina as restrições do SDK para exigir uma versão de idioma que tenha
suporte de segurança nulo. Por exemplo, seu pubspec.yaml arquivo pode
ter as seguintes restrições:
Language: Null Safety: Migrando para
segurança nula
Esta página descreve como e quando migrar seu código para segurança
nula. Aqui estão as etapas básicas para migrar cada pacote que você
possui:
1. Aguarde a migração dos pacotes dos quais você depende.
2. Migre o código do seu pacote, de preferência usando a ferramenta
de migração interativa.
3. Analise estaticamente o código do seu pacote.
4. Teste para ter certeza de que suas alterações funcionam.
5. Se o pacote já estiver no pub.dev, publique a versão null-safe como
uma versão de pré - lançamento.
Migrar um aplicativo é tecnicamente o mesmo que migrar um pacote.
Antes de migrar um aplicativo, considere esperar até que a segurança
nula esteja em uma versão estável e todas as suas dependências estejam
prontas.
Para uma visão informal da experiência de usar a ferramenta de
migração, assista a este vídeo:
Espere Para Migrar
É altamente recomendável migrar o código em ordem, com as folhas do
gráfico de dependência sendo migradas primeiro. Por exemplo, se o
pacote C depende do pacote B, que depende do pacote A, então A deve
ser migrado para segurança nula primeiro, depois B e depois C.

Embora você possa migrar antes que suas dependências suportem


segurança nula, pode ser necessário alterar seu código quando suas
dependências migrarem. Por exemplo, se você previr que uma função
receberá um parâmetro anulável, mas o pacote o migra para não ser
anulável, passar um argumento anulável se torna um erro de compilação.
Você pode - e deve - migrar seu pacote antes que os pacotes que
dependem dele sejam migrados. Seu pacote null-safe pode ser usado
por pacotes e aplicativos que ainda não usam null safety. Por exemplo, as
bibliotecas centrais Dart e Flutter são null safe e ainda podem ser usadas
por aplicativos que não migraram para null safety.
Esta seção explica como verificar e atualizar as dependências do seu
pacote, com a ajuda do dart pub outdated comando no modo de segurança
nula. As instruções presumem que seu código está sob controle de
origem, para que você possa desfazer facilmente quaisquer alterações.

Mudar para uma versão beta 2.12


Mude para a versão beta mais recente do Dart SDK ou do Flutter SDK.
A forma de obter a versão beta mais recente depende se você usa o SDK
do Flutter:
Se você usar o Flutter SDK, mude para o canal beta:

Caso contrário, baixe uma versão beta do arquivo do SDK do Dart.


Verifique o status de dependência
Obtenha o estado de migração das dependências do seu pacote, usando
o seguinte comando:

Se a saída disser que todos os pacotes suportam segurança nula, você


pode começar a migrar. Caso contrário, use a coluna Resolvable para
localizar releases nulos seguros, se houver.
Aqui está um exemplo da saída de um pacote simples. A versão com
marca de verificação verde para cada pacote oferece suporte à segurança
nula:

A saída mostra que todas as dependências do pacote têm pré-


lançamentos resolvíveis que suportam segurança nula.
Se alguma das dependências do seu pacote ainda não oferece suporte à
segurança nula, recomendamos que você entre em contato com o
proprietário do pacote. Você pode encontrar os detalhes de contato na
página do pacote em pub.dev.

Atualizar dependências
Antes de migrar o código do seu pacote, atualize suas dependências para
versões nulas-seguras:
1. Atualize pubspec.yaml para usar versões nulas-seguras (conforme
listado na coluna Resolvable) de suas dependências. Omita .x
sufixos para tornar a solução de versão mais flexível e não atualize a
restrição mínima do SDK. Por exemplo, o pubspec.yaml arquivo
pode ter a seguinte aparência:
2. Execute dart pub upgrade .
Migrar
A maioria das mudanças que seu código precisa para ser null safe são
facilmente previsíveis. Por exemplo, se uma variável pode ser null , seu
tipo precisa de um sufixo . Um parâmetro nomeado que não deve ser
anulável precisa ser marcado como obrigatório.
Você tem duas opções para migrar:
Use a ferramenta de migração , que pode fazer a maioria das mudanças
facilmente previsíveis para você.
Migre seu código manualmente.

Usando a ferramenta de migração


A ferramenta de migração pega um pacote de código Dart nulo-inseguro e
o converte em segurança nula. Você pode orientar a conversão da
ferramenta adicionando marcadores de dica ao seu código Dart.
Antes de iniciar a ferramenta, certifique-se de que você está pronto:
Use a versão beta 2.12 mais recente do Dart SDK.
Use dart pub outdated --mode=null-safety para certificar-se de que todas
as dependências são nulas seguras e atualizadas.
Inicie a ferramenta de migração executando o dart migrate comando no
diretório que contém o pubspec.yaml arquivo do pacote:

Se seu pacote estiver pronto para migrar, a ferramenta produzirá uma


linha como a seguinte:

Visite esse URL em um navegador Chrome para ver uma IU interativa


onde você pode guiar o processo de migração:
Para cada variável e tipo de anotação, você pode ver que nulidade a
ferramenta infere. Por exemplo, na captura de tela anterior, a ferramenta
infere que a ints lista (anteriormente uma lista de int ) na linha 1 é anulável
e, portanto, deve ser uma lista de int? .
Compreender os resultados da migração
Para ver os motivos de cada alteração (ou não alteração), clique em seu
número de linha no painel Edições propostas. Os motivos aparecem no
painel editar detalhes.
Por exemplo, considere o seguinte código, antes da segurança nula:

A migração padrão quando este código está fora de uma função (é


diferente dentro de uma função) é compatível com versões anteriores,
mas não ideal:

Ao clicar no link da linha 3, você pode ver os motivos da ferramenta de


migração para adicionar o !. Porque você sabe que zero não pode ser
nulo, você pode melhorar o resultado da migração.
Melhorar os resultados da migração
Quando a análise infere a nulidade incorreta, você pode substituir suas
edições propostas inserindo marcadores de dica temporários:
No painel editar detalhes da ferramenta de migração, você pode
inserir marcadores de dica usando os botões adicionar /*?*/ dica e
adicionar /*!*/ dica.
Esses botões adicionam comentários ao seu arquivo imediatamente
e não há desfazer. Se você não quiser uma dica de que a
ferramenta foi inserida, você pode usar seu editor de código usual
para removê-la.
Você pode usar um editor para adicionar marcadores de dica,
mesmo enquanto a ferramenta ainda está em execução. Como seu
código ainda não optou pela segurança nula, você não pode usar
novos recursos de segurança nula. Você pode, no entanto, fazer
alterações como refatoração que não dependem de recursos de
segurança nula.
Quando terminar de editar seu código, clique em Executar novamente a
partir das fontes para selecionar suas alterações.
A tabela a seguir mostra os marcadores de dica que você pode usar para
alterar as edições propostas da ferramenta de migração.

Uma única dica pode ter efeito cascata em outras partes do código. No
exemplo anterior, adicionar manualmente um /*!*/ marcador onde zero é
atribuído seu valor (na linha 2) faz a ferramenta de migração inferir o tipo
de as zero em int vez de int? . Essa mudança de tipo pode afetar o código
que usa direta ou indiretamente zero .
Com a dica acima, a ferramenta de migração muda suas edições
propostas, como mostram os trechos de código a seguir. A linha 3 não tem
mais um !depois zero e, na linha 4, zeroOne infere-se que é uma lista de
int , não int? .

Aplicando mudanças
Quando você gostar de todas as alterações propostas pela ferramenta de
migração, clique em Aplicar migração. A ferramenta de migração exclui
os marcadores de dica e salva o código migrado. A ferramenta também
atualiza a restrição mínima do SDK no pubspec, que opta pelo pacote
com segurança nula.
A próxima etapa é analisar estaticamente seu código . Se for válido, teste
seu código. Então, se você publicou seu código no pub.dev, publique um
pré-lançamento seguro para nulos.

Migrando manualmente
Se preferir não usar a ferramenta de migração, você pode migrar
manualmente.
Recomendamos que você primeiro migre as bibliotecas de folhas -
bibliotecas que não importam outros arquivos do pacote. Em seguida,
migre as bibliotecas que dependem diretamente das bibliotecas folha.
Termine migrando as bibliotecas que têm mais dependências dentro do
pacote.
Por exemplo, digamos que você tenha um lib/src/util.dart arquivo que
importa outros pacotes (null-safe) e bibliotecas centrais, mas que não tem
nenhuma import '<local_path>' diretiva. Considere migrar util.dart primeiro e
depois migrar arquivos simples que dependem apenas do util.dart . Se
alguma biblioteca tiver importações cíclicas (por exemplo, A importa B que
importa C e C importa A), considere migrar essas bibliotecas juntas.
Para migrar um pacote manualmente, siga estas etapas:
1. Edite o pubspec.yaml arquivo do pacote, definindo a restrição
mínima do SDK para 2.12.0-0 :
2. Gere novamente o arquivo de configuração do pacote:

3. A execução dart pub get com uma restrição SDK inferior de 2.12.0-0
define a versão de idioma padrão de cada biblioteca no pacote para
2.12, optando por segurança nula.
4. Abra o pacote em seu IDE. Você provavelmente verá muitos erros
de análise. Isso está ok.
5. Migre o código de cada arquivo Dart, usando o analisador para
identificar erros estáticos. Eliminar erros estáticos adicionando ? , ! ,
required , e late , conforme necessário.
Consulte Segurança nula não sólida para obter mais ajuda sobre como
migrar o código manualmente.
Análise
Atualize seus pacotes (usando pub get em seu IDE ou na linha de
comando). Em seguida, use seu IDE ou a linha de comando para realizar
uma análise estática em seu código:
Teste
Se seu código passar na análise, execute os testes:

Pode ser necessário atualizar os testes que esperam valores nulos.


Se você precisar fazer grandes alterações em seu código, talvez precise
migrá-lo novamente. Nesse caso, reverta suas alterações de código antes
de usar a ferramenta de migração novamente.
Publique
Incentivamos você a publicar pacotes como pré-lançamentos assim que
migrar:
Defina as restrições do SDK para a versão beta testada.
Defina a versão do pacote para indicar uma alteração significativa e
incluir um sufixo nullsafety .

Restrições SDK
Defina a restrição inferior do SDK para a versão beta de 2.12 que você
usou para testar a migração e a restrição superior do SDK para <3.0.0 .
Por exemplo, se você estiver usando 2.12.0-29.10.beta, suas restrições
devem ser assim:

Com essas restrições, os pacotes publicados durante o beta de


segurança nulo ainda podem funcionar com a próxima versão estável do
Dart SDK.

Versão do pacote
Atualize a versão do pacote para indicar uma alteração importante e
incluir um sufixo nullsafety :
Se o seu pacote já estiver em 1.0.0 ou superior, aumente a versão
principal. Por exemplo, se a versão anterior for 2.3.2 , a nova versão
será 3.0.0-nullsafety.0 .
Se o seu pacote não atingiu 1.0.0 ainda, quer aumentar a versão
secundária ou atualizar a versão para 1.0.0. Por exemplo, se a
versão anterior for 0.3.2 , a nova versão será uma das seguintes:
0.4.0-nullsafety.0
1.0.0-nullsafety.0
Para atualizações subsequentes do pré-lançamento seguro para nulos do
pacote, incremente o sufixo de pré-lançamento. Por exemplo, se a
primeira versão segura para nulos for 3 .0.0-nullsafety.0 , então a próxima é
3.0.0-nullsafety.1 .
Você pode manter uma versão estável e um pré-lançamento seguro para
nulos ao mesmo tempo. Por exemplo, se você tiver uma versão estável
1.0.0 e um pré-lançamento 2.0.0-nullsafety.0 , ainda poderá publicar novas
versões da versão estável ( 1.0.1 ) e do pré-lançamento seguro para nulos
( 2.0.0-nullsafety.1 ).
Uma vez que a segurança nula está disponível em uma versão estável do
SDK do Dart, nós o encorajamos a publicar uma versão estável do seu
pacote de segurança nula.
Bem-Vindo Ao Null Safety
Se você chegou até aqui, deve ter um pacote Dart totalmente migrado e
seguro para nulos. Se todos os pacotes dos quais você depende também
forem migrados, então seu programa está correto com relação a erros de
referência nula.
De toda a equipe Dart, obrigado por migrar seu código.
Language: Null Safety: Compreendendo a
Segurança Nula
Escrito por Bob Nystrom em julho de 2020

Segurança nula é a maior mudança que fizemos no Dart desde que


substituímos o sistema de tipo opcional não sólido original por um sistema
de tipo estático de som no Dart 2.0. Quando o Dart foi lançado, a
segurança nula em tempo de compilação era um recurso raro que
precisava de uma longa introdução. Hoje, Kotlin, Swift, Rust e outras
linguagens têm suas próprias respostas para o que se tornou um
problema familiar. A qui está um exemplo:

Se você executar este programa Dart sem segurança nula, ele lançará
uma exceção na chamada NoSuchMethodError para .length . O valor
null é uma instância da classe Null e Null não tem getter de
“comprimento”. Falhas de tempo de execução são uma droga. Isso é
especialmente verdadeiro em uma linguagem como o Dart, projetado para
ser executado no dispositivo do usuário final. Se um aplicativo de servidor
falhar, você pode reiniciá-lo antes que alguém perceba. Mas quando um
aplicativo Flutter trava no telefone de um usuário, ele não fica feliz.
Quando seus usuários não estão satisfeitos, você não fica feliz.
Os desenvolvedores gostam de linguagens com tipos estáticos, como o
Dart, porque permitem que o verificador de tipo encontre erros no código
em tempo de compilação, geralmente direto no IDE. Quanto mais cedo
você encontrar um bug, mais cedo poderá corrigi-lo. Quando os designers
de linguagem falam sobre “consertar erros de referência nula”, eles
significam enriquecer o verificador de tipo estático para que a linguagem
possa detectar erros como a tentativa acima de chamar um valor .length
que possa ser null .
Não existe uma solução verdadeira para este problema. Rust e Kotlin têm
sua própria abordagem que faz sentido no contexto dessas linguagens.
Este doc aborda todos os detalhes de nossa resposta para Dart. Inclui
alterações no sistema de tipo estático e um conjunto de outras
modificações e novos recursos de linguagem para permitir que você não
apenas escreva código seguro para nulos, mas também que aproveite
isso.
Este documento é longo. Se você deseja algo mais curto que cubra
apenas o que você precisa saber para começar a trabalhar, comece com a
visão geral. Quando você estiver pronto para um entendimento mais
profundo e tiver tempo, volte aqui para entender como funciona a
linguagem null , porque a projetamos dessa forma e como escrever Dart
idiomático, moderno e seguro para nulos. (Alerta de spoiler: termina
surpreendentemente próximo de como você escreve Dart hoje.)
Cada uma das várias maneiras como uma linguagem pode lidar com
erros de referência nula tem seus prós e contras. Esses princípios
guiaram as escolhas que fizemos:
O código deve ser seguro por padrão. Se você escrever um novo
código Dart e não usar nenhum recurso explicitamente inseguro, ele
nunca lançará um erro de referência nula no tempo de execução.
Todos os erros de referência nula possíveis são detectados
estaticamente. Se quiser adiar parte dessa verificação para o tempo
de execução para obter maior flexibilidade, você pode, mas precisa
escolher isso usando algum recurso que seja textualmente visível no
código.
Em outras palavras, não estamos lhe dando um colete salva-vidas e
deixando que você se lembre de colocá-lo toda vez que entrar na
água. Em vez disso, oferecemos a você um barco que não afunda.
Você fica seco, a menos que pule no mar.
O código seguro nulo deve ser fácil de escrever. A maior parte do
código Dart existente é dinamicamente correto e não lança erros de
referência nula. Você gosta do seu programa Dart da maneira como
ele se parece agora, e queremos que seja capaz de continuar
escrevendo códigos dessa forma. A segurança não deve exigir o
sacrifício da usabilidade, o pagamento de penitências ao revisor de
tipos ou a mudança significativa de sua maneira de pensar.
O código de segurança nulo resultante deve ser totalmente correto.
“Solidez” no contexto da verificação estática significa coisas
diferentes para pessoas diferentes. Para nós, no contexto de
segurança nula, isso significa que se uma expressão tem um tipo
estático que não permite nenhuma execução null , possível dessa
expressão pode ser avaliada como null . A linguagem fornece essa
garantia principalmente por meio de verificações estáticas, mas
também pode haver algumas verificações de tempo de execução
envolvidas. (Porém, observe o primeiro princípio: qualquer lugar
onde essas verificações de tempo de execução acontecem será sua
escolha.)
Solidez é importante para a confiança do usuário. Um barco que quase
sempre flutua não é aquele em que você está entusiasmado para
enfrentar o mar aberto. Mas também é importante para nossos intrépidos
hackers de compiladores. Quando a linguagem oferece garantias rígidas
sobre as propriedades semânticas de um programa, isso significa que o
compilador pode realizar otimizações que pressupõem que essas
propriedades sejam verdadeiras. Quando se trata de null , isso significa
que podemos gerar um código menor que elimina verificações null
desnecessárias e um código mais rápido que não precisa verificar se um
receptor não é null antes de chamar métodos nele.
Uma advertência: só garantimos a integridade em programas Dart que
são totalmente seguros para nulos. O Dart oferece suporte a programas
que contêm uma mistura de código seguro nulo mais recente e código
legado mais antigo. Nesses programas de versão mista, erros de
referência nula ainda podem ocorrer. Em um programa de versão mista,
você obtém todos os benefícios de segurança estática nas partes que são
seguras para nulos, mas não obtém segurança total do tempo de
execução até que todo o aplicativo seja seguro para nulos.
Observe que eliminar null não é um objetivo. Não há nada de errado com
null . Pelo contrário, é muito útil poder representar a ausência de um valor.
Construir suporte para um valor especial “ausente” diretamente no idioma
torna o trabalho com ausência flexível e utilizável. Ele sustenta os
parâmetros opcionais, o prático ?. operador com reconhecimento de nulo
e a inicialização padrão. Não é null que isso seja ruim, é null ir aonde
você não espera que cause problemas.
Portanto, com segurança nula, nosso objetivo é dar a você controle e
percepção de onde null pode fluir seu programa e a certeza de que ele
não pode fluir em algum lugar que possa causar um travamento.
Anulabilidade No Sistema De Tipo
A segurança nula começa no sistema de tipo estático porque tudo o mais
depende disso. Seu programa Dart possui todo um universo de tipos: tipos
primitivos como int e String , tipos de coleção como List , e todas as
classes e tipos que você e os pacotes que você usa definem. Antes da
segurança nula, o sistema de tipo estático permitia que o valor null
fluísse para expressões de qualquer um desses tipos.
No jargão da teoria de tipo, o tipo Null foi tratado como um subtipo de
todos os tipos:

O conjunto de operações - getters, setters, métodos e operadores -


permitido em alguma expressão é definido por seu tipo. Se o tipo for List ,
você pode ligar .add() ou ligar [] . Se for int , você pode ligar + . Mas o
valor null não define nenhum desses métodos. Permitir o fluxo null para
uma expressão de algum outro tipo significa que qualquer uma dessas
operações pode falhar. Este é realmente o ponto crucial dos erros de
referência nula - toda falha vem da tentativa de pesquisar um método ou
propriedade null que não existe.

Tipos não anuláveis e anuláveis


A segurança nula elimina esse problema na raiz, alterando a hierarquia de
tipos. O tipo Null ainda existe, mas não é mais um subtipo de todos os
tipos. Em vez disso, a hierarquia de tipos se parece com isto:
Como Null não é mais um subtipo, nenhum tipo, exceto a classe especial
Null , permite o valor null . Tornamos todos os tipos não anuláveis por
padrão. Se você tiver uma variável do tipo String , ela sempre conterá
uma string . Lá, corrigimos todos os erros de referência nula.
Se não pensássemos que null fosse útil, poderíamos parar por aqui. Mas
null é útil, então ainda precisamos encontrar uma maneira de lidar com
isso. Os parâmetros opcionais são um bom caso ilustrativo. Considere
este código Dart seguro nulo:

Aqui, queremos permitir que o parâmetro dairy aceite qualquer string, ou


o valor null , mas nada mais. Para expressar isso, damos dairy um tipo
anulável batendo ? no final do tipo base subjacente String . Nos
bastidores, isso define essencialmente uma união do tipo subjacente e do
tipo Null . Portanto, String? seria uma abreviação para String|Null se Dart
tivesse tipos de associação completos.

Usando tipos anuláveis


Se você tiver uma expressão com um tipo anulável, o que pode fazer com
o resultado? Visto que nosso princípio é seguro por padrão, a resposta
não é muito. Não podemos permitir que você chame métodos do tipo
subjacente porque eles podem falhar se o valor for null :
Isso travaria se deixássemos você executá-lo. Os únicos métodos e
propriedades que podemos permitir que você acesse com segurança são
aqueles definidos tanto pelo tipo subjacente quanto pela classe Null . Isso
é apenas toString() , == e hashCode . Portanto, você pode usar tipos
anuláveis como chaves de mapa, armazená-los em conjuntos, compará-
los a outros valores e usá-los na interpolação de strings, mas isso é tudo.
Como eles interagem com tipos não anuláveis? É sempre seguro passar
um tipo não anulável para algo que espera um tipo anulável. Se uma
função aceita String? , String é permitido passar um porque não causará
problemas. Modelamos isso tornando cada tipo anulável um supertipo de
seu tipo subjacente. Você também pode passar null com segurança para
algo que espera um tipo anulável, portanto, Null também é um subtipo de
cada tipo anulável:

Mas ir à outra direção e passar um tipo anulável para algo que espera o
tipo não anulável subjacente não é seguro. O código que espera um
String pode chamar métodos String no valor. Se você passar um String?
para ele, null pode fluir e isso pode falhar:
Este programa não é seguro e não devemos permitir isso. No entanto,
Dart sempre teve essa coisa chamada downcasts implícitos. Se você, por
exemplo, passar um valor de tipo Object para uma função esperando um
String , o verificador de tipo permite:

Para manter a integridade, o compilador insere silenciosamente um as


String elenco no argumento para requireStringNotObject() . Esse elenco pode
falhar e lançar uma exceção em tempo de execução, mas em tempo de
compilação, Dart diz que está tudo bem. Como os tipos não anuláveis são
modelados como subtipos de tipos anuláveis, os downcasts implícitos
permitiriam que você passasse a String? para algo que esperava a String .
Permitir isso violaria nosso objetivo de estar seguro por padrão. Portanto,
com segurança nula, estamos removendo totalmente os downcasts
implícitos.
Isso faz com que a chamada requireStringNotNull() produza um erro de
compilação, que é o que você deseja. Mas também significa que todos os
downcasts implícitos se tornam erros de compilação, incluindo a chamada
para requireStringNotObject() . Você mesmo terá que adicionar o downcast
explícito:

Achamos que esta é uma boa mudança geral. Nossa impressão é que a
maioria dos usuários nunca gostou de downcasts implícitos. Em particular,
você pode ter se queimado por isso antes:
Identificar o bug? O método .where() é preguiçoso, então ele retorna um
Iterable , não a List . Este programa compila, mas lança uma exceção em
tempo de execução ao tentar converter Iterable no tipo List que declara
filterEvens que ele retorna. Com a remoção de downcasts implícitos, isso se
torna um erro de compilação.
Onde nós estávamos?
Certo, OK, então é como se pegássemos o universo de tipos em seu
programa e os dividíssemos em duas metades:

Existe uma região de tipos não anuláveis. Esses tipos permitem que você
acesse todos os métodos interessantes, mas nunca podem conter null . E
então há uma família paralela de todos os tipos anuláveis
correspondentes. Esses permitem null , mas você não pode fazer muito
com eles. Deixamos os valores fluírem do lado não anulável para o lado
anulável porque fazer isso é seguro, mas não na outra direção.
Parece que os tipos anuláveis são basicamente inúteis. Eles não têm
métodos e você não pode fugir deles. Não se preocupe, temos um
conjunto completo de recursos para ajudá-lo a mover os valores da
metade anulável para o outro lado, que veremos em breve.

Cabeçalho e rodapé
Esta seção é um pouco esotérica. Você pode geralmente ignorá-lo, exceto
por dois marcadores no final, a menos que você goste de coisas do
sistema de tipos. Imagine todos os tipos em seu programa com arestas
entre aqueles que são subtipos e supertipos uns dos outros. Se você
fosse desenhá-lo, como os diagramas neste documento, ele formaria um
enorme gráfico direcionado com supertipos como Object próximo ao topo
e classes folha como seus próprios tipos próximo ao fundo.
Se esse gráfico direcionado chega a um ponto no topo onde há um único
tipo que é o supertipo (direta ou indiretamente), esse tipo é chamado de
tipo superior. Da mesma forma, se houver um tipo estranho naquele fundo
que é um subtipo de todos os tipos, você tem um tipo fundo. (Neste caso,
seu gráfico direcionado é uma rede.)
É conveniente se o seu sistema de tipos tiver um tipo superior e inferior,
porque isso significa que as operações de nível de tipo, como limite
superior mínimo (que a inferência de tipo usa para descobrir o tipo de uma
expressão condicional com base nos tipos de seus dois ramos) podem
sempre produzir um tipo. Antes da segurança nula, Object era o tipo
superior do Dart e Null era o tipo inferior.
Como Object agora não é anulável, não é mais um tipo superior. Null não é
um subtipo disso. Dart não tem um tipo de topo nomeado. Se você precisa
de um tipo superior, você deseja Object?. Da mesma forma, Null não é
mais o tipo de fundo. Se fosse, tudo ainda seria anulável. Em vez disso,
adicionamos um novo tipo de fundo chamado Never :

Na prática, isso significa:


Se você quiser indicar que permite um valor de qualquer tipo, use
em Object? vez de Object . Na verdade, torna-se bastante incomum
de usar, Object uma vez que esse tipo significa “poderia ser qualquer
valor possível, exceto este valor estranhamente proibido null ”.
Nas raras ocasiões em que você precisar de um tipo de fundo, use
em Never vez de Null . Se você não sabe se precisa de um tipo de
fundo, provavelmente não precisa.
Garantindo Correção
Dividimos o universo de tipos em metades anuláveis e não anuláveis.
Para manter a solidez e nosso princípio de que você nunca pode obter um
erro de referência nula em tempo de execução, a menos que solicite,
precisamos garantir que null nunca apareça em nenhum tipo no lado não
anulável.
Livrar-se de downcasts implícitos e remover Null como um tipo inferior
cobre todos os locais principais em que os tipos fluem por meio de um
programa através de atribuições e de argumentos para parâmetros em
chamadas de função. Os principais lugares restantes onde você pode
entrar null sorrateiramente são quando uma variável surge pela primeira
vez e quando você deixa uma função. Portanto, existem alguns erros de
compilação adicionais:

Devoluções inválidas
Se uma função tiver um tipo de retorno não anulável, cada caminho
através da função deve alcançar uma instrução return que retorna um
valor. Antes da segurança nula, Dart era muito negligente quanto a
retornos perdidos. Por exemplo:

Se você analisar isso, terá uma leve sugestão de que talvez tenha
esquecido uma devolução, mas se não, não é grande coisa. Isso porque
se a execução chegar ao final do corpo de uma função, o Dart retornará
implicitamente null . Como todo tipo pode ser anulado, tecnicamente essa
função é segura, embora provavelmente não seja o que você deseja.
Com tipos de som não anuláveis, este programa é totalmente errado e
inseguro. Sob segurança nula, você obtém um erro de compilação se uma
função com um tipo de retorno não anulável não retornar um valor
confiável. Por “confiável”, quero dizer que a linguagem analisa todos os
caminhos do fluxo de controle através da função. Contanto que todos eles
devolvam algo, ele está satisfeito. A análise é muito inteligente, então
mesmo esta função está OK:
Iremos mergulhar mais profundamente na nova análise de fluxo na
próxima seção.

Variáveis não inicializadas


Quando você declara uma variável, se você não fornece um inicializador
explícito, o Dart inicializa a variável com null . Isso é conveniente, mas
obviamente totalmente inseguro se o tipo da variável não for anulável.
Portanto, temos que restringir as coisas para variáveis não anuláveis:
Variável de nível superior e declarações de campo estático
devem ter um inicializador. Visto que eles podem ser acessados e
atribuídos de qualquer lugar no programa, é impossível para o
compilador garantir que a variável recebeu um valor antes de ser
usada. A única opção segura é exigir que a própria declaração tenha
uma expressão de inicialização que produza um valor do tipo certo:

Os campos de instância devem ter um inicializador na


declaração, usar um formal de inicialização ou ser inicializados
na lista de inicialização do construtor. Isso é muito jargão. Aqui
estão os exemplos:

Em outras palavras, contanto que o campo tenha um valor antes de


você alcançar o corpo do construtor, você está bem.
Variáveis locais são o caso mais flexível. Uma variável local não
anulável não precisa ter um inicializador. Isso está perfeitamente
bem :

A regra é apenas que uma variável local deve ser definitivamente


atribuída antes de ser usada. Também podemos contar com a
nova análise de fluxo a que aludi para isso. Desde que cada
caminho para o uso de uma variável a inicialize primeiro, o uso está
OK.
Os parâmetros opcionais devem ter um valor padrão. Se você
não passar um argumento para um parâmetro opcional posicional ou
nomeado, o idioma o preencherá com o valor padrão. Se você não
especificar um valor padrão, o valor padrão será null , e isso não
voará se o tipo do parâmetro não for anulável.
Portanto, se você quiser que um parâmetro seja opcional, você
precisa torná-lo anulável ou especificar um valor null não padrão
válido.
Essas restrições parecem onerosas, mas não são tão ruins na prática.
Eles são muito semelhantes às restrições existentes em torno de
variáveis final e provavelmente você já trabalhou com eles por anos,
mesmo sem realmente perceber. Além disso, lembre-se de que eles se
aplicam apenas a variáveis não anuláveis. Você sempre pode tornar o tipo
anulável e, em seguida, obter a inicialização padrão para null .
Mesmo assim, as regras causam atrito. Felizmente, temos um conjunto de
novos recursos de linguagem para lubrificar os padrões mais comuns em
que essas novas limitações deixam você lento. Mas primeiro é hora de
falar sobre análise de fluxo.
Análise De Fluxo
A análise de fluxo de controle já existe nos compiladores há anos. Ele é
geralmente escondido dos usuários e usado durante a otimização do
compilador, mas algumas linguagens mais novas começaram a usar as
mesmas técnicas para recursos de linguagem visíveis. O Dart já tem uma
pitada de análise de fluxo na forma de promoção de tipo:

Note como a linha marcada, podemos chamar isEmpty on object . Esse


método é definido em List , não Object . Isso funciona porque o verificador
de tipo examina todas as expressões is e os caminhos do fluxo de
controle no programa. Se o corpo de alguma construção de fluxo de
controle só executa quando uma certa expressão is em uma variável é
verdadeira, então, dentro desse corpo, o tipo da variável é “promovido” ao
tipo testado.
No exemplo aqui, a continuação then da instrução if só é executado
quando object na verdade contém uma lista. Portanto, o Dart promove o
tipo object em vez de List seu tipo declarado Object . Este é um recurso
útil, mas bastante limitado. Antes da segurança nula, o seguinte programa
funcionalmente idêntico não funcionava:

Novamente, você só pode alcançar a chamada .isEmpty quando contém


uma lista object , portanto, este programa está dinamicamente correto.
Mas as regras de promoção de tipo não eram inteligentes o suficiente para
ver que a instrução return significa que a segunda instrução só pode ser
alcançada quando object é uma lista.
Para segurança nula, pegamos essa análise limitada e a tornamos muito
mais poderosa de várias maneiras.

Análise de acessibilidade
Em primeiro lugar, corrigimos a reclamação de longa data de que a
promoção de tipo não é inteligente sobre devoluções antecipadas e outros
caminhos de código inacessíveis. Ao analisar uma função, agora leva em
conta return , break , throw , e qualquer outra execução maneira pode
terminar no início de uma função. Sob segurança nula, esta função:

Agora é perfeitamente válido. Como a instrução if sairá da função quando


não for um object , Dart promove para estar na segunda instrução. Esta é
uma melhoria muito boa que ajuda muito o código Dart, mesmo coisas
não relacionadas à anulação. ListobjectList

Nunca para código inacessível


Você também pode programar esta análise de acessibilidade. O novo tipo
de função Never não tem valores. (Que tipo de valor é simultaneamente a
String , bool e int ?) Então, o que significa uma expressão ter tipo Never ?
Isso significa que a avaliação da expressão nunca pode ser concluída
com êxito. Ele deve lançar uma exceção, abortar ou, de outra forma,
garantir que o código circundante, que espera o resultado da expressão,
nunca seja executado.
Na verdade, de acordo com a linguagem, o tipo estático de uma
expressão throw é Never . O tipo Never é declarado nas bibliotecas
principais e você pode usá-lo como uma anotação de tipo. Talvez você
tenha uma função auxiliar para tornar mais fácil lançar um certo tipo de
exceção:

Você pode usá-lo assim:


Este programa analisa sem erros. Observe que a última linha do método
== acessa .x e .y on other . Foi promovido a Point embora a função não
tem nenhum return ou throw . A análise de fluxo de controle sabe que o
tipo declarado de wrongType() é, o Never que significa que o branch then
da instrução if deve abortar de alguma forma. Como a segunda afirmação
só pode ser alcançada quando other é um Point , Dart a promove.
Em outras palavras, usar Never em suas próprias APIs permite estender a
análise de acessibilidade do Dart.

Análise de atribuição definitiva


Mencionei este brevemente com variáveis locais. O Dart precisa garantir
que uma variável local não anulável seja sempre inicializada antes de ser
lida. Usamos a análise de atribuição definida para ser o mais flexível
possível. A linguagem analisa cada corpo de função e rastreia as
atribuições a variáveis e parâmetros locais por meio de todos os caminhos
de fluxo de controle. Contanto que a variável seja atribuída em cada
caminho que alcance algum uso de uma variável, a variável é considerada
inicializada. Isso permite declarar uma variável sem inicializador e, em
seguida, inicializá-la depois usando um fluxo de controle complexo,
mesmo quando a variável tem um tipo não anulável.
Também usamos a análise de atribuição definida para tornar as variáveis
finais mais flexíveis. Antes da segurança nula, pode ser difícil usar final
para variáveis locais se você precisar inicializá-las de alguma forma
interessante:
Isso seria um erro, pois a variável result é final, mas não tem inicializador.
Com a análise de fluxo mais inteligente sob segurança nula, este
programa é bom. A análise pode dizer que result é definitivamente
inicializado exatamente uma vez em cada caminho de fluxo de controle,
portanto, as restrições para marcar uma variável final são satisfeitas.

Tipo de promoção em verificações nulas


A análise de fluxo mais inteligente ajuda muito código Dart, até mesmo
código não relacionado à nulidade. Mas não é uma coincidência que
estejamos fazendo essas mudanças agora. Temos tipos particionados em
conjuntos anuláveis e não anuláveis. Se você tem um valor do tipo
anulável, você não pode realmente fazer nada útil com ele. Nos casos em
que o valor for null , essa restrição é boa. Está impedindo você de cair.
Mas se o valor não for null , seria bom movê-lo para o lado não anulável
para que você possa chamar métodos nele. A análise de fluxo é uma das
principais maneiras de fazer isso para variáveis e parâmetros locais.
Estendemos a promoção de tipo para também olhar para == null e !=
expressões null .
Se você verificar uma variável com tipo anulável para ver se não é null , o
Dart então promove a variável para o tipo não anulável subjacente:
Aqui, arguments tem um tipo anulável. Normalmente, isso o proíbe de
ligar .join() para ele. Mas, como protegemos essa chamada em uma
instrução if que verifica se o valor não é null , o Dart o promove de
List<String>? para List<String> e permite que você chame métodos nele
ou o passe para funções que esperam listas não anuláveis.
Isso soa como uma coisa pequena, mas essa promoção baseada em
fluxo em verificações de nulos é o que faz com que a maioria dos códigos
Dart existentes funcionem sob segurança nula. A maior parte do código
Dart é dinamicamente correto e evita lançar erros de referência nula
verificando null antes de chamar os métodos. A nova análise de fluxo em
verificações nulas transforma essa correção dinâmica em correção
estática comprovável.
É claro que também funciona com a análise mais inteligente que fazemos
para acessibilidade. A função acima pode ser escrita tão bem como:

A linguagem também é mais inteligente sobre quais tipos de expressões


causam promoção. Um explícito == null ou != null claro funciona. Mas
casts explícitos usando as , ou atribuições, ou o operador ! postfix que
veremos em breve também causam promoção. O objetivo geral é que, se
o código estiver dinamicamente correto e for razoável descobrir isso
estaticamente, a análise deve ser inteligente o suficiente para fazer isso.

Avisos de código desnecessários


Ter uma análise de acessibilidade mais inteligente e saber onde null pode
fluir em seu programa ajuda a garantir que você adicione código para
manipular null . Mas também pode usar essa mesma análise para detectar
código que você não precisa. Antes de segurança nula, se você escreveu
algo como:
Dart não tinha como saber se aquele operador ?. com reconhecimento de
nulo é útil ou não. Pelo que sabe, você pode passar null para a função.
Mas no Dart seguro nulo, se você anotou essa função com o tipo agora
não anulável List , então ela sabe que list nunca será null . Isso implica
que você nunca fará nada de útil e você pode e deve apenas usar ?. .
Para ajudá-lo a simplificar seu código, adicionamos avisos para código
desnecessário como este, agora que a análise estática é precisa o
suficiente para detectá-lo. Usar um operador com reconhecimento de nulo
ou até mesmo uma verificação como == null ou != null em um tipo não
anulável é relatado como um aviso.
E, claro, isso joga com a promoção de tipo não anulável também. Depois
que uma variável for promovida a um tipo não anulável, você receberá um
aviso se verificar de forma redundante null :

Você recebe um aviso aqui porque no ponto em que ele executa ?. , já


sabemos que list não pode ser null . O objetivo com esses avisos não é
apenas limpar o código inútil. Ao remover verificações desnecessárias
null , garantimos que as verificações significativas restantes se
destaquem. Queremos que você possa olhar para o seu código e ver
onde ele pode fluir null .
Trabalhando Com Tipos Anuláveis
Agora nos concentramos null no conjunto de tipos anuláveis. Com a
análise de fluxo, podemos permitir com segurança que alguns não
valores null saltem por cima da cerca para o lado não anulável, onde
podemos usá-los. É um grande passo, mas se pararmos por aqui, o
sistema resultante ainda é dolorosamente restritivo. A análise de fluxo só
ajuda com locais e parâmetros.
Para tentar recuperar o máximo da flexibilidade que o Dart tinha antes da
segurança nula - e ir além em alguns lugares - temos um punhado de
outros novos recursos.

Métodos nulos mais inteligentes


O operador nulo do Dart é muito mais antigo do que a segurança nula ?. .
A semântica do tempo de execução afirma que, se o receptor for null , o
acesso à propriedade no lado direito será ignorado e a expressão será
avaliada como null :

Em vez de lançar uma exceção, imprime “null”. O operador com


reconhecimento de nulo é uma boa ferramenta para tornar os tipos
anuláveis utilizáveis no Dart. Embora não possamos permitir que você
chame métodos em tipos anuláveis, podemos e permitimos que você use
operadores com reconhecimento de nulos neles. A versão de segurança
pós-nula do programa é:

Funciona exatamente como o anterior.


No entanto, se você já usou operadores com reconhecimento de nulos no
Dart, provavelmente encontrou um aborrecimento ao usá-los em cadeias
de métodos. Digamos que você queira ver se o comprimento de uma
string potencialmente ausente é um número par (não é um problema
particularmente realista, eu sei, mas trabalhe comigo aqui):
Mesmo que este programa use ?. , ele ainda lança uma exceção em
tempo de execução. O problema é que o receptor da expressão .isEven é
o resultado de toda expressão a notAString?.length à sua esquerda. Essa
expressão é avaliada como null , portanto, obtemos um erro de referência
nula ao tentar chamar .isEven . Se você já usou o ?. Dart, provavelmente
aprendeu da maneira mais difícil que precisa aplicar o operador nulo a
cada propriedade ou método em uma cadeia depois de usá-lo uma vez:

Isso é irritante, mas, pior, obscurece informações importantes. Considerar:

Aqui está uma pergunta para você: O doohickey getter pode Thing voltar
null ?
Parece que ele poderia porque você está usando ?. no resultado. Mas
pode ser que o segundo ?. esteja lá apenas para lidar com casos em que
thing está null , não o resultado de doohickey . Você não pode dizer.
Para resolver isso, pegamos emprestada uma ideia inteligente do design
do C# do mesmo recurso. Quando você usa um operador com
reconhecimento de nulo em uma cadeia de método, se o receptor for
avaliado como null , todo o resto da cadeia de método sofre um curto-
circuito e é ignorado. Isso significa que se doohickey tiver um tipo de
retorno não anulável, você pode e deve escrever:

Na verdade, você receberá um aviso de código desnecessário na


segunda ?. se não o fizer. Se você vir um código como:
Então você sabe com certeza que significa que doohickey ele mesmo tem
um tipo de retorno anulável. Cada um ?. corresponde a um caminho único
que pode causar o fluxo null para a cadeia do método. Isso torna os
operadores com reconhecimento de nulos em cadeias de métodos mais
concisos e precisos.
Enquanto estávamos nisso, adicionamos alguns outros operadores nulos:

Não há um operador de chamada de função com reconhecimento de nulo,


mas você pode escrever:

Operador de asserção nula


A grande vantagem de usar a análise de fluxo para mover uma variável
anulável para o lado não anulável do mundo é que fazer isso é
comprovadamente seguro. Você pode chamar métodos na variável
anteriormente anulável sem renunciar a qualquer segurança ou
desempenho de tipos não anuláveis.
Mas muitos usos válidos de tipos anuláveis não podem ser comprovados
como seguros de uma forma que agrade a análise estática. Por exemplo:
Se você tentar executá-lo, obterá um erro de compilação na chamada
para toUpperCase() . O campo error é anulável porque não terá um valor
em uma resposta bem-sucedida. Podemos ver, inspecionando a classe,
que nunca acessamos a mensagem error quando ela está null . Mas isso
requer a compreensão da relação entre o valor de code e a nulidade de
error . O verificador de tipo não consegue ver essa conexão.
Em outras palavras, nós, mantenedores humanos do código, sabemos
que o erro não estará no ponto null em que o usaremos e precisamos de
uma maneira de afirmar isso. Normalmente, você declara tipos usando um
elenco as e pode fazer a mesma coisa aqui:

A conversão error para o tipo String não anulável lançará uma exceção
de tempo de execução se a conversão falhar. Caso contrário, ele nos dá
uma string não anulável na qual podemos chamar os métodos.
“Descartando a nulidade” surge com frequência suficiente para que
tenhamos uma nova sintaxe abreviada. Um ponto de exclamação pós-
fixado ( ! ) pega a expressão à esquerda e a converte em seu tipo não
anulável subjacente. Portanto, a função acima é equivalente a:
Este “operador bang” de um caractere é particularmente útil quando o tipo
subjacente é prolixo. Seria muito chato ter que escrever as
Map<TransactionProviderFactory, List<Set<ResponseFilter>>> apenas
para jogar fora um single ? de algum tipo.
Claro, como qualquer elenco, o uso ! vem com uma perda de segurança
estática. O elenco deve ser verificado em tempo de execução para
preservar a integridade e pode falhar e lançar uma exceção. Mas você
tem controle sobre onde esses casts são inseridos e sempre pode vê-los
examinando seu código.

Variáveis atrasadas
O lugar mais comum onde o verificador de tipo não pode provar a
segurança do código é em torno de variáveis e campos de nível superior.
Aqui está um exemplo:

Aqui, o método heat() é chamado antes serve() . Isso significa


_temperature que será inicializado com um valor não nulo antes de ser
usado. Mas não é viável que uma análise estática determine isso. (Pode
ser possível para um exemplo trivial como este, mas o caso geral de
tentar rastrear o estado de cada instância de uma classe é intratável.)
Como o verificador de tipo não pode analisar o uso de campos e variáveis
de nível superior, ele tem uma regra conservadora de que os campos não
anuláveis devem ser inicializados em sua declaração (ou na lista de
inicialização do construtor para campos de instância). Portanto, Dart relata
um erro de compilação nesta classe.
Você pode corrigir o erro tornando o campo anulável e, em seguida,
usando operadores de asserção nula nos usos:

Isso funciona bem. Mas isso envia um sinal confuso para o mantenedor
da classe. Ao marcar como _temperature anulável, você indica que null é
um valor útil e significativo para esse campo. Mas essa não é a intenção.
O campo _temperature nunca deve ser observado em seu estado null .
Para lidar com o padrão comum de estado com inicialização atrasada,
adicionamos um novo modificador late . Você pode usá-lo assim:

Observe que o campo _temperature tem um tipo não anulável, mas não
foi inicializado. Além disso, não há nenhuma asserção nula explícita
quando é usada. Existem alguns modelos que você pode aplicar à
semântica late , mas penso assim: O modificador late significa “impor as
restrições desta variável em tempo de execução em vez de em tempo de
compilação”. É quase como a palavra “atrasado” descreve quando reforça
as garantias da variável.
Nesse caso, como o campo não foi inicializado definitivamente, toda vez
que o campo é lido, uma verificação de tempo de execução é inserida
para garantir que um valor foi atribuído a ele. Se não tiver, uma exceção é
lançada. Atribuir à variável o tipo String significa “você nunca deve me ver
com um valor diferente de uma string” e o modificador late significa
“verificar isso em tempo de execução”.
De certa forma, o modificador late é mais “mágico” do que o uso, ?
porque qualquer uso do campo pode falhar e não há nada textualmente
visível no local de uso. Mas você não tem que escrever a declaração late
para obter esse comportamento, e nossa crença é que, vendo o
modificador há bastante explícita para que isso seja sustentável.
Em troca, você obtém melhor segurança estática do que usar um tipo
anulável. Como o tipo do campo agora é não anulável, é um erro de
compilação tentar atribuir null ou anulável ao campo String . O
modificador late permite adiar a inicialização, mas ainda proíbe tratá-la
como uma variável anulável.

Inicialização lenta
O modificador late também possui alguns outros poderes especiais. Pode
parecer paradoxal, mas você pode usar late em um campo que tenha um
inicializador:

Quando você faz isso, o inicializador fica lento. Em vez de executá-lo


assim que a instância é construída, ele é adiado e executado lentamente
na primeira vez que o campo é acessado. Em outras palavras, ele
funciona exatamente como um inicializador em uma variável de nível
superior ou campo estático. Isso pode ser útil quando a expressão de
inicialização é cara e pode não ser necessária.
Executar o inicializador preguiçosamente oferece um bônus extra ao usar
late em um campo de instância. Normalmente, os inicializadores de
campo de instância não podem acessar this porque você não tem acesso
ao novo objeto até que todos os inicializadores de campo tenham sido
concluídos. Mas com um campo late , isso não é mais verdade, então
você pode acessar this , chamar métodos ou acessar campos na
instância.

Variáveis finais tardias


Você também pode combinar late com final :
Ao contrário dos campos final normais, você não precisa inicializar o
campo em sua declaração ou na lista de inicialização do construtor. Você
pode atribuir a ele mais tarde no tempo de execução. Mas você só pode
atribuir a ele uma vez, e esse fato é verificado no tempo de execução. Se
você tentar atribuir a ele mais de uma vez, como chamar ambos heat() e
chill() aqui, a segunda atribuição lançará uma exceção. Esta é uma ótima
maneira de modelar o estado que é inicializado eventualmente e é
imutável depois disso.
Em outras palavras, o novo modificador late em combinação com outros
modificadores de variável do Dart cobre a maior parte do espaço de
recursos do Kotlin lateinit e lazy do Swift. Você pode até mesmo usá-lo
em variáveis locais se quiser uma pequena avaliação local preguiçosa.

Parâmetros nomeados obrigatórios


Para garantir que você nunca veja um parâmetro null com um tipo não
anulável, o verificador de tipo requer que todos os parâmetros opcionais
tenham um tipo anulável ou um valor padrão. E se você quiser ter um
parâmetro nomeado com um tipo anulável e nenhum valor padrão? Isso
implicaria que você deseja que o chamador sempre o passe. Em outras
palavras, você deseja um parâmetro nomeado, mas não opcional.
Eu visualizo os vários tipos de parâmetros do Dart com esta tabela:

Por razões pouco claras, o Dart há muito apoia três cantos desta tabela,
mas deixou a combinação de nomeado + obrigatório vazia. Com
segurança nula, preenchemos isso. Você declara um parâmetro nomeado
obrigatório colocando required antes do parâmetro:

Aqui, todos os parâmetros devem ser passados por nome. Os parâmetros


a e c são opcionais e podem ser omitidos. Os parâmetros b e d são
obrigatórios e devem ser passados. Observe que a condição necessária é
independente da nulidade. Você pode ter parâmetros nomeados
obrigatórios de tipos anuláveis e parâmetros nomeados opcionais de tipos
não anuláveis (se eles tiverem um valor padrão).
Este é outro dos recursos que eu acho que torna o Dart melhor,
independentemente da segurança nula. Simplesmente faz com que a
linguagem pareça mais completa para mim.

Trabalhando com campos anuláveis


Esses novos recursos abrangem muitos padrões comuns e tornam o
trabalho com null bastante indolor na maioria das vezes. Mas, mesmo
assim, nossa experiência é que campos anuláveis ainda podem ser
difíceis. Nos casos em que você pode tornar o campo late não anulável,
você está certo. Mas, em muitos casos, você precisa verificar se o campo
tem um valor, e isso requer torná-lo anulável para que você possa
observar o null .
Você pode esperar que isso funcione:

Lá dentro de checkTemp() , verificamos se _temperature está null . Se


não, a gente acessa e acaba chamando + . Infelizmente, isso não é
permitido. A promoção de tipo com base no fluxo não se aplica a campos
porque a análise estática não pode provar que o valor do campo não
muda entre o ponto que você verifica null e o ponto que você o usa.
(Considere que, em casos patológicos, o próprio campo pode ser
substituído por um getter em uma subclasse que retorna null na segunda
vez em que é chamado.)
Portanto, como nos preocupamos com a integridade, os campos não
promovem e o método acima não compila. Isso é irritante. Em casos
simples como aqui, sua melhor aposta é dar um tapa ! no uso do campo.
Parece redundante, mas é mais ou menos assim que Dart se comporta
hoje.
Outro padrão que ajuda é copiar o campo para uma variável local primeiro
e, em seguida, usá-lo:

Uma vez que a promoção de tipo se aplica aos habitantes locais, agora
funciona bem. Se você precisar alterar o valor, lembre-se de armazenar de
volta no campo e não apenas no local.

Nulidade e genéricos
Como a maioria das linguagens tipadas estaticamente modernas, o Dart
possui classes e métodos genéricos. Eles interagem com a nulidade de
algumas maneiras que parecem contraintuitivas, mas fazem sentido
quando você pensa nas implicações. Em primeiro lugar, "este tipo é
anulável?" não é mais uma simples pergunta sim ou não. Considerar:
Na definição de Box , é T um tipo anulável ou um tipo não anulável?
Como você pode ver, ele pode ser instanciado com qualquer tipo. A
resposta é que T é um tipo potencialmente anulável. Dentro do corpo de
uma classe ou método genérico, um tipo potencialmente anulável tem
todas as restrições de tipos anuláveis e tipos não anuláveis.
O primeiro significa que você não pode chamar nenhum método nele,
exceto um punhado definido em Object. O último significa que você deve
inicializar quaisquer campos ou variáveis desse tipo antes de serem
usados. Isso pode tornar os parâmetros de tipo muito difíceis de trabalhar.
Na prática, alguns padrões aparecem. Em classes semelhantes a
coleções, onde o parâmetro de tipo pode ser instanciado com qualquer
tipo, você só precisa lidar com as restrições. Na maioria dos casos, como
no exemplo aqui, significa garantir que você tenha acesso a um valor do
tipo do argumento type sempre que precisar trabalhar com um.
Felizmente, classes semelhantes a coleções raramente chamam métodos
em seus elementos.
Em locais onde você não tem acesso a um valor, você pode tornar o uso
do parâmetro de tipo anulável:

Observe o ? na declaração de object . Agora o campo tem um tipo


explicitamente anulável, portanto, não há problema em deixá-lo não
inicializado.
Quando você torna um tipo de parâmetro de tipo anulável como T? aqui,
pode ser necessário converter a nulidade para longe. A maneira correta
de fazer isso é usando uma conversão explícita as T , não o operador ! :

O operador ! sempre lança se o valor for null . Mas se o parâmetro de tipo


foi instanciado com um tipo anulável, então null é um valor perfeitamente
válido para T :

Este programa deve ser executado sem erros. Usando as T faz isso. Usar
! lançaria uma exceção.
Outros tipos genéricos têm algum limite que restringe os tipos de
argumentos de tipo que podem ser aplicados:

Se o limite não for anulável, o parâmetro de tipo também não será


anulável. Isso significa que você tem as restrições de tipos não anuláveis -
você não pode deixar campos e variáveis não inicializados. A classe de
exemplo aqui deve ter um construtor que inicializa os campos.
Em troca dessa restrição, você pode chamar quaisquer métodos em
valores do tipo de parâmetro de tipo que são declarados em seu limite. Ter
um limite não anulável, entretanto, evita que os usuários de sua classe
genérica o instanciem com um argumento de tipo anulável. Essa é
provavelmente uma limitação razoável para a maioria das classes.
Você também pode usar um limite anulável:
Isso significa que no corpo da classe você obtém a flexibilidade de tratar o
parâmetro de tipo como anulável. Observe que não temos construtor
neste momento, e está tudo OK. Os campos serão inicializados
implicitamente para null . Você pode declarar variáveis não inicializadas do
tipo do parâmetro de tipo.
Mas você também tem as limitações de nulidade - você não pode chamar
nada em uma variável desse tipo, a menos que você lide com a nulidade
primeiro. No exemplo aqui, copiamos os campos nas variáveis locais e
verificamos esses locais para null que a análise de fluxo os promova para
tipos não anuláveis antes de usarmos <= .
Observe que um limite anulável não impede que os usuários instanciem a
classe com tipos não anuláveis. Um limite anulável significa que o
argumento de tipo pode ser anulável, não que deve. (Na verdade, o limite
padrão nos parâmetros de tipo, se você não escrever uma cláusula
extends , é o limite anulável Object? .) Não há como exigir um argumento
de tipo anulável. Se você deseja que os usos do parâmetro de tipo sejam
confiavelmente anuláveis, você pode usar T? dentro do corpo da classe.
Mudanças Na Biblioteca Central
Existem alguns outros ajustes aqui e ali no idioma, mas eles são
menores. Coisas como o tipo padrão de a catch sem cláusula on agora é
em Object vez de dynamic . A análise de queda em declarações de switch
usa a nova análise de fluxo.
As alterações restantes que realmente importam para você estão nas
bibliotecas principais. Antes de embarcarmos na Grand Null Safety
Adventure, estávamos preocupados com a possibilidade de que não
houvesse nenhuma maneira de tornar nossas bibliotecas centrais nulas e
seguras sem destruir o mundo em massa. Não foi tão terrível. Não são
poucas mudanças significativas, mas para a maior parte, a migração
ocorreu sem problemas. A maioria das bibliotecas centrais não aceitava
null e naturalmente se movia para tipos não anuláveis, ou o faz e aceita
normalmente com um tipo anulável.
Existem alguns cantos importantes, no entanto:

O operador de índice do mapa é anulável


Isso não é realmente uma mudança, mas uma coisa a saber. O operador
de índice [] na classe Map retorna null se a chave não estiver presente.
Isso implica que o tipo de retorno desse operador deve ser anulável: em
V? vez de V .
Poderíamos ter mudado esse método para lançar uma exceção quando a
chave não estiver presente e, em seguida, dando a ele um tipo de retorno
não anulável mais fácil de usar. Mas o código que usa o operador de
índice e verifica se a chave null está ausente é muito comum, cerca de
metade de todos os usos com base em nossa análise. Quebrar todo esse
código teria colocado o ecossistema Dart em chamas.
Em vez disso, o comportamento do tempo de execução é o mesmo e,
portanto, o tipo de retorno é obrigado a ser anulável. Isso significa que
geralmente você não pode usar imediatamente o resultado de uma
pesquisa de mapa:

Isso dá a você um erro de compilação na tentativa de chamar .length em


uma string anulável. Nos casos em que você sabe que a chave está
presente, você pode ensinar o verificador de tipo usando ! :
Consideramos adicionar outro método ao Map que faria isso por você:
procurar a chave, lançar se não for encontrada ou retornar um valor não
anulável caso contrário. Mas como chamá-lo? Nenhum nome seria mais
curto do que o caractere único ! e nenhum nome de método seria mais
claro do que ver um ! com sua semântica incorporada ali mesmo no site
da chamada. Portanto, a maneira idiomática de acessar um elemento
presente conhecido em um mapa é usar []! . Você se acostuma com isso.

Nenhum construtor de lista sem nome


O construtor sem nome em List cria uma lista com o tamanho fornecido,
mas não inicializa nenhum dos elementos. Isso abriria um buraco muito
grande nas garantias de integridade se você criasse uma lista de um tipo
não anulável e, em seguida, acessasse um elemento.
Para evitar isso, removemos o construtor inteiramente. É um erro chamar
List() um código seguro nulo, mesmo com um tipo anulável. Isso soa
assustador, mas na prática a maioria de código cria listas usando a lista
literais, List.filled() , List.generate() , ou como resultado de transformar
alguma outra coleção. Para o caso extremo em que você deseja criar uma
lista vazia de algum tipo, adicionamos um novo construtor List.empty() .
O padrão de criar uma lista completamente não inicializada sempre
pareceu deslocado no Dart, e agora é ainda mais estranho. Se o código
foi quebrado por isso, você sempre pode corrigi-lo usando uma das muitas
outras maneiras de produzir uma lista.

Não é possível definir um comprimento maior em listas


não anuláveis
Isso é pouco conhecido, mas o length getter on List também tem um
setter correspondente. Você pode definir o comprimento para um valor
menor para truncar a lista. E você também pode configurá-lo com um
comprimento maior para preencher a lista com elementos não
inicializados.
Se você fizesse isso com uma lista de um tipo não anulável, você violaria
a integridade ao acessar posteriormente esses elementos não escritos.
Para evitar isso, o configurador length lançará uma exceção de tempo de
execução se (e somente se) a lista tiver um tipo de elemento não anulável
e você definir um comprimento maior. Ainda não há problema em truncar
listas de todos os tipos, e você pode aumentar lista de tipos anuláveis.
Há uma consequência importante disso se você definir seus próprios tipos
de lista que se estendem ListBase ou se aplicam ListMixin . Ambos os
tipos fornecem uma implementação insert() daquele espaço previamente
criado para o elemento inserido, definindo o comprimento. Isso falharia
com segurança nula, então, em vez disso, alteramos a implementação de
insert() in ListMixin (que ListBase compartilha) para chamar add() . Sua
classe de lista personalizada deve fornecer uma definição de add() se
você deseja ser capaz de usar esse método insert() herdado.

Não é possível acessar Iterator.current antes ou depois


da iteração
A classe Iterator é a classe “cursor” mutável usada para percorrer os
elementos de um tipo que implementa Iterable . Espera-se que você
chame moveNext() antes de acessar qualquer elemento para avançar
para o primeiro elemento. Quando esse método retorna false , você
alcançou o fim e não há mais elementos.
Costumava ser current retornado null se você o chamasse antes de
chamar moveNext() pela primeira vez ou após o término da iteração. Com
segurança nula, isso exigiria o tipo de retorno de current ser E? e não E .
Isso, por sua vez, significa que cada acesso de elemento exigiria uma null
verificação de tempo de execução.
Essas verificações seriam inúteis, visto que quase ninguém acessa o
elemento atual dessa maneira errônea. Em vez disso, criamos o tipo de
current ser E . Como pode haver um valor desse tipo disponível antes ou
depois da iteração, deixamos o comportamento do iterador indefinido se
você o chamar quando não deveria. A maioria das implementações de
Iterator lança um StateError .
Resumo
Esse é um tour muito detalhado por todas as alterações de linguagem e
biblioteca em torno da segurança nula. É um monte de coisas, mas essa é
uma grande mudança de linguagem. Mais importante, queríamos chegar a
um ponto em que o Dart ainda parecesse coeso e usável. Isso requer a
mudança não apenas do sistema de tipos, mas de uma série de outros
recursos de usabilidade em torno dele. Não queríamos que parecesse que
a segurança nula estava aparafusada.
Os pontos principais a serem aprendidos são:
Os tipos não são anuláveis por padrão e tornam-se anuláveis pela
adição ? .
Os parâmetros opcionais devem ser anuláveis ou ter um valor
padrão. Você pode usar required para tornar os parâmetros
nomeados não opcionais. Variáveis de nível superior não anuláveis e
campos estáticos devem ter inicializadores. Os campos de instância
não anuláveis devem ser inicializados antes do início do corpo do
construtor.
Cadeias de métodos após operadores com reconhecimento de nulos
entrarem em curto-circuito se o receptor estiver null . Existem novos
operadores cascade ( ?.. ) e index ( ?[] ) com reconhecimento de
nulos. O operador "bang" da asserção nula pós-fixada ( ! ) converte
seu operando anulável para o tipo não anulável subjacente.
A análise de fluxo permite que você transforme com segurança
variáveis locais anuláveis e parâmetros em não anuláveis utilizáveis.
A nova análise de fluxo também tem regras mais inteligentes para
promoção de tipo, devoluções perdidas, código inacessível e
inicialização de variável.
O modificador late permite que você use tipos não anuláveis e final
em locais que, de outra forma, não seria possível, às custas da
verificação do tempo de execução. Ele também fornece campos com
inicialização lenta.
A classe List é alterada para evitar elementos não inicializados.
Finalmente, depois de absorver tudo isso e colocar seu código no mundo
da segurança nula, você obtém um programa de som que os
compiladores podem otimizar e onde cada lugar em que um erro de tempo
de execução pode ocorrer é visível em seu código. Esperamos que você
sinta que vale o esforço para chegar lá.
Language: Null Safety: Segurança nula não
sonora
Um programa Dart pode conter algumas bibliotecas que são null safe e
outras que não são. Esses programas de versão mista são executados
com segurança nula inadequada.
A capacidade de misturar versões de idioma libera os mantenedores do
pacote para migrar seu código, com o conhecimento de que até mesmo
usuários legados podem obter novas correções de bugs e outras
melhorias. No entanto, os programas de versão mista não obtêm todas as
vantagens que a segurança nula pode trazer.
Esta página descreve as diferenças entre segurança nula sonora e não
sólida, com o objetivo de ajudá-lo a decidir quando migrar para segurança
nula. Após a discussão conceitual, há instruções para migrar de forma
incremental, seguidas por detalhes sobre como testar e executar
programas de versão mista.
Nota: Recomendamos que, se possível, você aguarde a migração das
dependências antes de migrar seu pacote. Para obter detalhes, consulte o
guia de migração .
Segurança Nula Sã E Doentia
O Dart fornece segurança nula sólida por meio de uma combinação de
verificações estáticas e de tempo de execução. Cada biblioteca Dart que
opta por segurança nula obtém todas as verificações estáticas, com erros
de tempo de compilação mais rígidos. Isso é verdadeiro mesmo em um
programa de versão mista que contém bibliotecas nulas não seguras.
Você começa a obter esses benefícios assim que começar a migrar
alguns de seus códigos para segurança nula.
No entanto, um programa de versão mista não pode ter as garantias de
solidez de execução que um aplicativo totalmente seguro para nulos
possui. É possível null vazar das bibliotecas nulas não seguras para o
código nulo seguro, porque impedir isso quebraria o comportamento
existente do código não migrado.
Para manter a compatibilidade do tempo de execução com bibliotecas
legadas, ao mesmo tempo que oferece solidez a programas totalmente
seguros para nulos, as ferramentas Dart suportam dois modos:
Programas de versão mista são executados com segurança nula
incorreta . É possível que null erros de referência ocorram em tempo
de execução, mas apenas porque um null tipo ou anulável escapou
de alguma biblioteca nula não segura e entrou no código nulo
seguro.
Quando um programa é totalmente migrado e todas as suas
bibliotecas são null safe, ele é executado com segurança nula
sólida , com todas as garantias e otimizações do compilador que a
solidez permite.
Segurança nula sonora é o que você deseja, se possível. As ferramentas
Dart executam automaticamente o seu programa no modo de som se a
biblioteca principal do entrypoint do seu programa optou pela segurança
nula. Se você importar uma biblioteca nula-insegura, as ferramentas
imprimirão um aviso para informá-lo de que só podem ser executadas com
segurança nula inadequada .
Migrando Incrementalmente
Como o Dart oferece suporte a programas de versão mista, você pode
migrar uma biblioteca (geralmente um arquivo Dart) por vez, enquanto
ainda consegue executar o programa e seus testes. Se você deseja
migrar seu pacote arquivo por arquivo, a ferramenta de migração pode
ajudar, mas você precisará fazer mais manualmente.
Recomendamos que você primeiro migre as bibliotecas de folhas -
bibliotecas que não importam outros arquivos do pacote. Em seguida,
migre as bibliotecas que dependem diretamente das bibliotecas folha.
Termine migrando as bibliotecas que têm mais dependências dentro do
pacote.
Por exemplo, digamos que você tenha um lib/src/util.dart arquivo que
importa outros pacotes (null-safe) e bibliotecas centrais, mas que não tem
nenhuma diretiva import '<local_path>' . Considere migrar util.dart primeiro e
depois migrar os arquivos que dependem apenas do util.dart . Se alguma
biblioteca tiver importações cíclicas (por exemplo, A importa B que importa
C e C importa A), considere migrar essas bibliotecas juntas.
Para migrar um pacote manualmente, siga estas etapas:
1. Edite o pubspec.yaml arquivo do pacote, definindo a restrição
mínima do SDK para 2.12.0-0 :

2. Gere novamente o arquivo de configuração do pacote:

A execução dart pub get com uma restrição SDK inferior de 2.12.0-0
define a versão de idioma padrão de cada biblioteca no pacote para
2.12, optando por segurança nula.
3. Abra o pacote em seu IDE. Você provavelmente verá muitos erros
de análise. Isso está ok.
4. Adicione um comentário sobre a versão do idioma na parte superior
de todos os arquivos DART que você não deseja considerar durante
a migração atual:
Usar a versão 2.9 da linguagem para uma biblioteca que está em
um pacote 2.12 pode reduzir os erros de análise (rabiscos
vermelhos) provenientes de código não migrado. No entanto, a
segurança nula inadequada reduz as informações que o analisador
pode usar. Por exemplo, o analisador pode presumir que um tipo de
parâmetro não é anulável, embora um arquivo 2.9 possa passar um
valor nulo.
5. Migre o código de cada arquivo Dart, usando o analisador para
identificar erros estáticos.
Eliminar erros estáticos adicionando ? , ! , required , e late , conforme
necessário.
Testar Ou Executar Programas De Versão
Mista
Para testar ou executar o código de versão mista, você precisa desativar
a segurança de som nulo. Você pode fazer isso de duas maneiras:
Desative o som de segurança nula usando o --no-sound-null-safety
sinalizador para o comando dart ou flutter :

Como alternativa, defina a versão do idioma no ponto de entrada - o


arquivo que contém a main()função - para 2.9. Em aplicativos Flutter,
esse arquivo costuma ser nomeado lib/main.dart. Em aplicativos de
linha de comando, esse arquivo costuma ser nomeado
bin/<packageName>.dart. Você também pode desativar arquivos em test,
porque eles também são pontos de entrada. Exemplo:

Desativar os testes usando qualquer um desses mecanismos pode ser útil


para testar durante o processo de migração incremental, mas fazer isso
significa que você não está testando seu código com segurança nula total
habilitada. É importante ativar seus testes de volta para a segurança nula
quando você terminar a migração incremental de suas bibliotecas.
Language: Null Safety: Perguntas
Frequentes
Esta página coleta algumas perguntas comuns que ouvimos sobre
segurança nula com base na experiência de migração do código interno
do Google.
Quais Alterações De Tempo De Execução,
Devo Estar Ciente Para Usuários De Código
Migrado?
A maioria dos efeitos da migração não afeta imediatamente os usuários
do código migrado:
As verificações de segurança estáticas nulas para usuários são
aplicadas primeiro quando eles migram seu código.
As verificações de segurança nulas completas acontecem quando
todo o código é migrado e o modo de som está ativado.
Duas exceções a serem observadas são:
O ! operador é uma verificação nula de tempo de execução em todos
os modos, para todos os usuários. Portanto, ao migrar, certifique-se
de adicionar apenas ! onde é um erro um null fluir para esse local,
mesmo que o código de chamada ainda não tenha migrado.
As verificações de tempo de execução associadas à late palavra -
chave se aplicam a todos os modos, para todos os usuários. Apenas
marque um campo late se tiver certeza de que ele sempre será
inicializado antes de ser usado.
E Se Um Valor Estiver Apenas null Em
Testes?
Se um valor estiver apenas null em testes, o código pode ser melhorado
marcando-o como não anulável e fazendo com que os testes passem em
valores não nulos.
Como se @required Compara À Nova
required Palavra-Chave?
A @required anotação marca os argumentos nomeados que devem ser
passados; se não, o analisador relata uma dica.
Com segurança nula, um argumento nomeado com um tipo não anulável
deve ter um padrão ou ser marcado com a nova required palavra-chave.
Caso contrário, não faria sentido ser não anulável, porque o padrão seria
null quando não fosse aprovado.
Quando o código seguro nulo é chamado a partir do código legado, a
required palavra-chave é tratada exatamente como a @required anotação: a
falha em fornecer o argumento causará uma dica do analisador.
Quando o código seguro nulo é chamado a partir do código seguro nulo, a
falha em fornecer um required argumento é um erro.
O que isso significa para a migração? Tenha cuidado ao adicionar required
onde não havia @required antes. Qualquer chamador que não passar o
argumento recém-exigido não será mais compilado. Em vez disso, você
pode adicionar um padrão ou tornar o tipo de argumento anulável.
Como Devo Migrar Campos Não Anuláveis
Que Deveriam Ser final , Mas Não São?
Alguns cálculos podem ser movidos para o inicializador estático. Ao invés
de:

você pode fazer:

No entanto, se um campo é inicializado fazendo cálculos no construtor,


então não pode ser final . Com segurança nula, você descobrirá que isso
também torna mais difícil ser não anulável; se for inicializado tarde
demais, será null até que seja inicializado e deve ser anulável. Felizmente,
você tem opções:
Transforme o construtor em uma fábrica e, a seguir, delegue a um
construtor real que inicializa todos os campos diretamente. Um nome
comum para um construtor tais privada é apenas um sublinhado: _ .
Então, o campo pode ser final e não anulável. Essa refatoração pode
ser feita antes da migração para segurança nula.
Ou marque o campo late final . Isso garante que ele seja inicializado
exatamente uma vez. Ele deve ser inicializado antes de poder ser
lido.
Como Devo Migrar Uma built_value
Classe?
Os getters que foram anotados @nullable devem ter tipos anuláveis; em
seguida, remova todas as @nullable anotações. Por exemplo:

torna-se

Getters que foram não marcados @nullable devem não ter tipos
anuláveis, mesmo se a ferramenta de migração os sugerir. Adicione
! dicas conforme necessárias e execute novamente a análise.
Como Devo Migrar Uma Fábrica Que Posso
Retornar null ?
Prefira fábricas que não retornem nulos. Vimos um código que pretendia
lançar uma exceção devido a uma entrada inválida, mas acabou
retornando nulo.
Ao invés de:

Faz:

Se a intenção da fábrica era realmente retornar nulo, você pode


transformá-lo em um método estático para que seja permitido retornar
null .
Como Devo Migrar Um assert(x != null)
Que Agora Parece Desnecessário?
A declaração será desnecessária quando tudo estiver totalmente migrado,
mas por enquanto é necessário se você realmente deseja manter a
verificação. Opções:
Decida que a afirmação não é realmente necessária e remova-a.
Esta é uma mudança no comportamento quando as declarações são
ativadas.
Decida que a afirmação pode ser verificada sempre e transforme-a
em ArgumentError.checkNotNull . Esta é uma mudança no
comportamento quando as afirmações não estão habilitadas.
Mantenha o comportamento exatamente como está: adicione //
ignore: unnecessary_null_comparison para ignorar o aviso.
Como Devo Migrar Uma Verificação Nula De
Tempo De Execução Que Agora Mostra
Como Desnecessária?
Uma verificação explícita de nulo em tempo de execução, por exemplo if
(arg == null) throw ArgumentError(...) , será sinalizada como uma
comparação desnecessária se você tornar arg não anulável.
Mas, a verificação ainda é necessária se o programa for uma versão
mista. Até que tudo seja totalmente migrado e o código alterne para
execução com segurança nula sonora, será possível arg ser nulo.
A maneira mais simples de preservar o comportamento é alterar o cheque
para ArgumentError.checkNotNull.
Ele se aplica a algumas verificações de tipo de tempo de execução. Se
arg tiver tipo estático String , if (arg is! String) na verdade está verificando
se arg é null . Pode parecer que a migração para meios de segurança
nulos arg nunca pode ser null , mas pode ser null em segurança nula
doentia. Portanto, para preservar o comportamento, a verificação de nulo
deve permanecer.
O Iterable.firstWhere Método Não Aceita
Mais orElse: () => null .
Importe package:collection e use o método de extensão em
firstWhereOrNull vez de firstWhere .
Como Faço Para Lidar Com Atributos Que
Possuem Setters?
Ao contrário da late final sugestão acima, esses atributos não podem ser
marcados como finais. Frequentemente, os atributos configuráveis
também não têm valores iniciais, pois espera-se que sejam configurados
algum tempo depois.
Nesses casos, você tem duas opções:
Defina-o com um valor inicial. Muitas vezes, a omissão de um valor
inicial é por engano, e não deliberada.
Se você tiver certeza de que o atributo precisa ser definido antes de
acessado, marque-o como late .
AVISO: A palavra-chave late adiciona uma verificação de tempo de
execução. Se algum usuário ligar get antes de set , obterá um erro no
tempo de execução.
Como Posso Sinalizar Que O Valor De
Retorno De Um Mapa Não É Anulável?
O operador lookup em Map ( [] ) por padrão retorna um tipo anulável. Não
há como sinalizar para o idioma que o valor está garantido.
Nesse caso, você deve usar o operador bang ( ! ) para converter o valor
de volta para V:

Que será lançado se o mapa retornar nulo. Se você quiser um tratamento


explícito para esse caso:
Por Que O Tipo Genérico Em Minha Lista /
Mapa É Anulável?
Normalmente é um cheiro de código terminar com um código anulável
como este:

Isso significa que fooList pode conter valores nulos. Isso pode acontecer
se você estiver inicializando a lista com comprimento e preenchendo-a por
meio de um loop.
Se você está simplesmente inicializando a lista com o mesmo valor, você
deve usar o filled construtor.

Se você estiver definindo os elementos da lista por meio de um índice,


deverá usar a add função para construir a lista. Isso é menos sujeito a
erros e mais legível.
O Que Aconteceu Com O Construtor List
Padrão?
Você pode encontrar este erro:
The default 'List' constructor isn't available when null safety is enabled.
#default_list_constructor

O construtor de lista padrão preenche a lista com null , o que é um


problema.
Em List.filled(length, default) vez disso, mude para.
Estou Usando package:ffi E Obtenho Uma
Falha Dart_CObject_kUnsupported
Quando Migro. O Que Aconteceu?
As listas enviadas via ffi só podem ser List<dynamic> , não List<Object>
ou List<Object?> . Se você não alterou um tipo de lista explicitamente em
sua migração, um tipo ainda pode ter sido alterado devido às alterações
na inferência de tipo que acontecem quando você ativa a segurança nula.
A correção é criar explicitamente listas como List<dynamic> .
Language: Evolução da linguagem Dart
Esta página lista mudanças notáveis e adições à linguagem de
programação Dart. Se você quiser detalhes sobre o idioma atualmente
suportado, consulte o tour de idiomas ou as especificações do idioma.
Para usar um recurso de linguagem que foi introduzido após 2.0,
especifique as restrições do SDK que não são inferiores à versão quando
o recurso foi suportado pela primeira vez. Por exemplo, para usar métodos
de extensão, que eram suportados a partir de 2.7, o arquivo pubspec.yaml
pode ter 2.7.0 como a restrição inferior:
Mudanças Em Cada Versão
Dart 2.0
O Dart 2.0 implementou um novo sistema de tipo de som. Antes do Dart
2.0, os tipos não eram totalmente sólidos e o Dart dependia muito da
verificação de tipos em tempo de execução. O código Dart 1.x teve que
ser migrado para o Dart 2.

Dart 2.1
O Dart 2.1 adicionou suporte para conversão int-para-double , permitindo
aos desenvolvedores definir valores double usando literais inteiros. Esse
recurso removeu o incômodo de ser forçado a usar um literal double (por
exemplo, 4.0 ) quando o valor era conceitualmente um inteiro. No seguinte
código Flutter horizontal e vertical tipo double :

Dart 2.2
O Dart sempre deu suporte a listas e mapas literais, mas o Dart 2.2
adicionou suporte para conjuntos de literais:

Dart 2.3
O Dart 2.3 adicionou três operadores projetados para melhorar o código
que executa a manipulação de lista, como o código de IU declarativo.
O operador de propagação permite desempacotar os elementos de uma
lista em outra. No exemplo a seguir, a lista retornada por
buildMainElements() é descompactada na lista que está sendo passada
para o argumento children :
O operador coleção if permite adicionar elementos condicionalmente. O
exemplo a seguir adiciona um elemento FlatButton , a menos que esta
seja a última página:

A coleção para o operador permite construir elementos repetidos. O


exemplo a seguir adiciona um elemento HeadingAction para cada seção
em sections :

Dart 2.5
O Dart 2.5 não adicionou nenhum recurso à linguagem Dart, mas
adicionou suporte para chamar o código C nativo do código Dart usando
uma nova biblioteca central dart:ffi .

Dart 2.6
Dart 2.6 ainda não adicionou nenhum recurso para a linguagem Dart, mas
fez adicionar uma nova ferramenta, dart2native, para compilar o código
Dart para executáveis nativas.

Dart 2.7
O Dart 2.7 adicionou suporte para métodos de extensão, permitindo
adicionar funcionalidade a qualquer tipo - mesmo tipos que você não
controla - com a brevidade e a experiência de autocompletar de
chamadas de métodos regulares. Como a visualização técnica desse
recurso estava no 2.6, você pode usar métodos de extensão sem avisos
se especificar 2.6.0 ou uma versão posterior como a restrição inferior do
SDK.
O exemplo a seguir estende a classe String dart:core com um novo
método parseInt() :

Dart 2.8
O Dart 2.8 não adicionou nenhum recurso à linguagem Dart, mas continha
várias alterações preparatórias para garantir grande usabilidade e
desempenho relacionados à anulação no próximo recurso de segurança
nula.
Ele também continha uma ferramenta pub mais rápida e um novo
comando pub desatualizado.

Dart 2.9
O Dart 2.9 não adicionou nenhum recurso à linguagem Dart.

Dart 2.10
O Dart 2.10 não adicionou nenhum recurso à linguagem Dart, mas
adicionou uma ferramenta Dart expandida que é análoga à Flutter SDK
ferramenta Flutter .
Controle De Versão De Idioma
Um único Dart SDK pode suportar simultaneamente várias versões da
linguagem Dart. O compilador determina a versão que o código tem como
objetivo e interpreta o código de acordo com essa versão.
O controle de versão de idioma é importante nas raras ocasiões em que o
Dart introduz um recurso incompatível como segurança nula. O código que
costumava compilar de forma limpa antes da segurança nula (mas talvez
travar no tempo de execução) pode não ser mais compilado quando a
segurança nula é ativada. Como a migração de seus aplicativos e pacotes
- e de todos os pacotes dos quais eles dependem - para segurança nula
pode demorar um pouco, o Dart usa o controle de versão de idioma para
oferecer suporte ao código seguro não nulo junto com o código seguro
nulo.
Cada pacote tem uma versão de idioma padrão, igual à <major>.<minor>
parte da restrição inferior do SDK no pubspec. Por exemplo, a seguinte
entrada em um arquivo pubspec.yaml indica que este pacote usa a versão
de idioma Dart 2.7.

Números da versão do idioma


As versões de idioma do Dart são identificadas por um número principal e
secundário que correspondem aos dois primeiros componentes do SDK
do DART. Por exemplo, a versão de idioma mais recente compatível com
o 2.7.3 Dart SDK é o Dart 2.7. Cada Dart SDK suporta todas as versões
de idioma cobertas por seu número de versão principal. Isso significa que
o 2.7.3 Dart SDK oferece suporte às versões de idioma 2.7, 2.6, 2.5 e
assim por diante, até 2.0.
Derivar a versão do idioma da versão do SDK implica o seguinte:
Sempre que uma versão secundária do SDK é enviada, uma nova
versão do idioma é exibida. Na prática, muitas dessas versões de
idioma são muito semelhantes e totalmente compatíveis com as
versões anteriores. Por exemplo, a linguagem Dart 2.9 é
essencialmente idêntica à linguagem Dart 2.8.
Quando uma versão de patch do SDK é enviada, ela não pode
apresentar nenhum recurso de linguagem. Por exemplo, como 2.7.2
é a versão 2.7 do idioma, ele deve ser totalmente compatível com
2.7.1 e 2.7.0.

Seleção de versão de idioma por biblioteca


Por padrão, cada arquivo Dart em um pacote usa a mesma versão de
idioma - a versão de idioma indicada pela restrição SDK inferior no
pubspec. Às vezes, no entanto, um arquivo DART pode precisar usar uma
versão de idioma mais antiga. Por exemplo, você pode não conseguir
migrar todos os arquivos em um pacote para segurança nula ao mesmo
tempo.
O compilador Dart 2.8 introduziu suporte para seleção de versão de
idioma por biblioteca. Uma biblioteca Dart pode optar por ter uma versão
em um idioma diferente usando um comentário no seguinte formato:

Por exemplo:

A string @dart deve estar em um // comentário (não /// ou /* ) e deve


aparecer antes de qualquer código Dart no arquivo. Espaços em branco
(tabulações e espaços) não importam, exceto dentro das @dart e strings
de versão. Como mostra o exemplo acima, outros comentários podem
aparecer antes do comentário @dart .
Para obter mais informações sobre como funciona o controle de versão
de idioma, consulte a especificação de controle de versão de idioma.
Language: Especificação da linguagem Dart
Use esta página para encontrar a especificação formal da linguagem Dart.
Para uma introdução mais suave ao Dart, consulte o tour de idiomas.
Dart 2
A especificação de idioma do Dart 2 está disponível em formato PDF:
Especificação formal (Dart 2.2)
Especificação mais recente em andamento (produzida a partir de
um arquivo LaTeX)
Os novos recursos de linguagem são normalmente descritos usando
especificações de recursos de linguagem informal no dart-lang / language
repo:
Propostas informais aceitas
Rascunhos de recursos potenciais
Dart 1.x
A especificação formal da linguagem Dart 1.x está disponível no site da
Ecma International:
Especificação da linguagem de programação Dart, 4ª edição
Language: Um Tour Pela Linguagem Dart
Esta página mostra como usar cada recurso principal do Dart, de
variáveis e operadores a classes e bibliotecas, presumindo que você já
saiba programar em outro idioma.
Para saber mais sobre as principais bibliotecas do Dart, consulte o tour
pela biblioteca. Sempre que quiser mais detalhes sobre um recurso de
idioma, consulte as especificações de idioma do Dart.
Um Programa Básico De Dart
O código a seguir usa muitos dos recursos mais básicos do Dart:

Aqui está o que este programa usa que se aplica a todos (ou quase
todos) os aplicativos Dart:
// This is a comment.
Um comentário de uma linha. O Dart também oferece suporte a
comentários de várias linhas e documentos. Para obter detalhes, consulte
os comentários.
void
Um tipo especial que indica um valor que nunca é usado. Funções como
printInteger() e main() que não retornam explicitamente um valor têm o
tipo void de retorno. Para obter mais informações, consulte este artigo.

int
Outro tipo, indicando um inteiro. Alguns adicionais tipos built-in são
String , List e bool .
42
Um número literal. Literais de número são um tipo de constante de tempo
de compilação.
print()
Uma maneira prática de exibir a saída.
'...' (ou "..." )
Um literal de string.
$ variableName (ou) ${ expression }
Interpolação de string: incluindo uma variável ou equivalente de string de
expressão dentro de um literal de string. Para obter mais informações,
consulte Strings .
main()
A função especial de nível superior necessária onde a execução do
aplicativo começa. Para obter mais informações, consulte A função main().
var
Uma maneira de declarar uma variável sem especificar seu tipo.
Conceitos Importantes
Conforme você aprende sobre a linguagem Dart, mantenha estes fatos e
conceitos em mente:
Tudo o que você pode colocar em uma variável é um objeto, e cada
objeto é uma instância de uma classe. Mesmo números, funções e
null são objetos. Todos os objetos são herdados da classe Object.
Embora o Dart seja fortemente tipado, as anotações de tipos são
opcionais porque o Dart pode inferir tipos. No código acima, number
infere-se que é do tipo int . Quando você quiser dizer explicitamente
que nenhum tipo é esperado, use o tipo especial dynamic.
O Dart suporta tipos genéricos, como List<int> (uma lista de inteiros)
ou List<dynamic> (uma lista de objetos de qualquer tipo).
O Dart oferece suporte a funções de nível superior (como main() ),
bem como funções vinculadas a uma classe ou objeto ( métodos
estáticos e de instância, respectivamente). Você também pode criar
funções dentro de funções (funções aninhadas ou locais).
Da mesma forma, o Dart suporta variáveis de nível superior, bem
como variáveis vinculadas a uma classe ou objeto (variáveis
estáticas e de instância). Variáveis de instância às vezes são
conhecidas como campos ou propriedades.
Ao contrário de Java, Dart não tem as palavras-chave public ,
protected e private . Se um identificador começa com um sublinhado
(_), ele é privado para sua biblioteca. Para obter detalhes, consulte
Bibliotecas e visibilidade.
Os identificadores podem começar com uma letra ou sublinhado (_),
seguido por qualquer combinação desses caracteres mais dígitos.
O Dart tem expressões (que têm valores de tempo de execução) e
instruções (que não têm). Por exemplo, a expressão condicional
condition ? expr1 : expr2 tem um valor de expr1 ou expr2 . Compare
isso com uma instrução if-else , que não tem valor. Uma instrução
geralmente contém uma ou mais expressões, mas uma expressão
não pode conter diretamente uma instrução.
As ferramentas Dart podem relatar dois tipos de problemas: avisos e
erros. Os avisos são apenas indicações de que seu código pode não
funcionar, mas não impedem que seu programa seja executado. Os
erros podem ser em tempo de compilação ou em tempo de
execução. Um erro em tempo de compilação impede que o código
seja executado; um erro em tempo de execução resulta em uma
exceção sendo levantada enquanto o código é executado.
Palavras-Chave
A tabela a seguir lista as palavras que a linguagem Dart trata
especialmente.
abstract 2 else import 2 super
2
as enum in switch
assert export 2 interface 2 sync 1
async 1 extends is this
3 2 2
await extension library throw
break external 2 mixin 2 true
2
case factory new try
catch false null typedef 2
class final on 1 var
2
const finally operator void
2
continue for part while
2 2
covariant Function rethrow with
2
default get return yield 3
deferred 2 hide 1 set 2
do if show 1
dynamic 2 implements 2 static 2

EVITAR usar essas palavras como identificadores. No entanto, se


necessário, as palavras-chave marcadas com sobrescritos podem ser
identificadores:
Palavras com o sobrescrito 1 são palavras-chave contextuais ,
que têm significado apenas em lugares específicos. Eles são
identificadores válidos em todos os lugares.
Palavras com o sobrescrito 2 são identificadores embutidos . Para
simplificar a tarefa de portar o código JavaScript para o Dart, essas
palavras-chave são identificadores válidos na maioria dos lugares,
mas não podem ser usados como nomes de classe ou tipo, ou como
prefixos de importação.
Palavras com o sobrescrito 3 são palavras reservadas mais novas
e limitadas relacionadas ao suporte de assincronia que foi
adicionado após o lançamento 1.0 do Dart. Você não pode usar
await ou yield como um identificador em qualquer corpo da função
marcada com async , async* ou sync* .
Todas as outras palavras da tabela são palavras reservadas , que não
podem ser identificadores.
Variáveis
Aqui está um exemplo de criação de uma variável e inicializá-la:

Variáveis armazenam referências. A variável chamada name contém uma


referência a um objeto String com o valor “Bob”.
O tipo da variável name é considerado String , mas você pode alterar
esse tipo especificando-o. Se um objeto não estiver restrito a um único
tipo, especifique o tipo Object ou dynamic , seguindo as diretrizes de
design.

Outra opção é declarar explicitamente o tipo que seria inferido:

Valor padrão
Variáveis não inicializadas têm um valor inicial de null . Mesmo as
variáveis com tipos numéricos são inicialmente nulas, porque os números
- como tudo o mais no Dart - são objetos.

Final e constante
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. (Variáveis const são implicitamente finais.) Uma variável final
de nível superior ou classe é inicializada na primeira vez em que é usada.

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

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

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 da classe,
marque-a static const . Onde você declara a variável, defina o valor como
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:

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.

Você pode omitir a expressão de inicialização const de uma declaração


const , como baz acima. Para obter detalhes, consulte não use const
redundantemente.
Você pode alterar o valor de uma variável não final e não const, mesmo
se ela costumava ter um valor const :

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

A partir do Dart 2.5, você pode definir constantes que usam verificações e
conversões de tipo ( is e as ), operadores de coleta se e propagação (. .. e
...? ):

Para obter mais informações sobre const como criar valores constantes,
consulte Listas, Mapas e Classes.
Tipos Integrados
A linguagem Dart tem suporte especial para os seguintes tipos:
números
strings
booleanos
listas (também conhecidas como matrizes)
conjuntos
mapas
runas (para expressar caracteres Unicode em uma string)
símbolos
Você pode inicializar um objeto de qualquer um desses tipos especiais
usando um literal. Por exemplo, 'this is a string' é um literal de string e
true é um literal booleano.
Como cada variável no Dart se refere a um objeto - uma instância de uma
classe - você geralmente pode usar construtores para inicializar variáveis.
Alguns dos tipos integrados têm seus próprios construtores. Por exemplo,
você pode usar construtor o Map() para criar um mapa.

Números
Os números de Dart vêm em dois sabores:
int
Valores inteiros não maiores que 64 bits, dependendo da plataforma. No
Dart VM, os valores podem ser de -2 63 a 2 63-1. O Dart compilado para
JavaScript usa números JavaScript, permitindo valores de -2 53 a 2 53-1.
double
Números de ponto flutuante de 64 bits (precisão dupla), conforme
especificado pelo padrão IEEE 754.
Ambos int e double são subtipos de num . O tipo num inclui operadores
básicos, tais como +, -, / e *, e também é onde você vai encontrar abs() ,
ceil() e floor() , entre outros métodos. (Operadores bit a bit, como >>, são
definidos na classe int .) Se num e seus subtipos não tiverem o que você
está procurando, a biblioteca dart:math pode.
Os inteiros são números sem ponto decimal. Aqui estão alguns exemplos
de definição de literais inteiros:
Se um número inclui um decimal, é um double . Aqui estão alguns
exemplos de definição de literais double :

A partir do Dart 2.1, literais inteiros são automaticamente convertidos em


duplos quando necessário:

Veja como você transforma uma string em um número, ou vice-versa:

O tipo int especifica os operadores tradicionais de deslocamento bit a bit


(<<, >>), AND (&) e OR (|). Por exemplo:

Os números literais são constantes de tempo de compilação. Muitas


expressões aritméticas também são constantes de tempo de compilação,
desde que seus operandos sejam constantes de tempo de compilação
que resultam em números.
Strings
Uma string Dart é 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 ${expression} . Se a expressão for um identificador, você pode
pular a {} . Para obter a string correspondente a um objeto, Dart chama o
método do objeto toString()

Você pode concatenar strings usando literais de string adjacentes ou o


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

Você pode criar uma string “bruta” prefixando-a com r :

Consulte Runas e grupos de grafemas para obter detalhes sobre como


expressar caracteres Unicode em uma string.
Strings literais são constantes de tempo de compilação, desde que
qualquer expressão interpolada seja uma constante de tempo de
compilação avaliada como nula ou um valor numérico, string ou booleano.

Para obter mais informações sobre o uso de strings, consulte Strings e


expressões regulares.
Booleanos
Para representar valores booleanos, o Dart tem um tipo denominado
bool . Apenas dois objetos têm tipo bool : os literais booleanos true e
false , que são constantes de tempo de compilação.
A segurança de tipos do Dart significa que você não pode usar códigos
como if (nonbooleanValue) ou assert (nonbooleanValue) . Em vez disso,
verifique explicitamente os valores, como este:

Listas
Talvez a coleção mais comum em quase todas as linguagens de
programação seja a matriz, ou grupo ordenado de objetos. No Dart,
matrizes são objetos List , então a maioria das pessoas os chama de
listas.
Literais de lista de DART parecem literais de listas JavaScript. Aqui está
uma lista simples de Dart:

Você pode adicionar uma vírgula após o último item em um literal de


coleção Dart. Essa vírgula final não afeta a coleção, mas pode ajudar a
evitar erros de copiar e colar.
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 e consultar os valores da lista, assim como faria
em JavaScript:

Para criar uma lista que é uma constante de tempo de compilação,


adicione const antes do literal da lista:

O Dart 2.3 introduziu o operador de propagação ( ... ) e o operador de


propagação ciente de nulos ( ...? ), que fornecem uma maneira concisa
de inserir vários valores em uma coleção.
Por exemplo, você pode usar o operador spread ( ... ) para inserir todos
os valores de uma lista em outra lista:

Se a expressão à direita do operador de propagação pode ser nula, você


pode evitar exceções usando um operador de propagação com
reconhecimento de nulo ( ...? ):
Para mais detalhes e exemplos de uso do operador de spread, consulte a
proposta do operador de spread.
O Dart 2.3 também introduziu coleção if e coleção for , que você pode
usar para construir coleções usando condicionais ( if ) e repetições ( for ).
Aqui está um exemplo de como usar a coleção if para criar uma lista
com três ou quatro itens nela:

Aqui está um exemplo de uso da coleção for para manipular os itens de


uma lista antes de adicioná-los a outra lista:

Para mais detalhes e exemplos de como usar a coleção if e for , consulte


a proposta de coleção de fluxo de controle.
O tipo List possui muitos métodos úteis para manipular listas. Para obter
mais informações sobre listas, consulte Genéricos e Coleções.

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

Aqui está um conjunto Dart simples, criado usando um conjunto literal:


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

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


addAll() :

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

Para criar um conjunto que é uma constante de tempo de compilação,


adicione const antes do conjunto literal:
A partir do Dart 2.3, os conjuntos suportam operadores de propagação ( ...
e ...? ) e coleção ifs e fors, assim como as listas. Para obter mais
informações, consulte as discussões do operador de difusão de lista e
operador de coleção de lista.
Para obter mais informações sobre conjuntos, consulte Genéricos e
Conjuntos.

Mapas
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 de DART para mapas é fornecido por literais de mapa e o tipo
de mapa.
Aqui estão alguns mapas Dart simples, criados usando literais de mapa:

Você pode criar os mesmos objetos usando um construtor de mapa:

Adicione um novo par de valores-chave a um mapa existente, assim como


faria em JavaScript:

Recupere um valor de um mapa da mesma forma que faria em


JavaScript:
Se você procurar uma chave que não está em um mapa, receberá um
valor nulo em troca:

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

Para criar um mapa que é uma constante de tempo de compilação,


adicione const antes do literal do mapa:

A partir do Dart 2.3, os mapas suportam operadores de propagação ( ... e


...? ) e coleção se é para, assim como as listas. Para detalhes e exemplos,
consulte a proposta do operador de spread e a proposta de cobrança do fluxo de
controle.
Para obter mais informações sobre mapas, consulte Genéricos e Mapas.

Grupos de runas e grafemas


No Dart, as runas expõem os pontos de código Unicode de uma string. No
Dart 2.6, use o pacote de caracteres para visualizar ou manipular caracteres
percebidos pelo usuário, também conhecidos como clusters de grafemas
Unicode (estendidos).
O Unicode define um valor numérico exclusivo para cada letra, dígito e
símbolo usado em todos os sistemas de escrita do mundo. Como uma
string Dart é uma sequência de unidades de código UTF-16, expressar
pontos de código Unicode em uma string requer sintaxe especial. A
maneira usual de expressar um ponto de código Unicode é \uXXXX , onde
XXXX é um valor hexadecimal de 4 dígitos. Por exemplo, o caractere de
coração ( ♥ ) é \u2665 . Para especificar mais ou menos de 4 dígitos
hexadecimais, coloque o valor entre colchetes. Por exemplo, o emoji
risonho ( �� ) é \u{1f606} .
Se você precisar ler ou gravar caracteres Unicode individuais, use o
characters getter definido em String pelo pacote de caracteres. O
Characters objeto retornado é a string como uma sequência de grupos de
grafemas. Aqui está um exemplo de uso da API de caracteres:

A saída, dependendo do seu ambiente, é mais ou menos assim:

Para obter detalhes sobre como usar o pacote de caracteres para


manipular strings, consulte o exemplo e a referência da API do pacote de
caracteres.

Símbolos
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 os nomes dos identificadores, mas não os
símbolos dos identificadores.
Para obter o símbolo de um identificador, use um literal de símbolo, que é
apenas # seguido pelo identificador:

Literais de símbolo são constantes de tempo de compilação.


Funções
O Dart é uma verdadeira linguagem orientada a objetos, então até
funções são objetos e têm o tipo, Função. 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. Para obter detalhes, consulte Classes
chamáveis.
Aqui está um exemplo de implementação de uma função:

Embora eficaz Dart recomenda anotações de tipo para APIs públicas, a


função ainda funcionará se você omitir os tipos:

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

A sintaxe => expr é uma abreviação de {return expr;} . A notação => às


vezes é chamada de sintaxe de seta.

Parâmetros
Uma função pode ter qualquer número de parâmetros posicionais
necessários. Eles podem ser seguidos por parâmetros nomeados ou por
parâmetros posicionais opcionais (mas não ambos).
Parâmetros nomeados
Os parâmetros nomeados são opcionais, a menos que sejam
especificamente marcados como obrigatórios.
Ao chamar uma função, você pode especificar parâmetros nomeados
usando paramName: value . Por exemplo:

Ao definir uma função, use: {param1, param2, …} para especificar os


parâmetros nomeados

Embora os parâmetros nomeados sejam um tipo de parâmetro opcional,


você pode anotá-los com @required para indicar que o parâmetro é
obrigatório - que os usuários devem fornecer um valor para o parâmetro.
Por exemplo:

Se alguém tentar criar um Scrollbar sem especificar o argumento child , o


analisador relatará um problema.
Para usar a anotação @required, necessitamos do pacote meta, importe
usando package:meta/meta.dart .
Parâmetros posicionais opcionais
Envolver um conjunto de parâmetros de função em [] marca-os como
parâmetros posicionais opcionais:
Aqui está um exemplo de chamada desta função sem o parâmetro
opcional:

E aqui está um exemplo de como chamar essa função com o terceiro


parâmetro:

Valores de parâmetro padrão


Sua função pode usar = para definir valores padrão para parâmetros
nomeados e posicionais. Os valores padrões devem ser constantes de
tempo de compilação. Se nenhum valor padrão for fornecido, o valor
padrão será null .
Aqui está um exemplo de configuração de valores padrão para
parâmetros nomeados:

O próximo exemplo mostra como definir valores padrão para parâmetros


posicionais:
Você também pode passar listas ou mapas como valores padrão. O
exemplo a seguir define uma função doStuff() , que especifica uma lista
padrão para o parâmetro list e um mapa padrão para o parâmetro gifts .

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 opcional List<String> para argumentos.
Aqui está um exemplo da função main() de um aplicativo da web:

Aqui está um exemplo de função main() para um aplicativo de linha de


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

Funciona como objetos de primeira classe


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

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

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

Funções anônimas
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 encerramento. 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:

O exemplo a seguir define uma função anônima com um parâmetro não


digitado item . A função, invocada para cada item da lista, imprime uma
string que inclui o valor no índice especificado.
O resultado do código é:

Se a função contém apenas uma instrução, você pode encurtá-la usando


a notação de seta. Cole a seguinte linha no DartPad e clique em Executar
para verificar se é funcionalmente equivalente.

O resultado do código é:
Âmbito lexical
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:

Observe como nestedFunction() pode usar variáveis de todos os níveis,


até o nível superior.
Fechamentos lexicais
Um fechamento é 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
circundantes. No exemplo a seguir, makeAdder() captura a variável
addBy . Onde quer que a função retornada vá, ela se lembra addBy .

Testando funções de 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:
Valores de retorno
Todas as funções retornam um valor. Se nenhum valor de retorno for
especificado, a instrução return null; será anexada implicitamente ao
corpo da função.
Operadores
O Dart oferece suporte aos operadores mostrados na tabela a seguir.
Você pode implementar muitos desses operadores como membros da classe.

Ao usar operadores, você cria expressões. Aqui estão alguns exemplos


de expressões de operador:
Na tabela de operadores, cada operador tem precedência mais alta do
que os operadores nas linhas seguintes. Por exemplo, o operador
multiplicativo % tem precedência mais alta do que (e, portanto, é
executado antes) o operador de igualdade == , que tem precedência mais
alta do que o operador lógico AND && . Essa precedência significa que as
duas linhas de código a seguir são executadas da mesma maneira:

Operadores aritméticos
O Dart suporta os operadores aritméticos usuais, conforme mostrado na
tabela a seguir.
Exemplo:

O Dart também oferece suporte a operadores de incremento e


decremento de prefixo e pós-correção.
Exemplo:

Operadores relacionais e de igualdade


A tabela a seguir lista os significados de operadores de igualdade e
relacionais.

Para testar se dois objetos x e y representam a mesma coisa, use o


operador. == (No caso raro em que você precisa saber se dois
objetos são exatamente o mesmo objeto, use a função identical().
Veja como o == operador funciona:
Se x ou y for nulo, retorna verdadeiro se ambos forem nulos e
falso se apenas um for nulo.
Retorne o resultado da invocação do método x.==(y) . (Isso
mesmo, operadores == como são métodos que são chamados
em seu primeiro operando. Para obter detalhes, consulte
Operadores.)
Aqui está um exemplo de uso de cada um dos operadores
relacionais e de igualdade:

Operadores de teste de tipo


Os operadores as , is e is ! são úteis para verificação de tipos em
tempo de execução.

O resultado de obj is T é verdadeiro se obj implementa a interface


especificada por T . Por exemplo, obj is Object é sempre verdade.
Use o operador as para converter um objeto em um tipo específico
se e somente se você tiver certeza de que o objeto é desse tipo.
Exemplo:

Se você não tiver certeza de que o objeto é do tipo T , use is T para


verificar o tipo antes de usar o objeto.
Operadores de atribuição
Como você já viu, você pode atribuir valores usando o operador = .
Para atribuir apenas se a variável atribuída for nula, use o operador
??= .

Operadores de atribuição compostos, como += combina uma


operação com uma atribuição.

Veja como funcionam os operadores de atribuição composta:

O exemplo a seguir usa operadores de atribuição e de atribuição


composta:
Operadores lógicos
Você pode inverter ou combinar expressões booleanas usando os
operadores lógicos.

Aqui está um exemplo de uso de operadores lógicos:

Operadores bit a bit e shift


Você pode manipular as partes individuais dos números no Dart.
Normalmente, você usaria esses operadores bit a bit e shift com
inteiros.

Aqui está um exemplo de uso de operadores bit a bit e shift:


Expressões condicionais
O Dart tem dois operadores que permitem avaliar de forma concisa
as expressões que, de outra forma, poderiam exigir instruções if-
else :
condition ? expr1 : expr2
Se a condição for verdadeira, avalia expr1 (e retorna seu valor); caso
contrário, avalia e retorna o valor de expr2.
expr1 ?? expr2
Se expr1 não for nulo, retorna seu valor; caso contrário, avalia e retorna o
valor de expr2.
Quando você precisar atribuir um valor com base em uma
expressão booleana, considere o uso ? .

Se a expressão booleana for nula, considere o uso ?? .

O exemplo anterior poderia ter sido escrito pelo menos de duas


outras maneiras, mas não de forma tão sucinta:
Notação em cascata (..)
Cascatas ( .. ) permite que você faça uma sequência de operações
no mesmo objeto. Além de chamadas de função, você também pode
acessar campos nesse mesmo objeto. Isso geralmente evita a etapa
de criação de uma variável temporária e permite que você escreva
um código mais fluido.
Considere o seguinte código:

A primeira chamada de método, querySelector() retorna um objeto


seletor. O código que segue a notação em cascata opera neste
objeto seletor, ignorando quaisquer valores subsequentes que
possam ser retornados.
O exemplo anterior é equivalente a:

Você também pode aninhar suas cascatas. Por exemplo:


Tenha o cuidado de construir sua cascata em uma função que
retorna um objeto real. Por exemplo, o seguinte código falha:

A chamada sb.write() retorna void , e você não pode construir uma


cascata void .

Outras operadoras
Você viu a maioria dos operadores restantes em outros exemplos:
Para mais informações sobre os operadores . , ?. , e .. , consulte
Classes.
Declarações De Fluxo De Controle
Você pode controlar o fluxo de seu código Dart usando qualquer um
dos seguintes:
if e else
for loops
while e do - while loops
break e continue
switch e case
assert
Você também pode afetar o fluxo de controle usando try-catch e
throw , conforme explicado em Exceções.

If e else
O Dart oferece suporte a instruções if com instruções opcionais
else , como mostra o próximo exemplo. Veja também as expressões
condicionais.

Ao contrário do JavaScript, as condições devem usar valores


booleanos, nada mais. Consulte Booleanos para obter mais
informações.

For loops
Você pode iterar com o loop for padrão. Por exemplo:
Os fechamentos dentro dos loops, for do Dart capturam o valor do
índice, evitando uma armadilha comum encontrada no JavaScript.
Por exemplo, considere:

A saída é 0 e então 1 , conforme o esperado. Em contraste, o


exemplo seria impresso 2 e, 2 em seguida, em JavaScript.
Se o objeto sobre o qual você está iterando for um Iterável, você
poderá usar o método forEach(). Usar forEach() é uma boa opção se
você não precisa saber o contador de iteração atual:

Classes iteráveis, como List e Set, também suportam a for-in forma


de iteração:

While e do-while
Um loop while avalia a condição antes do loop:

Um circuito do - while avalia a condição depois de o circuito:


Break e continue
Use break para parar o loop:

Use continue para pular para a próxima iteração de loop:

Você pode escrever esse exemplo de forma diferente se estiver


usando um Iterável, como uma lista ou conjunto:

Switch e case
As instruções de switch no Dart comparam constantes de tempo de
compilação, string ou inteiros usando == . Os objetos comparados
devem ser todas instâncias da mesma classe (e não de nenhum de
seus subtipos) e a classe não deve ser substituída == . Os tipos
enumerados funcionam bem em declarações switch .

Cada cláusula case não vazia termina com uma instrução break ,
como regra. Outras formas válidas para acabar com um não-vazia
cláusula case são uma continue , throw ou declaração return .
Use uma cláusula default para executar o código quando nenhuma
cláusula case corresponder:
O exemplo a seguir omite a instrução break em uma cláusula case ,
gerando um erro:

No entanto, o Dart suporta cláusulas case vazias, permitindo uma


forma de cair em:
Se você realmente deseja uma falha, pode usar uma declaração
continue e um rótulo:

Uma cláusula case pode ter variáveis locais, que são visíveis
apenas dentro do escopo dessa cláusula.

Assert
Durante o desenvolvimento, use uma instrução assert -
assert(condition, optionalMessage) ; - para interromper a execução
normalmente se uma condição booleana for falsa. Você pode
encontrar exemplos de declarações assert ao longo deste tour. Aqui
estão mais alguns:
Para anexar uma mensagem a uma afirmação, adicione uma string
como o segundo argumento para assert (opcionalmente com uma
vírgula no final):

O primeiro argumento para assert pode ser qualquer expressão que


resolva para um valor booleano. Se o valor da expressão for
verdadeiro, a afirmação é bem-sucedida e a execução continua. Se
for falso, a asserção falha e uma exceção (um AssertionError ) é
lançada.
Quando exatamente as asserções funcionam? Isso depende das
ferramentas e da estrutura que você está usando:
Flutter permite asserções no modo de depuração.
Ferramentas apenas de desenvolvimento, como dartdevc ,
normalmente habilitam asserções por padrão.
Algumas ferramentas, como o Dart e dart2js, afirmações de apoio
através de um sinalizador de linha de comando: --enable-asserts .
No código de produção, as asserções são ignoradas e os
argumentos para assert não são avaliados.
Exceções
Seu código Dart pode lançar e capturar exceções. As exceções são
erros que indicam que algo inesperado aconteceu. Se a exceção
não for detectada, o isolate que gerou a exceção é suspenso e,
normalmente, o isolate e seu programa são encerrados.
Em contraste com Java, todas as exceções do Dart são exceções
não verificadas. Os métodos não declaram quais exceções podem
lançar e você não é obrigado a capturar nenhuma exceção.
O Dart fornece tipos de exceção e erro, bem como vários subtipos
predefinidos. Você pode, é claro, definir suas próprias exceções. No
entanto, os programas Dart podem lançar qualquer objeto não nulo -
não apenas objetos Exception e Error - como uma exceção.

Throw
Aqui está um exemplo de lançamento ou aumento de uma exceção:

Você também pode lançar objetos arbitrários:

Como lançar uma exceção é uma expressão, você pode lançar


exceções em instruções => , bem como em qualquer outro lugar que
permita expressões:

Catch
Capturar, ou capturar, uma exceção interrompe a propagação da
exceção (a menos que você relançar a exceção). A captura de uma
exceção dá a você a chance de lidar com ela:

Para lidar com o código que pode lançar mais de um tipo de


exceção, você pode especificar várias cláusulas catch. A primeira
cláusula catch que corresponde ao tipo do objeto lançado trata a
exceção. Se a cláusula catch não especifica um tipo, essa cláusula
pode lidar com qualquer tipo de objeto lançado:

Como mostra o código anterior, você pode usar tanto on ou catch ou


ambos. Use on quando precisar especificar o tipo de exceção. Use
catch quando seu manipulador de exceção precisar do objeto de
exceção.
Você pode especificar um ou dois parâmetros para catch() . O
primeiro é a exceção que foi lançada e o segundo é o rastreamento
de pilha (um objeto StackTrace).
Para tratar parcialmente uma exceção, enquanto permite que ela se
propague, use a palavra-chave rethrow .

Finally
Para garantir que algum código seja executado, independentemente
de uma exceção ser lançada ou não, use uma cláusula finally . Se
nenhuma cláusula catch corresponder à exceção, a exceção será
propagada após a execução da cláusula finally :
A cláusula finally é executada após qualquer cláusula catch
correspondente:

Saiba mais lendo a seção Exceções do tour pela biblioteca.


Classes
Dart é uma linguagem orientada a objetos com classes e herança
baseada em mixin. Cada objeto é uma instância de uma classe e
todas as classes descendem de Object. A herança baseada em mixin
significa que embora cada classe (exceto Object) tenha exatamente
uma superclasse, um corpo de classe pode ser reutilizado em várias
hierarquias de classes. Os métodos de extensão são uma forma de
adicionar funcionalidade a uma classe sem alterar a classe ou criar
uma subclasse.

Usando membros da classe


Os objetos têm membros que consistem em funções e dados
(métodos e variáveis de instância, respectivamente). Ao chamar um
método, você o invoca em um objeto: o método tem acesso às
funções e aos dados desse objeto.
Use um ponto ( . ) para se referir a uma variável de instância ou
método:

Use ?. em vez de . para evitar uma exceção quando o operando


mais à esquerda for nulo:

Usando construtores
Você pode criar um objeto usando um construtor. Os nomes dos
construtores podem ser ClassName ou ClassName.identifier . Por
exemplo, o código a seguir cria objetos Point usando os
construtores Point() e Point.fromJson():

O código a seguir tem o mesmo efeito, mas usa a palavra-chave


opcional new antes do nome do construtor:

Algumas classes fornecem construtores constantes. Para criar uma


constante de tempo de compilação usando um construtor constante,
coloque a palavra-chave const antes do nome do construtor:

Construir duas constantes de tempo de compilação idênticas resulta


em uma única instância canônica:

Dentro de um contexto constante, você pode omitir o const antes de


um construtor ou literal. Por exemplo, observe este código, que cria
um mapa const:
Você pode omitir tudo, exceto o primeiro uso da palavra-chave
const :

Se um construtor constante estiver fora de um contexto constante e


for invocado sem const , ele criará um objeto não constante :

Obter o tipo de um objeto


Para obter o tipo de um objeto em tempo de execução, você pode
usar a propriedade objeto runtimeType , que retorna um objeto Type.

Até aqui, você viu como usar as classes. O restante desta seção
mostra como implementar classes.

Variáveis de instância
Veja como você declara variáveis de instância:
Todas as variáveis de instância não inicializadas têm o valor null .
Todas as variáveis de instância geram um método getter implícito.
Variáveis de instância não finais também geram um método setter
implícito. Para obter detalhes, consulte Getters e setters.

Se você inicializar uma variável de instância onde ela é declarada


(em vez de em um construtor ou método), o valor é definido quando
a instância é criada, que é antes do construtor e sua lista de
inicializadores serem executados.

Construtores
Declare um construtor criando uma função com o mesmo nome de
sua classe (mais, opcionalmente, um identificador adicional
conforme descrito em Construtores nomeados). A forma mais comum de
construtor, o construtor generativo, cria uma instância de uma
classe:
A palavra-chave this se refere à instância atual.

O padrão de atribuir um argumento de construtor a uma variável de


instância é tão comum que o Dart tem açúcar sintático para torná-lo
mais fácil:

Construtores padrão
Se você não declarar um construtor, um construtor padrão será
fornecido para você. O construtor padrão não tem argumentos e
invoca o construtor sem argumento na superclasse.
Construtores não são herdados
As subclasses não herdam construtores de sua superclasse. Uma
subclasse que não declara nenhum construtor possui apenas o
construtor padrão (sem argumento, sem nome).
Construtores nomeados
Use um construtor nomeado para implementar vários construtores
para uma classe ou para fornecer clareza extra:
Lembre-se de que os construtores não são herdados, o que
significa que o construtor nomeado de uma superclasse não é
herdado por uma subclasse. Se você quiser que uma subclasse seja
criada com um construtor nomeado definido na superclasse, você
deve implementar esse construtor na subclasse.
Invocar um construtor de superclasse não padrão
Por padrão, um construtor em uma subclasse chama o construtor
sem nome e sem argumento da superclasse. O construtor da
superclasse é chamado no início do corpo do construtor. Se uma
lista de inicializadores também estiver sendo usada, ela será executada
antes de a superclasse ser chamada. Em resumo, a ordem de
execução é a seguinte:
lista de inicializadores
construtor sem arg da superclasse
construtor sem arg da classe principal
Se a superclasse não tiver um construtor sem nome e sem
argumento, você deverá chamar manualmente um dos construtores
na superclasse. Especifique o construtor da superclasse após dois
pontos ( : ), logo antes do corpo do construtor (se houver).
Como os argumentos para o construtor da superclasse são
avaliados antes de invocar o construtor, um argumento pode ser
uma expressão, como uma chamada de função:
Lista de inicializadores
Além de invocar um construtor de superclasse, você também pode
inicializar variáveis de instância antes que o corpo do construtor seja
executado. Separe os inicializadores com vírgulas.

Durante o desenvolvimento, você pode validar as entradas usando


assert na lista de inicializadores.

As listas de inicializadores são úteis ao configurar os campos finais.


O exemplo a seguir inicializa três campos finais em uma lista de
inicializadores.
Redirecionando construtores
Às vezes, o único propósito de um construtor é redirecionar para
outro construtor na mesma classe. O corpo de um construtor de
redirecionamento está vazio, com a chamada do construtor
aparecendo depois de dois pontos (:).

Construtores constantes
Se sua classe produz objetos que nunca mudam, você pode fazer
esses objetos constantes de tempo de compilação. Para fazer isso,
defina um construtor const e certifique-se de que todas as variáveis
de instância sejam final .
Os construtores constantes nem sempre criam constantes. Para
obter detalhes, consulte a seção sobre como usar construtores.
Construtores de fábrica
Use a palavra-chave factory ao implementar um construtor que nem
sempre cria uma instância de sua classe. Por exemplo, um
construtor de fábrica pode retornar uma instância de um cache ou
pode retornar uma instância de um subtipo. Outro caso de uso para
construtores de fábrica é inicializar uma variável final usando uma
lógica que não pode ser tratada na lista de inicializadores.
No exemplo a seguir, o construtor Logger de fábrica retorna objetos
de um cache e o construtor Logger.fromJson de fábrica inicializa
uma variável final de um objeto JSON.
Invoque um construtor de fábrica como faria com qualquer outro
construtor:

Métodos
Métodos são funções que fornecem comportamento para um objeto.
Métodos de instância
Métodos de instância em objetos podem acessar variáveis de
instância e this . O método distanceTo() no exemplo a seguir é um
exemplo de método de instância:

Operadores
Operadores são métodos de instância com nomes especiais. O Dart
permite que você defina operadores com os seguintes nomes:

Uma declaração do operador é identificada usando o identificador


embutido operator . O exemplo a seguir define adição de vetor ( + )
e subtração ( - ):
Getters e setters
Getters e setters são métodos especiais que fornecem acesso de
leitura e gravação às propriedades de um objeto. Lembre-se de que
cada variável de instância tem um getter implícito, mais um setter,
se apropriado. Você pode criar propriedades adicionais
implementando getters e setters, usando as palavras-chave get e
set :
Com getters e setters, você pode começar com variáveis de
instância, depois envolvendo-as com métodos, tudo sem alterar o
código do cliente.

Métodos abstratos
Os métodos de instância, getter e setter podem ser abstratos,
definindo uma interface, mas deixando sua implementação para
outras classes. Os métodos abstratos só podem existir em classes
abstratas.
Para tornar um método abstrato, use um ponto-e-vírgula (;) em vez
do corpo do método:
Classes abstratas
Use o modificador abstract para definir uma classe abstrata - uma
classe que não pode ser instanciada. As classes abstratas são úteis
para definir interfaces, geralmente com alguma implementação. Se
você deseja que sua classe abstrata pareça ser instanciada, defina
um construtor de fábrica.
As classes abstratas geralmente possuem métodos abstratos. Aqui
está um exemplo de declaração de uma classe abstrata que possui
um método abstrato:

Interfaces implícitas
Cada classe define implicitamente uma interface contendo todos os
membros da instância da classe e de quaisquer interfaces que ela
implementa. Se você deseja criar uma classe A que suporte a API
da classe B sem herdar a implementação de B, a classe A deve
implementar a interface B.
Uma classe implementa uma ou mais interfaces declarando-as em
uma cláusula implements e fornecendo as APIs exigidas pelas
interfaces. Por exemplo:
Aqui está um exemplo de especificação de que uma classe
implementa várias interfaces:

Extensão de uma classe


Use extends para criar uma subclasse e super para se referir à
superclasse:
Substituindo membros
As subclasses podem substituir métodos de instância (incluindo
operadores), getters e setters. Você pode usar a anotação
@override para indicar que está substituindo intencionalmente um
membro:

Para restringir o tipo de um parâmetro de método ou variável de


instância em um código com segurança de tipos, você pode usar a
palavra-chave covariant.

noSuchMethod()
Para detectar ou reagir sempre que o código tentar usar um método
ou variável de instância inexistente, você pode substituir
noSuchMethod() :
Você não pode invocar um método não implementado, a menos
que uma das opções a seguir seja verdadeira:
O receptor é do tipo estático dynamic .
O receptor tem um tipo estático que define o método não
implementado (abstrato está OK), e o tipo dinâmico do receptor
tem uma implementação noSuchMethod() diferente daquela
da classe Object .
Para obter mais informações, consulte a especificação de
encaminhamento informal noSuchMethod.

Métodos de extensão
Os métodos de extensão, introduzidos no Dart 2.7, são uma forma
de adicionar funcionalidade às bibliotecas existentes. Você pode
usar métodos de extensão mesmo sem saber. Por exemplo, quando
você usa o autocompletar de código em um IDE, ele sugere
métodos de extensão junto com métodos regulares.
Aqui está um exemplo de uso de um método de extensão em String
nomeado parseInt() que é definido em string_apis.dart :

Para obter detalhes sobre como usar e implementar métodos de


extensão, consulte a página de métodos de extensão.

Tipos enumerados
Tipos enumerados, frequentemente chamados de enumerações ou
enums, são um tipo especial de classe usado para representar um
número fixo de valores constantes.
Usando enums
Declare um tipo enumerado usando a palavra-chave enum :

Você pode usar vírgulas finais ao declarar um tipo enumerado.


Cada valor em um enum tem um getter index , que retorna à
posição baseada em zero do valor na declaração de enum. Por
exemplo, o primeiro valor possui índice 0 e o segundo valor possui
índice 1.

Para obter uma lista de todos os valores no enum, use a constante


values do enum.

Você pode usar enums em instruções switch e receberá um aviso se


não manipular todos os valores de enum:

Os tipos enumerados têm os seguintes limites:


Você não pode criar subclasses, misturar ou implementar um
enum.
Você não pode instanciar explicitamente um enum.
Para obter mais informações, consulte as especificações da linguagem
Dart.

Adicionando recursos a uma classe: mixins


Mixins são uma forma de reutilizar o código de uma classe em
várias hierarquias de classes.
Para usar um mixin, use a palavra-chave with seguida por um ou
mais nomes mixin. O exemplo a seguir mostra duas classes que
usam mixins:

Para implementar um mixin, crie uma classe que estenda Object e


não declare nenhum construtor. A menos que você queira que seu
mixin possa ser usado como uma classe regular, use a palavra-
chave mixin ao invés de class . Por exemplo:
Às vezes você pode querer restringir os tipos que podem usar um
mixin. Por exemplo, o mixin pode depender de ser capaz de invocar
um método que o mixin não define. Como mostra o exemplo a
seguir, você pode restringir o uso de um mixin usando a palavra-
chave on para especificar a superclasse necessária:

No código anterior, apenas as classes que estendem ou


implementam a classe Musician podem usar o mixin
MusicalPerformer . Porque SingerDancer estende Musician ,
SingerDancer pode se misturar MusicalPerformer .
Nota de versão: o suporte para a palavra - chave mixin foi
introduzido no Dart 2.1. O código em versões anteriores geralmente
é usado abstract class . Para obter mais informações sobre as
alterações do mixin 2.1, consulte o changelog do Dart SDK e a
especificação do mixin 2.1.

Variáveis e métodos de classe


Use a palavra-chave static para implementar variáveis e métodos
em toda a classe.
Variáveis estáticas
Variáveis estáticas (variáveis de classe) são úteis para o estado e
constantes de toda a classe:

Variáveis estáticas não são inicializadas até que sejam usadas.

Métodos estáticos
Os métodos estáticos (métodos de classe) não operam em uma
instância e, portanto, não têm acesso a this . Por exemplo:
Você pode usar métodos estáticos como constantes de tempo de
compilação. Por exemplo, você pode passar um método estático
como parâmetro para um construtor de constante.
Genéricos
Se você olhar a documentação da API para o tipo de array básico,
List, verá que o tipo é realmente List<E> . A notação <…> marca List
como um tipo genérico (ou parametrizado) - um tipo que possui
parâmetros de tipo formal. Por convenção, a maioria das variáveis
de tipo têm nomes de uma única letra, como E, T, S, K e V.

Por que usar genéricos?


Geralmente, os genéricos são necessários para a segurança dos
tipos, mas eles têm mais benefícios do que apenas permitir que seu
código seja executado:
A especificação adequada de tipos genéricos resulta em um
código gerado melhor.
Você pode usar genéricos para reduzir a duplicação de código.
Se você pretende que uma lista contenha apenas strings, pode
declará-la como List<String> (leia isso como “lista de strings”).
Dessa forma, você, seus colegas programadores e suas
ferramentas podem detectar que atribuir um não string à lista é
provavelmente um erro. Aqui está um exemplo:

Outra razão para usar genéricos é reduzir a duplicação de código.


Os genéricos permitem que você compartilhe uma única interface e
implementação entre vários tipos, ao mesmo tempo em que tira
proveito da análise estática. Por exemplo, digamos que você crie
uma interface para armazenar em cache um objeto:

Você descobre que deseja uma versão específica de string dessa


interface, então cria outra interface:
Mais tarde, você decide que deseja uma versão específica de um
número dessa interface... Você entendeu.
Os tipos genéricos podem evitar o trabalho de criar todas essas
interfaces. Em vez disso, você pode criar uma única interface que
leva um parâmetro de tipo:

Neste código, T é o tipo substituto. É um espaço reservado que


você pode pensar como um tipo que um desenvolvedor definirá
posteriormente.

Usando literais de coleção


Literais de lista, conjunto e mapa podem ser parametrizados.
Literais parametrizados são como os literais que você já viu, exceto
que você adiciona <type> (para listas e conjuntos) ou <keyType,
valueType> (para mapas) antes do colchete de abertura. Aqui está
um exemplo de uso de literais digitados:

Usando tipos parametrizados com construtores


Para especificar um ou mais tipos ao usar um construtor, coloque os
tipos entre colchetes angulares ( <...> ) logo após o nome da classe.
Por exemplo:
O código a seguir cria um mapa que possui chaves inteiras e
valores do tipo View:

Coleções genéricas e os tipos que contêm


Os tipos genéricos do DART são reificados, o que significa que eles
carregam suas informações de tipo durante a execução. Por
exemplo, você pode testar o tipo de uma coleção:

Restringindo o tipo parametrizado


Ao implementar um tipo genérico, você pode querer limitar os tipos
de seus parâmetros. Você pode fazer isso usando extends .

É normal usar SomeBaseClass ou qualquer uma de suas


subclasses como argumento genérico:
Também não há problema em especificar nenhum argumento
genérico:

A especificação de qualquer tipo SomeBaseClass não resulta em


erro:

Usando métodos genéricos


Inicialmente, o suporte genérico do Dart era limitado às classes.
Uma sintaxe mais recente, chamada de métodos genéricos, permite
argumentos de tipo em métodos e funções:

Aqui, o parâmetro de tipo genérico on first ( <T> ) permite que você


use o argumento de tipo T em vários lugares:
No tipo de retorno da função ( T ).
No tipo de um argumento ( List<T> ).
No tipo de variável local ( T tmp ).
Para obter mais informações sobre genéricos, consulte Usando
métodos genéricos.
Bibliotecas E Visibilidade
As diretivas import e library podem ajudá-lo a criar uma base de
código modular e compartilhável. As bibliotecas não fornecem
apenas APIs, mas são uma unidade de privacidade: os
identificadores que começam com um sublinhado (_) são visíveis
apenas dentro da biblioteca. Cada aplicativo Dart é uma biblioteca,
mesmo que não use uma library diretiva.
As bibliotecas podem ser distribuídas usando pacotes.

Usando bibliotecas
Use import para especificar como um namespace de uma biblioteca
é usado no escopo de outra biblioteca.
Por exemplo, os aplicativos da web Dart geralmente usam a
biblioteca dart:html, que eles podem importar assim:

O único argumento necessário para import é um URI especificando


a biblioteca. Para bibliotecas integradas, o URI tem o dart: esquema
especial. Para outras bibliotecas, você pode usar um caminho do
sistema de arquivos ou o package: esquema. O package: esquema
especifica bibliotecas fornecidas por um gerenciador de pacotes,
como a ferramenta pub. Por exemplo:

Especificando um prefixo de biblioteca


Se você importar duas bibliotecas com identificadores conflitantes,
poderá especificar um prefixo para uma ou ambas as bibliotecas.
Por exemplo, se library1 e library2 têm uma classe Element, então
você pode ter um código como este:
Importando apenas parte de uma biblioteca
Se quiser usar apenas parte de uma biblioteca, você pode importar
a biblioteca seletivamente. Por exemplo:

Carregando lentamente uma biblioteca


O carregamento adiado (também chamado de carregamento lento)
permite que um aplicativo da web carregue uma biblioteca sob
demanda, se e quando a biblioteca for necessária. Aqui estão
alguns casos em que você pode usar o carregamento adiado:
Para reduzir o tempo de inicialização inicial de um aplicativo da
web.
Para realizar o teste A / B - experimentar implementações
alternativas de um algoritmo, por exemplo.
Para carregar funcionalidades raramente usadas, como telas e
caixas de diálogo opcionais.

Para carregar lentamente uma biblioteca, você deve primeiro


importá-la usando deferred as .
Quando você precisar da biblioteca, invoque loadLibrary() usando o
identificador da biblioteca.

No código anterior, a palavra-chave await pausa a execução até que


a biblioteca seja carregada. Para obter mais informações sobre
async e await , consulte o suporte a assincronia.
Você pode invocar loadLibrary() várias vezes em uma biblioteca
sem problemas. A biblioteca é carregada apenas uma vez.
Lembre-se do seguinte ao usar o carregamento adiado:
As constantes de uma biblioteca adiada não são constantes no
arquivo de importação. Lembre-se de que essas constantes
não existem até que a biblioteca adiada seja carregada.
Você não pode usar tipos de uma biblioteca adiada no arquivo
de importação. Em vez disso, considere mover os tipos de
interface para uma biblioteca importada pela biblioteca adiada
e pelo arquivo de importação.
O Dart é inserido implicitamente loadLibrary() no namespace
que você define usando deferred as namespace . A função
loadLibrary() retorna um Future.

Implementando bibliotecas
Consulte Criar pacotes de biblioteca para obter conselhos sobre
como implementar um pacote de biblioteca, incluindo:
Como organizar o código-fonte da biblioteca.
Como usar a diretiva export .
Quando usar a diretiva part .
Quando usar a diretiva library .
Como usar importações e exportações condicionais para
implementar uma biblioteca que oferece suporte a várias
plataformas.
Suporte para assincronia
As bibliotecas Dart estão cheias de funções que retornam objetos
Future ou Stream. Essas funções são assíncronas: elas retornam após
a configuração de uma operação possivelmente demorada (como E
/ S), sem esperar que a operação seja concluída.
As palavras-chave async - await e suportam programação
assíncrona, permitindo escrever código assíncrono que se parece
com o código síncrono.

Manipulação de futuros
Quando você precisa do resultado de um Futuro concluído, você
tem duas opções:
Use async e await .
Use a API Future, conforme descrito no tour pela biblioteca.
Código que usa async e await é assíncrono, mas se parece muito
com o código síncrono. Por exemplo, aqui está um código que usa
await para esperar o resultado de uma função assíncrona:

Para usar await , o código deve estar em uma função async - uma
função marcada como async :

Use try , catch e finally para lidar com erros e limpeza no código que
usa await :
Você pode usar várias vezes await em uma função async . Por
exemplo, o código a seguir espera três vezes pelos resultados das
funções:

Em await expression, o valor expression e geralmente é um


Futuro; se não for, o valor será automaticamente incluído em um
Future. Este objeto future indica uma promessa de retornar um
objeto. O valor de await expression é aquele objeto retornado. A
expressão await faz a execução pausar até que o objeto esteja
disponível.
Se você receber um erro de tempo de compilação ao usar await ,
certifique-se de que await está em uma função async . Por
exemplo, para usar await na função main() do seu aplicativo, o
corpo de main() deve ser marcado como async :
Declaração de funções assíncronas
Uma função async é uma função cujo corpo é marcado com o
modificador async .
Adicionar a palavra-chave async a uma função faz com que ela retorne
um Future. Por exemplo, considere esta função síncrona, que retorna uma
String:

Se você o alterar para uma função async , por exemplo, porque uma
implementação futura consumirá muito tempo, o valor retornado é um
Futuro:

Observe que o corpo da função não precisa usar a API Future. O Dart cria
o objeto future, se necessário. Se sua função não retornar um valor útil,
faça seu tipo de retorno Future<void> .
Para obter uma introdução interativa sobre o uso de futuros async , e
await , consulte o codelab de programação assíncrona.

Manipulando streams
Quando você precisa obter valores de um fluxo, você tem duas opções:
Use async e um loop for assíncrono ( await for ).
Use a API Stream, conforme descrito no tour pela biblioteca.

Um loop for assíncrono tem o seguinte formato:

O valor de expression. deve ter o tipo Stream. A execução prossegue da


seguinte forma:
Espere até que o stream emita um valor.
Execute o corpo do loop for, com a variável definida para aquele valor
emitido.
Repita 1 e 2 até que o fluxo seja fechado.
Para parar de ouvir o fluxo, você pode usar uma instrução break ou
return , que sai do loop for e cancela a assinatura do fluxo.
Se você receber um erro de tempo de compilação ao implementar um
loop for assíncrono, certifique-se de que await for estar em uma função
async . Por exemplo, para usar um loop for assíncrono na função
main() do seu aplicativo, o corpo de main() deve ser marcado como
async :

Para obter mais informações sobre programação assíncrona, em geral,


consulte a seção dart:async do tour pela biblioteca.
Geradores
Quando você precisa produzir preguiçosamente uma sequência de
valores, considere o uso de uma função geradora. O Dart possui suporte
integrado para dois tipos de funções geradoras:
Gerador síncrono : retorna um objeto Iterable.
Gerador assíncrono : Retorna um objeto Stream.
Para implementar uma função geradora síncrona , marque o corpo da
função como sync* e use instruções yield para fornecer valores:

Para implementar uma função geradora assíncrona , marque o corpo da


função como async* e use instruções yield para fornecer valores:

Se seu gerador for recursivo, você pode melhorar seu desempenho


usando yield* :
Classes chamáveis
Para permitir que uma instância de sua classe Dart seja chamada como
uma função, implemente o método call() .
No exemplo a seguir, a classe WannabeFunction define uma função call()
que pega três strings e as concatena, separando cada uma com um
espaço e acrescentando uma exclamação.
Isolados
A maioria dos computadores, mesmo em plataformas móveis, possui
CPUs multi-core. Para tirar proveito de todos esses núcleos, os
desenvolvedores tradicionalmente usam threads de memória
compartilhada em execução simultaneamente. No entanto, a
simultaneidade de estado compartilhado está sujeita a erros e pode levar
a códigos complicados.
Em vez de threads, todo o código Dart é executado dentro de isolados.
Cada isolado possui seu próprio heap de memória, garantindo que
nenhum estado isolado seja acessível a partir de qualquer outro isolado.
Para obter mais informações, consulte o seguinte:
Programação assíncrona Dart: isola e loops de evento
Dart: isolar a referência da API, incluindo Isolate.spawn() e
TransferableTypedData
Livro de receitas de análise em segundo plano no site Flutter
Isolar aplicativo de amostra
Typedefs
No Dart, funções são objetos, assim como strings e números são objetos.
Um typedef, ou alias de tipo de função, dá a um tipo de função um nome
que você pode usar ao declarar campos e tipos de retorno. Um typedef
retém informações de tipo quando um tipo de função é atribuído a uma
variável.
Considere o seguinte código, que não usa um typedef:

As informações de tipo são perdidas ao atribuir f a compare . O tipo de f é


(Object, Object) → int (onde → significa retorna), mas o tipo de compare
é Função. Se alterarmos o código para usar nomes explícitos e reter
informações de tipo, tanto os desenvolvedores quanto as ferramentas
podem usar essas informações.
Como typedefs são simplesmente aliases, eles oferecem uma maneira de
verificar o tipo de qualquer função. Por exemplo:
Metadados
Use metadados para fornecer informações adicionais sobre seu código.
Uma anotação de metadados começa com o caractere @ , seguido por
uma referência a uma constante de tempo de compilação (como
deprecated ) ou uma chamada a um construtor de constante.
Duas anotações estão disponíveis para todos os códigos DART:
@deprecated e @override . Para obter exemplos de uso @override ,
consulte Estendendo uma classe. Aqui está um exemplo de uso da
anotação @deprecated :

Você pode definir suas próprias anotações de metadados. Aqui está um


exemplo de definição de uma anotação @todo que leva dois argumentos:

E aqui está um exemplo de como usar essa anotação @todo:

Os metadados podem aparecer antes de uma biblioteca, classe, typedef,


parâmetro de tipo, construtor, fábrica, função, campo, parâmetro ou
declaração de variável e antes de uma diretiva de importação ou
exportação. Você pode recuperar metadados em tempo de execução
usando reflexão.
Comentários
O Dart suporta comentários de uma linha, comentários de várias linhas e
comentários de documentação.

Comentários de linha única


Um comentário de uma única linha começa com // . Tudo entre // e o final
da linha é ignorado pelo compilador Dart.

Comentários multilinhas
Um comentário de várias linhas começa com /* e termina com */ . Tudo
entre /* e */ é ignorado pelo compilador Dart (a menos que o comentário
seja um comentário de documentação; consulte a próxima seção).
Comentários multilinhas podem ser aninhados.

Comentários de documentação
Comentários de documentação são comentários de várias linhas ou de
uma linha que começam com /// ou /** . Usar /// em linhas consecutivas
tem o mesmo efeito que um comentário de documento com várias linhas.
Dentro de um comentário de documentação, o compilador Dart ignora
todo o texto, a menos que esteja entre colchetes. Usando colchetes, você
pode se referir a classes, métodos, campos, variáveis de nível superior,
funções e parâmetros. Os nomes entre colchetes são resolvidos no
escopo léxico do elemento do programa documentado.
Aqui está um exemplo de comentários de documentação com referências
a outras classes e argumentos:

Na documentação gerada, [Food] torna-se um link para os documentos da


API para a classe Food.
Para analisar o código Dart e gerar documentação HTML, você pode usar
a ferramenta de geração de documentação do SDK . Para obter um exemplo de
documentação gerada, consulte a documentação da API do Dart. Para obter
conselhos sobre como estruturar seus comentários, consulte diretrizes
para comentários de documentos do Dart.
Resumo
Esta página resumiu os recursos comumente usados na linguagem Dart.
Mais recursos estão sendo implementados, mas esperamos que eles não
quebrem o código existente. Para obter mais informações, consulte a
especificação da linguagem Dart e Effective Dart.
Para saber mais sobre as principais bibliotecas do Dart, consulte Um tour
pelas bibliotecas do Dart.
Language: O Sistema de Tipo Dart
A linguagem Dart é segura para o tipo: ela usa uma combinação de
verificação de tipo estático e verificações de tempo de execução para
garantir que o valor de uma variável sempre corresponda ao tipo estático
da variável, às vezes referido como digitação sonora. Embora os tipos
sejam obrigatórios, as anotações de tipos são opcionais devido à
inferência de tipo.
Para obter uma introdução completa à linguagem Dart, incluindo tipos,
consulte o tour de idiomas.
Um benefício da verificação de tipo estático é a capacidade de encontrar
bugs em tempo de compilação usando o analisador estático Dart .
Você pode corrigir a maioria dos erros de análise estática adicionando
anotações de tipo a classes genéricas. As classes genéricas mais comuns
são os tipos de coleção List<T> e Map<K,V> .
Por exemplo, no código a seguir, a função printInts() imprime uma lista de
inteiros e main() cria uma lista e a passa para printInts() .

O código anterior resulta em um erro de tipo em list (destacado acima) na


chamada de printInts(list) :
erro - O tipo de argumento 'List <dynamic>' não pode ser atribuído ao tipo
de parâmetro 'List <int>' em lib / strong_analysis.dart: 27: 17 -
(argument_type_not_assignable)

O erro destaca uma conversão implícita incorreta de List<dynamic> para


List<int> . A variável list tem tipo estático List<dynamic> . Isso ocorre porque a
declaração de inicialização var list = [] não fornece ao analisador
informações suficientes para inferir um argumento de tipo mais específico
do que dynamic . A função printInts() espera um parâmetro de tipo List<int> ,
causando uma incompatibilidade de tipos.
Ao adicionar uma anotação de tipo ( <int> ) na criação da lista (destacada
abaixo), o analisador reclama que um argumento de string não pode ser
atribuído a um parâmetro int . A remoção das aspas list.add("2") resulta em
um código que passa na análise estática e é executado sem erros ou
avisos.
O Que É Solidez?
Solidez é garantir que seu programa não entre em determinados estados
inválidos. Um sistema de tipo de som significa que você nunca pode entrar
em um estado em que uma expressão seja avaliada como um valor que
não corresponda ao tipo estático da expressão. Por exemplo, se o tipo
estático de uma expressão for String , em tempo de execução você terá a
garantia de obter apenas uma string ao avaliá-la.
O sistema de tipos de Dart, como os sistemas de tipos em Java e C#, é
sólido. Ele reforça essa solidez usando uma combinação de verificação
estática (erros de tempo de compilação) e verificações de tempo de
execução. Por exemplo, atribuir um String a int é um erro em tempo de
compilação. A conversão de um Object para uma string usando as String
falha com um erro de tempo de execução se o objeto não for uma string.
Os Benefícios Da Solidez
Um sistema de tipo de som tem vários benefícios:
Revelando bugs relacionados ao tipo em tempo de compilação. Um
sistema de tipo de som força o código a não ser ambíguo sobre seus
tipos, então bugs relacionados ao tipo que podem ser difíceis de
encontrar em tempo de execução são revelados no tempo de
compilação.
Código mais legível. O código é mais fácil de ler porque você pode
confiar em um valor que realmente possui o tipo especificado. No
Dart sonoro, os tipos não podem mentir.
Código mais sustentável. Com um sistema de tipo de som, quando
você altera uma parte do código, o sistema de tipo pode avisá-lo
sobre as outras partes do código que acabaram de quebrar.
Melhor compilação com antecedência (AOT). Embora a compilação
AOT seja possível sem tipos, o código gerado é muito menos
eficiente.
Dicas para passar na análise estática
A maioria das regras para tipos estáticos são fáceis de entender. Aqui
estão algumas das regras menos óbvias:
Use tipos de retorno de som ao substituir métodos.
Use tipos de parâmetros de som ao substituir métodos.
Não use uma lista dinâmica como uma lista digitada.
Vamos ver essas regras em detalhes, com exemplos que usam a seguinte
hierarquia de tipos:

Use tipos de retorno de som ao substituir métodos


O tipo de retorno de um método em uma subclasse deve ser o mesmo
tipo ou subtipo do tipo de retorno do método na superclasse. Considere o
método getter na classe Animal:

O método getter parente retorna um Animal. Na subclasse HoneyBadger,


você pode substituir o tipo de retorno do getter por HoneyBadger (ou
qualquer outro subtipo de Animal), mas um tipo não relacionado não é
permitido.
Use tipos de parâmetros de som ao substituir métodos
O parâmetro de um método substituído deve ter o mesmo tipo ou um
supertipo do parâmetro correspondente na superclasse. Não “aperte” o
tipo de parâmetro substituindo o tipo por um subtipo do parâmetro original.

Considere o chase(Animal) método para a classe Animal:

O método chase() leva um Animal. Um HoneyBadger persegue qualquer


coisa. Não há problema em substituir o método chase() para obter qualquer
coisa (Objeto).
O código a seguir reforça o parâmetro no método chase() de Animal para
Mouse, uma subclasse de Animal.

Este código não é seguro para o tipo porque seria então possível definir
um gato e enviá-lo após um jacaré:

Não use uma lista dinâmica como uma lista digitada


Uma lista dinâmica é boa quando você deseja ter uma lista com diferentes
tipos de coisas. No entanto, você não pode usar uma lista dinâmica como
uma lista digitada.
Esta regra também se aplica a instâncias de tipos genéricos.
O código a seguir cria uma lista dinâmica de Cachorro e a atribui a uma
lista do tipo Cat, que gera um erro durante a análise estática.
Verificações de tempo de execução
As verificações de tempo de execução em ferramentas como o Dart VM e
o dartdevc lidam com problemas de segurança de tipo que o analisador
não consegue detectar.
Por exemplo, o código a seguir lança uma exceção no tempo de
execução porque é um erro atribuir uma lista de cães a uma lista de gatos:
Inferência De Tipo
O analisador pode inferir tipos para campos, métodos, variáveis locais e a
maioria dos argumentos de tipo genérico. Quando o analisador não tem
informações suficientes para inferir um tipo específico, ele usa o tipo
dynamic .
Aqui está um exemplo de como a inferência de tipo funciona com
genéricos. Neste exemplo, uma variável chamada contém um mapa
arguments que emparelha chaves de string com valores de vários tipos.
Se você digitar explicitamente a variável, poderá escrever isto:

Como alternativa, você pode usar var e deixar o Dart inferir o tipo:

O literal de mapa infere seu tipo a partir de suas entradas e, em seguida,


a variável infere seu tipo a partir do tipo do literal de mapa. Neste mapa,
as chaves são strings, mas os valores têm tipos diferentes (String e int,
que têm o objeto de limite superior). Portanto, o literal do mapa tem o tipo
Map<String, Object> e a variável arguments também.

Inferência de campo e método


Um campo ou método que não possui um tipo especificado e que substitui
um campo ou método da superclasse, herda o tipo do método ou campo
da superclasse.
Um campo que não possui um tipo declarado ou herdado, mas que é
declarado com um valor inicial, obtém um tipo inferido com base no valor
inicial.

Inferência de campo estático


Os campos estáticos e as variáveis obtêm seus tipos inferidos de seu
inicializador. Observe que a inferência falha se encontrar um ciclo (ou
seja, inferir um tipo para a variável depende de saber o tipo dessa
variável).

Inferência de variável local


Os tipos de variáveis locais são inferidos de seu inicializador, se houver.
As atribuições subsequentes não são consideradas. Isso pode significar
que um tipo muito preciso pode ser inferido. Nesse caso, você pode
adicionar uma anotação de tipo.

Inferência de argumento de tipo


Argumentos de tipo para chamadas de construtor e invocações de
método genérico são inferidos com base em uma combinação de
informações descendentes do contexto de ocorrência e informações
ascendentes dos argumentos para o construtor ou método genérico. Se a
inferência não estiver fazendo o que você deseja ou espera, você sempre
pode especificar explicitamente os argumentos de tipo.

No último exemplo, x é inferido como double usando informações


descendentes. O tipo de retorno do fechamento é inferido como int usando
informações ascendentes. Dart utiliza este tipo de retorno como
informações para cima quando inferir o map() argumento de tipo de
método: <int> .
Substituindo Tipos
Ao substituir um método, você está substituindo algo de um tipo (no
método antigo) por algo que pode ter um novo tipo (no novo método). Da
mesma forma, quando você passa um argumento para uma função, está
substituindo algo que possui um tipo (um parâmetro com um tipo
declarado) por algo que possui outro tipo (o argumento real). Quando
você pode substituir algo que tem um tipo por algo que tem um subtipo ou
supertipo?
Ao substituir os tipos, ajuda a pensar em termos de consumidores e
produtores . Um consumidor absorve um tipo e um produtor gera um tipo.
Você pode substituir o tipo de consumidor por um supertipo e o tipo
de produtor por um subtipo.
Vejamos exemplos de atribuição de tipo simples e atribuição com tipos
genéricos.

Atribuição de tipo simples


Ao atribuir objetos a objetos, quando você pode substituir um tipo por um
tipo diferente? A resposta depende se o objeto é um consumidor ou um
produtor.
Considere a seguinte hierarquia de tipos:

Considere a seguinte atribuição simples, onde Cat c é um consumidor e


Cat() é um produtor :
Em uma posição de consumo, é seguro substituir algo que consome um
tipo específico ( Cat ) por algo que consome qualquer coisa ( Animal ),
portanto, substituir Cat c por Animal c é permitido, porque animal é um
supertipo de Gato.

Mas substituindo Cat c por MaineCoon c quebras tipo segurança, pois a


superclasse pode fornecer um tipo de Gato com comportamentos
diferentes, como Leão:

Em uma posição de produção, é seguro substituir algo que produz um tipo


(Cat) por um tipo mais específico (MaineCoon). Portanto, o seguinte é
permitido:

Atribuição de tipo genérico


As regras são as mesmas para tipos genéricos? Sim. Considere a
hierarquia de listas de animais - uma Lista de Gato é um subtipo de uma
Lista de Animais e um supertipo de uma Lista de MaineCoon:

No exemplo a seguir, você pode atribuir uma MaineCoon lista a myCats


porque List<MaineCoon> é um subtipo de List<Cat> :
Que tal ir à outra direção? Você pode atribuir uma lista Animal a um
List<Cat> ?

Essa atribuição passa na análise estática, mas cria um elenco implícito. É


equivalente a:

O código pode falhar em tempo de execução. Você pode proibir as


conversões implícitas, especificando implicit-casts: false no arquivo de
opções de análise.

Métodos
Ao substituir um método, as regras do produtor e do consumidor ainda se
aplicam. Por exemplo:

Para um consumidor (como o método chase(Animal) ), você pode substituir


o tipo de parâmetro por um supertipo. Para um produtor (como o método
parente getter), você pode substituir o tipo de retorno por um subtipo.
Para obter mais informações, consulte Usar tipos de retorno de som ao
substituir métodos e usar tipos de parâmetro de som ao substituir
métodos.
Outros Recursos
Os seguintes recursos contêm mais informações sobre o Dart de som:
Corrigindo problemas comuns de tipo - Erros que você pode encontrar ao
escrever código DART de som e como corrigi-los.
Dart 2 - Como atualizar o código Dart 1.x para Dart 2.
Personalização da análise estática - como configurar e personalizar o
analisador e o linter usando um arquivo de opções de análise.
Core Libraries: Bibliotecas centrais
O Dart possui um rico conjunto de bibliotecas centrais que fornecem o
essencial para muitas tarefas de programação diárias, como trabalhar em
coleções de objetos ( dart:collection ), fazer cálculos ( dart:math ) e codificar /
decodificar dados ( dart:convert ). APIs adicionais estão disponíveis em
pacotes contribuídos pela comunidade.
Bibliotecas Multiplataforma
A tabela a seguir lista as bibliotecas principais do Dart que funcionam em
todas as plataformas Dart.
Bibliotecas De Plataforma Nativa
A tabela a seguir lista as bibliotecas principais do Dart que funcionam na
plataforma nativa do Dart (código compilado AOT e JIT).
Bibliotecas De Plataforma Web
A tabela a seguir lista as bibliotecas principais do Dart que funcionam na
plataforma da web Dart (código compilado em JavaScript).
Core Libraries: Um tour pelas principais
bibliotecas
Esta página mostra como usar os principais recursos das principais
bibliotecas do Dart. É apenas uma visão geral e de forma alguma
abrangente. Sempre que precisar de mais detalhes sobre uma classe,
consulte a referência da API do Dart.
dart:core
Tipos integrados, coleções e outras funcionalidades básicas. Esta
biblioteca é importada automaticamente para cada programa Dart.
dart:async
Suporte para programação assíncrona, com classes como Future e
Stream.
dart:math
Constantes e funções matemáticas, além de um gerador de números
aleatórios.
dart:convert
Codificadores e decodificadores para conversão entre diferentes
representações de dados, incluindo JSON e UTF-8.
dart:html
DOM e outras APIs para aplicativos baseados em navegador.
dart:io
E / S para programas que podem usar o Dart VM, incluindo aplicativos
Flutter, servidores e scripts de linha de comando.
Esta página é apenas uma visão geral; ele cobre apenas alguns Darts: *
bibliotecas e nenhuma biblioteca de terceiros.
Outros lugares para encontrar informações da biblioteca são o site pub.dev
e o guia da biblioteca do desenvolvedor da Web Dart. Você pode
encontrar a documentação da API para todas as bibliotecas do dart: * na
referência da API do Dart ou, se estiver usando o Flutter, a referência da API do
Flutter.
Dart: Núcleo - Números, Coleções, Strings E
Muito Mais
A biblioteca dart: core (referência da API ) fornece um pequeno, mas crítico
conjunto de funcionalidades embutidas. Esta biblioteca é importada
automaticamente para cada programa Dart.

Imprimir para o console


O método print() de nível superior recebe um único argumento (qualquer
objeto) e exibe o valor da string desse objeto (conforme retornado por
toString() ) no console.

Para obter mais informações sobre strings básicas e toString() , consulte


Strings no tour de idioma.

Números
A biblioteca Dart: core define as classes num , int e double , que possuem
alguns utilitários básicos para trabalhar com números.
Você pode converter uma string em um número inteiro ou duplo com o
parse() métodos int e double, respectivamente:

Ou use o método parse() de num que cria um número inteiro se possível


e, caso contrário, um duplo:

Para especificar a base de um inteiro, adicione um parâmetro radix :


Use o método toString() para converter um int ou double em uma string.
Para especificar o número de dígitos à direita do decimal, use
toStringAsFixed(). Para especificar o número de dígitos significativos na
string, use toStringAsPrecision():

Para obter mais informações, consulte a documentação da API para int,


double e num. Veja também a seção dart:math.

Strings e expressões regulares


Uma string no Dart é uma sequência imutável de unidades de código
UTF-16. O tour pelo idioma tem mais informações sobre strings. Você
pode usar expressões regulares (objetos RegExp) para pesquisar em
strings e substituir partes de strings.
A classe de cadeia define métodos tais como split() , contains() ,
startsWith() , endsWith() , e mais.
Procurando dentro de uma string
Você pode encontrar locais específicos dentro de uma string, bem como
verificar se uma string começa ou termina com um padrão específico. Por
exemplo:
Extração de dados de uma string
Você pode obter os caracteres individuais de uma string como Strings ou
ints, respectivamente. Para ser mais preciso, você obtém unidades de
código UTF-16 individuais; caracteres de numeração alta, como o símbolo
de clave de sol ('\ u {1D11E}'), são duas unidades de código cada.
Você também pode extrair uma substring ou dividir uma string em uma
lista de substrings:

Convertendo para maiúsculas ou minúsculas


Você pode converter strings com facilidade em suas variantes em
maiúsculas e minúsculas:
Corte e strings vazias
Remova todos os espaços em branco à esquerda e à direita com trim() .
Para verificar se uma string está vazia (o comprimento é zero), use
isEmpty .

Substituindo parte de uma string


Strings são objetos imutáveis, o que significa que você pode criá-los, mas
não pode alterá-los. Se você observar atentamente a referência da API String,
perceberá que nenhum dos métodos realmente altera o estado de uma
String. Por exemplo, o método replaceAll() retorna uma nova String sem
alterar a String original:

Construindo uma string


Para gerar uma string de maneira programática, você pode usar
StringBuffer. Um StringBuffer não gera um novo objeto String até toString()
ser chamado. O writeAll() método possui um segundo parâmetro opcional
que permite especificar um separador - neste caso, um espaço.
Expressões regulares
A classe RegExp fornece os mesmos recursos que as expressões
regulares JavaScript. Use expressões regulares para pesquisa eficiente e
correspondência de padrões de strings.

Você também pode trabalhar diretamente com a classe RegExp. A classe


Match fornece acesso a uma correspondência de expressão regular.

Mais Informações
Consulte a referência da API String para obter uma lista completa de
métodos. Consulte também a referência da API para StringBuffer, Pattern,
RegExp e Match.

Coleções
O Dart é fornecido com uma API de coleções básicas, que inclui classes
para listas, conjuntos e mapas.
Listas
Como mostra o tour pelo idioma, você pode usar literais para criar e
inicializar listas. Como alternativa, use um dos construtores List. A classe
List também define vários métodos para adicionar e remover itens de lista.
Use indexOf() para encontrar o índice de um objeto em uma lista:

Classifique uma lista usando o método sort() . Você pode fornecer uma
função de classificação que compare dois objetos. Esta função de
classificação deve retornar <0 para menor , 0 para o mesmo e> 0 para
maior . O exemplo a seguir usa compareTo() , que é definido por
Comparable e implementado por String.
Listas são tipos parametrizados, então você pode especificar o tipo que
uma lista deve conter:

Consulte a referência da API List para obter uma lista completa de métodos.
Conjuntos
Um conjunto no Dart é uma coleção não ordenada de itens exclusivos. Como um
conjunto não está ordenado, você não pode obter os itens de um conjunto por índice
(posição).

Use contains() e containsAll() para verificar se um ou mais objetos estão em um conjunto:

Uma interseção é um conjunto cujos itens estão em dois outros conjuntos.


Consulte a referência Set API para obter uma lista completa de métodos.

Mapas
Um mapa, comumente conhecido como dicionário ou hash , é uma
coleção não ordenada de pares chave-valor. Os mapas associam uma
chave a algum valor para fácil recuperação. Ao contrário do JavaScript, os
objetos Dart não são mapas.
Você pode declarar um mapa usando uma sintaxe literal concisa ou pode
usar um construtor tradicional:

Você adiciona, obtém e define itens de mapa usando a sintaxe de


colchetes. Use remove() para remover uma chave e seu valor de um
mapa.
Você pode recuperar todos os valores ou todas as chaves de um mapa:

Para verificar se um mapa contém uma chave, use containsKey() . Como


os valores do mapa podem ser nulos, você não pode depender
simplesmente de obter o valor da chave e verificar se há nulos para
determinar a existência de uma chave.
Use o método putIfAbsent() quando quiser atribuir um valor a uma chave
se e somente se a chave ainda não existir em um mapa. Você deve
fornecer uma função que retorne o valor.

Consulte a referência da API do mapa para uma lista completa de métodos.


Métodos de coleta comuns
Lista, Conjunto e Mapa compartilham funcionalidades comuns
encontradas em muitas coleções. Algumas dessas funcionalidades
comuns são definidas pela classe Iterable, que List e Set implementam.

Use isEmpty ou isNotEmpty para verificar se uma lista, conjunto ou mapa


contém itens:

Para aplicar uma função a cada item em uma lista, conjunto ou mapa,
você pode usar forEach() :

Quando você invoca forEach() em um mapa, sua função deve levar dois
argumentos (a chave e o valor):
Os iteráveis fornecem o método map() , que fornece todos os resultados
em um único objeto:

Para forçar sua função a ser chamada imediatamente em cada item, use
map() .toList() ou map() .toSet() :

Use o método Iterable where() para obter todos os itens que


correspondem a uma condição. Use os métodos Iterable any() e every()
para verificar se alguns ou todos os itens correspondem a uma condição.

Para obter uma lista completa de métodos, consulte a referência da API


Iterable, bem como os de List, Set e Map.

URIs
A classe Uri fornece funções para codificar e decodificar strings para uso
em URIs (que você pode conhecer como URLs ). Essas funções tratam
de caracteres que são especiais para URIs, como & e = . A classe Uri
também analisa e expõe os componentes de um URI - host, porta,
esquema e assim por diante.
Codificação e decodificação de URIs totalmente
qualificados
Para codificar e descodificar caracteres exceto aqueles com significado
especial em um URI (tais como / , : , & , # ), utilizar o encodeFull() e
métodos decodeFull() . Esses métodos são bons para codificar ou
decodificar um URI totalmente qualificado, deixando os caracteres URI
especiais intactos.

Observe como apenas o espaço entre some e message foi codificado.


Componentes de codificação e decodificação de URI
Para codificar e decodificar todos os caracteres de uma string que têm
significado especial em um URI, incluindo (mas não limitado a) / , & e : ,
use o encodeComponent() e métodos decodeComponent() .

Observe como cada caractere especial é codificado. Por exemplo, / está


codificado para %2F .
Analisando URIs
Se você tiver um objeto Uri ou uma string URI, poderá obter suas partes
usando campos Uri, como path . Para criar um Uri a partir de uma string,
use o método estático parse() :

Consulte a referência da API Uri para mais componentes URI que você pode
obter.
Construindo URIs
Você pode construir um URI a partir de partes individuais usando o
construtor Uri() :

Datas e horas
Um objeto DateTime é um ponto no tempo. O fuso horário é UTC ou o
fuso horário local.
Você pode criar objetos DateTime usando vários construtores:
A propriedade millisecondsSinceEpoch de uma data retorna o número de
milissegundos desde a “época Unix” —1 de janeiro de 1970, UTC:

Use a classe Duration para calcular a diferença entre duas datas e para
mudar uma data para frente ou para trás:
Para obter uma lista completa de métodos, consulte a referência da API
para DateTime e Duration.

Classes de utilidades
A biblioteca principal contém várias classes de utilitários, úteis para
classificar, mapear valores e iterar.
Comparando objetos
Implemente a interface Comparable para indicar que um objeto pode ser
comparado a outro objeto, geralmente para classificação. O método
compareTo() retorna <0 para menor , 0 para o mesmo e> 0 para maior .
Implementando chaves de mapa
Cada objeto no Dart fornece automaticamente um código hash inteiro e,
portanto, pode ser usado como uma chave em um mapa. No entanto,
você pode substituir o getter hashCode para gerar um código hash
personalizado. Se você fizer isso, também poderá substituir o operador
== . Os objetos que são iguais (via == ) devem ter códigos hash idênticos.
Um código hash não precisa ser exclusivo, mas deve ser bem distribuído.

Iteração
As classes Iterable e Iterator oferecem suporte a loops for-in. Estenda (se
possível) ou implemente Iterable sempre que criar uma classe que possa
fornecer Iteradores para uso em loops for-in. Implemente o Iterator para
definir a capacidade real de iteração.
Exceções
A biblioteca central do Dart define muitas exceções e erros comuns. As
exceções são consideradas condições que você pode planejar com
antecedência e capturar. Erros são condições que você não espera ou
planeja.
Alguns dos erros mais comuns são:
NoSuchMethodError
Lançado quando um objeto receptor (que pode ser nulo) não implementa
um método.
ArgumentError
Pode ser lançado por um método que encontra um argumento
inesperado.
Lançar uma exceção específica do aplicativo é uma maneira comum de
indicar que ocorreu um erro. Você pode definir uma exceção
personalizada implementando a interface Exception:
Para obter mais informações, consulte Exceções (no tour pelo idioma) e a
referência da API de exceção.
dart:async - Programação Assíncrona
A programação assíncrona geralmente usa funções de retorno de
chamada, mas o Dart oferece alternativas: Objetos Future e Stream. Um
futuro é como uma promessa de um resultado a ser fornecido em algum
momento no futuro. Um Stream é uma forma de obter uma sequência de
valores, como eventos. Future, Stream e muito mais estão na biblioteca
dart:async (referência de API).

A biblioteca dart: async funciona tanto em aplicativos da web quanto em


aplicativos de linha de comando. Para usá-lo, importe dart:async:

Futuro
Objetos futuros aparecem em todas as bibliotecas Dart, geralmente como
o objeto retornado por um método assíncrono. Quando um futuro se
completa , seu valor está pronto para ser usado.
Usando o await
Antes de usar diretamente a API Future, considere usá-la await . O código
que usa expressões await pode ser mais fácil de entender do que o
código que usa a API Future.
Considere a seguinte função. Ele usa o método then() de future para
executar três funções assíncronas seguidas, esperando que cada uma
seja concluída antes de executar a próxima.
O código equivalente com expressões await se parece mais com o código
síncrono:

Uma função async pode capturar exceções de Futuros. Por exemplo:

Para obter mais informações sobre o uso await e os recursos da


linguagem Dart relacionados, consulte o codelab de programação
assíncrona.
Uso básico
Você pode usar then() para agendar o código que será executado quando
o futuro terminar. Por exemplo, HttpRequest.getString() retorna um Future,
já que as solicitações HTTP podem demorar um pouco. Usar then()
permite que você execute algum código quando esse Future for concluído
e o valor da string prometido estiver disponível:
Use catchError() para lidar com quaisquer erros ou exceções que um
objeto future possa lançar.

O then().catchError() padrão é a versão assíncrona de try - catch .

Encadeamento de vários métodos assíncronos


O método then() retorna um Future, fornecendo uma maneira útil de
executar várias funções assíncronas em uma determinada ordem. Se o
retorno de chamada registrado com then() retornar um Future, then()
retornará um Future equivalente. Se o retorno de chamada retornar um
valor de qualquer outro tipo, then() cria um future que é concluído com o
valor.

No exemplo anterior, os métodos são executados na seguinte ordem:


costlyQuery()
expensiveWork()
lengthyComputation()
Aqui está o mesmo código escrito usando await:
Esperando por múltiplos futuros
Às vezes, seu algoritmo precisa invocar muitas funções assíncronas e
esperar que todas sejam concluídas antes de continuar. Use o método
estático Future.wait() para gerenciar vários Futures e espere que sejam
concluídos:

Corrente
Objetos de fluxo aparecem em todas as APIs do Dart, representando
sequências de dados. Por exemplo, eventos HTML, como cliques em
botões, são entregues por meio de fluxos. Você também pode ler um
arquivo como um fluxo.
Usando um loop for assíncrono
Às vezes, você pode usar um loop ( await for ) for assíncrono em vez de
usar a API de fluxo.
Considere a seguinte função. Ele usa o listen() método do Stream para
assinar uma lista de arquivos, passando um literal de função que pesquisa
cada arquivo ou diretório.
O código equivalente com expressões await, incluindo um loop ( await
for ) assíncrono, se parece mais com o código síncrono:

Para obter mais informações sobre o uso await e os recursos da


linguagem Dart relacionados, consulte o codelab de programação
assíncrona.
Ouvindo dados de fluxo
Para obter cada valor conforme ele chega, use await for ou inscreva-se no
fluxo usando o método listen() :

Neste exemplo, a propriedade onClick é um objeto Stream fornecido pelo


botão “submitInfo”.
Se você se preocupa apenas um evento, você pode obtê-lo usando uma
propriedade, como first , last ou single . Para testar o evento antes
manuseá-lo, utilizar um método tal como firstWhere() , lastWhere() ou
singleWhere() .
Se você se preocupa com um subconjunto de eventos, você pode usar
métodos, como skip() , skipWhile() , take() , takeWhile() , e where() .
Transformando dados de fluxo
Frequentemente, você precisa alterar o formato dos dados de um fluxo
antes de usá-lo. Use o método transform() para produzir um fluxo com um
tipo diferente de dados:

Este exemplo usa dois transformadores. Primeiro, ele usa utf8.decoder


para transformar o fluxo de inteiros em um fluxo de strings. Em seguida,
ele usa um LineSplitter para transformar o fluxo de strings em um fluxo de
linhas separadas. Esses transformadores são da biblioteca dart:convert
(consulte a seção dart:convert ).
Tratamento de erros e conclusão
Como você especifica o erro e o código de tratamento de conclusão
depende se você usa um loop ( await for ) for assíncrono ou a API Stream.
Se você usar um loop for assíncrono, use try-catch para tratar os erros. O
código executado após o fechamento do fluxo segue o loop for
assíncrono.
Se você usar a API Stream, trate os erros registrando um ouvinte
onError . Execute o código depois que o fluxo for fechado, registrando um
ouvinte onDone .

Mais Informações
Para alguns exemplos de uso do Future e Stream em aplicativos de linha
de comando, consulte o tour dart:io. Veja também estes artigos, codelabs e
tutoriais:
Programação assíncrona: futuros, assíncronos, aguardar
Futuros e tratamento de erros
Programação assíncrona: fluxos
Criação de fluxos no Dart
Programação assíncrona Dart: isola e loops de evento
dart:math - matemática e aleatório
A biblioteca dart:math (referência da API ) fornece funcionalidades comuns,
como seno e cosseno, máximo e mínimo, e constantes como pi e. A
maior parte da funcionalidade da biblioteca de matemática é
implementada como funções de nível superior.
Para usar esta biblioteca em seu aplicativo, importe dart:math.

Trigonometria
A biblioteca de matemática fornece funções trigonométricas básicas:

Máximo e mínimo
A biblioteca de matemática fornece métodos max() e min() :

Constantes matemáticas
Encontre suas constantes favoritas - pi , e mais - na biblioteca de
matemática:
Números aleatórios
Gere números aleatórios com a classe Random. Você pode opcionalmente
fornecer uma semente para o construtor Random.

Você pode até gerar booleanos aleatórios:

Mais Informações
Consulte a referência da API de matemática para obter uma lista completa de
métodos. Consulte também a referência da API para num, int e double.
dart: convert - Decodificação E Codificação
Json, Utf-8 E Mais
A biblioteca dart:convert ( referência da API ) tem conversores para JSON e
UTF-8, bem como suporte para a criação de conversores adicionais. JSON
é um formato de texto simples para representar coleções e objetos
estruturados. UTF-8 é uma codificação comum de largura variável que
pode representar todos os caracteres no conjunto de caracteres Unicode.
Para usar esta biblioteca, importe dart:convert.

Decodificando e codificando JSON


Decodifique uma string codificada em JSON em um objeto Dart com
jsonDecode() :

Codifique um objeto Dart compatível em uma string formatada em JSON


com jsonEncode() :
Somente objetos do tipo int, double, String, bool, null, List ou Map (com
chaves de string) podem ser codificados diretamente em JSON. Os
objetos List e Map são codificados recursivamente.
Você tem duas opções para codificar objetos que não podem ser
codificados diretamente. A primeira é invocar jsonEncode() com um
segundo argumento: uma função que retorna um objeto que pode ser
codificado diretamente. Sua segunda opção é omitir o segundo
argumento, caso em que o codificador chama o toJson() método do
objeto.
Para obter mais exemplos e links para pacotes relacionados a JSON,
consulte Usando JSON.

Decodificando e codificando caracteres UTF-8


Use utf8.decode() para decodificar bytes codificados em UTF8 para uma
string Dart:

Para converter um fluxo de caracteres UTF-8 em uma string Dart,


especifique utf8.decoder a o método Stream transform() :
Use utf8.encode() para codificar uma string Dart como uma lista de bytes
codificados em UTF8:

Outras funcionalidades
A biblioteca dart:convert também possui conversores para ASCII e ISO-
8859-1 (Latin1). Para obter detalhes, consulte a referência da API para a
biblioteca dart:convert.
dart: html - Aplicativos Baseados Em
Navegador
Use a biblioteca dart:html para programar o navegador, manipular objetos e
elementos no DOM e acessar APIs HTML5. DOM significa Document
Object Model , que descreve a hierarquia de uma página HTML.
Outros usos comuns de dart: html são manipular estilos (CSS), obter
dados usando solicitações HTTP e trocar dados usando WebSockets.
HTML5 (e dart: html) tem muitas APIs adicionais que esta seção não
cobre. Apenas aplicativos da web podem usar dart: html, não aplicativos
de linha de comando.

Para usar a biblioteca HTML em seu aplicativo da web, importe dart: html:

Manipulando o DOM
Para usar o DOM, você precisa saber sobre janelas , documentos ,
elementos e nós .
Um objeto Window representa a janela real do navegador da web. Cada
janela tem um objeto Documento, que aponta para o documento que está
carregado no momento. O objeto Window também tem acessadores para
várias APIs, como IndexedDB (para armazenamento de dados),
requestAnimationFrame (para animações) e muito mais. Em navegadores
com guias, cada guia tem seu próprio objeto Janela.
Com o objeto Document, você pode criar e manipular objetos Element dentro
do documento. Observe que o próprio documento é um elemento e pode
ser manipulado.
O DOM modela uma árvore de nós. Esses nós geralmente são elementos,
mas também podem ser atributos, texto, comentários e outros tipos de
DOM. Exceto pelo nó raiz, que não tem pai, cada nó no DOM tem um pai
e pode ter muitos filhos.
Encontrando elementos
Para manipular um elemento, você precisa primeiro de um objeto que o
represente. Você pode obter este objeto usando uma consulta.
Encontre um ou mais elementos usando as funções de nível superior
querySelector() e querySelectorAll() . Você pode consultar por ID, classe,
tag, nome ou qualquer combinação destes. O guia CSS Selector Specification
define os formatos dos seletores, como o uso de um prefixo # para
especificar IDs e um ponto (.) para classes.
A função querySelector() retorna o primeiro elemento que corresponde ao
seletor, enquanto querySelectorAll() retorna uma coleção de elementos
que correspondem ao seletor.

Manipulando elementos
Você pode usar propriedades para alterar o estado de um elemento. Nó e
seu subtipo Elemento definem as propriedades de todos os elementos.
Por exemplo, todos os elementos têm classes , hidden , id , style , e title
propriedades que você pode usar para estado set. As subclasses de
Element definem propriedades adicionais, como a propriedade href de
AnchorElement.
Considere este exemplo de especificação de um elemento âncora em
HTML:

Esta tag <a> especifica um elemento com um atributo href e um nó de


texto (acessível por meio de uma propriedade text ) que contém a string
“linktext”. Para alterar o URL para o qual o link vai, você pode usar a
propriedade href de AnchorElement:
Frequentemente, você precisa definir propriedades em vários elementos.
Por exemplo, o código a seguir define a propriedade hidden de todos os
elementos que possuem uma classe de “mac”, “win” ou “linux”. Definir a
propriedade hidden como true tem o mesmo efeito que adicionar
display:none ao CSS.

Quando a propriedade certa não está disponível ou não é conveniente,


você pode usar a propriedade atributes do Element. Esta propriedade é a
Map<String, String> , onde as chaves são nomes de atributos. Para obter
uma lista de nomes de atributos e seus significados, consulte a página
Atributos MDN. Aqui está um exemplo de configuração do valor de um
atributo:
Criação de elementos
Você pode adicionar páginas HTML existentes criando elementos e
anexando-os ao DOM. Aqui está um exemplo de criação de um elemento
de parágrafo (<p>):

Você também pode criar um elemento analisando o texto HTML. Todos os


elementos filhos também são analisados e criados.

Observe que elem2 é um ParagraphElement no exemplo anterior.


Anexe o elemento recém-criado ao documento atribuindo um pai ao
elemento. Você pode adicionar um elemento aos filhos de qualquer
elemento existente. No exemplo a seguir, body é um elemento e seus
elementos filhos são acessíveis (como List <Element>) a partir da
children propriedade.

Adicionar, substituir e remover nós


Lembre-se de que os elementos são apenas uma espécie de nó. Você
pode encontrar todos os filhos de um nó usando a propriedade nodes de
node, que retorna um List <Node> (em oposição a children , que omite
nós não-Elemento). Assim que tiver essa lista, você pode usar os métodos
e operadores usuais de List para manipular os filhos do nó.
Para adicionar um nó como o último filho de seu pai, use o método List
add() :

Para substituir um nó, use o método node replaceWith() :

Para remover um nó, use o método node remove() :


Manipulando estilos CSS
CSS, ou folhas de estilo em cascata , define os estilos de apresentação
dos elementos DOM. Você pode alterar a aparência de um elemento
anexando atributos de ID e classe a ele.
Cada elemento possui um campo classes , que é uma lista. Adicione e
remova classes CSS simplesmente adicionando e removendo strings
desta coleção. Por exemplo, o exemplo a seguir adiciona a classe
warning a um elemento:

Geralmente é muito eficiente encontrar um elemento por ID. Você pode


definir dinamicamente um ID de elemento com a propriedade id :

Você pode reduzir o texto redundante neste exemplo usando cascatas de


método:

Embora usar IDs e classes para associar um elemento a um conjunto de


estilos seja a prática recomendada, às vezes você deseja anexar um estilo
específico diretamente ao elemento:

Tratamento de eventos
Para responder a eventos externos, como cliques, mudanças de foco e
seleções, adicione um ouvinte de eventos. Você pode adicionar um
ouvinte de evento a qualquer elemento da página. O envio e propagação
de eventos é um assunto complicado; pesquise os detalhes se você for novo
na programação da web.
Adicione um manipulador de eventos usando
element.onEvent.listen(function) , onde é o nome do evento Eventfunction
é o manipulador de eventos.
Por exemplo, veja como você pode lidar com cliques em um botão:

Os eventos podem se propagar para cima e para baixo na árvore DOM.


Para descobrir qual elemento disparou originalmente o evento, use
e.target :

Para ver todos os eventos para os quais você pode registrar um ouvinte
de evento, procure as propriedades “onEventType” nos documentos da
API para Element e suas subclasses. Alguns eventos comuns incluem:
mudança
borrão
keyDown
keyUp
mouseDown
mouseUp

Usando recursos HTTP com HttpRequest


Anteriormente conhecido como XMLHttpRequest, a classe HttpRequest
fornece acesso a recursos HTTP de dentro de seu aplicativo baseado em
navegador. Tradicionalmente, os aplicativos de estilo AJAX fazem uso
intenso de HttpRequest. Use HttpRequest para carregar dados JSON
dinamicamente ou qualquer outro recurso de um servidor da web. Você
também pode enviar dados dinamicamente para um servidor da web.
Obtendo dados do servidor
O método estático HttpRequest getString() é uma maneira fácil de obter
dados de um servidor web. Use await com a chamada getString() para
garantir que você tenha os dados antes de continuar a execução.

Use try-catch para especificar um gerenciador de erros:

Se você precisar acessar o HttpRequest, não apenas os dados de texto


que ele recupera, você pode usar o método estático request() em vez de
getString() . Aqui está um exemplo de leitura de dados XML:

Você também pode usar a API completa para lidar com casos mais
interessantes. Por exemplo, você pode definir cabeçalhos arbitrários.
O fluxo geral para usar a API completa de HttpRequest é o seguinte:
1. Crie o objeto HttpRequest.
2. Abra o URL com GET ou POST .
3. Anexe manipuladores de eventos.
4. Envie o pedido.
Por exemplo:
Enviando dados para o servidor
HttpRequest pode enviar dados para o servidor usando o método HTTP
POST. Por exemplo, você pode desejar enviar dados dinamicamente a um
manipulador de formulários. O envio de dados JSON para um serviço da
Web REST ful é outro exemplo comum.
O envio de dados a um manipulador de formulários requer que você
forneça pares nome-valor como strings codificadas por URI. (Informações
sobre a classe URI estão na seção URIs do Dart Library Tour.) Você
também deve definir o cabeçalho Content-type como application/x-www-
form-urlencode se deseja enviar dados a um manipulador de formulários.

Envio e recebimento de dados em tempo real com


WebSockets
Um WebSocket permite que seu aplicativo da web troque dados com um
servidor de forma interativa - sem a necessidade de sondagem. Um
servidor cria o WebSocket e escuta as solicitações em um URL que
começa com ws: // - por exemplo, ws: //127.0.0.1: 1337 / ws. Os dados
transmitidos por um WebSocket podem ser uma string ou um blob.
Freqüentemente, os dados são uma string formatada em JSON.
Para usar um WebSocket em seu aplicativo da web, primeiro crie um
objeto WebSocket, passando o URL do WebSocket como um argumento:
Enviando dados
Para enviar dados de string no WebSocket, use o método send() :

Recebendo dados
Para receber dados no WebSocket, registre um ouvinte para eventos de
mensagem:

O manipulador de eventos de mensagem recebe um objeto MessageEvent.


O campo data deste objeto contém os dados do servidor.
Tratamento de eventos WebSocket
Seu aplicativo pode manipular os seguintes eventos WebSocket: abrir,
fechar, erro e (como mostrado anteriormente) mensagem. Aqui está um
exemplo de método que cria um objeto WebSocket e registra
manipuladores para eventos de abertura, fechamento, erro e mensagem:
Mais Informações
Esta seção mal arranhou a superfície do uso da biblioteca dart:html. Para
obter mais informações, consulte a documentação do dart:html. O Dart tem
bibliotecas adicionais para APIs da web mais especializadas, como áudio
da web, IndexedDB e WebGL.
Para obter mais informações sobre as bibliotecas da web Dart, consulte a
visão geral da biblioteca da web.
dart:io – I/O Para Servidores E Aplicativos
De Linha De Comando
A biblioteca dart:io fornece APIs para lidar com arquivos, diretórios,
processos, sockets, WebSockets e clientes e servidores HTTP.

Em geral, a biblioteca dart:io implementa e promove uma API assíncrona.


Os métodos síncronos podem bloquear facilmente um aplicativo,
dificultando o dimensionamento. Portanto, a maioria das operações
retorna resultados por meio de objetos future ou Stream, um padrão
comum com plataformas de servidor modernas, como Node.js.
Os poucos métodos síncronos na biblioteca dart: io são claramente
marcados com um sufixo Sync no nome do método. Os métodos
síncronos não são abordados aqui.
Para usar a biblioteca dart:io, você deve importá-la:

Arquivos e diretórios
A biblioteca de I/O permite que aplicativos de linha de comando leiam e
gravem arquivos e naveguem em diretórios. Você tem duas opções para
ler o conteúdo de um arquivo: tudo de uma vez ou streaming. Ler um
arquivo de uma vez requer memória suficiente para armazenar todo o
conteúdo do arquivo. Se o arquivo for muito grande ou se você quiser
processá-lo durante a leitura, use um Stream, conforme descrito em
Streaming de conteúdo do arquivo.
Lendo um arquivo como texto
Ao ler um arquivo de texto codificado em UTF-8, você pode ler todo o
conteúdo do arquivo com readAsString() . Quando as linhas individuais
são importantes, você pode usar readAsLines() . Em ambos os casos, um
objeto future é retornado, fornecendo o conteúdo do arquivo como uma ou
mais strings.
Lendo um arquivo como binário
O código a seguir lê um arquivo inteiro como bytes em uma lista de ints. A
chamada para readAsBytes() retorna um Future, que fornece o resultado
quando disponível.

Tratamento de erros
Para capturar erros para que eles não resultem em exceções não
detectadas, você pode registrar um manipulador catchError no Future ou
(em uma função async ) usar try-catch:

Streaming de conteúdo de arquivo


Use um Stream para ler um arquivo, um pouco de cada vez. Você pode
usar a API Stream ou await for parte do suporte de assincronia do Dart.
Gravando o conteúdo do arquivo
Você pode usar um IOSink para gravar dados em um arquivo. Use o
método file openWrite() para obter um IOSink no qual você possa gravar.
O modo padrão, FileMode.write substitui completamente os dados
existentes no arquivo.

Para adicionar ao final do arquivo, use o parâmetro opcional mode para


especificar FileMode.append :

Para escrever dados binários, use add(List<int> data) .


Listando arquivos em um diretório
Encontrar todos os arquivos e subdiretórios de um diretório é uma
operação assíncrona. O método list() retorna um Stream que emite um
objeto quando um arquivo ou diretório é encontrado.
Outra funcionalidade comum
As classes File e Directory contêm outras funcionalidades, incluindo, mas
não se limitando a:
Criando um arquivo ou diretório: create() em Arquivo e Diretório
Excluindo um arquivo ou diretório: delete() em Arquivo e Diretório
Obtendo o comprimento de um arquivo: length() em Arquivo
Obter acesso aleatório a um arquivo: open() em Arquivo
Consulte os documentos da API para Arquivo e Diretório para obter uma lista
completa de métodos.

Clientes e servidores HTTP


A biblioteca dart:io fornece classes que aplicativos de linha de comando
podem usar para acessar recursos HTTP, bem como executar servidores
HTTP.
Servidor HTTP
A classe HttpServer fornece a funcionalidade de baixo nível para construir
servidores web. Você pode combinar manipuladores de solicitação, definir
cabeçalhos, transmitir dados e muito mais.
O exemplo de servidor da web a seguir retorna informações de texto
simples. Este servidor escuta na porta 8888 e no endereço 127.0.0.1
(localhost), respondendo às solicitações do caminho /dart . Para qualquer
outro caminho, a resposta é o código de status 404 (página não
encontrada).
Cliente HTTP
A classe HttpClient ajuda você a se conectar a recursos HTTP de sua linha
de comando Dart ou aplicativo do lado do servidor. Você pode definir
cabeçalhos, usar métodos HTTP e ler e gravar dados. A classe HttpClient
não funciona em aplicativos baseados em navegador. Ao programar no
navegador, use a classe dart: html HttpRequest. Aqui está um exemplo de uso
de HttpClient:

Mais Informações
Esta página mostrou como usar os principais recursos da biblioteca dart:io.
Além das APIs discutidas nesta seção, a biblioteca dart:io também fornece
APIs para processos, sockets e web sockets. Para obter mais informações
sobre o desenvolvimento de aplicativos do lado do servidor e de linha de
comando, consulte a visão geral do Dart do lado do servidor.
Para obter informações sobre outras bibliotecas dart: *, consulte o tour da
biblioteca.
Resumo
Esta página apresentou a funcionalidade mais comumente usada nas
bibliotecas integradas do Dart. Não cobriu todas as bibliotecas integradas,
no entanto. Outros que você pode querer examinar incluem dart:collection e
dart:typed_data, bem como bibliotecas específicas de plataforma, como as
bibliotecas de desenvolvimento web Dart e as bibliotecas Flutter.
Você pode obter ainda mais bibliotecas usando o gerenciador de pacotes pub.
As bibliotecas de coleção, criptografia, http, intl e teste são apenas uma amostra
do que você pode instalar usando o pub.
Para saber mais sobre a linguagem Dart, consulte o tour de idiomas.
Core Libraries: Articles: introdução à
biblioteca dart:io
Escrito por Mads Ager em março de 2012 (atualizado em setembro de 2018)
A biblioteca dart:io é voltada para o código executado no Flutter e na VM
autônoma do Dart. Neste artigo, daremos uma ideia do que atualmente é
possível com o dart:io, passando por alguns exemplos.

Dart é uma linguagem de programação de thread único. Se uma operação


bloquear o thread do DART, o aplicativo não fará nenhum progresso antes
que a operação seja concluída. Para escalabilidade, portanto, é crucial
que nenhum bloco de operações de E / S. Em vez de bloquear as
operações de E / S, o dart:io usa um modelo de programação assíncrona
inspirado em node.js, EventMachine e Twisted.
O Dart VM E O Loop De Eventos
Antes de mergulharmos nas operações de E / S assíncronas, pode ser útil
explicar como a VM Dart autônoma opera.
Ao executar um aplicativo do lado do servidor, o Dart VM é executado em
um loop de eventos com uma fila de eventos associada de operações
assíncronas pendentes. A VM termina quando executa o código atual até
a conclusão e não há mais operações pendentes na fila.
Uma maneira simples de adicionar um evento à fila de eventos é agendar
uma função para ser chamada no futuro. Você pode fazer isso criando um
objeto Timer. O exemplo a seguir registra um cronômetro com a fila de
eventos e, em seguida, cai no final de main() . Como uma operação
pendente está na fila de eventos, a VM não termina nesse ponto. Após um
segundo, o cronômetro é disparado e o código no retorno de chamada do
argumento é executado. Depois que o código é executado até a
conclusão, nenhuma outra operação pendente fica na fila de eventos e a
VM é encerrada.

Executando este exemplo na linha de comando, obtemos:

Se tivéssemos repetido o cronômetro usando o construtor Timer.periodic,


a VM não terminaria e continuaria a imprimir 'cronômetro' a cada segundo.
Acesso Ao Sistema De Arquivos
A biblioteca dart:io fornece acesso a arquivos e diretórios por meio das
classes Arquivo e Diretório.
O exemplo a seguir imprime seu próprio código-fonte. Para determinar a
localização do código-fonte que está sendo executado, usamos a classe
Platform.

Observe que o método readAsString() é assíncrono; ele retorna um Future


que retorna o conteúdo do arquivo depois que o arquivo foi lido do sistema
subjacente. Essa assincronicidade permite que o encadeamento do Dart
execute outro trabalho enquanto aguarda a conclusão da operação de I/O.
Para ilustrar operações de arquivo mais detalhadas, vamos mudar o
exemplo para ler o conteúdo apenas até o primeiro ponto e vírgula e
depois imprimir isso. Você pode fazer isso de duas maneiras: abrir o
arquivo para acesso aleatório ou abrir um Stream para o arquivo e
transmitir os dados.
Aqui está uma versão que abre o arquivo para operações de acesso
aleatório. O código abre o arquivo para leitura e, em seguida, lê um byte
de cada vez até encontrar o código char para ;
Quando você vê o uso de async ou await , está vendo um Futuro em ação.
Os métodos open() e readByte() retornam um objeto future.
Este código é, obviamente, um uso muito simples de operações de
acesso aleatório. As operações estão disponíveis para escrita, buscando
uma determinada posição, truncando e assim por diante.
Vamos implementar uma versão usando um fluxo. O código a seguir abre
o arquivo para leitura apresentando o conteúdo como um fluxo de listas de
bytes. Como todos os fluxos no Dart, você escuta neste fluxo (usando
await for ) e os dados são fornecidos em blocos.
A assinatura do stream é tratada implicitamente por await for . Sair da
instrução await for - usando break , return ou uma exceção não capturada -
cancela a assinatura.
Stream<List<int>> é usado em vários lugares em dart: io: ao trabalhar com
stdin, arquivos, sockets, conexões HTTP e assim por diante. Da mesma
forma, os objetos IOSink são usados para transmitir dados para stdout,
arquivos, soquetes, conexões HTTP e assim por diante.
Interagindo Com Processos
Para o caso simples, use Process.run() para executar um processo e
coletar sua saída. Use run() quando não precisar de controle interativo
sobre o processo.

Você também pode iniciar um processo criando um objeto Process


usando Process.start().
Depois de ter um objeto Process, você pode interagir com o processo
gravando dados em seu coletor stdin, lendo dados de seus streams stderr
e stdout e eliminando o processo. Quando o processo sai, o futuro
exitCode se completa com o código de saída do processo.
O exemplo a seguir é executado ls -l em um processo separado e imprime
a saída e o código de saída do processo para stdout. Como estamos
interessados em obter linhas, usamos um Utf8Decoder (que decodifica
blocos de bytes em strings) seguido por um LineSplitter (que divide as
strings nos limites das linhas).

Observe que exitCode pode ser concluído antes que todas as linhas de
saída tenham sido processadas. Observe também que não fechamos
explicitamente o processo. Para não vazar recursos, temos que ouvir os
streams stderr e stdout. Nós usamos await for para ouvir stdout, e
stderr.drain() para ouvir (e descarte) stderr.
Em vez de imprimir a saída em stdout, podemos usar as classes de
streaming para canalizar a saída do processo para um arquivo.
Escrevendo Servidores Web
dart:io torna mais fácil escrever servidores e clientes HTTP. Para escrever
um servidor web simples, tudo que você precisa fazer é criar um
HttpServer e conectar um ouvinte (usando await for ) a seu fluxo de
HttpRequest s.
Aqui está um servidor web simples que responde 'Olá, mundo' a qualquer
solicitação.

Executar este aplicativo e apontar seu navegador para


'http://127.0.0.1:8082’ fornece ‘Olá, mundo' como esperado.
Vamos adicionar um pouco mais e servir os arquivos. O caminho base
para cada arquivo que servimos será a localização do script. Se nenhum
caminho for especificado em uma solicitação, serviremos index.html. Para
uma solicitação com um caminho, tentaremos encontrar o arquivo e
atendê-lo. Se o arquivo não for encontrado, responderemos com o status
'404 Não encontrado'. Usamos a interface de streaming para canalizar
todos os dados lidos de um arquivo diretamente para o fluxo de resposta.
Escrever clientes HTTP é muito semelhante a usar a classe HttpClient.
Solicitações De Recursos São Bem-Vindas
A biblioteca dart:io já é capaz de realizar muitas tarefas. Por exemplo, o
site pub.dev usa dart:io.
Por favor, dê um giro de dart:io e diga-nos o que você achou. Pedidos de
recursos são muito bem-vindos! Ao registrar um bug ou solicitação de
recurso, use dartbug.com. Para localizar os problemas relatados, procure
o rótulo library-io.
Core Libraries: Articles: Criação de fluxos no
Dart
Escrito por Lasse Nielsen em abril de 2013 (atualizado em outubro de 2018)
A biblioteca dart:async contém dois tipos que são importantes para muitas
APIs Dart: Stream e Future. Onde um futuro representa o resultado de um
único cálculo, um fluxo é uma sequência de resultados. Você escuta em
um fluxo para ser notificado sobre os resultados (dados e erros) e sobre o
encerramento do fluxo. Você também pode pausar enquanto ouve ou
parar de ouvir a transmissão antes que ela seja concluída.
Mas este artigo não é sobre o uso de streams. É sobre como criar seus
próprios streams. Você pode criar streams de algumas maneiras:
Transformando fluxos existentes.
Criar um fluxo do zero usando uma função async* .
Criar um fluxo usando a StreamController .
Este artigo mostra o código para cada abordagem e fornece dicas para
ajudá-lo a implementar seu fluxo corretamente.
Para obter ajuda sobre como usar fluxos, consulte Programação
assíncrona: fluxos.
Transformando Um Fluxo Existente
O caso comum para criar streams é que você já tem um stream e deseja
criar um com base nos eventos do stream original. Por exemplo, você
pode ter um fluxo de bytes que deseja converter em um fluxo de strings
por UTF-8 decodificando a entrada. A abordagem mais geral é criar um
fluxo que espera por eventos no fluxo original e, em seguida, gera novos
eventos. Exemplo:

Para muitas transformações comuns, você pode usar métodos Stream de


transformação -Fornecido tais como map() , where() , expand() , e take() .
Por exemplo, suponha que você tenha um fluxo counterStream , que emite
um contador crescente a cada segundo. Veja como pode ser
implementado:

Para ver os eventos rapidamente, você pode usar um código como este:

Para transformar os eventos de fluxo, você pode invocar um método de


transformação, como map() no fluxo, antes de ouvi-lo. O método retorna
um novo fluxo.
Em vez de map() , você pode usar qualquer outro método de
transformação, como o seguinte:

Frequentemente, um método de transformação é tudo que você precisa.


No entanto, se você precisar de ainda mais controle sobre a
transformação, poderá especificar um StreamTransformer com Stream o
método transform() de. As bibliotecas de plataforma fornecem
transformadores de fluxo para muitas tarefas comuns. Por exemplo, o
código a seguir usa os transformadores utf8.decoder e LineSplitter fornecidos
pela biblioteca dart:convert.
Criando Um Stream Do Zero
Uma maneira de criar um fluxo é com uma função gerador ( async* )
assíncrona. O fluxo é criado quando a função é chamada e o corpo da
função começa a ser executado quando o fluxo é ouvido. Quando a
função retorna, o fluxo fecha. Até que a função retorne, ela pode emitir
eventos no fluxo usando instruções yield ou yield* .
Aqui está um exemplo primitivo que emite números em intervalos
regulares:

Esta função retorna um Stream . Quando esse fluxo é ouvido, o corpo


começa a correr. Ele atrasa repetidamente o intervalo solicitado e, em
seguida, produz o próximo número. Se o parâmetro count for omitido, não
há condição de parada no loop, então o fluxo produz números cada vez
maiores para sempre - ou até que o ouvinte cancele sua assinatura.
Quando o ouvinte cancela (invocando cancel() no StreamSubscription objeto
retornado pelo listen() método), na próxima vez que o corpo atinge uma
instrução yield , o yield atua como uma instrução return . Qualquer finally
bloco envolvente é executado e a função sai. Se a função tentar gerar um
valor antes de sair, isso falhará e funcionará como um retorno.
Quando a função finalmente termina, o futuro retornado pelo método
cancel() é concluído. Se a função sair com um erro, o futuro será concluído
com esse erro; caso contrário, é concluído com null .
Outro exemplo mais útil é uma função que converte uma sequência de
futuros em um fluxo:
Esta função pede ao iterável futures um novo futuro, espera por esse
futuro, emite o valor resultante e, em seguida, faz um loop. Se um futuro
for concluído com um erro, o fluxo será concluído com esse erro.
É raro ter uma função async* construindo um fluxo do nada. Ele precisa
obter seus dados de algum lugar e, na maioria das vezes, esse lugar é
outro fluxo. Em alguns casos, como a sequência de futuros acima, os
dados vêm de outras fontes de eventos assíncronos. Em muitos casos, no
entanto, uma função async* é muito simplista para lidar facilmente com
várias fontes de dados. É aí que StreamController entra a classe.
Usando Um Streamcontroller
Se os eventos do seu stream vêm de diferentes partes do seu programa,
e não apenas de um stream ou futuros que podem ser percorridos por
uma função async , use um StreamController para criar e preencher o
stream.
A StreamController oferece um novo fluxo e uma maneira de adicionar
eventos a ele em qualquer ponto e de qualquer lugar. O stream tem toda a
lógica necessária para lidar com ouvintes e pausas. Você retorna o fluxo e
mantém o controlador para você.
O exemplo a seguir (de stream_controller_bad.dart) mostra um uso
básico, embora falho, de StreamController para implementar a função
timedCounter() dos exemplos anteriores. Este código cria um fluxo para
retornar e, em seguida, alimenta os dados com base em eventos de
cronômetro, que não são futuros nem eventos de fluxo.

Como antes, você pode usar o stream retornado por timedCounter() desta
forma:

Esta implementação de timedCounter() tem alguns problemas:


Ele começa a produzir eventos antes de ter assinantes.
Ele continua produzindo eventos, mesmo se o assinante solicitar
uma pausa.
Como as próximas seções mostram, você pode corrigir esses dois
problemas especificando retornos de chamada como onListen e onPause ao
criar o StreamController .
Esperando Por Uma Assinatura
Como regra, os streams devem aguardar os assinantes antes de iniciar
seu trabalho. Uma função async* faz isso automaticamente, mas ao usar
um StreamController , você tem controle total e pode adicionar eventos
mesmo quando não deveria. Quando um fluxo não tem assinante, seus
eventos armazenam em buffer StreamController , o que pode levar a um
vazamento de memória se o fluxo nunca obtiver um assinante.
Tente alterar o código que usa o fluxo para o seguinte:

Quando esse código é executado, nada é impresso nos primeiros 5


segundos, embora o fluxo esteja funcionando. Em seguida, o ouvinte é
adicionado e os primeiros 5 ou mais eventos são impressos todos de uma
vez, uma vez que foram armazenados em buffer pelo StreamController .
Para ser notificado sobre as assinaturas, especifique um argumento
onListen ao criar o StreamController . O retorno de chamada onListen é
chamado quando o fluxo obtém seu primeiro assinante. Se você
especificar um retorno de chamada onCancel , ele será chamado quando o
controlador perder seu último assinante. No exemplo anterior,
Timer.periodic() deve passar para um manipulador onListen , conforme
mostrado na próxima seção.
Honrando O Estado De Pausa
EVITAR produzir eventos quando o ouvinte solicitar uma pausa. Uma
função async* pausa automaticamente em uma instrução yield enquanto a
assinatura do fluxo é pausada. A StreamController , por outro lado, armazena
eventos durante a pausa. Se o código que fornece os eventos não
respeitar a pausa, o tamanho do buffer pode crescer indefinidamente.
Além disso, se o ouvinte parar de ouvir logo após a pausa, o trabalho
gasto na criação do buffer será perdido.
Para ver o que acontece sem suporte para pausa, tente alterar o código
que usa o stream para o seguinte:

Quando os cinco segundos de pausa terminam, os eventos disparados


durante esse tempo são todos recebidos de uma vez. Isso acontece
porque a fonte do stream não respeita as pausas e continua adicionando
eventos ao stream. Assim, o fluxo armazena os eventos em buffer e, em
seguida, esvazia seu buffer quando o fluxo deixa de ser pausado.
A versão seguinte de timedCounter() (a partir de stream_controller.dart )
implementos pausa usando as chamadas onListen , onPause , onResume , e
onCancel de retorno sobre a StreamController .
Execute este código com a função listenWithPause() acima. Você verá que
ele para de contar durante a pausa e continua bem depois.
Você deve usar todo o listeners - onListen , onCancel , onPause , e onResume -
para ser notificado de mudanças no estado de pausa. O motivo é que, se
os estados de assinatura e pausa mudarem ao mesmo tempo, apenas o
retorno de chamada onListen ou onCancel será chamado.
Dicas Finais
Ao criar um stream sem usar uma função assíncrona *, lembre-se destas
dicas:
Tenha cuidado ao usar um controlador síncrono - por exemplo, um
criado com StreamController(sync: true) . Quando você envia um evento
em um controlador síncrono retomaram (por exemplo, usando os
add() , addError() ou métodos close() definidos pelo EventSink), o evento
é enviado imediatamente a todos os ouvintes sobre o fluxo. Stream os
ouvintes nunca devem ser chamados até que o código que
adicionou o ouvinte tenha retornado totalmente, e o uso de um
controlador síncrono no momento errado pode quebrar essa
promessa e fazer com que um bom código falhe. EVITAR usar
controladores síncronos.
Se você usar StreamController , o retorno de chamada onListen será
chamado antes de listen retornar o StreamSubscription . Não deixe que o
retorno de chamada onListen dependa da assinatura já existente. Por
exemplo, no código a seguir, um evento onListen é disparado (e
handler é chamado) antes que a variável subscription tenha um valor
válido.

As chamadas de retorno onListen , onPause , onResume , e onCancel


definidos por StreamController são chamados pela corrente quando o
estado ouvinte do fluxo muda, mas nunca durante o disparo de um
evento ou durante a chamada de outro manipulador de mudança de
estado. Nesses casos, o retorno de chamada de mudança de estado
é atrasado até que o retorno de chamada anterior seja concluído.
Não tente implementar a interface Stream sozinho. É fácil obter a
interação entre eventos, retornos de chamada e adicionar e remover
ouvintes de forma sutilmente errada. Sempre use um fluxo existente,
possivelmente de um StreamController , para implementar a chamada
listen de um novo fluxo.
Embora seja possível criar classes que se estendem Stream com
mais funcionalidade estendendo a Stream e implementando o método
listen e a funcionalidade extra na classe parte superior, isso
geralmente não é recomendado porque apresenta um novo tipo que
os usuários devem considerar. Em vez disso, você geralmente pode
criar uma classe que tenha um Stream (e mais) - em vez de uma que
seja um Stream (e mais).
Packages: Como usar pacotes
O ecossistema Dart usa pacotes para gerenciar software compartilhado,
como bibliotecas e ferramentas. Para obter pacotes Dart, você usa o
gerenciador de pacotes pub. Você pode encontrar pacotes disponíveis
publicamente sobre o site pub.dev, ou você pode carregar pacotes a partir
do sistema de arquivos local ou em outro lugar, como repositórios Git.
Independentemente de onde venham seus pacotes, o pub gerencia as
dependências de versão, ajudando você a obter versões de pacote que
funcionam entre si e com sua versão do SDK.
A maioria dos IDEs experientes em Dart oferece suporte para o uso de
pub que inclui a criação, download, atualização e publicação de pacotes.
Ou você pode usar pub na linha de comando.
No mínimo, um pacote Dart é um diretório que contém um arquivo
pubspec. O pubspec contém alguns metadados sobre o pacote. Além
disso, um pacote pode conter dependências (listadas no pubspec),
bibliotecas Dart, aplicativos, recursos, testes, imagens e exemplos.
Para usar um pacote, faça o seguinte:
Crie um pubspec (um arquivo denominado pubspec.yaml que lista as
dependências do pacote e inclui outros metadados, como um
número de versão).
Use o pub para obter as dependências do seu pacote.
Se o seu código Dart depender de uma biblioteca no pacote, importe
a biblioteca.
Criação De Um Pubspec
O pubspec é um arquivo nomeado pubspec.yaml que está no diretório
superior do seu aplicativo. O pubspec mais simples possível lista apenas o
nome do pacote:

Aqui está um exemplo de um pubspec que declara dependências em dois


pacotes ( js e intl ) que estão hospedados no site pub.dev:

Para obter detalhes sobre como criar um pubspec, consulte a


documentação do pubspec e a documentação dos pacotes que deseja
usar.
Obtendo pacotes
Depois de ter um pubspec, você pode executar pub get a partir do
diretório superior do seu aplicativo:

Esse processo é chamado de obtenção das dependências .


O comando pub get determina em quais pacotes seu aplicativo depende e
os coloca em um cache central do sistema. Se seu aplicativo depender de
um pacote publicado, o pub baixa esse pacote do site pub.dev. Para uma
dependência Git, pub clona o repositório Git. Dependências transitivas
também estão incluídas. Por exemplo, se o pacote js depende do pacote
test , pub obtém o pacote js e o pacote test .
Pub cria um arquivo .packages (no diretório superior do seu aplicativo)
que mapeia cada nome de pacote do qual seu aplicativo depende para o
pacote correspondente no cache do sistema.
Importando Bibliotecas De Pacotes
Para importar bibliotecas encontradas em pacotes, use o prefixo
package:

O tempo de execução do Dart pega tudo depois package: e procura


dentro do arquivo .packages do seu aplicativo.
Você também pode usar este estilo para importar bibliotecas de seu
próprio pacote. Digamos que o pacote transmogrify tenha o seguinte
formato:

O arquivo parser_test.dart pode ser importado assim parser.dart :


Atualizando Uma Dependência
Na primeira vez que você obtém uma nova dependência para seu pacote,
o pub baixa a versão mais recente dele que é compatível com suas outras
dependências. Em seguida, ele bloqueia seu pacote para sempre usar
essa versão, criando um arquivo de bloqueio. Este é um arquivo chamado
pubspec.lock, pub cria e armazena próximo ao seu pubspec. Ele lista as
versões específicas de cada dependência (imediata e transitiva) que seu
pacote usa.
Se o seu pacote for um pacote de aplicativo, você deve verificar esse
arquivo no controle de origem. Dessa forma, todos que trabalham em seu
aplicativo usam as mesmas versões de todos os pacotes. Verificar o
arquivo de bloqueio também garante que seu aplicativo implantado use as
mesmas versões de código.
Quando estiver pronto para atualizar suas dependências para as versões
mais recentes, use o comando pub upgrade:

O comando pub upgrade diz ao pub para gerar novamente o arquivo de


bloqueio, usando as versões mais recentes disponíveis das dependências
do seu pacote. Se você deseja atualizar apenas uma dependência, pode
especificar o pacote a ser atualizado:

Esse comando é atualizado transmogrify para a versão mais recente, mas


deixa todo o resto igual.
O comando pub upgrade nem sempre pode atualizar todos os pacotes
para sua versão mais recente, devido a restrições de versão conflitantes
no pubspec. Para identificar pacotes desatualizados que requerem a
edição do pubspec, use pub outdated.
Mais Informações
As páginas a seguir contêm mais informações sobre os pacotes e o
gerenciador de pacotes pub.

Como
Criação de pacotes
Publicação de pacotes

Referência
Dependências de pub
Variáveis de ambiente do pub
Glossário de pub
Convenções de layout de pacote de pub
Filosofia de versionamento de pub
Formato Pubspec

Comandos pub
A ferramenta pub fornece os seguintes comandos:
pub cache
pub deps
pub downgrade
pub get
pub global
pub outdated
pub publish
pub run
pub upgrade
pub uploader
Para uma visão geral de todos os comandos pub , consulte a
documentação da ferramenta pub.

Solução de problemas
A solução de problemas pub fornece soluções para problemas que você
pode encontrar ao usar o pub.
Packages: Pacotes comumente usados
Esta página lista alguns dos pacotes mais populares e úteis que os
desenvolvedores do DART publicaram. Para encontrar mais pacotes - e
pesquisar bibliotecas centrais, também - use o site pub.dev.
Os pacotes comumente usados se enquadram em três grupos:
Pacotes de uso geral
Pacotes que se expandem nas bibliotecas centrais do Dart
Pacotes especializados
Pacotes De Uso Geral
Os pacotes a seguir são úteis para uma ampla variedade de projetos.
APIs comumente
Pacote Descrição
usadas
Archive, ArchiveFile,
Codifica e decodifica vários TarEncoder,
archive formatos de arquivo e TarDecoder,
compressão. ZipEncoder,
ZipDecoder
Manipulação de string para
String.characters,
caracteres percebidos pelo
characters Characters,
usuário (clusters de grafemas
CharacterRange
Unicode).
Um conjunto de funções e
delete(), get(), post(),
http classes de alto nível que facilitam
read()
o consumo de recursos HTTP.
Facilidades de
internacionalização e localização,
Bidi, DateFormat,
com suporte para plurais e
intl MicroMoney,
gêneros, formatação e análise de
TextDirection
data e número e texto
bidirecional.
Um pacote de geração de código
fácil de usar. Para obter mais
json_serializable @JsonSerializable
informações, consulte Suporte
JSON.
Um mecanismo configurável para
LoggerHandler, Level,
logging adicionar registro de mensagens
LogRecord
ao seu aplicativo.
Uma estrutura popular para
simulação de objetos em testes.
Answering,
Especialmente útil se você estiver
mockito Expectation,
escrevendo testes para injeção de
Verification
dependência. Usado com o
pacote de teste.
path Operações comuns para absolute(),
manipular diferentes tipos de basename(),
caminhos. Para obter mais extension(), join(),
informações, consulte Unboxing normalize(), relative(),
Packages: path. split()
CountdownTimer
(quiver.async);
Utilitários que tornam o uso das MapCache
principais bibliotecas Dart mais (quiver.cache);
conveniente. Algumas das MultiMap, TreeSet
bibliotecas onde o Quiver fornece (quiver.collection);
quiver
suporte adicional incluem EnumerateIterable
assíncrono, cache, coleção, (quiver.iterables);
núcleo, iteráveis, padrões e center(),
testes. compareIgnoreCase(),
isWhiteSpace()
(quiver.strings)
Middleware de servidor da Web
para Dart. O Shelf facilita a Cascade, Pipeline,
shelf criação e composição de Request, Response,
servidores da web e partes de Server
servidores da web.
Métodos para analisar,
inspecionar e manipular
rastreamentos de pilha
produzidos pela implementação
Dart subjacente. Também fornece
funções para produzir Trace.current(),
stack_trace representações de string de Trace.format(),
rastreamentos de pilha em um Trace.from()
formato mais legível do que a
implementação StackTrace
nativa. Para obter mais
informações, consulte Unboxing
Packages: stack_trace.
Um gerador de projeto Dart. O
WebStorm e o IntelliJ usam Geralmente usado
modelos do Stagehand quando por meio de um IDE
stagehand
você cria um aplicativo, mas você ou do comando
também pode usar os modelos da stagehand .
linha de comando.
teste Uma maneira padrão de escrever expect(), group(),
e executar testes no Dart. test()
loadYaml(),
yaml Um analisador para YAML.
loadYamlStream()
Pacotes Que Se Expandem Nas Bibliotecas
Centrais Do Dart
Cada um dos pacotes a seguir se baseia em uma biblioteca central,
adicionando funcionalidade e preenchendo os recursos ausentes:
APIs comumente
Pacote Descrição
usadas
AsyncMemoizer,
Expande em dart:async, adicionando CancelableOperation,
classes de utilitário para trabalhar com FutureGroup,
async cálculos assíncronos. Para obter mais LazyStream, Result,
informações, consulte Unboxing StreamCompleter,
Packages: async part 1, part 2 e part 3. StreamGroup,
StreamSplitter
Equality,
Expande em coleção Dart, adicionando
CanonicalizedMap,
funções de utilitário e classes para tornar
MapKeySet,
collection o trabalho com coleções mais fácil. Para
MapValueSet,
obter mais informações, consulte
PriorityQueue,
Unboxing Packages: collection.
QueueList
Expande no Dart: converte, adicionando
codificadores e decodificadores para
converter entre diferentes representações
HexDecoder,
convert de dados. Uma das representações de
PercentDecoder
dados é a codificação percentual ,
também conhecida como codificação de
URL .
Contém duas bibliotecas, ansi e io, para
copyPath(),
simplificar o trabalho com arquivos, fluxos
isExecutable(),
padrão e processos. Use a biblioteca ansi
io ExitCode,
para personalizar a saída do terminal. A
ProcessManager,
biblioteca io possui APIs para lidar com
sharedStdIn
processos, stdin e duplicação de arquivos.
Pacotes Especializados
Aqui estão algumas dicas para encontrar pacotes mais especializados,
como pacotes para celular (Flutter) e desenvolvimento web.

Pacotes Flutter
O Flutter suporta o uso de pacotes compartilhados contribuídos por outros
desenvolvedores para os ecossistemas Flutter e Dart. Isso permite
construir rapidamente um aplicativo sem ter que desenvolver tudo do zero.
Os pacotes existentes permitem muitos casos de uso, por exemplo, fazer
solicitações de rede (http), navegação / manipulação de rota
personalizada (fluro), integração com APIs de dispositivo (url_launcher e
battery) e usar SDKs de plataforma de terceiros como Firebase (FlutterFire
).
Para escrever um novo pacote, consulte desenvolvimento de pacotes. Para
adicionar ativos, imagens ou fontes, sejam armazenados em arquivos ou
pacotes, consulte Adicionando ativos e imagens.
Usando pacotes
A seção a seguir descreve como usar os pacotes publicados existentes.
Procurando por pacotes
Os pacotes são publicados em pub.dev.
A página inicial do Flutter em pub.dev exibe os principais pacotes compatíveis
com o Flutter (aqueles que declaram dependências geralmente
compatíveis com o Flutter) e oferece suporte à pesquisa entre todos os
pacotes publicados.
A página Favoritos do Flutter em pub.dev lista os plug-ins e pacotes que
foram identificados como pacotes que você deve primeiro considerar o
uso ao escrever seu aplicativo. Para obter mais informações sobre o que
significa ser um favorito do Flutter, consulte o programa Favoritos do Flutter.
Você também pode navegar pelos pacotes no pub.dev filtrando por plug-ins
do Android, plug-ins do iOS, plug-ins da web ou qualquer combinação deles.
Adicionar uma dependência de pacote a um aplicativo
Para adicionar o pacote, css_colors a um aplicativo:
1. Depende disso
Abra o arquivo pubspec.yaml localizado dentro da pasta do aplicativo e
adicione css_colors: em dependencies .
2. Instale-o
Do terminal: Execute flutter pub get .
OU
No Android Studio / IntelliJ: Clique em Pacotes para entrar na faixa de
opções de ação na parte superior de pubspec.yaml .
Do VS Code: Clique em Get Packages localizado no lado direito da faixa de
ação na parte superior de pubspec.yaml .
3. Importar
Adicione uma instrução import correspondente no código Dart.
4. Pare e reinicie o aplicativo, se necessário
Se o pacote traz código específico da plataforma (Kotlin / Java para Android,
Swift / Objective-C para iOS), esse código deve ser integrado ao seu
aplicativo. O recarregamento e a reinicialização a quente atualizam apenas
o código do Dart, portanto, pode ser necessário reiniciar totalmente o
aplicativo para evitar erros, como MissingPluginException ao usar o pacote.
A guia instalando, disponível em qualquer página de pacote em pub.dev, é
uma referência útil para essas etapas.
Para um exemplo completo, veja o exemplo css_colors abaixo.
Resolução de conflitos
Suponha que você queira usar some_package e another_package em um
aplicativo, e ambos dependem url_launcher, mas em versões diferentes.
Isso causa um conflito potencial. A melhor maneira de evitar isso é os
autores do pacote usarem intervalos de versão em vez de versões
específicas ao especificar dependências.

Se declara some_package as dependências acima e declara


another_package uma dependência url_launcher compatível como '5.4.6'
ou ^5.5.0, pub resolve o problema automaticamente. Dependências
específicas da plataforma nos módulos Gradle e / ou CocoaPods são
resolvidas de maneira semelhante.
Mesmo se declarar versões some_package e another_package
incompatíveis para url_launcher, eles podem realmente usar url_launcher
de maneira compatível. Nessa situação, o conflito pode ser resolvido
adicionando uma declaração de substituição de dependência ao arquivo
pubspec.yaml do aplicativo, forçando o uso de uma versão específica.
Por exemplo, para forçar o uso da versão url_launcher 5.4.0, faça as
seguintes alterações no arquivo pubspec.yaml do aplicativo:
Se a dependência conflitante não for um pacote, mas sim uma biblioteca
específica do Android a declaração guava, de substituição de
dependência deve ser adicionada à lógica de compilação do Gradle.
Para forçar o uso da versão guava 28.0, faça as seguintes alterações no
android/build.gradle arquivo do aplicativo:

CocoaPods não oferece atualmente a funcionalidade de substituição de


dependência.
Desenvolvendo novos pacotes
Se nenhum pacote existir para o seu caso de uso específico, você pode
escrever um pacote personalizado.
Gerenciando dependências e versões de pacotes
Para minimizar o risco de colisões de versão, especifique um intervalo de
versão no pubspec.yamlarquivo.
Versões de pacote
Todos os pacotes têm um número de versão, especificado no arquivo do
pacote pubspec.yaml. A versão atual de um pacote é exibida ao lado de
seu nome (por exemplo, consulte o pacote url_launcher), bem como uma
lista de todas as versões anteriores (consulte as url_launcher versões).
Quando um pacote é adicionado pubspec.yaml, a forma abreviada
plugin1:significa que qualquer versão do pacote plugin1 pode ser usada.
Para garantir que o aplicativo não seja interrompido quando um pacote for
atualizado, especifique um intervalo de versão usando um dos seguintes
formatos:
Restrições de intervalo: especifique uma versão mínima e máxima. Por exemplo:
As restrições de intervalo com sintaxe circunflexa são semelhantes às
restrições de intervalo regulares:

Para obter detalhes adicionais, consulte o guia de versão do pacote .


Atualizando dependências de pacotes
Ao executar flutter pub get (os pacotes entram no IntelliJ ou Android
Studio) pela primeira vez após adicionar um pacote, o Flutter salva a
versão do pacote concreto encontrada no pubspec.lock arquivo de
bloqueio. Isso garante que você obtenha a mesma versão novamente se
você ou outro desenvolvedor de sua equipe executar flutter pub get.
Para atualizar para uma nova versão do pacote, por exemplo, para usar
novos recursos nesse pacote, execute flutter pub upgrade (Atualizar
dependências no IntelliJ ou Android Studio) para recuperar a versão mais
alta disponível do pacote que é permitida pela restrição de versão
especificada em pubspec.yaml. Observe que este é um comando diferente
de flutter upgrade ou flutter update-packages, que atualizam o próprio
Flutter.
Dependências de pacotes não publicados
Os pacotes podem ser usados mesmo quando não publicados no
pub.dev. Para plug-ins privados ou para pacotes não prontos para
publicação, opções adicionais de dependência estão disponíveis:
Caminho dependente
Um aplicativo Flutter pode depender de um plug-in por meio de um path:
dependência do sistema de arquivos. O caminho pode ser relativo ou
absoluto. Os caminhos relativos são avaliados em relação ao diretório que
o contém pubspec.yaml. Por exemplo, para depender de um plug-in
plugin1 localizado em um diretório próximo ao aplicativo, use a seguinte
sintaxe:

Dependência Git
Você também pode depender de um pacote armazenado em um
repositório Git. Se o pacote estiver localizado na raiz do repo, use a
seguinte sintaxe:
Dependência do Git em um pacote em uma pasta
A ferramenta pub assume que o pacote está localizado na raiz do
repositório Git. Se não for esse o caso, especifique a localização com o
argumento path. Por exemplo:

Finalmente, use o argumento ref para fixar a dependência a um git


commit, branch ou tag específico. Para obter mais detalhes, consulte
Dependências de pacote.
Exemplos
Os exemplos a seguir percorrem as etapas necessárias para usar
pacotes.
Exemplo: usando o pacote css_colors
O pacote css_colors define constantes de cores para cores CSS, então
use as constantes sempre que a estrutura Flutter espera o tipo Color.
Para usar este pacote:
1. Crie um projeto chamado cssdemo .
2. Abra pubspec.yaml e adicione a dependência css-colors :

3. Execute no terminal flutter pub get ou clique em Packages para obter no IntelliJ ou
Android Studio.
4. Abra lib/main.dart e substitua todo o seu conteúdo por:

5. Execute o aplicativo. O plano de fundo do aplicativo agora deve ser laranja.


Exemplo: usando o pacote url_launcher para iniciar o navegador
O pacote url_launcher de plug-in permite abrir o navegador padrão na
plataforma móvel para exibir um determinado URL e é compatível com
Android, iOS, web e macos. Este pacote é um pacote Dart especial
denominado pacote de plug - in (ou plug - in ), que inclui código
específico da plataforma.
Para usar este plugin:
1. Crie um projeto chamado launchdemo .
2. Abra pubspec.yaml e adicione a dependência url_launcher :

3. Execute flutter pub get no terminal ou clique em Pacotes para obter no IntelliJ ou
Android Studio.
4. Abra lib/main.dart e substitua todo o seu conteúdo pelo seguinte:

5. Execute o aplicativo (ou pare e reinicie-o, se já estiver em execução antes de


adicionar o plug-in). Clique em Mostrar página inicial do Flutter. Você deve ver o
navegador padrão aberto no dispositivo, exibindo a página inicial de flutter.dev.
Consulte o site pub.dev para pesquisar pacotes Flutter.

Pacotes da web
Consulte bibliotecas e pacotes da Web. Ou use o site pub.dev para
pesquisar pacotes da web.

Pacotes de linha de comando e servidor


Consulte Bibliotecas e pacotes de linha de comando e servidor. Ou use o
site pub.dev para pesquisar outros pacotes.
Packages: Criação de pacotes
O ecossistema Dart usa pacotes para compartilhar software, como
bibliotecas e ferramentas. Esta página explica como criar um pacote, com
foco no tipo mais comum de pacote, pacotes de biblioteca.
O Que Torna Um Pacote De Biblioteca
O diagrama a seguir mostra o layout do pacote de biblioteca mais
simples:

Os requisitos mínimos para uma biblioteca são:


arquivo pubspec
O arquivo pubspec.yaml para uma biblioteca é o mesmo que para um
pacote de aplicativo - não há designação especial para indicar que o
pacote é uma biblioteca.
diretório lib
Como você pode esperar, o código da biblioteca reside no diretório
lib e é público para outros pacotes. Você pode criar qualquer
hierarquia em lib, conforme necessário. Por convenção, o código de
implementação é colocado em lib / src . O código em lib / src é
considerado privado; outros pacotes nunca devem precisar ser
importados src/... . Para tornar as APIs em lib / src públicas, você
pode exportar arquivos lib / src de um arquivo que está diretamente
em lib.
Organizando Um Pacote De Biblioteca
Os pacotes de bibliotecas são mais fáceis de manter, estender e testar
quando você cria pequenas bibliotecas individuais, conhecidas como
minibibliotecas . Na maioria dos casos, cada classe deve estar em sua
própria minibiblioteca, a menos que você tenha uma situação em que
duas classes estejam fortemente acopladas.

Crie um arquivo de biblioteca “principal” diretamente em lib, lib /


<package-name> .dart, que exporta todas as APIs públicas. Isso permite
que o usuário obtenha todas as funcionalidades de uma biblioteca
importando um único arquivo.
O diretório lib também pode incluir outras bibliotecas importáveis, não src.
Por exemplo, talvez sua biblioteca principal funcione em várias
plataformas, mas você cria bibliotecas separadas que dependem de dart:
io ou dart: html. Alguns pacotes têm bibliotecas separadas que devem ser
importadas com um prefixo, quando a biblioteca principal não é.
Vejamos a organização de um pacote de biblioteca do mundo real:
prateleira. O pacote de prateleira fornece uma maneira fácil de criar
servidores da web usando Dart e é apresentado em uma estrutura que é
comumente usada para pacotes de biblioteca Dart:

Diretamente em lib, o arquivo de biblioteca principal, shelf.dart exporta


vários arquivos de lib / src:
O pacote de prateleira também contém uma mini biblioteca: shelf_io. Este
adaptador lida com objetos HttpRequest de dart:io.
Importando Arquivos De Biblioteca
Ao importar um arquivo de biblioteca de outro pacote, use a diretiva
package: para especificar o URI desse arquivo.

Ao importar um arquivo de biblioteca de seu próprio pacote, use um


caminho relativo quando ambos os arquivos estiverem dentro da lib ou
quando ambos os arquivos estiverem fora da lib. Use package: quando o
arquivo importado estiver em lib e o importador estiver fora.
O gráfico a seguir mostra como importar lib/foo/a.dart de lib e web.
Importação E Exportação Condicional De
Arquivos De Biblioteca
Se sua biblioteca oferece suporte a várias plataformas, talvez seja
necessário importar ou exportar arquivos de biblioteca condicionalmente.
Um caso de uso comum é uma biblioteca que oferece suporte a
plataformas da web e nativas.
Para importar ou exportar condicionalmente, você precisa verificar a
presença de dart:* bibliotecas. Aqui está um exemplo de código de
exportação condicional que verifica a presença de dart:io e dart:html :

Aqui está o que esse código faz:


Em um aplicativo que pode usar dart:io (por exemplo, um aplicativo
de linha de comando), exporte src/hw_io.dart .
Em um aplicativo que pode ser usado dart:html (um aplicativo da
web), exporte src/hw_html.dart .
Caso contrário, exporte src/hw_none.dart .
Para importar um arquivo condicionalmente, use o mesmo código acima,
mas mude export para import .

Todas as bibliotecas exportadas condicionalmente devem implementar a


mesma API. Por exemplo, aqui está a implementação dart:io :

E aqui está a implementação padrão, que é um esboço que lança


UnsupportedErrors:
Em qualquer plataforma, você pode importar a biblioteca que contém o
código de exportação condicional:
Fornecimento De Arquivos Adicionais
Um pacote de biblioteca bem projetado é fácil de testar. Recomendamos
que você escreva testes usando o pacote de teste, colocando o código de
teste no diretório test na parte superior do pacote.
Se você criar ferramentas de linha de comando destinadas ao consumo
público, coloque-as no diretório bin , que é público. Ative a execução de
uma ferramenta a partir da linha de comando, usando pub global activate.
Listar a ferramenta na seção executables do pubspec permite que um
usuário a execute diretamente, sem chamar pub global run.
É útil incluir um exemplo de como usar sua biblioteca. Isso vai para o
diretório example na parte superior do pacote.
Quaisquer ferramentas ou executáveis que você criar durante o
desenvolvimento e que não sejam para uso público vão para o diretório
tool .
Outros arquivos necessários se você publicar sua biblioteca no site
pub.dev, como README.md e CHANGELOG.md , são descritos em Publicação
de um pacote. Para obter mais informações sobre como organizar um
diretório de pacotes, consulte as convenções de layout de pacote pub.
Documentando Uma Biblioteca
Você pode gerar documentos API para sua biblioteca usando a
ferramenta dartdoc. O Dartdoc analisa a fonte em busca de comentários
de documentação, que usam a sintaxe /// :

Para obter um exemplo de documentos gerados, consulte a documentação


da prateleira.
Distribuir Uma Biblioteca De Código Aberto
Se sua biblioteca for de código aberto, recomendamos compartilhá-la no
site pub.dev. Para publicar ou atualizar a biblioteca, use pub publish, que
carrega seu pacote e cria ou atualiza sua página. Por exemplo, consulte a
página do pacote de prateleira. Consulte Publicando um pacote para obter
detalhes sobre como preparar seu pacote para publicação.
O site pub.dev não apenas hospeda seu pacote, mas também gera e
hospeda os documentos de referência da API de seu pacote. Um link para
os documentos gerados mais recentemente está na caixa Sobre do
pacote; por exemplo, consulte os documentos da API do pacote de
prateleira . Os links para os documentos das versões anteriores estão na
guia Versões da página do pacote.
Para garantir que os documentos da API do seu pacote tenham uma boa
aparência no site pub.dev, siga estas etapas:
Antes de publicar seu pacote, execute a ferramenta dartdoc para
garantir que seus documentos sejam gerados com êxito e tenham a
aparência esperada.
Depois de publicar seu pacote, verifique a guia Versões para
certificar-se de que os documentos foram gerados com sucesso.
Se os documentos não foram gerados, clique em falhou na guia
Versões para ver a saída do dartdoc.
Recursos
Use os seguintes recursos para aprender mais sobre os pacotes de
bibliotecas:
Bibliotecas e visibilidade nas capas do tour de idioma usando
arquivos de biblioteca.
A documentação do pacote é útil, particularmente as convenções de
layout do pacote.
O que não confirmar abrange o que não deve ser verificado em um
repositório de código-fonte.
Os pacotes de bibliotecas mais recentes sob a organização dart-lang
tendem a mostrar as melhores práticas. Considere estudar estes
exemplos: dart_style, path, shelf, source_gen e test.
Packages: Publicação de pacotes
O gerenciador de pacotes pub não serve apenas para usar pacotes de
outras pessoas. Também permite que você compartilhe seus pacotes com
o mundo. Se você tem um projeto útil e deseja que outras pessoas
possam usá-lo, use o comando pub publish .
Publicar É Para Sempre
Lembre-se de que publicar é para sempre. Assim que você publicar seu
pacote, os usuários podem confiar nele. Assim que começarem a fazer
isso, remover o pacote quebrará o deles. Para evitar isso, a política
pub.dev proíbe pacotes de cancelamento de publicação, exceto em
alguns casos.
Você sempre pode fazer upload de novas versões do seu pacote, mas as
antigas continuarão disponíveis para usuários que ainda não estão
prontos para atualizar.
Para pacotes já publicados que não são mais relevantes ou em
manutenção, você pode marcá-los como descontinuados.
Preparando Para Publicar
Ao publicar um pacote, é importante seguir o formato pubspec e as
convenções de layout de pacote. Alguns deles são necessários para que
outros possam usar o seu pacote. Outros são sugestões para ajudar a
tornar mais fácil para os usuários entender e trabalhar com seu pacote.
Em ambos os casos, o pub tenta ajudá-lo apontando as mudanças que
ajudarão a tornar seu pacote mais agradável com o ecossistema Dart.
Existem alguns requisitos adicionais para o upload de um pacote:
Você deve incluir um arquivo LICENSE que contenha uma licença de
código aberto. Recomendamos a licença BSD, que é usada pela
própria Dart. Você também deve ter o direito legal de redistribuir
qualquer coisa que você enviar como parte de seu pacote.
Seu pacote deve ter menos de 10 MB após a compactação gzip. Se
for muito grande, considere dividi-lo em vários pacotes ou reduzir o
número de recursos ou exemplos incluídos.
Seu pacote deve depender apenas das dependências hospedadas
(do servidor de pacote pub padrão) e das dependências do SDK
( sdk: flutter ). Essas restrições garantem que as dependências de
seus pacotes não se tornem indisponíveis no futuro.
Você deve ter uma Conta do Google, que o pub usa para gerenciar
as permissões de upload de pacotes. Sua Conta do Google pode ser
associada a um endereço do Gmail ou a qualquer outro endereço de
e-mail.

Arquivos importantes
Pub usa o conteúdo de alguns arquivos para criar uma página para seu
pacote em pub.dev/packages/<your_package> . Aqui estão os arquivos que
afetam a aparência da página do seu pacote:
README.md: O arquivo README.md é o conteúdo principal
apresentado na página do seu pacote. O conteúdo do arquivo é
renderizado como Markdown.
CHANGELOG.md: O arquivo CHANGELOG.md do seu pacote, se
encontrado, também é apresentado em uma guia na página do seu
pacote, para que os desenvolvedores possam lê-lo diretamente do
pub.dev. O conteúdo do arquivo é renderizado como Markdown.
O pubspec: O arquivo pubspec.yaml do seu pacote é usado para
preencher detalhes sobre o seu pacote no lado direito da página do
pacote, como sua descrição, página inicial etc.

Vantagens de usar um editor verificado


Você pode publicar pacotes usando um editor verificado (recomendado)
ou uma Conta do Google independente. Usar um editor verificado tem as
seguintes vantagens:
Os consumidores de seu pacote sabem que o domínio do editor foi
verificado.
Você pode evitar que o pub.dev exiba seu endereço de e-mail
pessoal. Em vez disso, pub.dev exibe o domínio do editor e o
endereço de contato.
Um emblema de editor verificado é exibido ao lado do nome do
seu pacote nas páginas de pesquisa e nas páginas de pacotes
individuais.

Criação de um editor verificado


Para criar um editor verificado, siga estas etapas:
1. Acesse pub.dev.
2. Faça login em pub.dev usando uma conta do Google.
3. No menu do usuário no canto superior direito, selecione Criar
editor.
4. Insira o nome de domínio que deseja associar ao seu editor (por
exemplo, dart.dev ) e clique em Criar editor.
5. Na caixa de diálogo de confirmação, selecione OK.
6. Se solicitado, conclua o fluxo de verificação, que abre o Google
Search Console.
a. Ao adicionar registros DNS, pode levar algumas horas para que
o Search Console reflita as alterações.
b. Quando o fluxo de verificação for concluído, volte para a etapa
4.
Publicando Seu Pacote
Use o comando pub publish para publicar seu pacote pela primeira vez ou
atualizá-lo para uma nova versão.

Realizando um ensaio
Para testar como pub publish funcionará, você pode realizar uma simulação:

O Pub certifica-se de que seu pacote segue o formato pubspec e as


convenções de layout de pacote e, em seguida, carrega seu pacote para
pub.dev. O Pub também mostra todos os arquivos que pretende publicar.
Aqui está um exemplo de publicação de um pacote chamado transmogrify :

Publicação
Quando estiver pronto para publicar seu pacote, remova o argumento --
dry-run :
Depois que um pacote foi transferido para um editor, você pode atualizar
o pacote usando pub publish .
Após o upload do seu pacote para pub.dev com êxito, qualquer usuário do
pub pode fazer o download ou depender dele em seus projetos. Por
exemplo, se você acabou de publicar a versão 1.0.0 do seu pacote
transmogrify , outro desenvolvedor Dart pode adicioná-lo como uma
dependência em pubspec.yaml :

Transferir um pacote para um editor verificado


Para transferir um pacote para um editor verificado, você deve ser um
uploader para o pacote e um administrador para o editor verificado.

Faça login em pub.dev com uma conta do Google listada como


uploader do pacote.
Vá para a página de detalhes do pacote (por exemplo,
https://pub.dev/packages/http ).
Selecione a guia Admin .
Insira o nome do editor e clique em Transferir para editor .
Quais Arquivos São Publicados?
Todos os arquivos em seu pacote estão incluídos no pacote publicado,
com as seguintes exceções:
Quaisquer diretórios packages .
Arquivo de bloqueio do seu pacote.
Se você não estiver usando o Git, todos os arquivos ocultos (ou
seja, arquivos cujos nomes começam com . (ponto) ).
Se você estiver usando Git, todos os arquivos serão ignorados por
seu arquivo .gitignore .
Certifique-se de excluir todos os arquivos que não deseja incluir (ou
adicioná-los .gitignore ). pub publish lista todos os arquivos que serão
publicados antes de enviar seu pacote, portanto, examine a lista
cuidadosamente antes de concluir o upload.
Uploaders
Quem quer que publique a primeira versão de um pacote torna-se
automaticamente a primeira e única pessoa autorizada a fazer upload de
versões adicionais desse pacote. Para permitir ou proibir que outras
pessoas carreguem versões, use o comando pub uploader ou transfira o
pacote para um editor verificado.
Se um pacote tiver um editor verificado, a página pub.dev desse pacote
exibirá o domínio do editor. Caso contrário, a página exibe os endereços
de e-mail dos uploaders autorizados para o pacote.
Publicação De Pré-Lançamentos
Conforme você trabalha em um pacote, considere publicá-lo como um
pré-lançamento. Os pré-lançamentos podem ser úteis quando qualquer
uma das seguintes condições for verdadeira:
Você está desenvolvendo ativamente a próxima versão principal do
pacote.
Você quer testadores beta para o próximo candidato a lançamento
do pacote.
O pacote depende de uma versão instável do Dart ou Flutter SDK.
Conforme descrito no controle de versão semântico, para fazer um pré-
lançamento de uma versão, você anexa um sufixo à versão. Por exemplo,
para fazer um pré-lançamento de uma versão 2.0.0 você pode usar a
versão 2.0.0-dev.1 . Posteriormente, quando você lançar a versão 2.0.0 , ela
terá precedência sobre todos os pré-lançamentos 2.0.0-XXX .
Como o pub prefere lançamentos estáveis quando disponíveis, os
usuários de um pacote de pré-lançamento podem precisar alterar suas
restrições de dependência. Por exemplo, se um usuário deseja testar os
pré-lançamentos da versão 2.1, em vez de ^2.0.0 ou ^2.1.0 eles podem
especificar ^2.1.0-dev.1 .

Quando um pré-lançamento é publicado no pub.dev, a página do pacote


exibe links para o pré-lançamento e o lançamento estável. O pré-
lançamento não afeta a pontuação da análise, não aparece nos resultados
da pesquisa nem substitui o pacote README.md e a documentação.
Marcando Pacotes Como Descontinuados
Embora os pacotes sempre permaneçam publicados, pode ser útil
sinalizar aos desenvolvedores que um pacote não está mais sendo
mantido ativamente. Para isso, você pode marcar um pacote como
descontinuado . Um pacote descontinuado permanece publicado e pode
ser visualizado no pub.dev, mas tem um emblema claro de
DESCONTINUADO e não aparece nos resultados de pesquisa do
pub.dev.
Para marcar um pacote como descontinuado, faça login em pub.dev
usando uma Conta do Google de uploader ou administrador de editor
verificado para o pacote. Em seguida, use a guia Admin do pacote
individual para marcar o pacote como descontinuado.
Recursos
Para obter mais informações, consulte as páginas de referência para os
seguintes comandos pub :
publicação de pub
carregador de pub
Packages: Package Reference:
Dependências de pacote
Dependências são um dos conceitos principais do gerenciador de pacotes
pub. Uma dependência é outro pacote de que seu pacote precisa para
funcionar. As dependências são especificadas em seu pubspec. Você lista
apenas dependências imediatas - o software que seu pacote usa
diretamente. Pub lida com dependências transitivas para você.
Esta página contém informações detalhadas sobre como especificar
dependências. No final, está uma lista das melhores práticas para
dependências de pacotes.
Visão Global
Para cada dependência, você especifica o nome do pacote do qual
depende e o intervalo de versões desse pacote que você permite. Você
também pode especificar a fonte, que informa ao pub como localizar o
pacote e qualquer descrição adicional de que a fonte precisa para
encontrar o pacote.
Aqui está um exemplo de especificação de uma dependência:

Este código YAML cria uma dependência do pacote transmogrify usando a


fonte padrão (pub.dev) e permitindo qualquer versão de 1.0.0 a 2.0.0 (mas
não incluindo 2.0.0 ). Consulte a seção de restrições de versão desta
página para obter detalhes de sintaxe.
Se você deseja especificar uma fonte, a sintaxe é diferente:

Este código YAML cria uma dependência do pacote transmogrify usando o


código-fonte hosted . Tudo na chave da fonte (aqui, apenas um mapa com
uma chave url: ) é a descrição que é passada à fonte. Cada fonte tem seu
próprio formato de descrição, que é descrito na seção de fontes de
dependência desta página. A restrição de versão é opcional, mas
recomendada.
Use este formato longo quando não usar a fonte padrão ou quando tiver
uma descrição complexa que precisa ser especificada. Mas na maioria
dos casos, você usará apenas o formulário simples packagename:
version.
Fontes De Dependência
O Pub pode usar as seguintes fontes para localizar pacotes:
SDK
Pacotes hospedados
Pacotes Git
Pacotes de caminho

SDK
A origem do SDK é usada para quaisquer SDKs enviados junto com os
pacotes, que podem ser dependências. Atualmente, Flutter é o único SDK
compatível.
A sintaxe é semelhante a esta:

O identificador após sdk: indica de qual SDK o pacote vem. Se for flutter , a
dependência é satisfatória, desde que:
Pub está sendo executado no contexto do executável flutter
O Flutter SDK contém um pacote com o nome fornecido
A versão desse pacote corresponde à restrição de versão
Se for um identificador desconhecido, a dependência é sempre
considerada insatisfeita.
Um pacote com uma dependência sdk deve ter uma restrição Dart SDK
com uma versão mínima de pelo menos 1.19.0. Essa restrição garante
que as versões mais antigas do pub não instalem pacotes que tenham
dependências sdk .

Pacotes hospedados
Um pacote hospedado é aquele que pode ser baixado do site pub.dev
(ou de outro servidor HTTP que fala a mesma API). Aqui está um exemplo
de declaração de uma dependência em um pacote hospedado:

Este exemplo especifica que seu pacote depende de um pacote


hospedado chamado transmogrify e funcionará com qualquer versão de
1.4.0 a 2.0.0 (mas não 2.0.0 em si).
Se quiser usar seu próprio servidor de pacotes, você pode usar uma
descrição que especifica seu URL:

Pacotes Git
Às vezes, você vive no limite e precisa usar pacotes que ainda não foram
lançados formalmente. Talvez seu próprio pacote ainda esteja em
desenvolvimento e esteja usando outros pacotes que estão sendo
desenvolvidos ao mesmo tempo. Para tornar isso mais fácil, você pode
depender diretamente de um pacote armazenado em um repositório Git.

O git aqui diz que este pacote é encontrado usando Git, e a URL depois
disso é a URL do Git que pode ser usada para clonar o pacote.
Mesmo se o repo do pacote for privado, se você puder se conectar ao
repo usando SSH, poderá depender do pacote usando o URL SSH do
repo:

Se você quiser depender de um commit, branch ou tag específico,


adicione um argumento ref :

O ref pode ser qualquer coisa que o Git permitir para identificar um
commit.
Pub assume que o pacote está na raiz do repositório Git. Para especificar
um local diferente no repo, use o argumento path :

O caminho é relativo, a raiz do repositório Git.

Pacotes de caminho
Às vezes, você se pega trabalhando em vários pacotes relacionados ao
mesmo tempo. Talvez você esteja criando uma estrutura enquanto
constrói um aplicativo que a usa. Nesses casos, durante o
desenvolvimento, você realmente deseja depender da versão ativa desse
pacote em seu sistema de arquivos local. Dessa forma, as alterações em
um pacote são instantaneamente detectadas por aquele que depende
dele.
Para lidar com isso, pub oferece suporte a dependências de caminho .

Isso diz que o diretório raiz transmogrify e /Users/me/transmogrify . Para esta


dependência, pub gera um link simbólico diretamente para o diretório lib do
diretório do pacote referenciado. Todas as alterações feitas no pacote
dependente são vistas imediatamente. Você não precisa executar o pub
toda vez que alterar o pacote dependente.
Caminhos relativos são permitidos e considerados relativos ao diretório
que contém seu pubspec.
As dependências de caminho são úteis para o desenvolvimento local,
mas não funcionam ao compartilhar código com o mundo externo - nem
todos podem acessar seu sistema de arquivos. Por isso, você não pode
carregar um pacote no site pub.dev se ele tiver alguma dependência de
caminho em seu pubspec.
Em vez disso, o fluxo de trabalho típico é:
1. Edite seu pubspec localmente para usar uma dependência de
caminho.
2. Trabalhe no pacote principal e no pacote do qual ele depende.
3. Assim que ambos estiverem funcionando, publique o pacote
dependente.
4. Altere seu pubspec para apontar para a versão agora hospedada de
seu dependente.
5. Publique seu pacote principal também, se quiser.
Restrições De Versão
Especificar restrições de versão permite que as pessoas que usam seu
pacote saibam em quais versões de suas dependências podem confiar
para serem compatíveis com sua biblioteca. Seu objetivo é permitir uma
gama de versões tão ampla quanto possível para dar flexibilidade aos
seus usuários. Mas deve ser estreito o suficiente para excluir versões que
você sabe que não funcionam ou não foram testadas.
A comunidade Dart usa versionamento semântico1, que ajuda você a
saber quais versões devem funcionar. Se você sabe que seu pacote
funciona bem com 1.2.3 alguma dependência, então o versionamento
semântico diz que ele deve funcionar com qualquer versão estável
subsequente antes 2.0.0 . Para obter detalhes sobre o sistema de versão
do pub, consulte a página de controle de versão do pacote.
Você pode expressar restrições de versão usando a sintaxe de
circunflexo ( ^1.2.3 ) ou a sintaxe tradicional ( '>=1.2.3 <2.0.0' ).

Sintaxe de acento circunflexo


A sintaxe do cursor é uma forma compacta de expressar o tipo mais
comum de restrição de versão. ^version significa a gama de todas as
versões com garantia de compatibilidade retroativa com a versão
especificada .
Por exemplo, ^1.2.3 é equivalente a '>=1.2.3 <2.0.0' e ^0.1.2 é equivalente a
'>=0.1.2 <0.2.0' . A seguir está um exemplo de sintaxe circunflexa:

Como a sintaxe circunflexa foi introduzida no Dart 1.8.3, ela requer uma
restrição do SDK (usando a sintaxe tradicional) para garantir que as
versões anteriores do pub não tentem processá-la. Por exemplo:

Sintaxe tradicional
Uma restrição de versão que usa sintaxe tradicional é uma série do
seguinte:
any
A string any permite qualquer versão. Isso é equivalente a uma
restrição de versão vazia, mas é mais explícito. Embora any seja
permitido, não o recomendamos.
1.2.3
Um número de versão concreto fixa a dependência para permitir
apenas aquela versão exata . EVITAR usar isso quando puder,
porque pode causar bloqueio de versão para seus usuários e
dificultar o uso de seu pacote junto com outros pacotes que também
dependem dele.
>=1.2.3
Permite a versão fornecida ou qualquer outra superior. Você
normalmente usará isso.
>1.2.3
Permite qualquer versão maior do que a especificada, mas não a
própria versão.
<=1.2.3
Permite qualquer versão menor ou igual à especificada. Você
normalmente não vai usar isso.
<1.2.3
Permite qualquer versão inferior à especificada, mas não a própria
versão. Isso é o que você normalmente usará porque permite
especificar a versão superior que você sabe que não funciona com
o seu pacote (porque é a primeira versão a apresentar algumas
alterações importantes).
Você pode especificar as partes da versão como quiser e seus intervalos
são interceptados. Por exemplo, '>=1.2.3 <2.0.0' permite qualquer versão de
1.2.3 a se 2.0.0 excluir 2.0.0 . Uma maneira mais fácil de expressar esse
intervalo é usando a sintaxe de circunflexo ou ^1.2.3 .
Dependências Dev
Pub oferece suporte a dois tipos de dependências: dependências
regulares e dependências de desenvolvimento. As dependências de
desenvolvimento diferem das dependências regulares em que as
dependências de desenvolvimento de pacotes dos quais você depende
são ignoradas . Aqui está um exemplo:
Digamos que o pacote transmogrify use o pacote test em seus testes e
apenas em seus testes. Se alguém quiser apenas usar transmogrify —
importar suas bibliotecas — realmente não precisa test . Nesse caso, ele é
especificado test como uma dependência dev. Seu pubspec terá algo
como:

O Pub obtém todos os pacotes dos quais seu pacote depende, e tudo de
que esses pacotes dependem, transitivamente. Ele também obtém as
dependências dev do seu pacote, mas ignora as dependências dev de
quaisquer pacotes dependentes. O Pub obtém apenas as dependências
de desenvolvimento do seu pacote. Então, quando seu pacote depender,
transmogrify ele vai pegar, transmogrify, mas não vai test .
A regra para decidir entre uma dependência regular ou dev é simples: se
a dependência for importada de algo em seus diretórios lib ou bin , ela
precisa ser uma dependência regular. Se for importado apenas de test ,
example etc., pode e deve ser uma dependência dev.
Usar dependências de dev torna os gráficos de dependências menores.
Isso torna a execução pub mais rápida e torna mais fácil encontrar um
conjunto de versões de pacote que satisfaça todas as restrições.
Modificações De Dependência
Você pode usar dependency_overrides para substituir temporariamente todas
as referências a uma dependência.
Por exemplo, talvez você esteja atualizando uma cópia local do
transmogrify, um pacote de biblioteca publicado. O transmogrify é usado
por outros pacotes em seu gráfico de dependência, mas você não quer
clonar cada pacote localmente e alterar cada pubspec para testar sua
cópia local do transmogrify.
Nessa situação, você pode substituir a dependência usando
dependency_overrides para especificar o diretório que contém a cópia local do
pacote.
O pubspec seria parecido com o seguinte:

Quando você executa pub get ou pub upgrade, o arquivo de bloqueio do


pubspec é atualizado para refletir o novo caminho para sua dependência
e, sempre que o transmogrify é usado, o pub usa a versão local.
Você também pode usar dependency_overrides para especificar uma
versão particular de um pacote:
Melhores Práticas
É importante gerenciar ativamente suas dependências e garantir que seus
pacotes usem as versões mais recentes possíveis. Se alguma
dependência estiver desatualizada, você pode ter não apenas uma versão
desatualizada desse pacote, mas também versões desatualizadas de
outros pacotes em seu gráfico de dependência que dependem desse
pacote. Essas versões desatualizadas podem ter um impacto negativo na
estabilidade, desempenho e qualidade dos aplicativos.
Recomendamos as seguintes práticas recomendadas para dependências
de pacote:

Use sintaxe circunflexa


Especificar dependências com intervalos de versão ^1.6.3 é uma boa
prática porque permite que a ferramenta pub selecione versões mais
recentes do pacote quando estiverem disponíveis. Além disso, coloca um
limite superior na versão permitida, com base na suposição de que os
pacotes usam versões semânticas, onde qualquer versão do caminho
versionado 1.x é compatível, mas onde uma nova versão 2.x seria uma
atualização importante que não é semanticamente compatível com as
versões 1.x .

Depende das últimas versões de pacotes estáveis


Use pub upgrade para atualizar para as versões de pacote mais recentes
permitidas pelo pubspec. Para identificar dependências em seu aplicativo
ou pacote que não estão nas versões estáveis mais recentes, use pub
outdated.

Teste sempre que atualizar as dependências do pacote


Se você executar pub upgrade sem atualizar seu pubspec, a API deve
permanecer a mesma e seu código deve ser executado como antes - mas
teste para ter certeza. Se você modificar o pubspec e atualizar para uma
nova versão principal, poderá encontrar alterações importantes, portanto,
é necessário testar ainda mais profundamente.
[1] Pub segue a versão 2.0.0-rc.1 da especificação de controle de versão semântica
porque essa versão permite que os pacotes usem identificadores de compilação
( +12345 ) para diferenciar as versões.
Packages: Package Reference: Glossário de
termos do pacote
Os termos a seguir são usados na documentação para gerenciamento de
pacotes e a ferramenta pub.
Pacote De Aplicativos
Um pacote que não se destina a ser usado como biblioteca. Os pacotes
de aplicativos podem ter dependências de outros pacotes, mas nunca
dependem de si mesmos. Normalmente, eles devem ser executados
diretamente, na linha de comando ou em um navegador. O oposto de um
pacote de aplicativo é um pacote de biblioteca.
Os pacotes de aplicativos devem verificar seus arquivos de bloqueio no
controle de origem, para que todos os que trabalham no aplicativo e cada
local em que o aplicativo é implantado tenha um conjunto consistente de
dependências. Como suas dependências são restringidas pelo lockfile, os
pacotes de aplicativos geralmente especificam any as restrições de versão
de suas dependências.
Dependência
Outro pacote do qual seu pacote depende. Se o seu pacote deseja
importar código de algum outro pacote, esse pacote deve ser uma
dependência. As dependências são especificadas no pubspec do seu
pacote e descritas em Dependências do pacote.
Para ver as dependências usadas por um pacote, use pub deps.
Ponto De Entrada
No contexto geral do Dart, um ponto de entrada é uma biblioteca Dart
que é chamada diretamente por uma implementação Dart. Quando você
faz referência a uma biblioteca Dart em uma tag <script> ou passa como
um argumento de linha de comando para a VM Dart independente, essa
biblioteca é o ponto de entrada. Em outras palavras, geralmente é o
arquivo .dart que contém main() .
No contexto do pub, um pacote de ponto de entrada ou pacote raiz é a
raiz de um gráfico de dependência. Geralmente será um aplicativo.
Quando você executa seu aplicativo, é o pacote de ponto de entrada.
Todos os outros pacotes dos quais depende não serão um ponto de
entrada nesse contexto.
Um pacote pode ser um ponto de entrada em alguns contextos e não em
outros. Digamos que seu aplicativo use um pacote de biblioteca A.
Quando você executa seu aplicativo, A não é o pacote de ponto de
entrada. No entanto, se você for até A e executar seus testes, nesse
contexto, é o ponto de entrada, já que seu aplicativo não está envolvido.
Diretório De Ponto De Entrada
Um diretório dentro do seu pacote que pode conter pontos de entrada do
DART .
Pub tem uma lista destes diretórios: benchmark , bin , example , test , tool e
web . Quaisquer subdiretórios desses (exceto bin ) também podem conter
pontos de entrada.
Dependência Imediata
Uma dependência que seu pacote usa diretamente. As dependências que
você lista em seu pubspec são as dependências imediatas do seu pacote.
Todas as outras dependências são dependências transitivas.
Pacote De Biblioteca
Um pacote do qual outros pacotes podem depender. Os pacotes de
biblioteca podem ter dependências de outros pacotes e podem ser eles
próprios dependências. Eles também podem incluir scripts para serem
executados diretamente. O oposto de um pacote de biblioteca é um
pacote de aplicativo.
Não verifique o arquivo de bloqueio de um pacote de biblioteca no
controle de origem, pois as bibliotecas devem oferecer suporte a uma
variedade de versões de dependência. As restrições de versão das
dependências imediatas de um pacote de biblioteca devem ser as mais
amplas possíveis, ao mesmo tempo em que garante que as dependências
serão compatíveis com as versões testadas.
Uma vez que o controle de versão semântico requer que as bibliotecas
incrementem seus números de versão principal para quaisquer alterações
incompatíveis com versões anteriores, os pacotes de biblioteca
geralmente requerem que as versões de suas dependências sejam
maiores ou iguais às versões que foram testadas e menores que a
próxima versão principal. Portanto, se sua biblioteca dependesse do
pacote (fictício) transmogrify e você o testasse na versão 1.2.1, sua restrição
de versão seria ^1.2.1 .
Lockfile
Um arquivo nomeado pubspec.lock que especifica as versões concretas e
outras informações de identificação para cada dependência imediata e
transitiva da qual um pacote depende.
Ao contrário do pubspec, que lista apenas dependências imediatas e
permite intervalos de versão, o arquivo de bloqueio fixa de forma
abrangente o gráfico de dependência inteiro para versões específicas de
pacotes. Um arquivo de bloqueio garante que você possa recriar a
configuração exata dos pacotes usados por um aplicativo.
O lockfile é gerado automaticamente para você por pub quando você
executar pub get, pub upgrade ou pub downgrade. Se o seu pacote for um
pacote de aplicativo, você normalmente verificará isso no controle de
origem. Para pacotes de biblioteca, você geralmente não vai.
Restrição SDK
As versões declaradas do próprio SDK do Dart que um pacote declara
que oferece suporte. Uma restrição do SDK é especificada usando a
sintaxe de restrição de versão normal, mas em uma seção de ambiente
especial no pubspec.
Fonte
Uma espécie de lugar onde o pub pode pegar pacotes. Uma fonte não é
um lugar específico como o site pub.dev ou algum URL específico do Git.
Cada fonte descreve um procedimento geral para acessar um pacote de
alguma forma. Por exemplo, git é uma fonte. O código-fonte git sabe como
fazer download de pacotes com um URL Git. Várias fontes diferentes com
suporte estão disponíveis.
Cache Do Sistema
Quando o pub obtém um pacote remoto, ele o baixa em um único diretório
de cache do sistema mantido pelo pub. No Mac e no Linux, esse diretório
é padronizado como ~/.pub-cache . No Windows, o arquivo reside
%LOCALAPPDATA%\Pub\Cache\bin , embora sua localização exata possa
variar dependendo da versão do Windows. Você pode especificar um local
diferente usando a variável de ambiente PUB_CACHE.
Depois que os pacotes estão no cache do sistema, o pub cria um arquivo
.packages que mapeia cada pacote usado pelo seu aplicativo para o pacote
correspondente no cache.
Você só precisa baixar uma determinada versão de um pacote uma vez e
poder reutilizá-la em quantos pacotes desejar. Você pode excluir e
regenerar seu arquivo .packages sem precisar acessar a rede.
Dependência Transitiva
Uma dependência que seu pacote usa indiretamente porque uma de suas
dependências a exige. Se o seu pacote depende de A, que por sua vez
depende de B, que depende de C, então A é uma dependência imediata e
B e C são transitivos.
Uploader
Alguém que tem permissões administrativas para um pacote. Um
carregador de pacote pode fazer upload de novas versões do pacote e
pode adicionar e remover outros uploaders desse pacote.
Se um pacote tiver um editor verificado, todos os membros do editor
podem fazer upload do pacote.
Editora Verificada
Um ou mais usuários que possuem um conjunto de pacotes. Cada editor
verificado é identificado por um nome de domínio verificado, como
dart.dev. Para obter informações gerais sobre editores verificados,
consulte a página de editores verificados. Para obter detalhes sobre como
criar um editor verificado e transferir pacotes para ele, consulte a
documentação para publicar pacotes.
Restrição De Versão
Uma restrição colocada em cada dependência de um pacote que
especifica com quais versões dessa dependência o pacote deve trabalhar.
Pode ser uma única versão ( 0.3.0 ) ou um intervalo de versões ( ^1.2.1 ).
Embora any também seja permitido, por motivos de desempenho não o
recomendamos.
Para obter mais informações, consulte Restrições de versão.
Os pacotes de bibliotecas devem sempre especificar restrições de versão
para todas as suas dependências, mas os pacotes de aplicativos
geralmente devem permitir qualquer versão de suas dependências, uma
vez que usam o arquivo de bloqueio para gerenciar suas versões de
dependência.
Para obter mais informações, consulte Filosofia de versão de pub.
Packages: Package Reference: Convenções
de layout de pacote
Quando você constrói um pacote pub, nós o encorajamos a seguir as
convenções que esta página descreve. Eles descrevem como você
organiza os arquivos e diretórios dentro do seu pacote e como dar nomes
às coisas.

Veja como um pacote completo (nomeado enchilada ) que usa todos os


cantos dessas diretrizes pode ser semelhante:
* O diretório .dart_tool e o arquivo .packages existem após a execução pub
get . Não os verifique no controle de origem.
** O arquivo pubspec.lock existe depois de executado pub get . Deixe-o fora
do controle de origem, a menos que seu pacote seja um pacote de
aplicativo.
*** O diretório doc/api existe localmente após você executar o dartdoc. Não
verifique o diretório api no controle de origem.
O Pubspec

Cada pacote possui um pubspec, um arquivo denominado pubspec.yaml ,


no diretório raiz do pacote. Isso é o que o torna um pacote.
Executar pub get, pub upgrade ou pub downgrade no pacote cria um
arquivo de bloqueio denominado pubspec.lock. Se o seu pacote for um
pacote de aplicativo, verifique o arquivo de bloqueio no controle de
origem. Caso contrário, não faça isso.
Para obter mais informações, consulte a página pubspec.
Licença

Se você estiver publicando seu pacote, inclua um arquivo de licença


chamado LICENSE . Recomendamos o uso de uma licença aprovada pela
OSI, como a BSD-3-Clause, para que outras pessoas possam reutilizar
seu trabalho.
README.md

Um arquivo que é muito comum em código aberto é um arquivo README


que descreve o projeto. Isso é especialmente importante no pub. Quando
você faz upload para o site pub.dev, seu arquivo README.md é mostrado -
renderizado como Markdown - na página do seu pacote. Este é o lugar
perfeito para apresentar seu código às pessoas.
CHANGELOG.md

Inclua um arquivo CHANGELOG.md que tenha uma seção para cada versão
do seu pacote, com notas para ajudar os usuários na atualização do seu
pacote. Os usuários do seu pacote frequentemente revisam o changelog
para descobrir correções de bugs e novos recursos, ou para determinar
quanto esforço será necessário para atualizar para a versão mais recente
do seu pacote.
Para oferecer suporte a ferramentas de análise CHANGELOG.md , use o
seguinte formato:
Cada versão possui sua própria seção com um título.
Os títulos das versões são todos de nível 1 ou todos de nível 2.
O texto do cabeçalho da versão contém um número de versão do
pacote, opcionalmente prefixado com “v”.
Quando você enviar o seu pacote para o local pub.dev, do seu pacote de
arquivo CHANGELOG.md (se houver) aparece no Changelog guia, rendida
como Markdown.
Aqui está um exemplo de arquivo CHANGELOG.md . Como mostra o
exemplo, você pode adicionar subseções.
Diretórios Públicos
Dois diretórios em seu pacote são públicos para outros pacotes: lib e
bin . Você coloca bibliotecas públicas em lib e ferramentas públicas em
bin .

Bibliotecas públicas
A seguinte estrutura de diretório mostra a lib parte da enchilada:

Muitos pacotes são pacotes de biblioteca: eles definem bibliotecas Dart


que outros pacotes podem importar e usar. Esses arquivos de biblioteca
pública Dart vão dentro de um diretório chamado lib .
A maioria dos pacotes define uma única biblioteca que os usuários podem
importar. Nesse caso, seu nome normalmente deve ser igual ao nome do
pacote, como enchilada.dart no exemplo aqui. Mas você também pode
definir outras bibliotecas com quaisquer nomes que façam sentido para o
seu pacote.
Ao fazer isso, os usuários podem importar essas bibliotecas usando o
nome do pacote e o arquivo da biblioteca, assim:

Se você deseja organizar suas bibliotecas públicas, também pode criar


subdiretórios dentro delas lib . Se você fizer isso, os usuários especificarão
esse caminho ao importá-lo. Digamos que você tenha a seguinte
hierarquia de arquivos:

Os usuários importam da olives.dart seguinte maneira:


Observe que apenas bibliotecas devem estar em lib . Pontos de entrada -
scripts de inicialização com uma função main() - não podem entrar lib . Se
você colocar um script Dart dentro lib , descobrirá que as importações
package: que ele contém não são resolvidas. Em vez disso, seus pontos de
entrada devem ir para o diretório de pontos de entrada apropriado.

Para obter mais informações sobre pacotes de biblioteca, consulte


Criando pacotes.

Ferramentas públicas
Os scripts do DART colocados dentro do diretório bin são públicos. Se
você estiver dentro do diretório de um pacote, poderá usar pub run para
executar scripts dos diretórios bin de qualquer outro pacote do qual o
pacote dependa. De qualquer diretório, você pode usar pub global run
para executar scripts de pacotes que ativou usando pub global activate .
Se você deseja que seu pacote seja confiável e deseja que seus scripts
sejam privados para seu pacote, coloque-os no diretório tool de nível
superior. Se você não deseja que seu pacote seja dependente, você pode
deixar seus scripts em bin .
Bens Públicos

Embora a maioria dos pacotes de biblioteca existam para permitir a


reutilização do código Dart, você também pode reutilizar outros tipos de
conteúdo. Por exemplo, um pacote para Bootstrap pode incluir vários
arquivos CSS para os consumidores do pacote usarem.
Eles vão para o diretório lib de nível superior. Você pode colocar qualquer
tipo de arquivo lá e organizá-lo com subdiretórios como quiser.
Você pode fazer referência a ativos de outro pacote usando o pacote de
recursos.
Arquivos De Implementação

As bibliotecas internas lib são visíveis publicamente: outros pacotes são


gratuitos para importá-los. Mas muito do código de um pacote são
bibliotecas de implementação interna que só devem ser importadas e
usadas pelo próprio pacote. Esses vão dentro de um subdiretório de lib
chamados src . Você pode criar subdiretórios lá se isso ajudar a organizar
as coisas.
Você é livre para importar bibliotecas que residem em lib/src outro código
Dart no mesmo pacote (como outras bibliotecas em lib , scripts em bin e
testes), mas você nunca deve importar de outro diretório de pacote lib/src .
Esses arquivos não fazem parte da API pública do pacote e podem ser
alterados de maneiras que podem quebrar seu código.
Quando você usa bibliotecas de dentro de seu próprio pacote, até mesmo
código src , você ainda pode (e deve) usar package: para importá-los. Por
exemplo:

O nome que você usa aqui (neste caso enchilada ) é o nome que você
específica para o seu pacote em seu pubspec.
Arquivos Da Web

Para pacotes da web, coloque o código do ponto de entrada - scripts Dart


que incluem arquivos de suporte main() , como CSS ou HTML - em web .
Você pode organizar o diretório web em subdiretórios, se desejar.
Coloque o código da biblioteca em lib . Se a biblioteca não for importada
diretamente pelo código em web ou por outro pacote, coloque-a em lib/src .
Coloque exemplos baseados na web em example . Consulte Ativos
públicos para dicas sobre onde colocar ativos, como imagens.
Aplicativos De Linha De Comando

Alguns pacotes definem programas que podem ser executados


diretamente na linha de comando. Podem ser scripts de shell ou qualquer
outra linguagem de script, incluindo Dart. O próprio aplicativo pub é um
exemplo: é um script de shell simples que invoca pub.dart .
Se o seu pacote define o código como este, coloque-o em um diretório
chamado bin . Você pode executar esse script de qualquer lugar na linha
de comando, se configurá-lo usando pub global.
Testes E Benchmarks

Cada pacote deve ter testes. Com o pub, a convenção é que eles vão
para um diretório test (ou algum diretório dentro dele, se preferir) e tenham
_test no final seus nomes de arquivo.
Normalmente, eles usam o pacote de teste.

Pacotes que possuem código crítico de desempenho também podem


incluir benchmarks . Estes testam a API não para correção, mas para
velocidade (ou uso de memória, ou talvez outras métricas empíricas).
Documentação

Se você tem código e testes, a próxima parte que você pode querer é
uma boa documentação. Isso vai dentro de um diretório chamado doc .
Quando você executa a ferramenta dartdoc, ela coloca a documentação
da API, por padrão, em doc/api . Como a documentação da API é gerada a
partir do código-fonte, você não deve colocá-la sob controle do código-
fonte.
Além do gerado api , não temos quaisquer diretrizes sobre o formato ou
organização da documentação de sua autoria. Use o formato de
marcação de sua preferência.
Exemplos

Código, testes, documentos, o que mais seus usuários podem querer?


Programas de exemplo independentes que usam seu pacote, é claro!
Esses vão dentro do diretório example . Se os exemplos forem complexos
e usarem vários arquivos, considere criar um diretório para cada exemplo.
Caso contrário, você pode colocar cada um dentro example .
Em seus exemplos, use package: para importar arquivos de seu próprio
pacote. Isso garante que o código de exemplo em seu pacote se pareça
exatamente com o código fora de seu pacote.
Se você deseja publicar seu pacote, considere a criação de um arquivo de
exemplo com um dos seguintes nomes (não diferencia maiúsculas de
minúsculas):
example/example[.md]
example[/lib]/main.dart
example[/lib]/package_name.dart
example[/lib]/package_name_example.dart
example[/lib]/example.dart
example/readme[.md]
Quando você publica um pacote que contém um ou mais dos arquivos
acima, o site pub.dev cria uma guia Exemplo para exibir o primeiro
arquivo que encontrar (pesquisando na ordem mostrada na lista acima).
Por exemplo, se seu pacote tiver muitos arquivos em seu diretório
example , incluindo um arquivo chamado README.md , a guia Exemplo de
seu pacote exibe o conteúdo de example/README.md (analisado como
Markdown).
Ferramentas E Scripts Internos

Pacotes maduros geralmente têm pequenos scripts e programas


auxiliares que as pessoas executam enquanto desenvolvem o próprio
pacote. Pense em coisas como executores de teste, geradores de
documentação ou outros bits de automação.
Ao contrário dos scripts em bin , eles não são para usuários externos do
pacote. Se você tiver algum desses, coloque-os em um diretório chamado
tool .
Packages: Package Reference:
Configurando variáveis de ambiente pub
As variáveis de ambiente permitem que você personalize o pub para
atender às suas necessidades.
PUB_CACHE
Algumas das dependências do pub são baixadas para o cache do
pub. Por padrão, esse diretório está localizado .pub-cache em seu
diretório inicial (no Mac e Linux) ou em %LOCALAPPDATA%\Pub\Cache
(no Windows). (A localização precisa do cache pode variar
dependendo da versão do Windows.) Você pode usar a variável de
ambiente PUB_CACHE para especificar outro local. Para obter mais
informações, consulte O cache do pacote do sistema.
PUB_HOSTED_URL
Pub baixa dependências do site pub.dev. Para especificar a
localização de um determinado servidor espelho, use a variável de
ambiente PUB_HOSTED_URL . Por exemplo:
Packages: Package Reference: O arquivo
pubspec
Cada pacote pub precisa de alguns metadados para que possa
especificar suas dependências. Os pacotes de publicação compartilhados
com outras pessoas também precisam fornecer algumas outras
informações para que os usuários possam descobri-los. Todos esses
metadados vão para o pubspec do pacote : um arquivo nomeado
pubspec.yaml que é escrito na linguagem YAML.
Campos Suportados
Um pubspec pode ter os seguintes campos:
name
Obrigatório para cada pacote.

version
Obrigatório para pacotes hospedados no site pub.dev.
description
Obrigatório para pacotes hospedados no site pub.dev.
homepage
Opcional. URL apontando para a página inicial do pacote (ou repositório de
código-fonte).
repository
Opcional. URL apontando para o repositório de código-fonte do pacote.
issue_tracker
Opcional. URL apontando para um rastreador de problemas para o pacote.

documentation
Opcional. URL apontando para a documentação do pacote.
dependencies
Pode ser omitido se seu pacote não tiver dependências.
dev_dependencies
Pode ser omitido se o seu pacote não tiver dependências de desenvolvimento.
dependency_overrides
Pode ser omitido se você não precisar substituir nenhuma dependência.

environment
Exigido a partir do Dart 2.
executables
Opcional. Usado para colocar os executáveis de um pacote em seu PATH.
publish_to
Opcional. Especifique onde publicar um pacote.
Pub ignora todos os outros campos,
Se você adicionar um campo personalizado, dê a ele um nome exclusivo
que não entre em conflito com os campos futuros do pubspec. Por
exemplo, em vez de adicionar bugs , você pode adicionar um campo
chamado my_pkg_bugs .
Exemplo
Um pubspec simples, mas completo, é parecido com o seguinte:
Detalhes
Esta seção contém mais informações sobre a maioria dos campos
pubspec.

Nome
Todo pacote precisa de um nome. É como outros pacotes se referem ao
seu e como ele aparece para o mundo, caso você o publique.
O nome deve estar todo em letras minúsculas, com sublinhados para
separar as palavras just_like_this . Use apenas letras latinas básicas e
algarismos arábicos: [a-z0-9_] . Além disso, certifique-se de que o nome
seja um identificador DART válido - que não comece com dígitos e não
seja uma palavra reservada.
Tente escolher um nome que seja claro, conciso e que ainda não esteja
em uso. Recomenda -se uma rápida pesquisa de pacotes no site pub.dev
para se certificar de que nada mais está usando seu nome.

Versão
Cada pacote possui uma versão. Um número de versão é necessário para
hospedar seu pacote no site pub.dev, mas pode ser omitido para pacotes
apenas locais. Se você o omitir, seu pacote terá versão implicitamente
0.0.0 .
O controle de versão é necessário para reutilizar o código enquanto
permite que ele evolua rapidamente. Um número de versão são três
números separados por pontos, como 0.2.43 . Ele também pode,
opcionalmente, ter uma configuração ( +1 , +2 , +hotfix.oopsie ) ou de pré-
lançamento ( -dev.4 , -alpha.12 , -beta.7 , -rc.5 ) sufixo.
Cada vez que você publica seu pacote, você o publica em uma versão
específica. Feito isso, considere-o hermeticamente fechado: você não
pode mais tocá-lo. Para fazer mais alterações, você precisará de uma
nova versão.
Ao selecionar uma versão, siga o controle de versão semântico.

Descrição
Isso é opcional para seus próprios pacotes pessoais, mas se você
pretende publicar seu pacote, você deve fornecer uma descrição, que
deve ser em inglês. A descrição deve ser relativamente curta - 60 a 180
caracteres - e dizer a um leitor casual o que ele pode querer saber sobre o
seu pacote.
Pense na descrição como o argumento de venda do seu pacote. Os
usuários veem quando procuram pacotes. A descrição é um texto simples:
sem marcação ou HTML.

Autor / autores
Descontinuada. Use um editor verificado.
Você pode ver uma seção author ou authors em pubspecs antigos. Esses
campos opcionais são uma forma de descrever o (s) autor (es) do seu
pacote e fornecer informações de contato. Cada autor pode ser um único
nome ( Natalie Weizenbaum ) ou um nome e um endereço de e-mail ( Natalie
Weizenbaum <nweiz@google.com> ). No entanto, esses valores não foram
verificados.
O site pub.dev não exibe mais os autores do pacote e (a partir do Dart
2.7) o pub publish comando exibe um aviso se o seu pubspec tiver uma
seção author ou authors .

Página inicial
Deve ser um URL apontando para o site do seu pacote. Para pacotes
hospedados, este URL está vinculado à página do pacote. Embora o
fornecimento de um homepage seja opcional, forneça -o ou repository (ou
ambos). Ajuda os usuários a entender de onde seu pacote está vindo.

Repositório
O repositor campo opcional deve conter a URL do repositório de código-
fonte do seu pacote - por exemplo https://github.com/<user>/<repository> . Se
você publicar seu pacote no site pub.dev, a página do pacote exibirá o
URL do repositório. Embora o fornecimento de um repositor seja opcional,
forneça -o ou homepage (ou ambos). Ajuda os usuários a entender de
onde seu pacote está vindo.

Rastreador de problemas
O issue_tracker campo opcional deve conter uma URL para o rastreador de
problemas do pacote, onde bugs existentes podem ser visualizados e
novos bugs podem ser arquivados. O site pub.dev tenta exibir um link para
o rastreador de problemas de cada pacote, usando o valor deste campo.
Se issue_tracker estiver faltando, mas repositor estiver presente e apontar
para o GitHub, o site pub.dev usa o rastreador de problemas padrão
( https://github.com/<user>/<repository>/issues ).

Documentação
Alguns pacotes têm um site que hospeda a documentação, separado da
página inicial principal e da referência da API gerada pelo Pub. Se o seu
pacote tiver documentação adicional, adicione um documentation: campo
com esse URL; pub mostra um link para esta documentação na página do
seu pacote.

Dependências
Dependências são a razão de ser do pubspec. Nesta seção, você lista
cada pacote de que seu pacote precisa para funcionar.
As dependências podem ser de dois tipos. As dependências regulares
estão listadas em dependencies: — estes são os pacotes que qualquer
pessoa usando seu pacote também precisará. As dependências que são
necessárias apenas na fase de desenvolvimento do próprio pacote estão
listadas em dev_dependencies .
Durante o processo de desenvolvimento, pode ser necessário substituir
temporariamente uma dependência. Você pode fazer isso usando
dependency_overrides .
Para obter mais informações, consulte Dependências de pacote.

Executáveis
Um pacote pode expor um ou mais de seus scripts como executáveis que
podem ser executados diretamente na linha de comando. Para tornar um
script disponível publicamente, liste-o no executables campo. As entradas
são listadas como pares chave / valor:

Por exemplo, a seguinte entrada pubspec lista dois scripts:

Depois que o pacote é ativado usando pub global activate , digitando slidy
executa bin/main.dart . Digitando fvm executa bin/fvm.dart . Se você não
especificar o valor, ele será inferido da chave.
Para obter mais informações, consulte pub global.

Publish_to
O padrão usa o site pub.dev. Especifique none para evitar que um pacote
seja publicado. Essa configuração pode ser usada para especificar um
servidor de pacote pub personalizado para publicar.

Restrições SDK
Um pacote pode indicar quais versões de suas dependências ele suporta,
mas os pacotes têm outra dependência implícita: a própria plataforma
Dart. A plataforma Dart evolui com o tempo, e um pacote pode funcionar
apenas com certas versões da plataforma.
Um pacote pode especificar essas versões usando uma restrição SDK .
Essa restrição vai dentro de um campo environment de nível superior
separado no pubspec e usa a mesma sintaxe de restrição de versão que
as dependências.

Por exemplo, a seguinte restrição diz que este pacote funciona com
qualquer Dart SDK versão 2.10.0 ou superior:

O Pub tenta encontrar a versão mais recente de um pacote cuja restrição


do SDK funciona com a versão do SDK do Dart que você instalou.
A partir do Dart 2.12, omitir a restrição SDK é um erro. Quando o pubspec
não tem restrição de SDK, pub get falha com uma mensagem como a
seguinte:
Restrições do Flutter SDK
A partir do Dart 1.19.0, o pub oferece suporte às restrições do Flutter SDK
no environment: campo:

Uma restrição do Flutter SDK é satisfeita apenas se pub estiver em


execução no contexto do Flutter executável e o arquivo version do Flutter
SDK corresponder à restrição de versão fornecida. Caso contrário, o
pacote não será selecionado.
Para publicar um pacote com uma restrição Flutter SDK, você deve
especificar uma restrição Dart SDK com uma versão mínima de pelo
menos 1.19.0, para garantir que as versões anteriores do pub não
instalem acidentalmente pacotes que precisam do Flutter.
Packages: Package Reference: Pub de
solução de problemas

Obtendo Um Erro “403” Ao Publicar Um


Pacote
Você recebe o seguinte erro ao executar pub publish :

Esse problema pode ocorrer se uma de suas contas tiver permissão para
publicar um pacote, mas o cliente pub o registrar em outra conta.
Você pode redefinir o processo de autenticação do pub removendo o
arquivo de credenciais:
Obtendo Um Erro “Unauthorizedaccess” Ao
Publicar Um Pacote
Você recebe o seguinte erro ao executar pub publish :
UnauthorizedAccess: Unauthorized user: <username> is not allowed to upload versions
to package '<foo>'.

Você verá esta mensagem se não estiver na lista de pessoas autorizadas


a publicar novas versões de um pacote.
A Compilação Do Pub Falha Com Erro
HttpException
Você recebe um erro HttpException semelhante ao seguinte ao executar
pub build :
Pub build failed, [1] IsolateSpawnException: 'HttpException: Connection closed while
receiving data,
...
library handler failed
...
Isso pode acontecer como resultado de algum software antivírus, como o
pacote de segurança da Internet AVG 2013. Verifique o manual do seu
pacote de segurança para ver como desativar temporariamente esse
recurso. Por exemplo, consulte Como desativar os componentes do AVG.
Falha Na Obtenção De Pub Por Trás De Um
Firewall Corporativo
Na linha de comando, pub respeita as variáveis de ambiente http_proxy e
https_proxy . Você pode definir a variável de ambiente do servidor proxy da
seguinte maneira.
No Linux / macOS:

No prompt de comando do Windows:

No Windows PowerShell:

Se o proxy exigir credenciais, você pode defini-las da seguinte maneira.


No Linux / macOS:

No prompt de comando do Windows:

No Windows PowerShell:
Localhost Inacessível Após O Login
Quando você executa pub publish em um contêiner ou em uma sessão
SSH, o localhost que pub está ouvindo pode ser diferente do localhost que
está acessível em seu navegador. Embora você possa entrar usando o
navegador, o navegador reclama que http://localhost:<port>?code=... não está
acessível.
Tente esta solução alternativa, que usa a linha de comando para concluir
o login:
1. Em uma janela de terminal, execute pub publish .
2. Na janela do navegador que aparece, entre. O navegador é
redirecionado para uma nova URL de host local ( http://localhost:
<port>?code=... ), mas reclama que a URL não está acessível.
3. Copie o novo URL do host local do navegador.
4. Em outra janela de terminal no mesmo contêiner ou no mesmo host
daquele em que pub publish foi chamado, use o curl comando para
concluir o login usando o novo URL de host local :
Packages: Package Reference: Editores
verificados
O selo de editor verificado pub.dev permite que você saiba que um
pacote foi publicado por um editor cuja identidade foi verificada. Por
exemplo, dart.dev é o editor verificado para pacotes que são suportados
pela equipe Dart do Google.
O emblema aparece em vários lugares no pub.dev, ao lado de pacotes
publicados por editores verificados:
Resultados da pesquisa de pacote
Páginas de detalhes do pacote
Páginas de perfil do editor
A página inicial do pub.dev
Cada editor tem uma página com uma lista de todos os pacotes
pertencentes a esse editor, além de detalhes adicionais, como o e-mail de
contato do editor. Para visitar a página do editor, clique no link de
identidade do editor (por exemplo, dart.dev ) próximo ao selo do editor
verificado .
Processo De Verificação
Para garantir que a criação de editores verificados tenha um custo baixo e
esteja disponível para todos, pub.dev depende de domínios DNS (sistema
de nomes de domínio) como um token de identificação. Escolhemos DNS
porque acreditamos que a maioria dos editores de pacotes já tem um
domínio e uma página inicial para esse domínio. Durante o processo de
criação do editor, pub.dev verifica se o usuário que está criando o editor
verificado tem acesso de administrador ao domínio associado, com base
na lógica existente no Google Search Console.
Criar Uma Conta De Editor Verificada
Se você publicar pacotes e quiser criar um editor verificado, consulte as
instruções na página de publicação.
Packages: Package Reference: Controle de
versão de pacote
Uma das principais funções do gerenciador de pacotes pub é ajudá-lo a
trabalhar com o controle de versão. Este documento explica um pouco
sobre a história do controle de versão e a abordagem do pub em relação a
ele. Considere isso como uma informação avançada. Se você quiser uma
imagem melhor de porque o pub foi projetado do jeito que é, continue
lendo. Se você quiser apenas usar o pub, os outros documentos irão
atendê-lo melhor.
O desenvolvimento de software moderno, especialmente o
desenvolvimento web, depende fortemente da reutilização de muitos e
muitos códigos existentes. Isso inclui código que você escreveu no
passado, mas também código de terceiros, desde grandes estruturas a
pequenas bibliotecas de utilitários. Não é incomum um aplicativo
depender de dezenas de pacotes e bibliotecas diferentes.
É difícil subestimar o quão poderoso isso é. Quando você vê histórias de
pequenas startups na web construindo um site que atrai milhões de
usuários em poucas semanas, a única razão pela qual eles podem fazer
isso é porque a comunidade de código aberto colocou um banquete de
software a seus pés.
Mas isso não vem de graça: há um desafio na reutilização de código,
especialmente reutilizando código que você não mantém. Quando seu
aplicativo usa código desenvolvido por outras pessoas, o que acontece
quando elas o alteram? Eles não querem quebrar seu aplicativo e você
certamente também não. Resolvemos esse problema criando versões .
Um Nome E Um Número
Quando você depende de algum código externo, não diz apenas “Meu
aplicativo usa widgets ”. Você diz: “Meu aplicativo usa widgets 2.0.5 ”. Essa
combinação de nome e número de versão identifica exclusivamente um
pedaço imutável de código. As pessoas que estão atualizando widgets
podem fazer todas as alterações que quiserem, mas prometem não mexer
em nenhuma das versões já lançadas. Eles podem ser lançados 2.0.6 ou
3.0.0 não afetarão você nem um pouco, porque a versão que você usa não
mudou.
Quando você não quiser obter essas mudanças, você sempre pode
apontar o seu aplicativo para uma versão mais recente widgets e você não
tem que coordenar com os desenvolvedores de fazê-lo. No entanto, isso
não resolve totalmente o problema.
Resolvendo Dependências Compartilhadas
Depender de versões específicas funciona bem quando seu gráfico de
dependências é na verdade apenas uma árvore de dependências. Se o
seu aplicativo depende de um monte de pacotes e essas coisas, por sua
vez, têm suas próprias dependências e assim por diante, tudo funciona
bem, desde que nenhuma dessas dependências se sobreponha.
Mas considere o seguinte exemplo:

Portanto, seu aplicativo usa widgets e templates , e ambos usam collection .


Isso é chamado de dependência compartilhada . Agora o que acontece
quando widgets quer usar collection 2.3.5 e templates quer collection 2.3.7 ? E se
eles não concordarem com uma versão?

Bibliotecas não compartilhadas (a abordagem npm)


Uma opção é permitir que o aplicativo use as duas versões do collection .
Ele terá duas cópias da biblioteca em versões diferentes widgets e templates
cada um receberá a que deseja.
Isso é o que o npm faz para node.js. Isso funcionária para o Dart?
Considere este cenário:
1. Collection define alguma classe Dictionary .
2. Widgets obtém uma instância dele de sua cópia de collection ( 2.3.5 ).
Em seguida, ele passa para my_app .
3. my_app envia o dicionário para templates .
4. Isso, por sua vez, o envia para sua versão de collection ( 2.3.7 ).
5. O método que o utiliza possui uma anotação de tipo Dictionary para
aquele objeto.
Tanto quanto Dart está em causa, collection 2.3.5 e collection 2.3.7 são
bibliotecas totalmente independentes. Se você pegar uma instância de
classe Dictionary de um e passá-la para um método no outro, esse é um
tipo Dictionary completamente diferente. Isso significa que não
corresponderá a uma anotação de tipo Dictionary na biblioteca receptora.
Opa.
Por causa disso (e por causa das dores de cabeça de tentar depurar um
aplicativo que tem várias versões de coisas com o mesmo nome),
decidimos que o modelo do npm não é um bom ajuste.

Bloqueio de versão (a abordagem de beco sem saída)


Em vez disso, quando você depende de um pacote, seu aplicativo usa
apenas uma única cópia desse pacote. Quando você tem uma
dependência compartilhada, tudo o que depende dela precisa concordar
com a versão a ser usada. Se não o fizerem, você obterá um erro.
Isso não resolve realmente o seu problema. Quando você fizer chegar
esse erro, você precisa ser capaz de resolvê-lo. Então, digamos que você
se meteu nessa situação no exemplo anterior. Você deseja usar widgets e
templates , mas eles estão usando versões diferentes de collection . O que
você faz?
A resposta é tentar atualizar um deles. Templates quer collection 2.3.7 . Existe
uma versão posterior widgets que você pode atualizar para que funcione
com essa versão?
Em muitos casos, a resposta será “não”. Olhe para isso da perspectiva
das pessoas em desenvolvimento widgets . Eles querem lançar uma nova
versão com novas mudanças em seu código e querem que o maior
número possível de pessoas seja capaz de atualizá-la. Se eles
mantiverem a versão atual do collection , qualquer pessoa que estiver
usando a versão atual também widgets poderá incluir esta nova.
Se eles tivessem que atualizar sua dependência em collection então, todos
os que atualizam também widgets deveriam, quer queiram ou não . Isso é
doloroso, então você acaba desestimulando a atualização das
dependências. Isso é chamado de bloqueio de versão : todo mundo quer
mover suas dependências para frente, mas ninguém pode dar o primeiro
passo porque isso força todos os outros a fazê-lo.

Restrições de versão (a abordagem Dart)


Para resolver o bloqueio de versão, afrouxamos as restrições que os
pacotes colocam em suas dependências. Se widgets e templates puderem
indicar uma gama de versões com as collection que trabalham, isso nos dá
espaço de manobra suficiente para mover nossas dependências para
versões mais recentes. Enquanto houver sobreposição em seus
intervalos, ainda podemos encontrar uma única versão que os faz felizes.
Este é o modelo que o bundler segue e é o modelo do pub. Ao adicionar
uma dependência em seu pubspec, você pode especificar um intervalo de
versões que pode aceitar. Se o pubspec para widgets for assim:

Então, poderíamos escolher a versão 2.3.7 para collection e, em seguida,


ambos widgets e templates ter suas restrições satisfeitas por uma única
versão concreta.
Versões Semânticas
Quando você adiciona uma dependência ao seu pacote, às vezes você
deseja especificar um intervalo de versões para permitir. Como você sabe
que intervalo escolher? Você precisa ser compatível com versões futuras,
então, idealmente, a gama abrange versões futuras que ainda não foram
lançadas. Mas como você sabe que seu pacote vai funcionar com alguma
nova versão que ainda nem existe?
Para resolver isso, você precisa concordar sobre o que significa um
número de versão. Imagine que os desenvolvedores de um pacote do
qual você depende digam: “Se fizermos qualquer alteração incompatível
com versões anteriores, prometemos incrementar o número da versão
principal”. Se você confia neles, então se você sabe que seu pacote
funciona com o 2.3.5 deles, você pode confiar que ele funcionará até 3.0.0 .
Você pode definir seu alcance como:

Para fazer isso funcionar, então, precisamos apresentar esse conjunto de


promessas. Felizmente, outras pessoas inteligentes fizeram o trabalho de
descobrir tudo isso e chamaram de versionamento semântico.
Isso descreve o formato de um número de versão e as diferenças
comportamentais exatas da API quando você incrementa para um número
de versão posterior. Pub exige que as versões sejam formatadas dessa
maneira e, para funcionar bem com a comunidade pub, seu pacote deve
seguir a semântica que especifica. Você deve assumir que os pacotes dos
quais você depende também o seguem. (E se você descobrir que não,
informe seus autores!)
Embora o versionamento semântico não prometa nenhuma
compatibilidade entre as versões anteriores 1.0.0 , a convenção da
comunidade Dart é tratar essas versões semanticamente também. A
interpretação de cada número é apenas deslocada para baixo um slot:
indo de 0.1.2 para 0.2.0 indica uma alteração significativa, indo para 0.1.3
indica um novo recurso e indo para 0.1.2+1 indica uma mudança que não
afeta a API pública. Para simplificar, EVITAR usar + depois que a versão
atingir 1.0.0 .
Temos quase todas as peças de que precisamos para lidar com o controle
de versão e a evolução da API agora. Vamos ver como eles jogam juntos
e o que o pub faz.
Resolução De Restrições
Ao definir seu pacote, você lista suas dependências imediatas - os
pacotes que ele próprio usa. Para cada um, você especifica o intervalo de
versões que ele permite. Cada um desses pacotes dependentes pode, por
sua vez, ter suas próprias dependências (chamadas de dependências
transitivas. O Pub passa por elas e cria todo o gráfico de dependências
profundas para seu aplicativo.
Para cada pacote no gráfico, o pub analisa tudo o que depende dele. Ele
reúne todas as restrições de versão e tenta resolvê-las simultaneamente.
(Basicamente, ele cruza seus intervalos.) Em seguida, analisa as versões
reais que foram lançadas para aquele pacote e seleciona a melhor (mais
recente) que atende a todas essas restrições.
Por exemplo, digamos que nosso gráfico de dependência contém
collection , e três pacotes dependem dele. Suas restrições de versão são:

Os desenvolvedores do collection lançaram estas versões dele:

O número de versão mais alto que se encaixa em todas essas faixas é


1.8.2 , portanto, o pub o escolhe. Isso significa que seu aplicativo e todos
os pacotes que ele usar usarão collection 1.8.2 .
Contexto De Restrição
O fato de a seleção de uma versão de pacote levar em consideração
todos os pacotes que dependem dela tem uma consequência importante:
a versão específica que será selecionada para um pacote é uma
propriedade global do aplicativo que usa esse pacote.
O exemplo a seguir mostra o que isso significa. Digamos que temos dois
aplicativos. Aqui estão seus pubspecs:

Ambos dependem de widgets , cujo pubspec é:

O pacote other_app depende diretamente da própria collection . A parte


interessante é que ele tem uma restrição de versão diferente da que tem
widgets .
Isso significa que você não pode apenas olhar o pacote widgets
isoladamente para descobrir qual versão collection será usada. Depende do
contexto. Em my_app , widgets usará collection 1.9.9 . Mas dentro other_app ,
widgets será sobrecarregado por collection 1.4.9 causa da outra restrição que
otherapp impõe a ele.
É por isso que cada aplicativo obtém seu próprio arquivo: .packages. A
versão concreta selecionada para cada pacote depende de todo o gráfico
de dependência do aplicativo que o contém.
Resolução De Restrições Para
Dependências Exportadas
Os autores do pacote devem definir as restrições do pacote com cuidado.
Considere o seguinte cenário:

O pacote Bookshelf depende do widgets . O pacote widgets , atualmente em


1.2.0, exporta collection via export 'package:collection/collection.dart' e está em
2.4.0. Os arquivos pubspec são os seguintes:

O pacote collection é então atualizado para 2.5.0. A versão 2.5.0 do


collection inclui um novo método chamado sortBackwards() . Bookshelf pode
chamar sortBackwards() , porque é parte da API exposta por widgets , apesar
de Bookshelf ter apenas uma dependência transitiva de collection .
Por widgets ter uma API que não é refletida em seu número de versão, o
aplicativo que usa o pacote Bookshelf e as chamadas sortBackwards() pode
falhar.
Exportar uma API faz com que essa API seja tratada como se estivesse
definida no próprio pacote, mas não pode aumentar o número da versão
quando a API adiciona recursos. Isso significa que Bookshelf não tem como
declarar que precisa de uma versão widgets que suporte sortBackwards() .
Por esse motivo, ao lidar com pacotes exportados, é recomendado que o
autor do pacote mantenha um limite mais rígido nos limites superior e
inferior de uma dependência. Nesse caso, o intervalo do pacote widgets
deve ser reduzido:

Isso se traduz em um limite inferior de 1.2.0 para widgets e 2.4.0 para


collection . Quando a versão 2.5.0 do collection é lançada, então widgets
também é atualizado para 1.3.0 e as restrições correspondentes também
são atualizadas.
O uso dessa convenção garante que os usuários tenham a versão correta
de ambos os pacotes, mesmo se um não for uma dependência direta.
Lockfiles
Então, depois que o pub resolver as restrições de versão do seu
aplicativo, o que acontecerá? O resultado é uma lista completa de todos
os pacotes dos quais seu aplicativo depende direta ou indiretamente e a
melhor versão desse pacote que funcionará com as restrições de seu
aplicativo.
Pub pega isso e grava em um arquivo de bloqueio no diretório do seu
aplicativo chamado pubspec.lock . Quando o pub cria o arquivo .packages
para seu aplicativo, ele usa o lockfile para saber a quais versões de cada
pacote se referir. (E se você estiver curioso para ver quais versões ele
selecionou, você pode ler o arquivo de bloqueio para descobrir.)
A próxima coisa importante que o pub faz é parar de tocar no arquivo de
bloqueio . Assim que você tiver um arquivo de bloqueio para seu
aplicativo, o pub não mexerá nele até que você o solicite. Isso é
importante. Isso significa que você não vai começar a usar
espontaneamente novas versões de pacotes aleatórios em seu aplicativo
sem essa intenção. Depois que seu aplicativo é bloqueado, ele
permanece bloqueado até que você diga a ele manualmente para
atualizar o arquivo de bloqueio.
Se o seu pacote for para um aplicativo, você verifica seu arquivo de
bloqueio em seu sistema de controle de origem! Dessa forma, todos em
sua equipe usarão exatamente as mesmas versões de todas as
dependências ao construir seu aplicativo. Você também usará isso ao
implantar seu aplicativo para que possa garantir que seus servidores de
produção estejam usando exatamente os mesmos pacotes com os quais
você está desenvolvendo.
Quando As Coisas Dão Errado
Claro, tudo isso presume que seu gráfico de dependência é perfeito e
sem falhas. Mesmo com intervalos de versão e solução de restrição de
pub e versionamento semântico, você nunca pode ser totalmente poupado
dos perigos do versionitis.
Você pode encontrar um dos seguintes problemas:

Você pode ter restrições disjuntas


Vamos dizer que seus usos de aplicativos widgets e templates e tanto para
uso collection . Mas widgets pede uma versão dele entre 1.0.0 e 2.0.0 e
templates deseja algo entre 3.0.0 e 4.0.0 . Esses intervalos nem mesmo se
sobrepõem. Não há versão possível que funcione.

Você pode ter intervalos que não contêm uma versão


lançada
Digamos que, depois de colocar todas as restrições em uma dependência
compartilhada juntas, você fique com o intervalo estreito de >=1.2.4 <1.2.6 .
Não é um intervalo vazio. Se houvesse uma versão 1.2.4 da dependência,
você seria ótimo. Mas talvez eles nunca tenham lançado isso e, em vez
disso, foram direto de 1.2.3 para 1.3.0 . Você tem um intervalo, mas nada
existe dentro dele.

Você pode ter um gráfico instável


Esta é, de longe, a parte mais desafiadora do processo de resolução de
versão do pub. O processo foi descrito como construir o gráfico de
dependência e então resolver todas as restrições e escolher as versões .
Mas na verdade não funciona dessa maneira. Como você poderia
construir todo o gráfico de dependência antes de escolher qualquer
versão? Os próprios pubspecs são específicos da versão. Diferentes
versões do mesmo pacote podem ter diferentes conjuntos de
dependências.
Conforme você seleciona as versões dos pacotes, eles mudam a forma
do próprio gráfico de dependência. Conforme o gráfico muda, isso pode
mudar as restrições, o que pode fazer com que você selecione versões
diferentes e, em seguida, você volte em um círculo.
Às vezes, esse processo nunca se estabelece em uma solução estável.
Olhe para o abismo:
Em todos esses casos, não há um conjunto de versões concretas que
funcionem para seu aplicativo e, quando isso acontece, o pub relata um
erro e informa o que está acontecendo. Definitivamente não o deixará em
um estado estranho onde você acha que as coisas podem funcionar, mas
não funcionam.
Resumo
Isso foi muita informação, mas aqui estão os postos-chaves:
A reutilização de código é ótima, mas para permitir que os
desenvolvedores avancem rapidamente, os pacotes precisam ser
capazes de evoluir de forma independente.
O controle de versão é como você ativa isso. Mas depender de uma
única versão concreta é muito preciso e com dependências
compartilhadas leva ao bloqueio de versão.
Para lidar com isso, você depende de intervalos de versões. O Pub
analisa seu gráfico de dependência e escolhe as melhores versões
para você. Se não puder, ele diz a você.
Depois que seu aplicativo tem um conjunto sólido de versões para
suas dependências, isso é fixado em um arquivo de bloqueio . Isso
garante que cada máquina em que seu aplicativo está usando as
mesmas versões de todas as suas dependências.
Se você quiser saber mais sobre o algoritmo de solução de versão do
pub, consulte o artigo PubGrub: Solução de versão de última geração
Development: Codelabs: Dicas do Dart
A linguagem Dart foi projetada para ser fácil de aprender para
programadores vindos de outras línguas, mas tem alguns recursos
exclusivos. Este codelab - que é baseado em uma cheatsheet da
linguagem Dart escrita por e para engenheiros do Google - mostra os mais
importantes desses recursos de linguagem.
Os editores incorporados neste codelab completaram parcialmente
fragmentos de código. Você pode usar esses editores para testar seu
conhecimento, completando o código e clicando no botão Executar . Se
precisar de ajuda, clique no botão Dica . Para executar o formatador de
código (dartfmt), clique em Formatar . O botão Reiniciar apaga seu
trabalho e restaura o editor ao seu estado original.
Interpolação De String
Para colocar o valor de uma expressão dentro de uma string, use
${expression} . Se a expressão for um identificador, você pode omitir o {} .
Aqui estão alguns exemplos de uso de interpolação de string:
String Resultado
'${3 + 2}' '5'
'${"word".toUpperCase()}' 'WORD'
'$myObject' O valor de myObject.toString()

Exemplo de código
A função a seguir usa dois inteiros como parâmetros. Faça com que ele
retorne uma string contendo ambos os inteiros separados por um espaço.
Por exemplo, stringify(2,3) deve retornar '2 3' .
Operadores Nulos
O Dart oferece alguns operadores úteis para lidar com valores que podem
ser nulos. Um é o ??= operador de atribuição, que atribui um valor a uma
variável apenas se essa variável for atualmente nula:

Outro operador com reconhecimento de nulo é ?? , que retorna a


expressão à sua esquerda, a menos que o valor da expressão seja nulo,
caso em que avalia e retorna a expressão à sua direita:

Exemplo de código
Experimente colocar os operadores ??= e ?? para trabalhar abaixo.
Acesso Condicional À Propriedade
Para proteger o acesso a uma propriedade ou método de um objeto que
pode ser nulo, coloque um ponto de interrogação ( ? ) antes do ponto ( . ):

O código anterior é equivalente ao seguinte:

Você pode encadear vários usos de ?. em uma única expressão:

O código anterior retorna nulo (e nunca chama someMethod() ) se myObject


ou myObject.someProperty for nulo.

Exemplo de código
Tente usar o acesso de propriedade condicional para terminar o trecho de
código abaixo.
Literais De Coleção
O Dart possui suporte integrado para listas, mapas e conjuntos. Você
pode criá-los usando literais:

A inferência de tipo do Dart pode atribuir tipos a essas variáveis para


você. Neste caso, os tipos inferidos são List<String> , Set<String> e Map<String,
int> .
Ou você mesmo pode especificar o tipo:

Especificar tipos é útil quando você inicializa uma lista com o conteúdo de
um subtipo, mas ainda deseja que a lista seja List<BaseType> :

Exemplo de código
Tente definir as seguintes variáveis para os valores indicados.
Sintaxe De Seta
Você deve ter visto o => símbolo no código do Dart. Esta sintaxe de seta é
uma forma de definir uma função que executa a expressão à sua direita e
retorna seu valor.
Por exemplo, considere esta chamada para o método List da classe any() :

Esta é uma maneira mais simples de escrever esse código:

Exemplo de código
Tente terminar as seguintes instruções, que usam sintaxe de seta.
Cascades
Para realizar uma sequência de operações no mesmo objeto, use
cascades ( .. ). Todos nós vimos uma expressão como esta:

Ele invoca someMethod() on myObject e o resultado da expressão é o valor


de retorno de someMethod() .
Aqui está a mesma expressão com uma cascata:

Embora ainda invoque someMethod() on myObject , o resultado da expressão


não é o valor de retorno - é uma referência para myObject ! Usando
cascatas, você pode encadear operações que, de outra forma, exigiriam
instruções separadas. Por exemplo, considere este código:

Com cascatas, o código se torna muito mais curto e você não precisa da
variável button :

Exemplo de código
Use cascatas para criar uma única instrução que define a anInt , aString , e
aList propriedades de um BigObject a 1 , 'String!' e [3.0] (respectivamente) e
em seguida, chama allDone() .
Getters E Setters
Você pode definir getters e setters sempre que precisar de mais controle
sobre uma propriedade do que um campo simples permite.
Por exemplo, você pode ter certeza de que o valor de uma propriedade é
válido:

Você também pode usar um getter para definir uma propriedade


computada:

Exemplo de código
Imagine que você tem uma classe de carrinho de compras que mantém
uma lista List<double> de preços privada. Adicione o seguinte:
Um getter chamado total que retorna a soma dos preços
Um setter que substitui a lista por uma nova, desde que a nova lista
não contenha nenhum preço negativo (neste caso, o setter deve
lançar um InvalidPriceException ).
Parâmetros Posicionais Opcionais
O Dart tem dois tipos de parâmetros de função: posicional e nomeado. Os
parâmetros posicionais são do tipo com que você provavelmente está
familiarizado:

Com o Dart, você pode tornar esses parâmetros posicionais opcionais


envolvendo-os entre colchetes:

Os parâmetros posicionais opcionais são sempre os últimos na lista de


parâmetros de uma função. Seu valor padrão é nulo, a menos que você
forneça outro valor padrão:

Exemplo de código
Implemente uma função chamada joinWithCommas() que aceite de um a
cinco inteiros e, em seguida, retorne uma string desses números
separados por vírgulas. Aqui estão alguns exemplos de chamadas de
função e valores retornados:
Chamada de função Valor devolvido
joinWithCommas(1) '1'
joinWithCommas(1, 2, 3) '1,2,3'
joinWithCommas(1, 1, 1, 1, 1) '1,1,1,1,1'
Parâmetros Nomeados Opcionais
Usando uma sintaxe de chaves, você pode definir parâmetros opcionais
que possuem nomes.

Como você pode esperar, o valor desses parâmetros é nulo por padrão,
mas você pode fornecer valores padrão:

Uma função não pode ter parâmetros posicionais e nomeados opcionais.

Exemplo de código
Adicione um método copyWith() de instância à classe MyDataObject . Deve
ter três parâmetros nomeados:
int newInt
String newString
double newDouble
Quando chamado, copyWith() deve retornar um novo MyDataObject baseado
na instância atual, com os dados dos parâmetros anteriores (se houver)
copiados nas propriedades do objeto. Por exemplo, se newInt não for nulo,
copie seu valor para anInt .
Exceções
O código do Dart pode lançar e capturar exceções. Em contraste com
Java, todas as exceções do Dart são exceções não verificadas. Os
métodos não declaram quais exceções podem lançar, e você não é
obrigado a capturar nenhuma exceção.
O Dart fornece os tipos Exception e Error , mas você pode lançar qualquer
objeto não nulo:

Use as palavras-chave try , on , e catch ao lidar com exceções:

A palavra-chave try funciona como na maioria dos outros idiomas. Use a


palavra-chave on para filtrar exceções específicas por tipo e a palavra-
chave catch para obter uma referência ao objeto de exceção.
Se você não puder lidar completamente com a exceção, use a palavra-
chave rethrow para propagar a exceção:

Para executar o código, independentemente de uma exceção ser lançada


ou não, use finally :
Exemplo de código
Implemente tryFunction() abaixo. Ele deve executar um método não
confiável e, em seguida, fazer o seguinte:
Se untrustworthy() lançar um ExceptionWithMessage , chame
logger.logException com o tipo de exceção e mensagem (tente usar on e
catch ).
Se untrustworthy() lançar um Exception , chame logger.logException com o
tipo de exceção (tente usar on para este).
Se untrustworthy() lançar qualquer outro objeto, não capture a exceção.
Depois que tudo for capturado e tratado, chame logger.doneLogging
(tente usar finally ).
Usando this Em Um Construtor
O Dart fornece um atalho útil para atribuir valores a propriedades em um
construtor: use this.propertyName ao declarar o construtor:

Essa técnica também funciona para parâmetros nomeados. Os nomes


das propriedades tornam-se os nomes dos parâmetros:

Para parâmetros opcionais, os valores padrão funcionam conforme o


esperado:

Exemplo de código
Adicione um construtor de uma linha a MyClass que usa sintaxe this. para
receber e atribuir valores para todas as três propriedades da classe.
Listas De Inicializadores
Às vezes, quando você implementa um construtor, é necessário fazer
algumas configurações antes de o corpo do construtor ser executado. Por
exemplo, os campos finais devem ter valores antes da execução do corpo
do construtor. Faça este trabalho em uma lista de inicializadores, que fica
entre a assinatura do construtor e seu corpo:

A lista de inicializadores também é um lugar útil para colocar declarações,


que são executadas apenas durante o desenvolvimento:

Exemplo de código
Complete o construtor FirstTwoLetters abaixo. Use uma lista de
inicializadores para atribuir os primeiros dois caracteres word às
propriedades letterOne e LetterTwo . Para crédito extra, adicione um assert
para capturar palavras com menos de dois caracteres.
Construtores Nomeados
Para permitir que as classes tenham vários construtores, o Dart oferece
suporte a construtores nomeados:

Para usar um construtor nomeado, invoque-o usando seu nome completo:

Exemplo de código
Dê à classe Color um construtor chamado Color.black que defina todas as
três propriedades como zero.
Construtores De Fábrica
O Dart oferece suporte a construtores de fábrica, que podem retornar
subtipos ou até mesmo nulos. Para criar um construtor de fábrica, use a
palavra-chave factory :

Exemplo de código
Preencha o construtor de fábrica denominado IntegerHolder.fromList ,
fazendo com que ele faça o seguinte:
Se a lista tiver um valor, crie um IntegerSingle com esse valor.
Se a lista tiver dois valores, crie um IntegerDouble com os valores em
ordem.
Se a lista tiver três valores, crie um IntegerTriple com os valores em
ordem.
Caso contrário, retorna nulo.
Redirecionando Construtores
Às vezes, o único propósito de um construtor é redirecionar para outro
construtor na mesma classe. O corpo de um construtor de
redirecionamento está vazio, com a chamada do construtor aparecendo
após dois pontos ( : ).

Exemplo de código
Lembra da classe Color de cima? Crie um construtor nomeado chamado
black , mas em vez de atribuir manualmente as propriedades, redirecione-o
para o construtor padrão com zeros como argumentos.
Construtores Const
Se sua classe produz objetos que nunca mudam, você pode fazer esses
objetos constantes de tempo de compilação. Para fazer isso, defina um
construtor const e certifique-se de que todas as variáveis de instância
sejam finais.

Exemplo de código
Modifique a classe Recipe para que suas instâncias sejam constantes e
crie um construtor constante que faça o seguinte:
Tem três parâmetros: ingredients , calories e milligramsOfSodium (nessa
ordem).
Usa sintaxe this. para atribuir automaticamente os valores dos
parâmetros às propriedades do objeto de mesmo nome.
É constante, com a palavra-chave const logo antes Recipe na
declaração do construtor.
Development: Codelabs: Coleções iteráveis
Este codelab ensina como usar coleções que implementam a classe
Iterable - por exemplo, List e Set. Iterables são blocos de construção
básicos para todos os tipos de aplicativos Dart, e provavelmente você já
os está usando, mesmo sem perceber. Este codelab ajuda você a
aproveitá-los ao máximo.
Usando os editores DartPad incorporados, você pode testar seus
conhecimentos executando códigos de exemplo e concluindo exercícios.
Para obter o máximo deste codelab, você deve ter conhecimento básico
da sintaxe do Dart.
Este codelab cobre o seguinte material:
Como ler os elementos de um Iterable.
Como verificar se os elementos de um Iterable satisfazem uma
condição.
Como filtrar o conteúdo de um Iterable.
Como mapear o conteúdo de um Iterable para um valor diferente.
O Que São Coleções?
Uma coleção é um objeto que representa um grupo de objetos, que são
chamados de elementos . Os iteráveis são uma espécie de coleção.
Uma coleção pode estar vazia ou pode conter muitos elementos.
Dependendo da finalidade, as coleções podem ter diferentes estruturas e
implementações. Estes são alguns dos tipos de coleção mais comuns:
Lista: usado para ler elementos por seus índices .
Conjunto: usado para conter elementos que podem ocorrer apenas
uma vez .
Mapa: usado para ler elementos usando uma chave.
O Que É Um Iterável?
Um Iterable é uma coleção de elementos que podem ser acessados
sequencialmente.
No Dart, a Iterable é uma classe abstrata, o que significa que você não
pode instanciá-la diretamente. No entanto, você pode criar um Iterable
criando um List ou Set .
Ambos List e Set são Iterable , portanto, eles têm os mesmos métodos e
propriedades da classe Iterable .
A Map usa uma estrutura de dados diferente internamente, dependendo de
sua implementação. Por exemplo, o HashMap usa uma tabela hash na
qual os elementos (também chamados de valores ) são obtidos por meio
de uma chave. Os elementos de um Map também podem ser lidos como
objetos Iterable usando a propriedade entries ou do mapa values .
Este exemplo mostra um List de int , que também é um Iterable de int :

A diferença com a List é que com o Iterable , você não pode garantir que a
leitura dos elementos por índice seja eficiente. Iterable , ao contrário List ,
não tem o operador [] .
Por exemplo, considere o seguinte código, que é inválido :

Se você ler elementos com [] , o compilador informará que o operador '[]'


não está definido para a classe Iterable , o que significa que você não pode
usar [index] neste caso.
Em vez disso, você pode ler os elementos com elementAt() , que percorre
os elementos do iterável até atingir essa posição.

Continue na próxima seção para aprender mais sobre como acessar os


elementos de um Iterable .
Elementos De Leitura
Você pode ler os elementos de um iterável sequencialmente, usando um
loop for-in .

Exemplo: usando um loop for-in


O exemplo a seguir mostra como ler elementos usando um loop for-in .

Exemplo: usando o primeiro e o último


Em alguns casos, você deseja acessar apenas o primeiro ou o último
elemento de um Iterable .
Com a classe Iterable , você não pode acessar os elementos diretamente,
portanto, não pode chamar iterable[0] para acessar o primeiro elemento. Em
vez disso, você pode usar first , que obtém o primeiro elemento.
Além disso, com a classe Iterable , você não pode usar o operador [] para
acessar o último elemento, mas pode usar a propriedade last .
Neste exemplo, você viu como usar first e last obtem os primeiros e os
últimos elementos de um Iterable . Também é possível encontrar o primeiro
elemento que satisfaça uma condição. A próxima seção mostra como
fazer isso usando um método chamado firstWhere() .

Exemplo: usando firstWhere()


Você já viu que pode acessar os elementos de um Iterable
sequencialmente, e pode facilmente obter o primeiro ou o último elemento.
Agora, você aprende como usar firstWhere() para encontrar o primeiro
elemento que satisfaça certas condições. Este método requer que você
passe um predicado , que é uma função que retorna verdadeiro se a
entrada satisfizer uma determinada condição.

Por exemplo, se você deseja encontrar o primeiro String que tem mais de 5
caracteres, deve passar um predicado que retorna verdadeiro quando o
tamanho do elemento é maior que 5.
Execute o exemplo a seguir para ver como firstWhere() funciona. Você acha
que todas as funções darão o mesmo resultado?
Neste exemplo, você pode ver três maneiras diferentes de escrever um
predicado:
Como uma expressão: O código de teste possui uma linha que usa
a sintaxe de seta ( => ).
Como um bloco: O código de teste possui várias linhas entre
colchetes e uma instrução de retorno.
Como uma função: o código de teste está em uma função externa
que é passada para o método firstWhere() como um parâmetro.
Não existe jeito certo ou errado. Use a maneira que funciona melhor para
você e que torna seu código mais fácil de ler e entender.
No exemplo, firstWhereWithOrElse() chamadas firstWhere() com o parâmetro
nomeado opcional orElse , que fornece uma alternativa quando um
elemento não é encontrado. Nesse caso, o texto 'None!' é retornado porque
nenhum elemento satisfaz a condição fornecida.
Exercício: Pratique escrever um predicado de teste
O exercício a seguir é um teste de unidade com falha que contém um
trecho de código parcialmente completo. Sua tarefa é completar o
exercício escrevendo código para fazer os testes passarem. Você não
precisa implementar main() .
Este exercício apresenta singleWhere() Este método funciona de maneira
semelhante firstWhere() , mas, nesse caso, espera apenas um elemento de
Iterable para satisfazer o predicado. Se mais de um ou nenhum elemento
Iterable satisfizer a condição do predicado, o método lançará uma exceção
StateError.

Seu objetivo é implementar o predicado para que singleWhere() satisfaça as


seguintes condições:
O elemento contém o personagem 'a' .
O elemento começa com o personagem 'M' .
Todos os elementos nos dados de teste são strings; você pode verificar a
documentação da classe para obter ajuda.
Verificando As Condições
Ao trabalhar com Iterable , às vezes você precisa verificar se todos os
elementos de uma coleção satisfazem alguma condição.
Você pode ficar tentado a escrever uma solução usando um loop for-in
como este:

No entanto, você pode fazê-lo usando o método every() :

Usar o método every() resulta em um código mais legível, compacto e


menos sujeito a erros.

Exemplo: usando any() e every()


A classe Iterable fornece dois métodos que você pode usar para verificar
as condições:
any() : Retorna verdadeiro se pelo menos um elemento satisfizer a
condição.
every() : Retorna verdadeiro se todos os elementos satisfizerem a
condição.
Execute este exercício para vê-los em ação.

No exemplo, any() verifica se pelo menos um elemento contém o caractere


a e se every() todos os elementos têm um comprimento igual ou superior a
5.
Depois de executar o código, tente alterar o predicado de any() para que
ele retorne falso:

Você também pode usar any() para verificar se nenhum elemento de uma
Iterable satisfaz uma determinada condição.

Exercício: Verifique se um Iterable satisfaz uma condição


O exercício a seguir fornece prática usando os métodos any() e every() ,
descritos no exemplo anterior. Neste caso, você trabalha com um grupo
de usuários, representados por objetos User que possuem o campo
membro age .
Use any() e every() para implementar duas funções:
Parte 1: Implementar anyUserUnder18() .
Retorne true se pelo menos um usuário tiver 17 anos ou menos.
Parte 2: Implementar everyUserOver13() .
Retorne true se todos os usuários tiverem 14 anos ou mais.
Filtrando
As seções anteriores abordam métodos como firstWhere() ou singleWhere()
que podem ajudá-lo a encontrar um elemento que satisfaça um
determinado predicado.
Mas e se você quiser encontrar todos os elementos que satisfaçam uma
determinada condição? Você pode fazer isso usando o método where() .

Neste exemplo, numbers contém um Iterable com vários valores int e where()
encontra todos os números pares.
A saída de where() é outra Iterable e você pode usá-la como tal para iterar
sobre ela ou aplicar outros métodos Iterable . No próximo exemplo, a saída
de where() é usada diretamente dentro do loop for-in .

Exemplo: usando where()


Execute este exemplo para ver como where() pode ser usado junto com
outros métodos como any() .
Neste exemplo, where() é usado para encontrar todos os números pares e,
em seguida, any() é usado para verificar se os resultados contêm um
número negativo.
Posteriormente no exemplo, where() é usado novamente para localizar
todos os números maiores que 1000. Como não há nenhum, o resultado é
um vazio Iterable .

Exemplo: usando takeWhile


Os métodos takeWhile() e skipWhile() também podem ajudá-lo a filtrar
elementos de um Iterable .
Execute este exemplo para ver como takeWhile() e skipWhile() pode dividir
um Iterable número de conteúdo.

Neste exemplo, takeWhile() retorna um Iterable que contém todos os


elementos que levam ao elemento que satisfaz o predicado. Por outro
lado, skipWhile() retorna um Iterable enquanto pula todos os elementos antes
daquele que satisfaz o predicado. Observe que o elemento que satisfaz o
predicado também está incluído.
Depois de executar o exemplo, mude takeWhile() para pegar os elementos
até atingir o primeiro número negativo.

Observe que a condição number.isNegative é negada com ! .

Exercício: Filtrando elementos de uma lista


O exercício a seguir fornece prática usando o where() método com a classe
User do exercício anterior.
Use where() para implementar duas funções:
Parte 1: Implementar filterUnder21() .
Retorne um Iterable contendo todos os usuários com 21 anos ou mais.
Parte 2: Implementar findShortNamed() .
Retorne um Iterable contendo todos os usuários com nomes de
comprimento 3 ou menos.
Mapeamento
O mapeamento Iterables com o método map() permite aplicar uma função
sobre cada um dos elementos, substituindo cada elemento por um novo.

Neste exemplo, cada elemento dos Iterable números é multiplicado por 10.
Você também pode usar map() para transformar um elemento em um
objeto diferente - por exemplo, para converter tudo int para String , como
você pode ver no exemplo a seguir.

Exemplo: usando o mapa para alterar os elementos


Execute este exemplo para ver como map() multiplica todos os elementos
de um Iterable por 2. Qual você acha que será o resultado?

Exercício: Mapeando para um tipo diferente


No exemplo anterior, você multiplicou os elementos de um Iterable por 2.
Tanto a entrada quanto a saída dessa operação eram Iterable de int .
Neste exercício, seu código recebe um Iterable de User e você precisa
retornar um Iterable que contém strings contendo o nome do usuário e a
idade.
Cada string no Iterable deve seguir este formato: '{name} is {age}' —por
exemplo 'Alice is 21' .
Exercício: Juntando Tudo
É hora de praticar o que você aprendeu, em um exercício final.
Este exercício fornece a classe EmailAddress , que possui um construtor
que recebe uma string. Outra função fornecida é isValidEmailAddress() , que
testa se um endereço de e-mail é válido.
Construtor /
função Assinatura de tipo Descrição
Endereço de e- EmailAddress(String address) Cria um EmailAddress para o
mail() endereço especificado.
isValidEmailAddress bool Retorna true se o fornecido
() isValidEmailAddress(EmailAddress) EmailAddress for válido.
Escreva o seguinte código:
Parte 1: Implementar parseEmailAddresses() .
Escreva a função parseEmailAddresses() , que recebe um Iterable<String>
endereço de e-mail contendo e retorna um Iterable<EmailAddress> .
Use o método map() para mapear de a String para EmailAddress .
Crie os objetos EmailAddress usando o construtor EmailAddress(String) .
Parte 2: Implementar anyInvalidEmailAddress() .
Escreva a função anyInvalidEmailAddress() , que recebe um
Iterable<EmailAddress> e retorna true se algum EmailAddress no Iterable não for
válido.
Use o método any() junto com a função fornecida isValidEmailAddress() .
Parte 3: Implementar validEmailAddresses() .
Escreva a função validEmailAddresses() , que recebe um Iterable<EmailAddress>
e retorna outra Iterable<EmailAddress> contendo apenas endereços válidos.
Use o método where() para filtrar o Iterable<EmailAddress> .
Use a função fornecida isValidEmailAddress() para avaliar se um EmailAddress é
válido.
Development: Codelabs: Programação
Assíncrona: futuros, assíncronos, aguardar
Este codelab ensina como escrever código assíncrono usando futuros e
as palavras-chave async - await . Usando editores DartPad incorporados,
você pode testar seus conhecimentos executando exemplos de código e
completando exercícios.
Para obter o máximo deste codelab, você deve ter o seguinte:
Conhecimento da sintaxe básica do DART.
Alguma experiência em escrever código assíncrono em outra
linguagem.
Este codelab cobre o seguinte material:
Como e quando usar as palavras-chave async - await .
Como usar async e await afetar a ordem de execução.
Como lidar com erros de uma chamada assíncrona usando try-catch
expressões em funções async .
Por Que O Código Assíncrono É Importante
As operações assíncronas permitem que seu programa conclua o
trabalho enquanto espera a conclusão de outra operação. Aqui estão
algumas operações assíncronas comuns:
Buscando dados em uma rede.
Gravando em um banco de dados.
Lendo dados de um arquivo.
Para realizar operações assíncronas no Dart, você pode usar a classe
future e as palavras-chave async - await .

Exemplo: uso incorreto de uma função assíncrona


O exemplo a seguir mostra a maneira errada de usar uma função
assíncrona ( fetchUserOrder() ). Posteriormente, você corrigirá o exemplo
usando async e await . Antes de executar este exemplo, tente identificar o
problema - qual você acha que será o resultado?

Veja por que o exemplo não consegue imprimir o valor que fetchUserOrder()
eventualmente produz:
fetchUserOrder() é uma função assíncrona que, após um atraso,
fornece uma string que descreve o pedido do usuário: um “Large
Latte”.
Para obter o pedido do usuário, createOrderMessage() deve ligar
fetchUserOrder() e aguardar o término. Porque createOrderMessage() é
que não esperar fetchUserOrder() até ao fim, createOrderMessage() não
consegue obter o valor de cadeia que fetchUserOrder() fornece
eventualmente.
Em vez disso, createOrderMessage() obtém uma representação do
trabalho pendente a ser feito: um futuro incompleto. Você aprenderá
mais sobre o futuro na próxima seção.
Como createOrderMessage() falha em obter o valor que descreve o
pedido do usuário, o exemplo falha ao imprimir “Large Latte” no
console e, em vez disso, imprime “Seu pedido é: Instância de
'_Future'".
Nas próximas seções você aprenderá sobre futuros e como trabalhar com
futuros (usando async e await ) para poder escrever o código necessário
para fetchUserOrder() imprimir o valor desejado (“Large Latte”) no console.
O Que É Futuro?
Um futuro (minúsculo “f”) é uma instância da classe Future (maiúsculo
“F”). Um futuro representa o resultado de uma operação assíncrona e
pode ter dois estados: incompleto ou concluído.

Incompleto
Quando você chama uma função assíncrona, ela retorna um futuro
incompleto. Esse futuro está esperando que a operação assíncrona da
função termine ou gere um erro.

Concluído
Se a operação assíncrona for bem-sucedida, o futuro será concluído com
um valor. Caso contrário, termina com um erro.
Completando com um valor
Um futuro de tipo é Future<T> concluído com um valor de tipo T . Por
exemplo, um futuro com tipo Future<String> produz um valor de string. Se
um futuro não produz um valor utilizável, então o tipo do futuro é
Future<void> .
Concluindo com um erro
Se a operação assíncrona executada pela função falhar por qualquer
motivo, o futuro será concluído com um erro.

Exemplo: Apresentando futuros


No exemplo a seguir, fetchUserOrder() retorna um futuro que termina após a
impressão no console. Porque não retorna um valor utilizável,
fetchUserOrder() tem o tipo Future<void> . Antes de executar o exemplo, tente
prever o que imprimirá primeiro: “Large Latte” ou “Buscando pedido do
usuário…”.
No exemplo anterior, embora seja fetchUserOrder() executado antes da
chamada print() na linha 8, o console mostra a saída da linha 8 (“Buscando
pedido do usuário ...”) antes da saída de fetchUserOrder() (“Large Latte”).
Isso ocorre porque fetchUserOrder() atrasos antes de imprimir "Large Latte".

Exemplo: Concluindo com um erro


Execute o exemplo a seguir para ver como um futuro termina com um
erro. Um pouco mais tarde você aprenderá como lidar com o erro.

Neste exemplo, é fetchUserOrder() concluído com um erro indicando que o


ID do usuário é inválido.
Você aprendeu sobre futuros e como eles se completam, mas como você
usa os resultados das funções assíncronas? Na próxima seção, você
aprenderá como obter resultados com as palavras-chave async - await .
Trabalhando Com Futuros: Assíncrono E
Aguardar
As palavras async - await chave e fornecem uma maneira declarativa de
definir funções assíncronas e usar seus resultados. Lembre-se dessas
duas diretrizes básicas ao usar async e await :
Para definir uma função assíncrona, adicione async antes do corpo da
função:
A palavra-chave await funciona apenas em funções async .
Aqui está um exemplo que converte main() de uma função síncrona em
assíncrona.
Primeiro, adicione a palavra-chave async antes do corpo da função:

Se a função tiver um tipo de retorno declarado, atualize o tipo para ser


Future<T> , onde T é o tipo do valor que a função retorna. Se a função não
retornar explicitamente um valor, o tipo de retorno será Future<void> :

Agora que você tem uma função async , pode usar a palavra-chave await
para esperar a conclusão de um futuro:

Como mostram os dois exemplos a seguir, as palavras-chave async - await


e resultam em código assíncrono que se parece muito com código
síncrono. As únicas diferenças são destacadas no exemplo assíncrono,
que - se sua janela for grande o suficiente - está à direita do exemplo
síncrono.
Exemplo: funções síncronas
Exemplo: funções assíncronas
O exemplo assíncrono é diferente de três maneiras:
O tipo de retorno de createOrderMessage() muda de String para Future
<String>
A palavra-chave async aparece antes dos corpos da função para
createOrderMessage() e main() .
A palavra-chave await aparece antes de chamar as funções
assíncronas fetchUserOrder() e createOrderMessage() .

Fluxo de execução com assíncrono e espera


Uma função async é executada de forma síncrona até a primeira await
palavra-chave. Isso significa que dentro de um corpo de função async ,
todo o código síncrono antes da primeira palavra-chave await é executado
imediatamente.

Exemplo: execução em funções assíncronas


Execute o exemplo a seguir para ver como a execução continua dentro de
um corpo de função async . Qual você acha que será o resultado?

Depois de executar o código do exemplo anterior, tente inverter as linhas


2 e 3:

Observe que o tempo da saída muda, agora que print('Awaiting user order')
aparece após a primeira palavra-chave await em printOrderMessage() .

Exercício: Pratique usando assíncrono e aguarde


O exercício a seguir é um teste de unidade com falha que contém trechos
de código parcialmente concluídos. Sua tarefa é completar o exercício
escrevendo código para fazer os testes passarem. Você não precisa
implementar main() .
Para simular operações assíncronas, chame as seguintes funções, que
são fornecidas para você:

Parte 1: reportUserRole()
Adicione o código à função reportUserRole() para que ela faça o seguinte:
Retorna um futuro que se completa com a seguinte string: "User role: <user role>"
Nota: Você deve usar o valor real retornado por fetchRole() ; copiar e colar o
valor de retorno de exemplo não fará o teste passar.
Valor de retorno de exemplo: "User role: tester"
Obtém a função do usuário chamando a função fornecida fetchRole() .
Parte 2: reportLogins()
Implemente uma função async reportLogins() para que ela faça o seguinte:
Retorna a string "Total number of logins: <# of logins>" .
Nota: Você deve usar o valor real retornado por fetchLoginAmount() ; copiar
e colar o valor de retorno de exemplo não fará o teste passar.
Exemplo de valor de retorno de reportLogins() : "Total number of logins: 57"
Obtém o número de logins chamando a função fornecida fetchLoginAmount() .
Tratamento De Erros
Para lidar com erros em uma função async , use try-catch :

Dentro de uma função async , você pode escrever cláusulas try-catch da


mesma forma que faria no código síncrono.

Exemplo: assíncrono e esperar com try-catch


Execute o exemplo a seguir para ver como lidar com um erro de uma
função assíncrona. Qual você acha que será o resultado?

Exercício: Pratique o tratamento de erros


O exercício a seguir fornece a prática de tratamento de erros com código
assíncrono, usando a abordagem descrita na seção anterior. Para simular
operações assíncronas, seu código chamará a seguinte função, que é
fornecida para você:
Use async e await para implementar uma função assíncrona
changeUsername() que faça o seguinte:
Chama a função assíncrona fornecida fetchNewUsername() e retorna seu
resultado.
Exemplo de valor de retorno de changeUsername() : "jane_smith_92"
Captura qualquer erro que ocorre e retorna o valor da string do erro.
Você pode usar o método toString() para sequenciar Exceções e Erros.

Você encontrou algo especial!


Exercício: Juntando Tudo
É hora de praticar o que você aprendeu em um exercício final. Para
simular operações assíncronas, este exercício fornece as funções
assíncronas fetchUsername() e logoutUser() :

Escreva o seguinte:

Parte 1: addHello()
Escreva uma função addHello() que receba um único argumento String.
addHello() retorna seu argumento String precedido por 'Hello'.
Exemplo: addHello('Jon') retorna 'Hello Jon' .

Parte 2: greetUser()
Escreva uma função greetUser() que não receba argumentos.
Para obter o nome de usuário, greetUser() chama a função assíncrona fornecida
fetchUsername() .
greetUser() cria uma saudação para o usuário chamando addHello() , passando o
nome de usuário e retornando o resultado.
Exemplo: se fetchUsername() retorna 'Jenny' , então greetUser() retorna 'Hello
Jenny' .

Parte 3: sayGoodbye()
Escreva uma função sayGoodbye() que faça o seguinte:
Não aceita argumentos.
Captura todos os erros.
Chama a função assíncrona fornecida logoutUser() .
Se logoutUser() falhar, sayGoodbye() retorna qualquer string que você quiser.
Se logoutUser() for bem-sucedido, sayGoodbye() retorna a string '<result> Thanks,
see you next time' , onde <result> é o valor da String retornado pela chamada
logoutUser() .
Qual É O Próximo?
Parabéns, você terminou o codelab! Se quiser saber mais, aqui estão
algumas sugestões sobre o que fazer a seguir:
Brinque com o DartPad.
Experimente outro codelab .
Saiba mais sobre futuros e assincronia:
Tutorial do Streams : Aprenda como trabalhar com uma sequência de
eventos assíncronos.
Vídeos Dart do Google: assista a um ou mais dos vídeos sobre codificação
assíncrona. Ou, se preferir, leia os artigos que se baseiam nesses vídeos.
(Comece com isolados e loops de eventos. )
Obtenha o SDK do Dart .
Se você estiver interessado em usar DartPads incorporados, como este
codelab faz, consulte as práticas recomendadas para usar DartPad em
tutoriais.
Development: Interoperability:
Interoperabilidade C usando dart: ffi
Aplicativos móveis, de linha de comando e de servidor Dart em execução
na plataforma Dart Native podem usar a biblioteca dart: ffi para chamar
APIs C nativas. FFI significa interface de função estrangeira. Outros termos
para funcionalidade semelhante incluem interface nativa e ligações de idioma.

A documentação da API está disponível em dev channel: dart: ffi API


reference.
Exemplos
Os exemplos a seguir mostram como usar a biblioteca dart: ffi:
Exemplo Descrição
Como chamar uma função C sem argumentos e sem valor
Olá Mundo
de retorno.
Como chamar funções C que possuem argumentos e
primitivos valores de retorno que são ints ou ponteiros . Também
demonstra varargs .
Como usar structs para passar strings de e para C e para
estruturas
lidar com estruturas C simples e complexas .
Um exemplo no repositório Dart SDK que vem com um
sqllite
mini tutorial.
Passo A Passo De Hello_World
O exemplo hello_world tem o código mínimo necessário para chamar uma
biblioteca C.

Arquivos
O exemplo hello_world tem os seguintes arquivos:
Arquivo fonte Descrição
hello.darte Um arquivo Dart que usa a hello_world() função de
uma biblioteca C.
pubspec.yaml O pubspec Dart usual, com limites inferiores no SDK
de pelo menos 2,5.
hello_library / Declara a hello_world() função.
hello.h
hello_library / Arquivo AC que importa hello.h e define a
hello.c hello_world() função.
hello_library / Um arquivo de construção CMake para compilar o
CMakeLists.txt código C em uma biblioteca dinâmica.
A construção da biblioteca C cria vários arquivos, incluindo um arquivo de
biblioteca dinâmica denominado libhello.dylib (macOS), libhello.dll
(Windows) ou libhello.so (Linux).

Construindo e funcionando
Aqui está um exemplo de construção da biblioteca dinâmica e execução
do aplicativo Dart:

Usando Dart: ffi


O hello.dart arquivo ilustra as etapas para usar dart: ffi para chamar uma
função C:
1. Dart de importação: ffi.
2. Crie um typedef com a assinatura de tipo FFI da função C.
3. Crie um typedef para a variável que você usará ao chamar a função
C.
4. Abra a biblioteca dinâmica que contém a função C.
5. Obtenha uma referência para a função C e coloque-a em uma
variável.
6. Chame a função C.
Aqui está o código para cada etapa.
1. Dart de importação: ffi.

2. Crie um typedef com a assinatura de tipo FFI da função C. Tipos comumente


usados definidos pelo Dart: biblioteca ffi incluem Double , Int32 , NativeFunction ,
Pointer , Struct , Uint8 e Void .

3. Crie um typedef para a variável que você usará ao chamar a função C.

4. Abra a biblioteca dinâmica que contém a função C.

5. Obtenha uma referência para a função C e coloque-a em uma variável. Este


código usa os typedefs definidos nas etapas 2 e 3, junto com a variável de
biblioteca dinâmica da etapa 4.
6. Chame a função C.

Depois de entender o exemplo hello_world, você deve estar pronto para


olhar para o outro dart: exemplos ffi.
Agrupando E Carregando Bibliotecas C
Como você agrupa (ou empacota ou distribui ) uma biblioteca C com seu
pacote ou aplicativo e, em seguida, carrega essa biblioteca depende de
sua plataforma e do tipo de biblioteca. Para obter detalhes, consulte o
seguinte:
Flutter dart: ffi page
exemplos dart: ffi
Development: Interoperability:
Interoperabilidade de JavaScript
A plataforma da web Dart suporta a chamada de JavaScript usando o
pacote js, também conhecido como pacote: js.
Para obter ajuda sobre como usar o pacote js , consulte o seguinte:
Documentação para o pacote js :
página do site pub.dev
Referência API
Pacotes que usam o pacote js :
firebase_web é um bom exemplo de fornecimento de uma API semelhante
ao Dart para uma biblioteca JavaScript.
sass é um exemplo de caso de uso mais incomum: fornece uma maneira
para o código JavaScript chamar o código Dart.
Development: Usando JSON
A maioria dos aplicativos móveis e da web usa JSON para tarefas como a
troca de dados com um servidor da web. Esta página discute o suporte
Dart para serialização e desserialização JSON: conversão de objetos
Dart para e de JSON.
Bibliotecas
As seguintes bibliotecas e pacotes são úteis em plataformas Dart:
dart: converter
conversores para JSON e UTF-8 (a codificação de caracteres que o
JSON requer).
pacote: json_serializable
Um pacote de geração de código fácil de usar. Quando você
adiciona algumas anotações de metadados e usa o construtor
fornecido por este pacote, o sistema de construção Dart gera código
de serialização e desserialização para você.
package: built_value Uma alternativa poderosa e opinativa para
json_serializable.
Recursos De Vibração
JSON e serialização
Mostra como aplicativos Flutter podem serializar e desserializar tanto com dart:
convert quanto com json_serializable.
Recursos De Aplicativos Da Web
Tutorial do Angular Dart, parte 6: HTTP
Ilustra como um aplicativo da web Dart pode interagir com um back-end RESTful
usando dados JSON.
Usando recursos HTTP com HttpRequest
Demonstra como usar HttpRequest para trocar dados com um servidor da web.
Parte do Dart: tour pela biblioteca html.
Recursos de VM
Escrever clientes e servidores HTTP
Explica como implementar clientes e servidores de linha de comando que trocam
dados JSON.
Development: Programação assíncrona:
fluxos

A programação assíncrona no Dart é caracterizada pelas classes Future e


Stream.
Um Futuro representa um cálculo que não é concluído imediatamente.
Onde uma função normal retorna o resultado, uma função assíncrona
retorna um Future, que eventualmente conterá o resultado. O futuro dirá
quando o resultado estiver pronto.
Um fluxo é uma sequência de eventos assíncronos. É como um Iterable
assíncrono - onde, em vez de obter o próximo evento quando você o
solicitar, o fluxo informa que há um evento quando ele está pronto.
Recebendo Eventos De Stream
Streams podem ser criados de várias maneiras, o que é um tópico para
outro artigo, mas todos podem ser usados da mesma maneira: o loop for
assíncrono (comumente chamado de await for) itera sobre os eventos de
um stream como o loop for iterar sobre um Iterable. Por exemplo:

Este código simplesmente recebe cada evento de um fluxo de eventos


inteiros, soma-os e retorna (um futuro de) a soma. Quando o corpo do
loop termina, a função é pausada até que o próximo evento chegue ou o
fluxo seja concluído.
A função é marcada com a palavra-chave async , que é necessária ao
usar o loop await for.
O exemplo a seguir testa o código anterior gerando um fluxo simples de
inteiros usando uma função async* :
Eventos De Erro
Os fluxos são concluídos quando não há mais eventos neles, e o código
que recebe os eventos é notificado disso da mesma forma que é
notificado de que um novo evento chega. Ao ler eventos usando um loop
await for, os loops param quando o stream é concluído.
Em alguns casos, um erro ocorre antes que o fluxo seja concluído; talvez
a rede tenha falhado ao buscar um arquivo de um servidor remoto ou
talvez o código que cria os eventos tenha um bug, mas alguém precisa
saber sobre isso.
Streams também pode entregar eventos de erro como entrega eventos de
dados. A maioria dos fluxos irá parar após o primeiro erro, mas é possível
ter fluxos que entreguem mais de um erro e fluxos que entreguem mais
dados após um evento de erro. Neste documento, discutimos apenas
fluxos que geram no máximo um erro.
Ao ler um fluxo usando await for, o erro é lançado pela instrução loop.
Isso termina o loop também. Você pode detectar o erro usando try-catch.
O exemplo a seguir lança um erro quando o iterador de loop é igual a 4:
Trabalhando Com Streams
A classe Stream contém vários métodos auxiliares que podem fazer
operações comuns em um fluxo para você, semelhantes aos métodos em
um Iterable. Por exemplo, você pode encontrar o último inteiro positivo em
um fluxo usando lastWhere() da API de fluxo.
Dois Tipos De Streams
Existem dois tipos de fluxos.

Streams de assinatura única


O tipo mais comum de fluxo contém uma sequência de eventos que
são partes de um todo maior. Os eventos precisam ser entregues na
ordem correta e sem perder nenhum deles. Esse é o tipo de fluxo
que você obtém quando lê um arquivo ou recebe uma solicitação da
web.
Esse fluxo só pode ser ouvido uma vez. Ouvir novamente mais
tarde pode significar perder eventos iniciais, e então o resto do fluxo
não faz sentido. Quando você começar a ouvir, os dados serão
buscados e fornecidos em blocos.

Transmitir streams
O outro tipo de fluxo é destinado a mensagens individuais que
podem ser tratadas uma de cada vez. Esse tipo de fluxo pode ser
usado para eventos de mouse em um navegador, por exemplo.
Você pode começar a ouvir esse tipo de transmissão a qualquer
momento e obter os eventos que são disparados enquanto você
ouve. Mais de um ouvinte pode ouvir ao mesmo tempo, e você pode
ouvir novamente depois de cancelar uma assinatura anterior.
Métodos que processam um fluxo
Os seguintes métodos em Stream <T> processam o stream e
retornam um resultado:

Todas essas funções, exceto drain() e pipe() , correspondem a uma


função semelhante em Iterable. Cada um pode ser escrito
facilmente usando uma função async com um loop await for (ou
apenas usando um dos outros métodos). Por exemplo, algumas
implementações podem ser:
(As implementações reais são um pouco mais complexas, mas
principalmente por razões históricas.)
Métodos Que Modificam Um Fluxo
Os métodos a seguir no Stream retornam um novo stream baseado
no stream original. Cada um espera até que alguém ouça a nova
transmissão antes de ouvir a original.

Os métodos anteriores correspondem a métodos semelhantes em


Iterable, que transformam um iterável em outro iterável. Tudo isso
pode ser escrito facilmente usando uma função async com um loop
await for.

As funções asyncExpand() e asyncMap() são semelhantes a


expand() e map() , mas permitem que seu argumento de função seja
uma função assíncrona. Na função distinct() não existe Iterable ,
mas poderia ter existido.

As três funções finais são mais especiais. Eles envolvem o


tratamento de erros que um loop de await for não poder fazer - o
primeiro erro que atinge os loops encerrará o loop e sua inscrição no
fluxo. Não há recuperação disso. O código a seguir mostra como
usar handleError() para remover erros de um fluxo antes de usá-lo
em um loop de await for.
A função transform()
A função transform() não serve apenas para tratamento de erros; é
um “mapa” mais generalizado para fluxos. Um mapa normalmente
requer um valor para cada evento de entrada. No entanto,
especialmente para fluxos de E / S, podem ser necessários vários
eventos de entrada para produzir um evento de saída. Um
StreamTransformer pode funcionar com isso. Por exemplo,
decodificadores como Utf8Decoder são transformadores. Um
transformador requer apenas uma função, bind(), que pode ser
facilmente implementada por uma função async .

Ler e decodificar um arquivo


O código a seguir lê um arquivo e executa duas transformações no
fluxo. Ele primeiro converte os dados do UTF8 e, em seguida, os
executa por meio de um LineSplitter. Todas as linhas são impressas,
exceto aquelas que começam com uma hashtag # .
O Método Listen()
O método final no Stream é listen() . Este é um método de “baixo
nível” - todas as outras funções de fluxo são definidas em termos de
listen() .

Para criar um tipo de Stream , você pode simplesmente estender a


classe Stream e implementar o método listen() todos os outros
métodos de Stream chamam listen() , para funcionar.
O método listen() permite que você comece a ouvir em um stream.
Até que você faça isso, o fluxo é um objeto inerte que descreve
quais eventos você deseja ver. Quando você escuta, um objeto
StreamSubscription é retornado, o que representa os eventos de
produção de fluxo ativo. Isso é semelhante há como um Iterable é
apenas uma coleção de objetos, mas o iterador é aquele que faz a
iteração real.
A assinatura do stream permite que você pause a assinatura,
retome-a após uma pausa e cancele-a completamente. Você pode
definir retornos de chamada a serem chamados para cada evento
de dados ou evento de erro e quando o fluxo for fechado.
Sobre o Autor

Reder Glauber Gad Weyers, é graduado em Engenharia Elétrica


com ênfase em Telecomunicações pelo INATEL – Instituto Nacional
de Telecomunicações de Minas Gerais, MBA em Marketing Digital
pelo CEPEDERH – Centro de Pesquisas Educacionais e de
Desenvolvimento de Recursos Humanos.
É programador nas linguagens C, C++, C# e Dart/Flutter

reder.weyers@icloud.com

Você também pode gostar