Apêndice A: Referência Rápida de Dart
Este apêndice reúne os principais conceitos de Dart usados ao longo do livro. Use como consulta rápida quando precisar lembrar de uma sintaxe ou padrão.
Tipos básicos
int vida = 100;
double dano = 7.5;
String nome = 'Guerreiro';
bool vivo = true;
Null safety
String? alvo; // pode ser nulo
String nome = 'Herói'; // nunca nulo
alvo?.length; // acesso seguro
alvo ?? 'ninguém'; // valor padrão se nulo
alvo ??= 'padrão'; // atribui se nulo
Coleções
// Lista
List<String> itens = ['espada', 'poção', 'escudo'];
itens.add('anel');
itens.removeAt(0);
// Map
Map<String, int> precos = {'espada': 50, 'poção': 20};
precos['escudo'] = 80;
// Set
Set<String> visitados = {'sala1', 'sala2'};
visitados.add('sala3');
Funções
// Função com retorno
int calcularDano(int forca, int nivel) {
return forca * nivel;
}
// Arrow function
int dobro(int x) => x * 2;
// Função com parâmetros nomeados
void criar({required String nome, int vida = 100}) {
print('$nome tem $vida HP');
}
Classes
class Jogador {
final String nome;
int _vida;
Jogador(this.nome, this._vida);
// Named constructor
Jogador.iniciante(this.nome) : _vida = 100;
// Getter
int get vida => _vida;
// Método
void receberDano(int dano) {
_vida = (_vida - dano).clamp(0, _vida);
}
@override
String toString() => '$nome (HP: $_vida)';
}
Herança e classes abstratas
abstract class Inimigo {
String get nome;
int get vida;
void agir(Jogador alvo);
}
class Zumbi extends Inimigo {
@override
String get nome => 'Zumbi';
@override
int get vida => 30;
@override
void agir(Jogador alvo) {
// anda aleatoriamente
}
}
Mixins
mixin Combatente {
int atacar(int forca) => forca + Random().nextInt(6);
}
mixin Curavel {
void curar(int quantidade) { /* ... */ }
}
class Guerreiro extends Jogador with Combatente, Curavel {
Guerreiro(String nome) : super(nome, 100);
}
Enums (Dart 3)
enum Direcao {
norte(0, -1),
sul(0, 1),
leste(1, 0),
oeste(-1, 0);
final int dx;
final int dy;
const Direcao(this.dx, this.dy);
}
Sealed classes e pattern matching
sealed class ComandoJogo {}
class CmdMover extends ComandoJogo {
final Direcao dir;
CmdMover(this.dir);
}
class CmdAtacar extends ComandoJogo {}
class CmdUsarItem extends ComandoJogo {
final Item item;
CmdUsarItem(this.item);
}
// Switch exaustivo
switch (comando) {
case CmdMover(:final dir):
jogador.mover(dir);
case CmdAtacar():
iniciarCombate();
case CmdUsarItem(:final item):
jogador.usar(item);
}
Async e Await: Programação Assíncrona
Sintaxe Básica
// Future<T> retorna um valor do tipo T no futuro
Future<String> carregarSave(String caminho) async {
final arquivo = File(caminho);
if (await arquivo.exists()) {
return await arquivo.readAsString();
}
return '{}';
}
// Usar com await (bloqueia até completar)
void main() async {
final dados = await carregarSave('save.json');
print(dados);
}
// Usar sem await (não bloqueia)
void executarEmBackground() {
carregarSave('save.json').then((dados) {
print('Carregado: $dados');
});
}
Tratamento de Erros
// Try/catch em async
Future<void> salvarProgresso(String arquivo, String dados) async {
try {
final file = File(arquivo);
await file.writeAsString(dados);
print('Salvo com sucesso!');
} catch (e) {
print('Erro ao salvar: $e');
} finally {
print('Operação finalizada');
}
}
// Capturar erros sem try/catch
Future<String> carregarComFallback(String caminho) async {
try {
return await File(caminho).readAsString();
} on FileSystemException {
return 'dados padrão';
}
}
Future: Computação Assíncrona
// Future completado imediatamente
Future<int> damoDB() => Future.value(42);
// Future que completa depois de delay
Future<int> damoComEspera() async {
await Future.delayed(Duration(seconds: 2));
return 42;
}
// Múltiplas futures em paralelo
Future<void> carregarTodos() async {
final dados1 = carregarSave('save1.json');
final dados2 = carregarSave('save2.json');
// Aguardar todas
final resultados = await Future.wait([dados1, dados2]);
print('Todos carregados: $resultados');
}
Streams: Fluxos de Dados Assíncronos
// Generator async retorna Stream
Stream<int> contarAte5() async* {
for (int i = 1; i <= 5; i++) {
yield i;
await Future.delayed(Duration(seconds: 1));
}
}
// Consumir stream
void main() async {
await for (final numero in contarAte5()) {
print('Número: $numero');
}
}
// Transformar stream
Stream<String> nomesEmMaiusculas(Stream<String> nomes) async* {
await for (final nome in nomes) {
yield nome.toUpperCase();
}
}
Leitura e Escrita de Arquivos (dart)
import 'dart:io';
import 'dart:convert';
// Ler arquivo completo
Future<String> lerArquivo(String caminho) async {
return await File(caminho).readAsString();
}
// Escrever arquivo
Future<void> escreverArquivo(String caminho, String conteudo) async {
await File(caminho).writeAsString(conteudo);
}
// Ler linha por linha
Future<void> processarLinhas(String caminho) async {
final arquivo = File(caminho);
final linhas = arquivo.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (final linha in linhas) {
print('Linha: $linha');
}
}
// Checar existência e criar diretório
Future<void> garantirDiretorio(String caminho) async {
final dir = Directory(caminho);
if (!await dir.exists()) {
await dir.create(recursive: true);
}
}
JSON
import 'dart:convert';
// Serializar
String json = jsonEncode({'nome': 'Herói', 'vida': 100});
// Deserializar
Map<String, dynamic> mapa = jsonDecode(json);
Testes
import 'package:test/test.dart';
void main() {
group('Jogador', () {
test('receber dano reduz vida', () {
final jogador = Jogador('Teste', 100);
jogador.receberDano(30);
expect(jogador.vida, equals(70));
});
test('vida não fica negativa', () {
final jogador = Jogador('Teste', 10);
jogador.receberDano(50);
expect(jogador.vida, equals(0));
});
});
}
Padrões de Projeto Usados no Livro
Strategy: cada inimigo tem uma estratégia de IA que decide seu comportamento. Permite trocar o comportamento em tempo de execução.
Command: ações do jogo (mover, atacar, usar item) são objetos com executar() e desfazer(). Permite histórico e desfazer.
Factory: criação centralizada de inimigos e itens por tipo e andar. Facilita balanceamento e extensão.
Observer: sistema de eventos com Stream. Quando algo acontece no jogo, vários sistemas são notificados (log, UI, estatísticas).
State: máquinas de estado para comportamento de inimigos (patrulha, alerta, perseguição, ataque, fuga) e fases de boss.
Para Explorar Depois
O calabouço vai mais fundo. Aqui estão os skills para a próxima aventura: recursos avançados que transformam seus programas Dart de “funcional” para “obra-prima”.
Streams: Fluxos de Dados Assíncronos
Streams são tubos por onde dados fluem continuamente. Perfeito para eventos em tempo real, atualizações de sensores, ou qualquer coisa que acontece ao longo do tempo. Use Stream, StreamController, ou async* com yield para criar seus próprios.
Stream<int> contadorStream() async* {
for (int i = 0; i < 5; i++) {
yield i;
await Future.delayed(Duration(seconds: 1));
}
}
Isolates: Concorrência de Verdade
Isolates são “threads” do Dart: mundos paralelos que executam código pesado sem travar a UI. Diferente de threads convencionais, eles não compartilham memória, o que evita deadlocks e race conditions. Use para cálculos pesados ou processamento de dados.
void computarFatorialPesado() async {
final resultado = await compute(fatorial, 1000000);
}
int fatorial(int n) => n <= 1 ? 1 : n * fatorial(n - 1);
typedef: Apelidos para Tipos
Typedefs criam aliases para funções e tipos complexos, melhorando legibilidade do código. Essencial para callbacks complicados.
typedef Comparador = int Function(dynamic a, dynamic b);
typedef DadosJogo = ({String nome, int vida});
Comparador minhaCmp =
(a, b) => a.toString().compareTo(b.toString());
Extensions: Superpoderes para Tipos Existentes
Extensions adicionam métodos a classes já existentes—String, List, num—sem herança ou modificação. Transforme a forma como você trabalha com tipos padrão.
extension on String {
String gritarEmMaisculas() => toUpperCase();
}
print('herói'.gritarEmMaisculas()); // HERÓI
Mixins Avançados: O Poder da Composição
Mixins com restrições (on) garantem que suas misturas só funcionam em classes específicas. Prefira mixins a herança múltipla—são mais seguros e flexíveis.
mixin Curioso on Jogador {
void investigar() => print('$nome investigou a sala');
}
class Paladino extends Jogador with Curioso {
Paladino(String nome) : super(nome, 150);
}
Recursos Úteis
- Documentação oficial:
dart.dev - Pacotes:
pub.dev - Guia de estilo:
dart.dev/effective-dart - Flutter:
flutter.dev