1. Introdução
O Dart 3 introduz padrões na linguagem, uma nova categoria importante de gramática. Além dessa nova maneira de escrever código Dart, há vários outras melhorias na linguagem, incluindo registros para agrupar dados de diferentes tipos, modificadores de classe para controlar o acesso e novas expressões switch e instruções if-case.
Esses recursos aumentam suas opções ao escrever código Dart. Neste codelab, você vai aprender a usá-los para tornar seu código mais compacto, simplificado e flexível.
Este codelab presume que você tenha alguma familiaridade com o Flutter e o Dart, embora isso não seja necessário. Antes de começar, revise os conceitos básicos com os seguintes recursos:
O que você vai criar
Este codelab cria um aplicativo que mostra um documento JSON no Flutter. O aplicativo simula o JSON recebido de uma origem externa. O JSON contém dados do documento, como data da modificação, título, cabeçalhos e parágrafos. Você vai escrever código para empacotar dados de forma organizada em registros para que possam ser transferidos e desempacotados sempre que os widgets do Flutter precisarem deles.
Em seguida, você vai usar padrões para criar o widget apropriado quando o valor corresponder a esse padrão. Você também vai aprender a usar padrões para desestruturar dados em variáveis locais.
O que você vai aprender
- Como criar um registro que armazene vários valores com tipos diferentes.
- Como retornar diversos valores de uma função usando um registro.
- Como usar padrões para corresponder, validar e desestruturar dados de registros e outros objetos.
- Como vincular valores correspondentes a padrões a variáveis novas ou existentes.
- Como usar os novos recursos de instrução switch, expressões switch e instruções if-case.
- Como usar a verificação de exaustão para garantir que todos os casos sejam tratados em uma instrução ou expressão switch.
2. Configurar o ambiente
- Instale o SDK do Flutter.
- Configure um editor, como o Visual Studio Code (VS Code).
- Siga as etapas de configuração para pelo menos uma plataforma segmentada (iOS, Android, computador ou navegador da Web).
3. Criar o projeto
Antes de mergulhar nos padrões, registros e outros novos recursos, reserve um momento para configurar seu ambiente e o projeto simples do Flutter para o qual você vai escrever todo o código.
Instalar o Dart
- Para garantir que está usando o Dart 3, execute os seguintes comandos:
flutter channel stable flutter upgrade dart --version # This should print "Dart SDK version: 3.0.0" or higher
Criar um projeto do Flutter
- Use o comando
flutter create
para criar um novo projeto chamadopatterns_codelab
. A flag--empty
impede a criação do app contador padrão no arquivolib/main.dart
que, de qualquer maneira, precisaria ser removido.
flutter create --empty patterns_codelab
- Depois, abra o diretório
patterns_codelab
usando o VS Code.
code patterns_codelab
Definir a versão mínima do SDK
- Defina a restrição de versão do SDK para que seu projeto dependa do Dart 3 ou mais recente.
pubspec.yaml (link em inglês)
environment:
sdk: ^3.0.0
4. Configurar o projeto
Nesta etapa, você vai criar dois arquivos Dart:
- O arquivo
main.dart
que contém widgets para o app. - O arquivo
data.dart
que fornece os dados do app.
Definir os dados para o app
- Crie um novo arquivo,
lib/data.dart
, e adicione o seguinte código:
lib/data.dart
import 'dart:convert';
class Document {
final Map<String, Object?> _json;
Document() : _json = jsonDecode(documentJson);
}
const documentJson = '''
{
"metadata": {
"title": "My Document",
"modified": "2023-05-10"
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
{
"type": "p",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
},
{
"type": "checkbox",
"checked": false,
"text": "Learn Dart 3"
}
]
}
''';
Imagine um programa que receba dados de uma fonte externa, como um stream E/S ou solicitação HTTP. Neste codelab, você vai simplificar esse caso de uso mais realista simulando dados JSON de entrada com uma string de várias linhas na variável documentJson
.
Os dados JSON são definidos na classe Document
e, mais adiante neste codelab, você vai adicionar funções que retornam dados do JSON analisado. Essa classe define e inicializa o campo _json
no construtor.
Executar o app
O comando flutter create
cria o arquivo lib/main.dart
como parte da estrutura de arquivos padrão do Flutter.
- Para criar um ponto de partida para o aplicativo, substitua o conteúdo de
main.dart
pelo seguinte código:
lib/main.dart
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(const DocumentApp());
}
class DocumentApp extends StatelessWidget {
const DocumentApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: DocumentScreen(
document: Document(),
),
);
}
}
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({
required this.document,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title goes here'),
),
body: Column(
children: [
Center(
child: Text('Body goes here'),
),
],
),
);
}
}
Você adicionou os dois widgets a seguir ao app:
- O
DocumentApp
configura a versão mais recente do Material Design para aplicação de temas na interface. - O
DocumentScreen
fornece o layout visual da página usando o widgetScaffold
.
- Para garantir que tudo esteja funcionando sem problemas, execute o app em sua máquina host clicando em Run and Debug:
- Por padrão, o Flutter escolhe a plataforma segmentada que estiver disponível. Para mudar, selecione a plataforma atual na barra de status:
Um frame vazio será exibido com os elementos title
e body
definidos no widget DocumentScreen
:
5. Criar e retornar registros
Nesta etapa, você usa registros para retornar vários valores de uma chamada de função. Em seguida, você chama essa função no widget DocumentScreen
para acessar os valores e refleti-los na interface.
Criar e retornar um registro
- Em
data.dart
, adicione uma nova função à classe de documentos chamadagetMetadata
, que retorna um registro:
lib/data.dart
(String, {DateTime modified}) getMetadata() {
var title = "My Document";
var now = DateTime.now();
return (title, modified: now);
}
O tipo de retorno para essa função é um registro com dois campos, um com o tipo String
e outro com DateTime
.
A instrução de retorno cria um novo registro colocando os dois valores entre parênteses, (title, modified: now)
.
O primeiro campo é de posicionamento e sem nome, e o segundo tem o nome modified
.
Acessar campos de registro
- No widget
DocumentScreen
, chamegetMetadata()
no métodobuild
para que você possa receber seu registro e acessar os valores:
lib/main.dart
@override
Widget build(BuildContext context) {
var metadataRecord = document.getMetadata();
return Scaffold(
appBar: AppBar(
title: Text(metadataRecord.$1),
),
body: Column(
children: [
Center(
child: Text(
'Last modified ${metadataRecord.modified}',
),
),
],
),
);
}
A função getMetadata()
retorna um registro, que é atribuído à variável local metadataRecord
. Registros são uma maneira leve e fácil de retornar diversos valores de uma única chamada de função e atribuí-los a uma variável.
Para acessar os campos individuais compostos nesse registro, é possível usar a sintaxe getter integrada dos registros.
- Para acessar um campo posicional (sem nome, como
title
), use o getter$<num>
no registro. Isso retorna apenas campos sem nome. - Campos nomeados, como
modified
, não têm um getter posicional, portanto você pode usar o nome diretamente, comometadataRecord.modified
.
Para determinar o nome de um getter para um campo posicional, comece em $1
e ignore os campos nomeados. Exemplo:
var record = (named: ‘v', ‘y', named2: ‘x', ‘z');
print(record.$1); // prints y
print(record.$2) // prints z
- Faça a recarga automática para verificar os valores JSON mostrados no app. O plugin Dart do VS Code executa a recarga automática sempre que você salva um arquivo.
Você pode verificar que cada campo, de fato, manteve o próprio tipo.
- O método
Text()
recebe uma string como primeiro argumento. - O campo
modified
é um DateTime e é convertido em umaString
usando interpolação de strings.
A outra maneira segura de retornar diferentes tipos de dados é definir uma classe, o que é mais detalhado.
6. Combinar e desestruturar com padrões
Os registros podem coletar com eficiência diferentes tipos de dados e transmiti-los com facilidade. Agora, melhore seu código usando padrões.
Um padrão representa uma estrutura que um ou mais valores podem assumir, como uma planta. Os padrões são comparados com os valores reais para determinar se eles são correspondentes.
Em caso positivo, alguns padrões desestruturam o valor correspondente, extraindo dados dele. A desestruturação permite que você desempacote os valores de um objeto para atribuí-los a variáveis locais ou realizar outras correspondências com eles.
Desestruturar um registro em variáveis locais
- Refatore o método
build
deDocumentScreen
para chamargetMetadata()
e use-o para inicializar uma declaração de variável padrão:
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata(); // New
return Scaffold(
appBar: AppBar(
title: Text(title), // New
),
body: Column(
children: [
Center(
child: Text(
'Last modified $modified', // New
),
),
],
),
);
}
O padrão de registro (title, :modified)
contém dois padrões variáveis que correspondem aos campos de registro retornados por getMetadata()
.
- A expressão corresponde ao subpadrão porque o resultado é um registro com dois campos, um dos quais é denominado
modified
. - Como eles são correspondentes, o padrão de declaração de variável desestrutura a expressão, acessando os valores e vinculando-os a novas variáveis locais dos mesmos tipos e nomes,
String title
eDateTime modified
.
A sintaxe do padrão variável :modified
é uma abreviação de modified: modified
. Se você quiser uma nova variável local com um nome diferente, pode escrever modified: localModified
.
- Faça a recarga automática para ter o mesmo resultado que na etapa anterior. O comportamento é exatamente o mesmo. Você só tornou o código mais conciso.
7. Usar padrões para extrair dados
Em determinados contextos, os padrões não apenas correspondem e desestruturam, mas também podem decidir sobre o que o código faz, dependendo se o padrão corresponde ou não. Esses são chamados de padrões refutáveis.
O padrão de declaração de variável que você usou na última etapa é um padrão irrefutável: o valor precisa corresponder ao padrão. Caso contrário, ocorrerá um erro e a desestruturação não vai acontecer. Pense em qualquer declaração ou atribuição de variável. Não é possível atribuir um valor a uma variável se elas não forem do mesmo tipo.
Por outro lado, padrões refutáveis são usados em contextos de fluxo de controle:
- Eles esperam que alguns valores comparados não correspondam.
- Eles se destinam a influenciar o fluxo de controle, com base na correspondência ou não do valor.
- Eles não interrompem a execução com um erro se não corresponderem, apenas passam para a próxima instrução.
- Eles podem desestruturar e vincular variáveis que só podem ser usadas quando correspondem.
Ler valores JSON sem padrões
Nesta seção, você vai ler dados sem correspondência de padrão para conferir como os padrões podem ajudar a trabalhar com dados JSON.
- Substitua a versão anterior de
getMetadata()
por uma que leia valores do mapa_json
. Copie e cole essa versão degetMetadata()
na classeDocument
:
lib/data.dart
(String, {DateTime modified}) getMetadata() {
if (_json.containsKey('metadata')) {
var metadataJson = _json['metadata'];
if (metadataJson is Map) {
var title = metadataJson['title'] as String;
var localModified = DateTime.parse(metadataJson['modified'] as String);
return (title, modified: localModified);
}
}
throw const FormatException('Unexpected JSON');
}
Esse código valida que os dados estão estruturados corretamente sem usar padrões. Em uma etapa posterior, você usa a correspondência de padrões para realizar a mesma validação usando menos código. Ela executa três verificações antes de qualquer outra coisa:
- Se o JSON contém a estrutura de dados que você espera:
if (_json.containsKey('metadata'))
- Se os dados têm o tipo que você espera:
if (metadataJson is Map)
- Se os dados não são nulos, o que é confirmado de forma implícita na verificação anterior.
Ler valores JSON usando um padrão de mapa
Com um padrão refutável, você pode verificar se o JSON tem a estrutura esperada usando um padrão de mapa.
- Substitua a versão anterior de
getMetadata()
por este código:
lib/data.dart
(String, {DateTime modified}) getMetadata() {
if (_json
case {
'metadata': {
'title': String title,
'modified': String localModified,
}
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
}
}
Aqui está um novo tipo de instrução if (introduzida no Dart 3), a if-case. O corpo do caso só é executado se o padrão corresponder aos dados em _json
. Essa correspondência realiza as mesmas verificações que você escreveu na primeira versão de getMetadata()
para validar o JSON de entrada. Esse código valida o seguinte:
_json
é do tipo mapa._json
contém uma chavemetadata
._json
não é nulo._json['metadata']
também é do tipo mapa._json['metadata']
contém as chavestitle
emodified
.title
elocalModified
são strings e não são nulos.
Se o valor não corresponder, o padrão refuta (se recusa a continuar a execução) e prossegue para a cláusula else
. Se não houver correspondência, o padrão desestrutura os valores de title
e modified
do mapa e os vincula a novas variáveis locais.
Para uma lista completa de padrões, consulte a tabela na seção Padrões da especificação de recursos.
8. Preparar o app para mais padrões
Até agora, você abordou a parte de metadata
dos dados JSON. Nesta etapa, você vai refinar um pouco mais sua lógica de negócios para tratar os dados da lista blocks
e renderizá-los no app.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Criar uma classe que armazena dados
- Adicione uma nova classe,
Block
, adata.dart
, que é usada para ler e armazenar os dados de um dos blocos de dados JSON.
lib/data.dart
class Block {
final String type;
final String text;
Block(this.type, this.text);
factory Block.fromJson(Map<String, dynamic> json) {
if (json case {'type': var type, 'text': var text}) {
return Block(type, text);
} else {
throw const FormatException('Unexpected JSON format');
}
}
}
O construtor de fábrica fromJson()
usa a mesma instrução if-case com um padrão de mapa que você já usou antes.
Observe que o json
corresponde ao padrão de mapa, embora uma das chaves, checked
, não seja considerada no padrão. Os padrões de mapa ignoram as entradas no objeto de mapa que não sejam explicitamente consideradas no padrão.
Retornar uma lista de objetos Block
- Em seguida, adicione uma nova função,
getBlocks()
, à classeDocument
.getBlocks()
analisa o JSON em instâncias da classeBlock
e retorna uma lista de blocos para renderizar na sua interface:
lib/data.dart
List<Block> getBlocks() {
if (_json case {'blocks': List blocksJson}) {
return <Block>[
for (var blockJson in blocksJson) Block.fromJson(blockJson)
];
} else {
throw const FormatException('Unexpected JSON format');
}
}
A função getBlocks()
retorna uma lista de objetos Block
, que você vai usar depois para criar a interface. Uma instrução if-case conhecida executa a validação e converte o valor dos metadados blocks
em uma nova List
chamada blocksJson
. Sem padrões, você precisaria do método toList()
para converter.
O literal da lista contém uma collection for para preencher a nova lista com objetos Block
.
Esta seção não apresenta nenhum recurso relacionado a padrões que você ainda não tenha experimentado neste codelab. Na próxima etapa, você vai se preparar para renderizar os itens da lista em sua interface.
9. Usar padrões para mostrar o documento
Agora você já pode desestruturar e recompor seus dados JSON usando uma instrução if-case e padrões refutáveis. Mas a if-case é apenas uma das melhorias para controlar as estruturas de fluxo que acompanham os padrões. Agora, você vai aplicar seu conhecimento de padrões refutáveis para instruções switch.
Controlar o que é renderizado usando padrões com instruções switch
- Em
main.dart
, crie um novo widget,BlockWidget
, que determina o estilo de cada bloco baseado no campotype
.
lib/main.dart
class BlockWidget extends StatelessWidget {
final Block block;
const BlockWidget({
required this.block,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
TextStyle? textStyle;
switch (block.type) {
case 'h1':
textStyle = Theme.of(context).textTheme.displayMedium;
case 'p' || 'checkbox':
textStyle = Theme.of(context).textTheme.bodyMedium;
case _:
textStyle = Theme.of(context).textTheme.bodySmall;
}
return Container(
margin: const EdgeInsets.all(8),
child: Text(
block.text,
style: textStyle,
),
);
}
}
A instrução switch no método build
alterna o campo type
do objeto block
.
- A primeira instrução de caso usa um padrão de string constante. O padrão corresponde se
block.type
for igual ao valor de constanteh1
. - A segunda instrução de caso usa um padrão "ou" lógico com dois padrões de strings constantes como subpadrões. O padrão corresponde se
block.type
for igual a um dos subpadrõesp
oucheckbox
.
- O caso final é um padrão curinga,
_
. Curingas em casos de alternância correspondem a tudo. Eles se comportam da mesma maneira que cláusulasdefault
, que ainda são permitidas em instruções switch. São apenas um pouco mais detalhados.
Padrões de curinga podem ser usados sempre que um padrão é permitido. Por exemplo, em um padrão de declaração variável: var (title, _) = document.getMetadata();
Nesse contexto, o curinga não vincula nenhuma variável. Ele descarta o segundo campo.
Na próxima seção, você vai saber mais sobre os recursos de switch depois de mostrar os objetos Block
.
Mostrar o conteúdo do documento
Crie uma variável local que contenha a lista de objetos Block
chamando getBlocks()
no método build
do widget DocumentScreen
.
- Substitua o método
build
existente emDocumentationScreen
por esta versão:
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata();
var blocks = document.getBlocks(); // New
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
// New
Text('Last modified: $modified'),
Expanded(
child: ListView.builder(
itemCount: blocks.length,
itemBuilder: (context, index) {
return BlockWidget(block: blocks[index]);
},
),
),
],
),
);
}
A linha BlockWidget(block: blocks[index])
cria um widget BlockWidget
para cada item na lista de blocos retornados do método getBlocks()
.
- Execute o aplicativo e os blocos vão aparecer na tela:
10. Usar expressões switch
Padrões adicionam muitos recursos a switch
e case
. Para que eles possam ser usados em mais lugares, o Dart tem as expressões switch. Uma série de casos pode fornecer um valor diretamente a uma atribuição variável ou instrução de retorno.
Converter a instrução switch em uma expressão switch
O analisador Dart fornece assistências para ajudar você a fazer mudanças no seu código.
- Mova o cursor da seção anterior para a instrução switch.
- Clique na lâmpada para conferir as assistências disponíveis.
- Selecione a assistência Convert to switch expression.
A nova versão desse código é parecida com o seguinte:
TextStyle? textStyle;
textStyle = switch (block.type) {
'h1' => Theme.of(context).textTheme.displayMedium,
'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
_ => Theme.of(context).textTheme.bodySmall
};
Uma expressão switch se parece com uma instrução switch, mas ela elimina a palavra-chave case
e usa =>
para separar o padrão do corpo do caso. Diferente das instruções switch, as expressões switch retornam um valor e podem ser usadas em qualquer lugar em que uma expressão possa ser utilizada.
11. Usar padrões de objeto
Dart é uma linguagem orientada a objetos, portanto, os padrões se aplicam a todos os objetos. Nesta etapa, você vai alternar um padrão de objeto e desestruturar as propriedades de objeto para aprimorar a lógica de renderização de data da interface.
Extrair propriedades de padrões de objeto
Nesta seção, você vai melhorar como a data da última modificação é mostrada usando padrões.
- Adicione o método
formatDate
amain.dart
:
lib/main.dart
String formatDate(DateTime dateTime) {
var today = DateTime.now();
var difference = dateTime.difference(today);
return switch (difference) {
Duration(inDays: 0) => 'today',
Duration(inDays: 1) => 'tomorrow',
Duration(inDays: -1) => 'yesterday',
Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
Duration(inDays: var days) => '$days days from now',
};
}
Esse método retorna uma expressão switch que alterna no valor difference
, um objeto de Duration
. Ele representa o intervalo de tempo entre today
e o valor modified
dos dados JSON.
Cada caso da expressão switch está usando um padrão de objeto correspondente ao chamar getters nas propriedades inDays
e isNegative
do objeto. A sintaxe parece estar criando um objeto Duration, mas, na verdade, está acessando campos no objeto difference
.
Os três primeiros casos usam subpadrões constantes 0
, 1
e -1
para corresponder à propriedade do objeto inDays
e retornar a string correspondente.
Os dois últimos casos lidam com durações além de hoje, ontem e amanhã:
- Se a propriedade
isNegative
corresponder ao padrão booleano constantetrue
, o que significa que a data da modificação foi no passado, ela vai mostrar dias atrás. - Se esse caso não detectar a diferença, a duração precisa ser um número positivo de dias. Não é necessário verificar explicitamente com
isNegative: false
. Portanto, a data de modificação está no futuro e mostra dias a partir de agora.
Adicionar lógica de formatação para semanas
- Adicione dois novos casos à função de formatação para identificar durações superiores a sete dias, para que a interface possa mostrar como semanas:
lib/main.dart
String formatDate(DateTime dateTime) {
var today = DateTime.now();
var difference = dateTime.difference(today);
return switch (difference) {
Duration(inDays: 0) => 'today',
Duration(inDays: 1) => 'tomorrow',
Duration(inDays: -1) => 'yesterday',
Duration(inDays: var days) when days > 7 => '${days ~/ 7} weeks from now', // New
Duration(inDays: var days) when days < -7 => '${days.abs() ~/ 7} weeks ago', // New
Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
Duration(inDays: var days) => '$days days from now',
};
}
Este código introduz cláusulas de proteção:
- Uma cláusula de proteção usa a palavra-chave
when
depois de um padrão de caso. - Elas podem ser usadas em if-cases, instruções e expressões switch.
- Elas só adicionam uma condição a um padrão depois de uma correspondência.
- Se a cláusula de proteção for avaliada como falsa, o padrão inteiro será refutado e a execução vai prosseguir para o próximo caso.
Adicionar a data recém formatada à interface
- Finalmente, atualize o método
build
naDocumentScreen
para usar a funçãoformatDate
:
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata();
var formattedModifiedDate = formatDate(modified); // New
var blocks = document.getBlocks();
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Text('Last modified: $formattedModifiedDate'), // New
Expanded(
child: ListView.builder(
itemCount: blocks.length,
itemBuilder: (context, index) =>
BlockWidget(block: blocks[index]),
),
),
],
),
);
}
- Use a recarga automática para conferir as mudanças no seu app:
12. Selar uma classe para switch exaustiva
Observe que você não usou um curinga ou um caso padrão no fim do último switch. Embora seja uma boa prática sempre incluir um caso para valores que possam falhar, tudo bem não fazer isso em um exemplo simples como esse, já que você sabe que os casos definidos representam todos os valores possíveis que inDays
poderia assumir.
Quando todos os casos em um switch são tratados, isso é chamado de switch exaustivo. Por exemplo, o switch em um tipo bool
é exaustivo quando tem casos para true
e false
. Switch do tipo enum
é exaustivo quando há casos para cada um dos valores do tipo enumerado, também porque eles representam um número fixo de valores constantes.
O Dart 3 ampliou a verificação de exaustão para objetos e hierarquias de classe com o novo modificador de classe sealed
. Refatore sua classe Block
como uma superclasse selada.
Criar as subclasses
- Em
data.dart
, crie três novas classes,HeaderBlock
,ParagraphBlock
eCheckboxBlock
, que estendemBlock
:
lib/data.dart
class HeaderBlock extends Block {
final String text;
HeaderBlock(this.text);
}
class ParagraphBlock extends Block {
final String text;
ParagraphBlock(this.text);
}
class CheckboxBlock extends Block {
final String text;
final bool isChecked;
CheckboxBlock(this.text, this.isChecked);
}
Cada uma dessas classes corresponde a valores type
diferentes do JSON original: 'h1'
, 'p'
e 'checkbox'
.
Selar a superclasse
- Marque a classe
Block
comosealed
. Depois, refatore a if-case como uma expressão switch que retorna a subclasse correspondente aotype
especificado no JSON:
lib/data.dart
sealed class Block {
Block();
factory Block.fromJson(Map<String, Object?> json) {
return switch (json) {
{'type': 'h1', 'text': String text} => HeaderBlock(text),
{'type': 'p', 'text': String text} => ParagraphBlock(text),
{'type': 'checkbox', 'text': String text, 'checked': bool checked} =>
CheckboxBlock(text, checked),
_ => throw const FormatException('Unexpected JSON format'),
};
}
}
A palavra-chave sealed
é um modificador de classe que significa que você só pode estender ou implementar essa classe na mesma biblioteca. Como o analisador conhece os subtipos dessa classe, ele vai informar um erro se uma opção não abranger um deles e não for exaustiva.
Usar uma expressão switch para mostrar widgets
- Atualize a classe BlockWidget em
main.dart
com uma expressão switch que usa padrões de objeto para cada caso:
lib/main.dart
class BlockWidget extends StatelessWidget {
final Block block;
const BlockWidget({
required this.block,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8),
child: switch (block) {
HeaderBlock(:var text) => Text(
text,
style: Theme.of(context).textTheme.displayMedium,
),
ParagraphBlock(:var text) => Text(text),
CheckboxBlock(:var text, :var isChecked) => Row(
children: [
Checkbox(value: isChecked, onChanged: (_) {}),
Text(text),
],
),
},
);
}
}
Na sua primeira versão do BlockWidget
, você alternou em um campo de um objeto Block
para retornar um TextStyle
. Agora, você alterna uma instância do próprio objeto Block
e corresponde a padrões de objetos que representam suas subclasses, extraindo as propriedades do objeto no processo.
O analisador Dart pode verificar se cada subclasse é tratada na expressão switch, porque você transformou Block
em uma classe selada.
Observe também que o uso de uma expressão switch aqui permite que você transmita o resultado diretamente para o elemento child
, em vez da instrução de retorno separada que era necessária anteriormente.
- Execute a recarga automática para fazer a verificação dos dados JSON na caixa de seleção renderizados pela primeira vez:
13. Parabéns
Você experimentou com sucesso padrões, registros, switch e casos aprimorados e classes seladas. Você cobriu uma grande quantidade de informações, mas apenas usou superficialmente esses recursos. Para mais informações sobre padrões, consulte a especificação de recursos.
Os diferentes tipos de padrões, os diferentes contextos em que podem aparecer e o possível aninhamento de subpadrões tornam as possibilidades de comportamento aparentemente infinitas. Mas são fáceis de observar.
Você pode imaginar todos os tipos de maneiras de mostrar conteúdo no Flutter usando padrões. Assim, você pode extrair dados com segurança para criar sua interface em poucas linhas de código.
Qual é a próxima etapa?
- Confira a documentação sobre padrões, registros, switch e casos aprimorados e modificadores de classe na seção Linguagem da documentação do Dart.
Documentos de referência
Confira o exemplo completo no próprio repositório.
Para especificações detalhadas de cada novo recurso, consulte os documentos de design originais: