pessoa = {
"nome" : "Cácio Costa",
"empresa": "Alura",
"cargo" : "Instrutor e pagodeiro nas horas vagas"
}Introdução às APIs
Fundamentos do REST
Design de APIs RESTful
Autenticação e Segurança de APIs REST
GraphQL
gRPC
API Gateways e BFF
Resiliência em APIs
Enterprise Integration Patterns
Event-driven Architecture
... Desenvolvimento Web;
... Spring Boot e o ecossistema do Spring (módulos populares);
... SGBDs e Docker.
Aula 1
Estilos de integração
Tecnologias de integração
CORBA, API e suas lições
Desafio
Arquiteturas de integração
Estilos de integração
Considerações
Marcos importantes (1960 - 2024)
RPC
Mensageria
Corba e DCOM
REST
WebServices SOAP
GraphQL, gRPC
e Event-Driven
A primeira grande tentativa de interoperabilidade universal
Desenvolvimento de sistemas distribuídos OO
Objetos remotos ou locais
Estabelecido pela OMG (Object Management Group) em 1991
Independência de linguagem
Grandes lições
A importância de contratos bem definidos entre sistema
Os desafios de criar abstrações universais
O equilíbrio entre poder e complexidade em APIs
module BankSystem {
interface Account {
void deposit(in float amount);
void withdraw(in float amount);
float balance();
};
};
Exemplo de IDL
A base de uma API
Endpoints/operaçõesO que um cliente é capaz de invocar no sistema remoto
Estruturas de dadosFormato das estruturas trafegadas nas requisições e respostas
ProtocoloO conjunto de regras que rege a comunicação para que os sistemas se entendam.
Elementos fundamentais
DocumentaçãoDetalhamento de como um cliente deve utilizar a API.
"One BUS to rule them all!"
Defina "micro"!
Plataformas com arquiteturas próprias
O aplicativo tem que ser compilado para a plataforma específica
Duas SDKs (Xcode e Android SDK)
Bases de códigos distintas, com linguagens distintas
A mesma base de código gera aplicativos para ambas plataformas
Curva de aprendizado menor
Mesma equipe de desenvolvimento
Novas plataformas (web e desktop)
SDKs de cada plataforma ainda é necessário
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}Dart
Tipos, variáveis e funções
Orientação a objetos
Estruturas de dados (listas, maps, ...)
Assincronismo (Future)
Programação funcional*
Intellij IDEA + plugins
do Flutter e Dart
VS Code + Flutter extension
Android Studio + Flutter Plugin
OU
OU
IDE
E
Dart
Flutter
Linguagem e framework
Xcode (iOS)
Android SDK
E
OU
SDKs das plataformas
Virtual
+
IDE
Flutter
SDK da plataforma
Diretórios e arquivos:
lib: fica o código fonte do projeto
test: testes automatizados
build: onde são geradas as construções
android, ios, macos, web, windows: arquivos específicos de cada plataforma
pubspec.yaml: arquivo de configuração do projeto (definir dependências, assets e outras configurações
main.dart: arquivo de entrada do aplicativo
Assim como um documento HTML é organizado numa árvore de tags, um aplicativo em Flutter é organizado como uma árvore de widgets!"
TUDO É WIDGET!
Biblioteca de widgets: https://docs.flutter.dev/ui/widgets
void main() {
runApp(
Text('Hello, World!', textDirection: TextDirection.ltr),
);
}
Blocos de construção (você constrói a interface com eles)
Descrevem como a UI deve ser de acordo com sua configuração e estado
Inspirados no React
Visual e comportamento é um único lugar
void main() {
runApp(
Center(
child: Text('Hello, World!', textDirection: TextDirection.ltr),
),
);
}
Center e Container
void main() {
runApp(
Center(
child: Container(color: Colors.purple, height: 200, width: 200),
),
);
}
Column(
children: [
Container(color: Colors.purple, height: 100, width: 100),
Container(color: Colors.deepPurpleAccent, height: 100, width: 100),
],
);
Column e Row
Row(
children: [
Container(color: Colors.purple, height: 100, width: 100),
Container(color: Colors.deepPurpleAccent, height: 100, width: 100),
],
);
Biblioteca de widgets: https://docs.flutter.dev/ui/widgets
Escolha alguns sistemas que se integram dentro da Vivo;
Desenhe o fluxo de comunicação entre esses sistemas (ESB, ponto a ponto)
Faça uma engenharia reversa e documente parte dessa API
Endpoints
Operações
Estruturas de dados
Protocolo
Aula 1
11/12 às 11h
→ Introdução às APIs
Aula 2
11/12 às 15h
→ Fundamentos do REST
Aula 2
Fundamentos do REST
Mergulhar fundo no HTTP
Richardson Maturity Model
Desafio
Mão na massa
Origem
Roy Fielding (2000)
Restrições e princípios
| Restrição | Descrição |
|---|---|
| Cliente-Servidor | A UI é separada do armazenamento dos dados. |
| Stateless | Sem estado entre requisições. |
| Cache | Rquisições podem ser classificados como cacheáveis. |
| Interface Uniforme | Recursos, métodos, cabeçalhos, etc, padronizados. |
| Sistema em Camadas | Componentes só conhecem a camada imediatamente inferior |
| Código sob Demanda | Cliente pode estender funcionalidades com scripts (opcional) |
Roy chama esse estilo arquitetural da Web de Representational State Transfer, ou simplesmente REST.
Serviços que possuem essa característica de (sufixo ful, em inglês) Web podem ser chamados de RESTful.
Coração do REST
https://cursos.alura.com.br/forum/subcategoria-java/todos/1
amqp://eats:caelum123@rabbitmq:5672
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
Mesmo ingrediente, outro sabor: JSON
URL de um livro da Casa do Código
{
"nome": "Controlando versões com Git e GitHub",
"autores": [
{ "autor": "Alexandre Aquiles" },
{ "autor": "Rodrigo Caneppele" }
],
"paginas": 220,
"ISBN": "978-85-66250-53-4"
}Mesmo ingrediente, outro sabor: XML
Mesma URL de um livro da Casa do Código
<livro>
<nome>Controlando versões com Git e GitHub</nome>
<autores>
<autor>Alexandre Aquiles</autor>
<autor>Rodrigo Caneppele</autor>
</autores>
<paginas>220</paginas>
<ISBN>978-85-66250-53-4</ISBN>
</livro>Media Types
Tipos comuns:
text/html -> HTMLtext/plain -> texto puroimage/png -> imagens PNGapplication/json -> JSONapplication/xml -> XMLapplication/pdf -> PDFapplication/epub+zip -> ebooks EPUBapplication/vnd.amazon.mobi8-ebook -> ebooks MOBIapplication/vnd.ms-excel -> arquivos .xls Excel
O que o servidor fará se você fizer uma requisição
para a URL acima?
GETUsado para obter uma representação de um recurso em uma determinada URL.
POSTCria um novo recurso no servidor, com sua própria URL.
O que deve-se fazer com o recurso
PUTAtualiza ou cria um recurso na URL informada.
DELETEO recurso da URL informada é removido do servidor.
PATCHAtualiza uma representação parcial do recurso da URL informada.
HEADUsado para obter os metadados de um recurso (sem a sua representação).
OPTIONSRetorna os métodos HTTP suportados por uma URL.
TRACERepete o request, para verificar se alguma alteração foi feita por intermediário.
CONNECTRequest vira um túnel TCP/IP para permitir comunicação por um proxy.
Como dizer para o servidor que eu quero
a representação em XML e não em JSON?
AcceptEnumera os formatos aceitos como resposta.
Enviado na resposta para indicar quais origins podem acessar um recurso.
Guiando a conversa entre cliente e servidor
Transporta credenciais de autenticação na requisição.
Indica a representação usada na requisição e na resposta
LocationIndica, na resposta, para indicar redirecionamento ou localização de recurso.
Como o cliente vai saber que foi criada uma nova fatura?
1XX (Informational)A requisição foi recebida e o processamento continua
Feedback para o cliente
2XX (Success)A requisição foi recebida, entendida e aceita com sucesso
3XX (Redirection)Mais ações são necessárias para completar a requisição
4XX (Client error)A requisição é inválida e contém erros causados pelo cliente
5XX (Server error)O servidor falhou em completar uma requisição válida.
Comunicação completa
Avaliando o quanto uma API está aderente ao REST
Pântano de POX (Plain Old XML)
Recursos
Métodos HTTP
Controle de Hypermedia (hyperlinks)
O que é o Dart?
Curiosidade: Foi criada para superar as limitações do JavaScript e melhorar a performance das aplicações web.
Tipagem e main
/*
* A função main é o ponto de entrada de uma aplicação Dart.
*/
void main() { // blocos delimitados por chaves
// Indentação com dois espaços
print("Meu primeiro programa em Dart.");
// Estaticamente tipada
int titulosEmCopa = 5;
double idhBrasil = 0.759;
String nome = "Brasil";
// Tem inferência de tipos
var palmeirasTemMundial = false;
print(palmeirasTemMundial.runtimeType); // bool
print('O Palmeiras tem ' + (palmeirasTemMundial ? 'um' : 'nenhum') + ' mundial.');
// Interpolação de strings
print('O $nome tem $titulosEmCopa títulos de Copa do Mundo e um IDH de $idhBrasil.');
}Padrão de codificação e nomenclatura
// Classes e enums em PascalCase
class PlanoTelefonico {
}
enum TipoPlano { basico, premium }
// Constantes em MAIÚSCULAS_COM_UNDERLINE
const double TAXA_ADESAO = 99.99;
// Variáveis, funções e parâmetros em camelCase
void calcularTarifaPlano(String nomePlano, String cliente) {
var valorMensal = 50.0;
// Indentação com 2 espaços
// Linhas com limite de 80 caracteres.
if (nomePlano == 'Vivo Pós 40GB com Netflix' && cliente == 'Cácio') {
darDescontoEspecial(99.99, cliente);
// Espaço antes das chaves dos blocos e nos parâmetros
}
}IF - ELSE
if (minutosDeLigacao > 100) {
print('Você ultrapassou o limite de minutos.');
} else if (minutosDeLigacao > 50) {
print('Você está perto de atingir o limite de minutos.');
} else {
print('Você está dentro do limite de minutos.');
}
for (int i = 0; i < 5; i++) {
print('Canal $i: ${canais[i]}');
}
int contador = 0;
while (contador < 5) {
print('Promoção ativa para o plano $plano');
contador++;
}
FOR - WHILE
Switch, switch expressions e exhaustiveness checking
String plano = 'Premium';
switch (plano) {
case 'Basico':
print('Plano Básico: Acesso limitado aos canais.');
case 'Premium':
print('Plano Premium: Acesso a todos os canais.');
default:
print('Plano desconhecido.');
}
String descricaoPlano = switch (plano) {
case 'Basico' => 'Plano Básico: Acesso limitado aos canais.',
case 'Premium' => 'Plano Premium: Acesso a todos os canais.',
default => 'Plano desconhecido.',
};
print(descricaoPlano);
enum TipoPlano { basico, premium, executivo }
TipoPlano tipo = TipoPlano.premium;
// Dart garante que todos os casos sejam cobertos
switch (plano) {
case TipoPlano.basico:
print('Plano Básico: Acesso limitado aos canais.');
case TipoPlano.premium:
print('Plano Premium: Acesso a todos os canais.');
case TipoPlano.executivo:
print('Plano Executivo: Benefícios exclusivos.');
}
Declaração e atribuição
// Não pode ser nulo
String nomeAssinante = 'Ana';
nameAssinante = null; ❌
// Pode ser nulo
String? planoAssinante = 'Premium';
planoAssinante = null; ✅
Null é uma das principais causas de falhas em tempo de execução
Variável nula deve ser explicitamente declarada
Operadores
// (??) -> Null Coalescing Operator
String? nomePlano;
String escolhido = nomePlano ?? 'Vivo Pós';
print(escolhido); // Saída: Vivo Pós
// (??=) -> Null Coalescing Assignment Operator
List<String>? canais = ['HBO', 'ESPN', 'Warner'];
canais ??= ['Globo', 'SBT', 'Record'];
print(canais); // Saída: [HBO, ESPN, Warner]
// (?.) -> Conditional Access Operator
String? assinante;
// Não lança exceção, apenas retorna null
print(assinante?.length);
// (!) -> Null Assertion Operator
print(assinante!); // Lança uma exceção se assinante for nuloDefinição e parâmetros posicionais
void exibirLogo() {
print('Sou a logo da VIVO');
}
exibirLogo(); // invoca função
// Os dois parâmetros são obrigatórios
void realizarAssinatura(String nome, String plano) {
print('Cliente $nome assinou o plano $plano');
}
// Passa parâmetros pela posição
realizarAssinatura('Bia', 'Ultra HD');Precisa especificar o tipo de retorno
void quer dizer que não retorna valor
Parâmetros posicionais:
são obrigatórios
não podem ter valor padrão
Parâmetros nomeados
double calculaDesconto(
double valorDoPlano,
{ String? categoria, bool comFidelidade = false }
) {
double desconto = 0.0;
if (categoria == 'V') {
desconto = 0.2;
} else if (categoria == 'Platinum') {
desconto = 0.15;
} else if (categoria == 'Gold') {
desconto = 0.10;
}
if (comFidelidade) {
desconto += 0.05;
}
return valorDoPlano - (valorDoPlano * desconto);
}São opcionais por padrão, a menos que estejam anotados com required
Podem receber valor padrão
Podem ser passados em qualquer ordem
double calculaDesconto(
double valorDoPlano,
{ String? categoria, bool comFidelidade = false }
) {
// código omitido
}
var descontoZero = calculaDesconto(100);
var descontoV = calculaDesconto(100, categoria: 'V');
var descontoFidelidade = calculaDesconto(100, comFidelidade: true);
var descontoPlatinum = calculaDesconto(100, categoria: 'Platinum', comFidelidade: true);Função anônima
List<String> canais = ['SporTV', 'HBO', 'Warner'];
/*
* Função anônima (lambda) passada como argumento.
* Repare que ela tem corpo delimitado por chaves.
*/
canais.forEach((String canal) {
print(canal);
});
// Lambda com uma linha pode usar arrow ( => )
canais.forEach((String canal) => print(canal));Podem ter bloco delimitado por chaves
Lambdas com uma linha podem usar expression body (arrow =>)
Classes, atributos e construtores
// Definição de classe com keywork class
class Assinante {
// atributos
String nome;
String plano;
// Generative construtor
Assinante(this.nome, this.plano);
@override
String toString() => 'Nome: $nome, Plano: $plano';
}
var assinante = Assinante('Ana', 'Básico');
print(assinante); // 'Nome: Ana, Plano: Básico
assinante.plano = 'Premium';
print(assinante.plano); // PremiumAtributos não nulos precisam ser inicializados
Generative constructor é um atalho para inicializar os atributos
valem as mesmas regras de atributos posicionais e nomeados das funções
Atributos geram getter e setter (quando pertinente) implícitos
Encapsulamento e construtores nomeados
class Celular {
String _marca;
String _modelo;
String _numero;
// Getters
String get marca => _marca;
String get modelo => _modelo;
String get numero => _numero;
// Setters
set marca(String novaMarca) => _marca = novaMarca;
set modelo(String novoModelo) => _modelo = novoModelo;
set numero(String novoNumero) => _numero = novoNumero;
// Construtor nomeado usando Initializing List
Celular.fromJson(Map<String, dynamic> json)
: _marca = json['marca'],
_modelo = json['modelo'],
_numero = json['numero'];
@override
String toString() => 'Celular{_marca: $_marca, _modelo: $_modelo, _numero: $_numero}';
}
Map<String, String> json = {
'marca': 'Vivo',
'modelo': 'Xiaomi Mi 11',
'numero': '11987654321'
};
Celular celular = Celular.fromJson(json);
print(celular);
celular.marca = 'Samsung';
celular.modelo = 'Galaxy S21';
celular.numero = '11912345678';
print(celular);Atributos com prefixo `_` são privados
Use get e set para criar métodos acessores
Dart suporta construtores nomeados que inicializam os atributos (initializing list)
Herança
class Plano {
String nome;
Plano(this.nome);
}
/*
* Palavra chave EXTENDS permite
* usar herança no Dart.
*/
class PlanoFamilia extends Plano {
int membros;
PlanoFamilia(String nome, this.membros) : super(nome);
void mostrarInfo() {
print('Plano: $nome, Membros: $membros');
}
}
PlanoFamilia planoFamilia = PlanoFamilia('Vivo Família', 4);Future
// Simulando uma operação assíncrona de verificação de crédito
Future<String> verificarCredito() {
return Future.delayed(Duration(seconds: 3), () {
return 'Crédito aprovado';
});
}
void main() {
print('Verificando crédito do cliente...');
verificarCredito()
.then((status) {
print('Status do crédito: $status');
});
}Async await
// Repare na palavra ASYNC
Future<String> verificarCredito() async {
await Future.delayed(Duration(seconds: 3));
return 'Crédito verificado';
}
void main() async {
print('Verificando crédito do cliente...');
String status = await verificarCredito();
print('Status do crédito: $status');
}Escolha alguns sistemas que se integram dentro da Vivo;
Desenhe o fluxo de comunicação entre esses sistemas (ESB, ponto a ponto)
Faça uma engenharia reversa e documente parte dessa API
Endpoints
Operações
Estruturas de dados
Protocolo
Aula 3
12/12 às 10h
Design de APIs RESTful
Aula 1
11/12 às 11h
→ Introdução às APIs
Aula 2
11/12 às 15h
→ Fundamentos do REST
Aula 3
HATEOAS
(Hypermedia)
Boas práticas
Documentação e Swagger
Desafio
Mão na massa
Links para recursos relacionados
{
"id": "12345",
"nome": "João Silva",
"email": "joao.silva@exemplo.com",
"telefone": "+55 11 91234-5678",
"_links": {
"self": {
"href": "https://api.vivo.com.br/clientes/12345"
},
"planos": {
"href": "https://api.vivo.com.br/clientes/12345/planos"
},
"dispositivos": {
"href": "https://api.vivo.com.br/clientes/12345/dispositivos"
},
"faturas": {
"href": "https://api.vivo.com.br/clientes/12345/faturas"
}
}
}
representação HAL+JSON
Links de transição
{
"id":1,
"valor":51.80,
"nome":"ANDERSON DA SILVA",
"numero":"1111 2222 3333 4444",
"expiracao":"2022-07",
"codigo":"123",
"status":"CRIADO",
"formaDePagamentoId":2,
"pedidoId":1,
"_links":{
"self":{
"href":"http://localhost:8081/pagamentos/1"
},
"confirma":{
"href":"http://localhost:8081/pagamentos/1"
},
"recusa":{
"href":"http://localhost:8081/pagamentos/1"
},
"cancela":{
"href":"http://localhost:8081/pagamentos/1"
}
}
}
Transição explícita no próprio recurso
Princípios
"A good API is not just a set of endpoints, but a carefully crafted interface that empowers developers."
Autor desconhecido
Recursos são coisas
| Ruim | Bom |
|---|---|
| /getUsers | /users |
| /article_by_id?id=123 | /articles/{id} |
| /get-pending-orders | /orders?status=pending |
Semântica importa
| Método | Uso típico | Códigos comuns |
|---|---|---|
| GET | Recuperar | 200, 404 |
| POST | Criar | 201, 400 |
| PUT | Atualizar | 200, 204 |
| DELETE | Remover | 200, 204, 404 |
Prós e contras
Local da versão:
/v1/users/users?version=1application/vnd.company.v1+json
Semântica:
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users:
get:
summary: Returns a list of users
responses:
'200':
description: Successful response
Transição explícita no próprio recurso
Exemplo de contrato OpenAPI
void main() {
runApp(
MaterialApp(
home: Text('Hello World'),
),
);
}
Column
Widget raiz de um aplicativo que segue o Material Design
Fornece estrutura básica e configurações essenciais
Permite navegação entre telas
Aplica as diretrizes do Material Design
e mais...
/**
* Representa o esqueleto de uma tela
* (AppBar, body, FloatActionButton, Drawer,
* BottomNavigationBar, etc...)
*/
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Histórico de faturas'),
),
body: Center(
child: Text('Mensagem de dentro de um Scaffold')
),
)
),
);
}
Column
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Histórico de faturas'),
),
/**
* CARD Representa um cartão de material design.
* Sombra, cantos arredondados.
* Diferente do Container, que tem propósito
* genérico.
*/
body: Card(
color: Colors.white,
/**
* ListTile exibe informações de forma
* organizada e interativa.
*/
child: ListTile(
leading: Icon(Icons.check_circle, color: Colors.green),
title: Text('Venceu em 25/01'),
subtitle: Text('Paga'),
trailing: Text("R\$ 149.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)),
),
),
),
),
);
}
Column
Column(
children: [
Card(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.watch_later_outlined, color: Colors.grey),
title: Text('Vence em 24/03'),
subtitle: Text('Aberta'),
trailing: Text("R\$ 153.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)
),
),
),
Card(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.watch_later_outlined, color: Colors.red),
title: Text('Venceu em 25/03'),
subtitle: Text('Atrasada'),
trailing: Text("R\$ 153.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)
),
),
),
Card(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.check_circle, color: Colors.green),
title: Text('Venceu em 25/02'),
subtitle: Text('Paga'),
trailing: Text("R\$ 146.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)),
),
),
Card(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.check_circle, color: Colors.green),
title: Text('Venceu em 25/01'),
subtitle: Text('Paga'),
trailing: Text("R\$ 149.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)),
),
),
],
);
Column
SingleChildScrollView(
child: Column(
children: [
Card(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.watch_later_outlined, color: Colors.grey),
title: Text('Vence em 24/03'),
subtitle: Text('Aberta'),
trailing: Text("R\$ 153.00",
style: TextStyle(color: Colors.black, fontSize: 20.0)
),
),
),
// ... muitos outros Cards
],
),
);
Column
void main() {
runApp(
Center(
child: Text(
'Olá, Mundo Flutter!',
textDirection: TextDirection.ltr,
),
),
);
}
Criados com widgets de baixo nível
Retornam a representação visual no método build
Podem encapsular uma lógica de apresentação
Trazem semântica e reaproveitamento
Estendem StatelessWidget ou StatefulWidget
void main() {
runApp(OlaMundo());
}
class OlaMundo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Olá, Mundo Flutter!', textDirection: TextDirection.ltr),
);
}
}
Não rastreiam valores ao longo do ciclo de vida
Não atualizam a tela com mudanças de estado
Encapsulam uma lógica de apresentação
Geralmente são imutáveis
Reduzem código duplicado e melhoram a coesão
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(
title: Text('Histórico de faturas'),
),
body: Column(
children: [
Card(
color: Colors.white,
child: ListTile(
leading: Icon(
Icons.watch_later_outlined,
color: Colors.grey,
),
title: Text('Vence em 24/03'),
subtitle: Text('Aberta'),
trailing: Text(
"R\$ 153.00",
style: TextStyle(color: Colors.black, fontSize: 20.0),
),
),
),
// ... demais Cards
],
),
),
),
);
}
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(
title: Text('Histórico de faturas'),
),
body: Column(
children: [
CardFatura(153.0, '25/04', aberta: true,),
CardFatura(146.0, '25/03', atrasada: true),
CardFatura(146.0, '25/02'),
CardFatura(146.0, '25/02'),
],
),
),
),
);
}
class CardFatura extends StatelessWidget {
double valor;
String vencimento;
bool aberta;
bool atrasada;
CardFatura(this.valor, this.vencimento,
{this.aberta = false, this.atrasada = false, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
String status = 'Paga';
Icon icone = Icon(Icons.check_circle, color: Colors.green);
if (atrasada) {
icone = Icon(Icons.watch_later_outlined, color: Colors.red);
status = 'Atrasada';
} else if (aberta) {
icone = Icon(Icons.error_outline, color: Colors.grey);
status = 'Aberta';
}
return Card(
color: Colors.white,
child: ListTile(
leading: icone,
title: Text('Venceu em $vencimento'),
subtitle: Text(status),
trailing: Text("R\$ ${valor.toStringAsFixed(2)}",
style: TextStyle(color: Colors.black, fontSize: 20.0)),
),
);
}
}
Possuem estado que muda com ações do usuário ou eventos externos
Repintam a tela para exibir o estado atualizado
Estado encapsulado numa classe separada do widget
class JogoDeDados extends StatefulWidget {
@override
JogoDeDadosState createState() => JogoDeDadosState();
}
class JogoDeDadosState extends State<JogoDeDados> {
String dado1 = '?';
String dado2 = '?';
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container( // Representa o primeiro dado
height: 100,
width: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Text(dado1, // Texto do dado 1
style: TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
),
),
),
),
SizedBox(width: 8),
Container( // Representa o segundo dado
height: 100,
width: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Text(dado2, // Texto do dado 2
style: TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
SizedBox(height: 24),
ElevatedButton(
onPressed: sorteia, // Precisa atualizar a tela
child: Text('Rolar dados'),
),
],
);
}
void sorteia() {
setState(() { // Autaliza o estado para atualizar a tela
dado1 = '${Random().nextInt(6) + 1}';
dado2 = '${Random().nextInt(6) + 1}';
});
}
}A tela pode se atualizar com widgets stateless.
Pode ser stateless, reaproveitado e evitar código duplicado.
class JogoDeDados extends StatefulWidget {
@override
JogoDeDadosState createState() => JogoDeDadosState();
}
class JogoDeDadosState extends State<JogoDeDados> {
String dado1 = '?';
String dado2 = '?';
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [Dado(dado1), SizedBox(width: 8), Dado(dado2)],
),
SizedBox(height: 24),
ElevatedButton(
onPressed: sorteia,
child: Text('Rolar dados'),
)
],
);
}
void sorteia() {
setState(() {
dado1 = '${Random().nextInt(6) + 1}';
dado2 = '${Random().nextInt(6) + 1}';
});
}
}
class Dado extends StatelessWidget {
final String numero;
const Dado(this.numero);
@override
Widget build(BuildContext context) {
return Container(
height: 100,
width: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child: Text(
numero,
style: TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
),
),
),
);
}
}Aula
12/12 às 15h
Autenticação e Segurança de APIs REST
Aula 3
12/12 às 10h
Design de APIs RESTful
Aula 2
11/12 às 15h
→ Fundamentos do REST
Aula 4
Segurança em camadas
Zero Trust
Cuidados nas APIs
Desafio
Cuidados no ambiente de execução
Visão geral das integrações
TradicionalMudando a mentalidade
Zero TrustChecklist da aplicação
Integração com OAuth e JWT
Considerações da implementação
Command Injection
Descrição:
Ocorre quando a entrada do usuário é utilizada em comandos de sistema operacional, permitindo que o atacante execute comandos arbitrários.
Impacto:
Resultado:
Arquivos críticos do sistema podem ser deletados.
ENTRADA: & rm -rf /Cross-Site Scripting (XSS)
Descrição:
Acontece quando entradas maliciosas, como scripts JavaScript, são refletidas na resposta da API ou armazenadas para serem exibidas posteriormente.
Impacto:
Resultado:
O script é executado no navegador de outro usuário.
<script>alert('Hacked');</script>Remote Code Execution (RCE)
Descrição:
Acontece quando entradas maliciosas, como scripts JavaScript, são refletidas na resposta da API ou armazenadas para serem exibidas posteriormente.
Resultado
GET https://server.com?controller=TarefasController&acao=listaTarefasDoUsuario
<?php
$controller = new $_GET['controller'];
$metodo = $_GET['acao']
$controller->$metodo()Path Injection
Descrição:
Ocorre quando um caminho (path) fornecido pelo usuário é concatenado diretamente para manipular arquivos ou URLs.
Impacto:
ENTRADA: /var/data/../../etc/passwdJSON/XML Injection
Descrição:
Ocorre quando entradas maliciosas são usadas para manipular o parser de JSON ou XML da API.
Impacto:
Resultado:
O conteúdo de /etc/passwd é exposto ao atacante.
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<data>&xxe;</data>Diretrizes
Validação e Sanitização de Dados:
Use Bibliotecas Confiáveis:
Limite o Tamanho da Requisição:
Restrinja Diretórios e Caminhos:
Desabilite Recursos Inseguros:
Log e Monitoramento:
Validação de Tipo e Estrutura:
Zero Trust na Infra
Use Imagens de Container Seguras:
Ferramentas:
Docker Security ScanTrivyClair.Zero Trust na Infra
Restringir Privilégios do Container:
root dentro do container.--user para definir um usuário não privilegiado.CAP_SYS_ADMIN.Exemplo:
securityContext:
runAsUser: 1000
runAsGroup: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Zero Trust na Infra
Gerencie Segredos com Segurança:
Exemplo:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
data:
username: dXNlcm5hbWU=
password: cGFzc3dvcmQ=Zero Trust na Infra
Isolamento Adequado:
cgroups) para isolar containers no nível do sistema operacional.Ferramentas:
PodSecurityPoliciesValidação de Configurações:
kube-bench ou docker-bench-security.Zero Trust na Infra
Implementar Logs e Monitoramento adequados:
Ferramentas:
FluentdPrometheusGrafana ELK StackZipkinColumn
Tela 1
Tela 2
Tela 3
Tela N
push
push
push
pop
pop
pop
popUntil
Eu vejo Futures ao
mudar de tela
Com que frequência?
O tempo todo!
Navegação anônimaMais simples e imperativas.
Navegação nomeadaDeclaradas no atributo `router` de MaterialApp.
Navegação 2.0Mais complexa e completa. Ideal para aplicativos web.
/**
* NAVIGATOR é o objeto responsável pela
* navegação no aplicativo.
*/
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) => TelaDeCobranca(),
));// Volta para a tela anterior
Navigator.of(context).pop();
// Navegação mais simplificada
Navigator.of(context).pushNamed('/fatura');
Navigator.of(context).pop();
MaterialApp(
routes: {
'/': (context) => TelaInicial(),
'/faturas': (context) => ListagemDeFaturas(),
'/faturas/cobranca': (context) => CobrancaDeFatura(),
},
);
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) => TelaDeCobranca(fatura),
))
.then((sucesso) => {
if (sucesso) {
retirarNomeDoSerasa();
} else {
tentarNovaNegociacao();
}
);// Em algum lugar em TelaDeCobranca
Navigator.of(context)
.pop(pagamentoRealizadoComSucesso);
Navigator.pushNamed(context, '/fatura/cobranca',
arguments: {'id': 42}
)
.then((sucesso) => {
if (sucesso) {
retirarNomeDoSerasa();
} else {
tentarNovaNegociacao();
}
);// Em algum lugar em TelaDeCobranca
final args = ModalRoute.of(context)!.settings.arguments as Map;
int id = args['id'];
Navigator.of(context)
.pop(pagamentoRealizadoComSucesso);
Aula
16/12 às 10h
GraphQL
Aula 3
12/12 às 10h
Design de APIs RESTful
Aula
12/12 às 15h
Autenticação e Segurança de APIs REST
Aula 5
Criada internamente no Facebook
Projeto
OpenSource
GraphQL
Foundation
Adoção em massa
Coração do GraphQL
type Cliente {
id: ID!
nome: String!
email: String!
faturas: [Fatura!]!
}
type Fatura {
id: ID!
valor: Float!
vencimento: String!
clienteId: ID!
status: StatusFatura!
}
enum StatusFatura {
ABERTA
ATRASADA
PAGA
}Tipos complexos
type Query {
consultaCliente(id: ID!): Cliente
pesquisaFaturasDoCliente(clienteId: ID!): [Fatura!]!
pesquisaFaturasPorStatus(
clienteId: ID!,
StatusFatura: status!
): [Fatura!]!
}
type Mutation {
cadastraCliente(
cliente: NovoClienteInput!
): Cliente
registraPagamento(faturaId: ID!): Boolean!
}
input NovoClienteInput {
nome: String!
email: String!
}Definição de operações
Payload e retorno
type Query {
consultaCliente(id: ID!): Cliente
}Definição da Query
query {
consultaCliente(id: "1") {
# Campos que deseja receber (não listou email)
id
nome
faturas {
valor
status
}
}
}Payload enviado
Implementando a busca
query {
consultaCliente(id: "1") {
id
nome
faturas {
valor
status
}
}
}Payload enviado
Payload enviado
@DgsComponent
public class ClientePorId {
private ClienteRepository clienteRepo;
private FaturaRespository faturaRepo;
@DgsQuery
public Cliente consultaCliente(@InputArgument Long id) {
return clienteRepo.getReferenceById(id);
}
@DgsData(parentType = "Cliente", field = "faturas")
public List<Fatura> buscaFaturasDoCliente(DgsDataFetchingEnvironment env) {
Cliente cliente = env.getSource();
return faturaRepo.findByClienteId(cliente.getId());
}
}Implementação no Spring Boot
Image.networkImage.network(
'https://pbs.twimg.com/media/Fwvbm_NWAAIpIOV?format=jpg&name=large',
width: 300,
height: 450,
);Image.assetflutter:
# Tema que descomentar essa linha no
# adicionar os recursos
assets:
- assets/images/flu_serie_b.jpgImage.asset('assets/images/flu_serie_b.jpg');pubspec.yaml
Image.fileImage.memoryImage.fileImage.memoryMaterialApp(
title: 'Meu App',
theme: ThemeData(
useMaterial3: true,
),
home: MyHomePage(),
);
ThemeData
ThemeData encapsula as propriedades do tema
MaterialApp aplica o tema a todos os widgets descendentes.
`useMaterial3: true` para habilitar os novos componentes e estilos
MaterialApp(
title: 'Meu App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF8533AD)),
// Roxinho VIVO
),
home: MyHomePage(),
);
Cores e ColorScheme
Novo sistema de cores que facilita a criação de paletas dinâmicas
ColorScheme completo a partir de uma cor base (seed color)
Promove uma estética mais coesa
MaterialApp(
title: 'Meu App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Color(0xFF8533AD)
// Roxinho VIVO
),
textTheme: TextTheme(
displayLarge: TextStyle(
fontSize: 32, fontWeight: FontWeight.bold
),
bodyLarge: TextStyle(
fontSize: 18, color: Colors.black87
),
),
),
home: MyHomePage(),
);
Tipografia e TextTheme
Nova abordagem de tipografia com TextTheme
Estilos flexíveis e consistentes.
MaterialApp(
title: 'Meu App',
theme: ThemeData(
// ... configurações anteriores
appBarTheme: AppBarTheme(
backgroundColor: Color(0xFF8533AD),
titleTextStyle: TextStyle(
color: Colors.white, fontSize: 24
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
),
home: MyHomePage(),
);
Temas de componentes específicos
Permite temas específicos a alguns componentes
`appBarTheme`, `filledButtonTheme`, etc...
MaterialApp(
title: 'Meu App',
theme: ThemeData(
// Configurações do tema normal
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.red,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.dark,
// poder ser (light|system)
home: MyHomePage(),
);
Temas dinâmicos
Capacidade de usar cores dinâmicas
Adaptabilidade ao sistema operacional
Necessário configurar um outro ColorScheme e definir themeMode em MaterialApp
Text(
'Olá, Flutter!',
style: Theme.of(context).textTheme.bodyLarge,
);
Theme.of(context)
Theme dá acesso ao tema adequado ao contexto do widget na árvore.
Reusa estilos e mantém consistência da identidade visual.
Aula
16/12 às 14h
gRPC
Aula
16/12 às 10h
GraphQL
Aula
12/12 às 15h
Autenticação e Segurança de APIs REST
Aula 6
Comparação com HTTP1 - Cenário 1
Serializar um objeto Curso com 5 objetos Pessoa e com um objeto Telefone associados, mostra o tamanho:
Comparação com HTTP1 - Cenário 2
Executar 10.000 chamadas por uma listagem de códigos de Curso e, em seguida, os dados do Curso associado a esse código.
Avalia: tempo de resposta e uso de CPU no servidor e no cliente.
Tempo de resposta:
Comparação com HTTP1 - Cenário 2 (continuação)
CPU no servidor:
CPU no cliente:
Arquitetura
Todo o poder o HTTP2
Unary (request/response)
O cliente envia um request para o servidor e espera um response
Server Streaming
O cliente envia uma requisição e o servidor responde com um fluxo de dados.
Client Streaming.
O cliente envia um fluxo de dados e o servidor responde com uma única resposta.
Bidirectional Streaming
Tanto o cliente e o servidor trabalham com fluxos de dados.
Descrevendo a comunicação
syntax = "proto3";
package plano.service; // não deve ser nome invertido
option java_package = "br.com.alura.fatura.plano"; // pacote na convenção Java
service Plano {
rpc ListaPlanos (ListaPlanosRequest) returns (RegistraPagamentoResponse) {}
rpc PlanoPorId (IntValue) returns (Plano) {}
}
message ListaPlanosRequest {
string tipo = 1;
}
message ListaPlanosResponse {
repeated Plano planos = 1;
}
message Plano {
int64 id = 1;
string nome = 2;
string tipo = 3;
}
message IntValue {
int64 value = 1;
}Descrevendo a comunicação
@GrpcService
public class PlanoService extends PlanoServiceGrpc.PlanoServiceImplBase {
@Autowired
private PlanoRepository planoRepository;
@Override
public void listaPlnos(ListaPlanosRequest request,
StreamObserver<ListaPlanosResponse> responseObserver) {
ListaPlanosResponse response = ListaPlanosResponse.newBuilder();
planoRepository.findAll()
.map(plano -> {
return Plano.newBuilder() // mensagem gerada
.setId(plano.getId())
.setNome(plano.getNome())
.setTipo("Mensal")
.build();
})
.forEach(response::add);
responseObserver.onNext(response.build());
responseObserver.onCompleted();
}
}
Considerações importantes
Ciclo de vida
do Stateful
Instalação de
dependências
Provider
Desafio
Sempre dentro de StatefulWidget
Preferência para TextFormField (suporte a validação)
Controlado com TextEditingController
Inputs dentro de Form para controlar estado/eventos do formulário
Form precisa de Key para recuperar facilmente o estado
class FormularioNovoChamado extends StatefulWidget {
FormularioNovoChamado({super.key});
@override
State<FormularioNovoChamado> createState() => _FormularioNovoChamadoState();
}
class _FormularioNovoChamadoState extends State<FormularioNovoChamado> {
final campoTitulo = TextEditingController();
final campoDescricao = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Novo chamado'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Título do chamado', style: TextStyle(fontSize: 28)),
TextFormField(
controller: campoTitulo,
decoration: InputDecoration(
hintText: 'Descreva o seu problema',
),
),
SizedBox(height: 40),
Text('Descreva o seu problema', style: TextStyle(fontSize: 28)),
TextFormField(
controller: campoDescricao,
decoration: InputDecoration(
hintText: 'Descreva o seu problema',
),
maxLines: 5,
),
SizedBox(height: 40),
Center(
child: FilledButton(
onPressed: () {
var chamado = criaChamado();
Navigator.of(context).pop(chamado);
},
child: const Text('Enviar chamado'),
),
),
],
),
),
),
)
);
}
Chamado criaChamado() {
return Chamado(
userId: 1,
titulo: campoTitulo.text,
dataDeAbertura: DateTime.now(),
descricao: campoDescricao.text,
);
}
}class CicloDeVida extends StatefulWidget {
const CicloDeVida({super.key});
@override
State<CicloDeVida> createState() => _CicloDeVidaState();
}
class _CicloDeVidaState extends State<CicloDeVida> {
/**
* Chamado uma única vez, então é o lugar ideal para
* inicializar atributos e realizar operações assíncronas.
*/
@override
void initState() { ... }
/**
* Responsável por construir a árvore de widgets.
* Também invocado sempre que o widget pai é reconstruído,
* ou quando o estado foi atualizado.
*/
@override
Widget build(BuildContext context) { ... }
/**
* Notifica que o estado interno mudou e a tela,
* a partir daquele widget, precisa ser reconstruída.
* Aciona invocação do método build.
*/
@override
void setState(VoidCallback fn) { ... }
/**
* Invocado quando o widget é removido da árvore. Usado
* para liberar recursos ou cancelar operações assíncronas.
*/
@override
void dispose() { ... }
}FormularioDeLogin
Home
AcessoRapido
ListaDeChamados
FormularioNovoChamado
Prop drilling
Estado centralizado
ChangeNotifierChangeNotifierProviderConsumerwatch e readclass UsuarioProvider extends ChangeNotifier {
Usuario? usuario;
bool get isLogado => usuario != null;
void login(Usuario usuario) {
this.usuario = usuario;
notifyListeners();
}
void logout() {
this.usuario = null;
notifyListeners();
}
}Classe que guarda o estado compartilhado
Quando uma informação muda, precisa notificar os widgets (observadores)
class VivoApp extends StatelessWidget {
const VivoApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UsuarioProvider()
),
],
child: MaterialApp(
title: 'Vivo App',
theme: TEMA_CLARO,
home: const FormularioDeLogin(),
debugShowCheckedModeBanner: false,
),
);
}
}ChangeNotifierProvider cria os providers somente quando requisitados
Pode-se ter vários providers configurados (MultiProvider)
MaterialApp construído com todos os providers configurados.
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Vivo App'),),
body: Padding(
padding: EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 24, bottom: 36),
child: RichText(
text: TextSpan(
text: 'Olá, ',
style: TextStyle(fontSize: 24, color: Colors.black),
children: [
/**
* Precisa tomar cuidado com o CONTEXT
* que está configurado o watch
*/
TextSpan(
text: context.watch<UsuarioProvider>().usuario!.nome,
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
),
PainelChamada(),
SizedBox(height: 48),
AcessoRapido(),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print('Floating action button clicado...');
},
child: const Icon(Icons.phone),
)
);
}
}import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:vivoapp/models/usuario.dart';
import 'package:vivoapp/providers/usuario_provider.dart';
import 'package:vivoapp/widgets/acesso_rapido.dart';
import 'package:vivoapp/widgets/chamada.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Vivo App'),),
body: Padding(
padding: EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 24, bottom: 36),
child: Consumer<UsuarioProvider>(
builder: (
BuildContext context, // Contexto diferente para otimização
UsuarioProvider provider, // o provider com os dados
Widget? child // usado para otimização
) {
return RichText(
text: TextSpan(
text: 'Olá, ',
style: TextStyle(fontSize: 24, color: Colors.black),
children: [
TextSpan(
text: '${provider.usuario!.nome}.',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
},
),
),
PainelChamada(),
SizedBox(height: 48),
AcessoRapido(),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print('Floating action button clicado...');
},
child: const Icon(Icons.phone),
)
);
}
}
FilledButton(
onPressed: () {
/**
* Somente acessa, sem ser notificado de mudanças
*/
int id = context.read<UsuarioProvider>().usuario!.id;
var chamado = criaChamado(id);
Navigator.of(context).pop(chamado);
},
child: const Text('Enviar chamado'),
)Aula
17/12 às 15h
API Gateways e Backend For Frontend (BFF)
Aula
16/12 às 10h
GraphQL
Aula
16/12 às 14h
gRPC
Aula 7
import 'package:http/http.dart' as http;
import 'dart:convert';
const String _BASE_URL = 'http://localhost:3000';
Future<List<Chamado>> listaChamados() async {
Uri uri = Uri.parse('$_BASE_URL/chamados');
http.Response resposta = await http.get(uri);
if (resposta.statusCode == 200) {
// Se for um objeto, seria Map<String, dynamic>
List<dynamic> listaJson = jsonDecode(resposta.body);
return listaJson.map((json) => Chamado.fromJson(json))
.toList();
} else {
debugPrint('ERRO: ${resposta.body}');
throw Exception('Erro ao buscar chamados');
}
}services/api.dart
Método GET
class Chamado {
// Demais meétodo e atributos
Chamado.fromJson(Map<String, dynamic> json)
: id = json['id'],
userId = json['userId'],
titulo = json['titulo'],
dataDeAbertura = DateTime.parse(json['dataDeAbertura']),
descricao = json['descricao'],
dataDeFechamento = json['dataDeFechamento'] != null
? DateTime.parse(json['dataDeFechamento'])
: null;
Map<String, dynamic> toJson() {
return {
'userId': userId,
'titulo': titulo,
'dataDeAbertura': dataDeAbertura.toIso8601String(),
'descricao': descricao,
'dataDeFechamento': dataDeFechamento?.toIso8601String(),
};
}
}models/chamado.dart
Serialização
Serialização é feita "na unha"
Dart não suporta reflection em tempo de execução
Há geradores de código (json_serializable) para quebrar o galho
Hoje é mais tranquilo com IA Generativa
Future<Chamado> cadastraChamado(Chamado chamado) async {
http.Response resposta = await http.post(
Uri.parse('$_BASE_URL/chamados'),
headers: <String, String>{
'Content-Type': 'application/json',
},
body: jsonEncode(chamado.toJson()),
);
if (resposta.statusCode == 201) {
return Chamado.fromJson(jsonDecode(resposta.body));
} else {
debugPrint('ERRO: ${resposta.body}');
throw Exception('Erro ao criar chamado');
}
}services/api.dart
Método POST
class _ListaDeChamadosState extends State<ListaDeChamados> {
List<Chamado> _chamados = [];
// Demais métodos...
@override
void initState() {
super.initState();
carregaListaDeChamados();
}
void carregaListaDeChamados() async {
debugPrint('Buscando chamados');
_chamados = await listaChamados();
_chamados.sort(
(a, b) => b.dataDeAbertura.compareTo(a.dataDeAbertura)
);
setState(() {});
}
}screens/suporte/lista_chamados.dart
Inicializando estado
// DEMAIS CÓDIGOS OMITIDOS
FilledButton(
onPressed: () {
String id = context.read<UsuarioProvider>().usuario!.id;
criaChamado(id).then(
(chamado) => Navigator.of(context).pop(chamado),
);
},
child: const Text('Enviar chamado'),
)
Future<Chamado> criaChamado(String userId) async {
return cadastraChamado(
Chamado(
userId: userId,
titulo: campoTitulo.text,
dataDeAbertura: DateTime.now(),
descricao: campoDescricao.text,
),
);
}screens/suporte/formulario_novo_chamado.dart
Inicializando estado
Características
Plugin para armazenamento chave-valor (similar ao Storage API do navegador)
Dados são salvos no dispositivo do usuário
Pequeno volume de dados
Consistente am ambas plataformas
import 'package:shared_preferences/shared_preferences.dart';
Future<void> registraLogin(Usuario usuario) async {
var prefs = await SharedPreferences.getInstance();
await prefs.setBool('logado', true);
await prefs.setString('usuario', jsonEncode(usuario.toJson()));
}
Future<Usuario?> usuarioLogado() async {
var prefs = await SharedPreferences.getInstance();
var usuarioJson = prefs.getString('usuario');
if (usuarioJson == null) {
return null;
}
return Usuario.fromJson(jsonDecode(usuarioJson));
}
Future<void> limpaLogin() async {
var prefs = await SharedPreferences.getInstance();
await prefs.setBool('logado', false);
await prefs.remove('usuario');
}services/autenticacao.dart
Salvando e lendo dados
Aula 8
Splash Screen
Conceitos elementais de animação
Animações
implícitas
Transição de
telas customizadas
Desafio
Nativo vs Flutter
Implementada nativamente fora do Flutter
Exibida enquanto o Flutter está carregando
Só aparece quando necessário
Obrigatória para apps no iOS
Implementada dentro do framework do Flutter
Apresentada depois do Flutter estar carregado
Exibida mesmo em warm start (processo do app é trazido para foreground)
Splash Screen Nativa
Tela de introdução Flutter
flutter_native_splash:
android: true
ios: true
web: false
image: assets/images/logo_vivo.jpg
color: "#8533AD"pubspec.yaml
Configuração e geração
dart run flutter_native_splash:createterminal
A
B
Interpolação
(movimento de A para B)
(class Tween)
Animação que ocorre quando um atributo do widget muda
Basta indicar a duração (e a curva, preferencialmente) que o Flutter controla
Fáceis de usar
Existem vários prontos (AnimatedXpto) 🥳
Usos: expandir/colapsar menus, seleção de elementos, esmaecer, transições suaves entre telas, etc...
/**
* CardChamado virou stateful
*/
class _CardChamadoState extends State<_CardChamado> {
bool selecionado = false;
Icon? icone;
@override
void initState() {
super.initState();
icone = widget._chamado.fechado
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.error_outline, color: Colors.grey);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => selecionado = !selecionado),
/**
* Trocou Container por AnimatedContainer,
* configurou curva e duração, e pronto!
*/
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.fastLinearToSlowEaseIn,
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: selecionado
? Colors.grey.shade300
: Colors.white,
border: Border(
bottom: BorderSide(color: Colors.grey.shade300),
),
),
// Demais configurações do container omitidas
),
);
}screens/suporte/lista_chamados.dart
class _AvisoDeFaturasState extends State<AvisoDeFaturas> {
double opacidade = 1;
bool removerAviso = false;
void removeAviso() {
setState(() => opacidade = 0);
Future.delayed(Duration(milliseconds: 800), () {
setState(() => removerAviso = true);
});
}
@override
Widget build(BuildContext context) {
return Visibility(
visible: !removerAviso,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutQuad,
opacity: opacidade,
child: Wrap(
runSpacing: 20,
children: [
Text(
'Você tem prontas para pagar. Quer pagar agora?',
style: TextStyle(fontSize: 28),
),
FilledButton(onPressed: () {}, child: Text('Pagar faturas')),
TextButton(
onPressed: removeAviso,
child: Text('Ocultar notificação'),
),
],
),
),
);
}
}screens/home.dart
AnimatedAlignAnimatedCrossFadeAnimatedSizeAnimatedListHero...Route rotaDeslizante(Widget tela) {
/**
* Dispensa o MaterialPageRoute (quem fazia a animação
* da transição e construía a página para inserir na tela).
*/
return PageRouteBuilder(
pageBuilder:
(context, animation, secondaryAnimation) => tela,
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (
context, animation, secondaryAnimation, child
) {
const begin = Offset(-1, 0);
const end = Offset.zero;
const curve = Curves.ease;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}widgets/util.dart
Sobrescrevendo a transição
context.read<UsuarioProvider>()
.login(usuario)
.then((_) {
Navigator.of(context)
.pushReplacement(
rotaDeslizante(const Home())
);
});widgets/util.dart
Usando o PageRouteBuilder
Aula 9
Tipos de teste
no Flutter
Escrever
testes
Boas (más) práticas
Desafio
Testes de unidadeTestes de WidgetsTestes de integraçãoAula 9
Revisão a jato
Semântica faz toda diferença
Reagindo a acontecimentos importante
Domain Event:
Uma ocorrência significativa em termos de negócio em um determinado Bounded Context.
PedidoRealizado
FaturaPaga
CancelamentoEfetuado
PagamentoRecusado
Natureza da comunicação
Desafios
Considerações
sobre interface
Interação com
código nativo
Estado atual das
novas plataformas
Alcance de Público Amplo
Consistência de Experiência do Usuário
Economia de Tempo e Recursos
Compatibilidade e Adaptação
Limitações de Desempenho
Limitações de API nativa
Usar elementos específicos da plataforma
Cupertino
Material
Padronização + interação + feedback
Diretrizes de design
disposição de elementos, navegação, gestos, etc
Espaçamento e alinhamento
+ que simples estética → usabilidade e harmonia
SizedBox e Padding
Testes de usabilidade
Feedback de usuários (insights de melhoria, etc)
Method Channel
1 - Dependência da plataforma
// build.gradle
dependencies {
implementation 'com.example:your-library:1.0.0'
}
# Podfile
pod 'YourLibrary', '~> 1.0'
2 - Código nativo
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example/native";
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(
flutterEngine.getDartExecutor().getBinaryMessenger(),
CHANNEL
)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("getData")) {
// Chame sua biblioteca aqui
String data = getNativeData();
result.success(data);
} else {
result.notImplemented();
}
}
);
}
}
2 - Código nativo
import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.example/native",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call, result) in
if call.method == "getData" {
let data = self.getNativeData() // Chame sua biblioteca aqui
result(data)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
3 - Ponte de comunicação
import 'package:flutter/services.dart';
class NativeBridge {
static const MethodChannel _channel = MethodChannel('com.example/native');
Future<String> getNativeData() async {
final String data = await _channel.invokeMethod('getData');
return data;
}
}
void fetchData() async {
String data = await NativeBridge().getNativeData();
print(data);
}
Considerações
Por quê?
Implementar algo que não tem no Flutter
Acessar recursos específicos da plataforma (sensor, câmera, etc)
Reusar dependência nativa
Cuidados
Compatibilidade e Manutenção 🏋🏼
Panorama
Versão Beta
Responsividade, PWA e canvas (algumas renderizações)
Desempenho, SEO e tamanho do bundle final
Versão Alpha
Renderização de alto desempenho, mais possibilidade de periféricos, etc
Estabilidade, recursos ainda indisponíveis, documentação
(def pessoa {:nome "Cácio José da Costa Silva"
:linkedin "/cacio-costa/"
:email "cacio.costa@alura.com.br"
:repo "https://github.com/cacio-costa/vivoapp"
:ig-grupo-qdqa "@grupoqdqa"})Aula 11
Biblioteca open source para a construção de UI
Baseada em componentes
Permite criar aplicações em grande escala, com o conceito de SPA
Criado pelo Facebook em 2013
Server Side Rendering
Client Side Rendering
Animação de loading no site da Coca-Cola
| Componentização | Virtual DOM |
| Fluxo de dados unidirecional | JSX (JavaScript XML) |
| Ecossistema de bibliotecas | React Native |
| SSR com Next.js | React DevTools |
| Atualizações frequentes | Comunidade de suporte |
Fluxo de dados unidirecional
Virtual DOM
JSX
Props
State
Renderização
condicional
Componentes
Axios
Testes
Memoization
CSS-in-JS
Gerenciamento de estado
<input type="text" placeholder="Pesquisar" />
<h1>Título Principal</h1>
<a href="https://www.fiap.com.br/" target="_blank">
FIAP
</a>
<img src="https://www.alura.com.br/assets/img/alura2023/home/formations-sub.1712930780.png" alt="Ilustração de um submarino parado no fundo do mar. A frente do submarino possui uma escotilha esférica grande. Ele é ladeado por outras 4 escotilhas menores. O submarino tem características futuristas. O submarino é iluminado por uma luz azulada que vem de cima. Atrás do submarino, em segundo plano, surgem de cima duas algas extensas e verticais. O fundo do mar está populado por corais e mais vegetação maritima.">HTML
css
const itemsMenu = [
{
item: 'Baixe o App Vivo',
link: 'https://vivo.com.br/para-voce/app-vivo'
},
{
item: 'Produtos e Serviços',
link: 'https://vivo.com.br/para-voce/produtos-e-servicos'
},
{
item: 'Ajuda',
link: 'https://vivo.com.br/para-voce/ajuda'
},
{
item: 'Por que Vivo',
link: 'https://vivo.com.br/para-voce/por-que-vivo'
},
{
item: 'Melhores Ofertas',
link: 'https://vivo.com.br/para-voce/produtos-e-servicos/melhores-ofertas'
}
]JavaScript
Arrays e objetos
Métodos para manipulação de arrays
Funções
function MenuList() {
return (
<nav>
<ul>
{itemsMenu.map((menuItem, index) => (
<li key={index}>
<a href={menuItem.link}>{menuItem.item}</a>
</li>
))}
</ul>
</nav>
)
}A base do React moderno são componentes funcionais, com a utilização de métodos nativos do JS.
Navegador
Node.js
VS Code
Build e transpilers
Servidor
Gerenciador de pacotes
Airbnb
export function Saudacao(props) {
return <h1>Olá, {props.nome}!</h1>;
}
function App() {
return (
<div>
<Header />
<Saudacao nome="Fulana" />
</div>
)
}
export default App
Blocos de construção de interfaces
Podem ser funcionais ou de classe
Encapsulam lógica e apresentação
Criar componente reutilizável para a seção "Por que escolher a Vivo?"
Criando uma aplicação React com Vite (artigo)
null
Aula 1
24/06 às 15h
→ Introdução ao React
Aula 2
25/06 às 14h
→ JXS e Elementos React
Aula 2
O que é o JSX
Renderização de elementos
Componentes
Eventos
Props
Desafio
HTML com JavaScript
Facilita a criação de componentes
Convertido em chamadas React.createElement
function Botao(props) {
return (
<button onClick={props.onClick}>
{props.texto}
</button>
)
}function Input(props) {
return (
<input
value={props.value}
onChange={props.onChange}
placeholder={props.placeholder}
/>
)
}function Lista(props) {
return (
<ul>
{props.itens.map(item => (
<li key={item.id}>{item.nome}</li>
))}
</ul>
)
}Elementos são blocos básicos do React
Renderiza elementos no DOM
Virtual DOM
ReactDOM.render()
function App() {
const [texto, setTexto] = React.useState('')
const [itens, setItens] = React.useState([])
const adicionarItem = () => {
const novoItem = { id: itens.length + 1, nome: texto }
setItens([...itens, novoItem])
setTexto('')
}
return (
<div>
<h1>Minha Lista</h1>
<Input
value={texto}
onChange={e => setTexto(e.target.value)}
placeholder="Digite um item"
/>
<Botao onClick={adicionarItem} texto="Adicionar" />
<Lista itens={itens} />
</div>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)Blocos de construção
São reutilizáveis
Encapsulam lógica e estilo
Podem ser funcionais ou de classe
function Botao(props) {
const handleClick = () => {
props.onClick()
}
return (
<button className={`btn ${props.className}`} onClick={handleClick}>
{props.texto}
</button>
)
}
Passam dados de um componente pai para um componente filho
São de leitura única (read-only)
Permitem a reutilização de componentes
"Olá, mundo!"
Pai
// Componente Filho
function Filho(props) {
return <p>{props.mensagem}</p>
}
// Componente Pai
function Pai() {
const mensagemParaFilho = "Olá, Mundo!"
return <Filho mensagem={mensagemParaFilho} />
}// O componente Post
function Post(props) {
return (
<div>
<h1>{props.titulo}</h1>
<img loading="lazy" src={props.urlImagem} />
<p>{props.texto}</p>
</div>
)
}
// Usando o componente Post
function App() {
return (
<div>
<Post
titulo="Primeiro post"
urlImagem="https://example.com/image1.jpg"
texto="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
/>
<Post
titulo="Segundo post"
urlImagem="https://example.com/image2.jpg"
texto="Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</div>
)
}Sintaxe similar ao HTML
Funções de evento como props
Usados para dinamismo e interatividade
Ações que acontecem no navegador
function Botao(props) {
return (
<button onClick={props.onClick}>
Clique aqui
</button>
)
}
function App() {
const handleClick = () => {
alert('Botão clicado!')
}
return (
<div>
<h1>Evento de Clique</h1>
<Botao onClick={handleClick} />
</div>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)
Criar um componente para Botão que possa ser reutilizado em toda a página.
Aula 3
26/06 às 10h
Componentes de Classe vs. Componentes Funcionais
Aula 2
JSX e Elementos React
Aula 1
Introdução ao React
Aula 3
Class components
Estado e ciclo de vida
Function components
Hooks em componentes funcionais
Desafio
Componente Funcional
function Saudacao() {
return <h1>Olá, mundo!</h1>
}Componente de Classe
import React, { Component } from 'React'
class Saudacao extends React.Component {
render() {
return <h1>Olá, mundo!</h1>
}
}Definidos como classes ES6
Estendem React.Component
Podem ter estado (state)
Possuem métodos de ciclo de vida
Devem implementar o método render
class Welcome extends React.Component {
render() {
return <h1>Hello, Vivo!</h1>
}
}import React, { Component } from 'react'
export class Input extends Component {
handleChange = (event) => {
this.props.onChange(event)
}
render() {
return (
<input
className={`input ${this.props.className}`}
value={this.props.value}
onChange={this.handleChange}
placeholder={this.props.placeholder}
/>
)
}
}→ Estado e Ciclo de Vida é mantido em this.state
→ Métodos como:
componentDidMountcomponentDidUpdatecomponentWillUnmount→ Itens de Class e OO, além da classe
No constructor
Armazena dados mutáveis
Usa this.setState()
Aceita objeto ou função
Mudanças no estado re-renderizam o componente
import React, { Component } from 'react'
class Contador extends Component {
constructor(props) {
super(props);
this.state = {
contagem: 0
}
}
incrementar = () => {
this.setState((prevState) => ({
contagem: prevState.contagem + 1
}))
}
render() {
return (
<div>
<h1>Contagem: {this.state.contagem}</h1>
<button onClick={this.incrementar}>Incrementar</button>
</div>
)
}
}
export default Contador
props
setState()
forceUpdate()
Montagem
Atualização
Desmontagem
Baseados em funções JavaScript
Retornam JSX
Simples e concisos
Recebem props como argumento
Originalmente sem estado (state)
Por isso não podíamos usar antes, mas os hooks resolveram isso 👍
function OlaMundo() {
return <h1>Olá, mundo!</h1>
}function Botao(props) {
return (
<button onClick={props.onClick}>
{props.texto}
</button>
)
}
export default Botao
Suporte a ciclo de vida
Mais verbosos
Ciclo de vida mais complexo
Mais simples, menos código
Hooks para estado e efeitos
Sem estado antes dos Hooks
Introduzidos no React 16.8
Adicionam estados
Atualizar dados ao longo de seu ciclo de vida
Controlam efeitos colaterais
Algo que está fora do escopo do componente
import { useState } from 'react'
export function Contador() {
const [contagem, setContagem] = useState(0)
const incrementar = () => {
setContagem(contagem + 1)
}
return (
<div>
<h1>Contagem: {contagem}</h1>
<button onClick={incrementar}>Incrementar</button>
</div>
)
}
useState
import { useState, useEffect } from 'react'
export function Relogio() {
const [hora, setHora] = useState(new Date())
useEffect(() => {
const intervalo = setInterval(() => {
setHora(new Date())
}, 1000)
return () => clearInterval(intervalo)
}, [])
return (
<div>
<h1>{hora.toLocaleTimeString()}</h1>
</div>
)
}
useEffect
Crie todos os componentes que faltam na nossa página, para podermos evoluir nosso aprendizado com o uso de states e hooks personalizados.
Aula 4
26/06 às 15h
Estado e Gerenciamento de Estado
Aula 3
Componentes de Classe vs. Componentes Funcionais
Aula 2
JSX e Elementos React
Aula 4
useState e useEffect
O que é o Gerenciamento de Estado?
useContext
Desafio
Context API
Flux vs. Redux
Hook do React para estados (a partir do React 16.8)
Podemos controlar / manusear o estado de um componente
Atribuído a eventos em funções de mudança de state
Inicializa estado
Atualiza com setState
import { useState } from 'react'
export function Contador() {
const [contador, setContador] = useState(0)
const incrementar = () => {
setContador(contador + 1)
}
const decrementar = () => {
setContador(contador - 1)
}
const resetar = () => {
setContador(0)
}
return (
<div>
<h1>Contador: {contador}</h1>
<button onClick={incrementar}>Incrementar</button>
<button onClick={decrementar}>Decrementar</button>
<button onClick={resetar}>Resetar</button>
</div>
)
}
Hook para efeitos colaterais (a partir do React 16.8)
Podemos averiguar esses efeitos:
a cada renderização do componente
em alguma alteração do estado do componente
apenas na primeira vez que o componente for renderizado
* Quando formos fazer requisições em API's, mas veremos isso na aula sobre AXIOS!
import { useState } from 'react'
export function Contador() {
const [contador, setContador] = useState(0)
const incrementar = () => {
setContador(contador + 1)
}
const decrementar = () => {
setContador(contador - 1)
}
const resetar = () => {
setContador(0)
}
useEffect(() => {
console.log(`O contador foi atualizado para: ${contador}`);
}, [])
return (
<div>
<h1>Contador: {contador}</h1>
<button onClick={incrementar}>Incrementar</button>
<button onClick={decrementar}>Decrementar</button>
<button onClick={resetar}>Resetar</button>
</div>
)
}
useState ou this.state
Estado Global: Compartilhado entre múltiplos componentes (Context API, Redux etc.)
Centraliza o estado da aplicação em um único lugar global. Isso simplifica o acesso e a atualização do estado, evitando a necessidade de passar props através de múltiplos componentes.
Facilita a manutenção e escalabilidade em aplicações grandes, especialmente quando várias partes da aplicação precisam estar sincronizadas e atualizadas conforme o estado muda
Prop Drilling
import { createContext, useContext, useState, useEffect } from 'react'
export const useUser = () => useContext(UserContext)
export const UserContext = createContext()
export function UserProvider ({ children }) {
const [user, setUser] = useState({})
useEffect(() => {
setUser({
isLogged: true,
firstName: 'Milena',
lastName: 'Emmert',
age: 28
})
}, [])
return <UserContext.Provider value={user}>{children}</UserContext.Provider>
}ReactDOM.createRoot(document.getElementById('root')).render(
<UserProvider>
<App />
</UserProvider>
)import { useUser } from '../contexts'
export function Componente() {
const user = useUser()
return (
<div>{user.firstName}</div>
)
}Fazer o Header do nossa página com o controle de login true ou false.
Aula 5
28/06 às 10h
Roteamento com React Router
Aula 4
Estado e Gerenciamento de Estado
Aula 3
Componentes de Classe vs. Componentes Funcionais
Aula 5
O que são Rotas
O que é a React Router
Navegação e parâmetros
Desafio
Proteção de Rotas
Configuração inicial
Organização e navegação das páginas, dentro do contexto de SPA
Permite a navegação sem recarregar a página
Mantém o estado da aplicação
Em rotas CSR o gerenciamento das rotas ocorre no navegador
Biblioteca de roteamento para React
Facilita a criação de rotas dinâmicas
Suporte para rotas aninhadas e parâmetros
Podemos ter navegação fluida
A React Router Dom não é uma lib nativa, portanto precisamos importá-la.
npm install react-router-domConfiguração da nossa primeira rota
→ Precisamos envelopar an app com Browser Router
Context de roteamento do React Router
Gerencia a URL da aplicação com a navegação entre componentes
Browser Routerimport ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { App } from './app'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
Pode substituir a tag <a> ou pode ser usado para navegação interna
NavLinkQuando a navegação precisa ser ativada programaticamente, como em eventos ou lógica interna do componente
Navigate ou useNavigateimport { NavLink } from 'react-router-dom';
<Link to="/about">About</Link>import { useNavigate } from 'react-router-dom'
function MyComponent() {
const navigate = useNavigate()
function handleClick() {
navigate('/about')
}
return (
<button onClick={handleClick}>Go to About</button>
)
}RouteRotas individuais
import { Route } from 'react-router-dom'
<Route path="/about" component={About} />RoutesAgrupa várias rotas em um único componente
import { Routes, Route } from 'react-router-dom'
<Routes>
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes><Routes>
<Route path='/' element={<MainScreen />} />
<Route path='/planos' element={<InternetScreen />} />
<Route path='/duvidas' element={<FaqScreen />} />
</Routes>const Auth = ({ children, condition }) => {
if (condition) {
return children
} else {
return <Navigate to='/' />
}
}<Route
path='/perfil'
element={
<Auth condition={isLogged}>
<ProfileScreen />
</Auth>
}
/>Verificar autenticação antes de renderizar componentes
Criar componente que protege a rota
Criar outras telas (apenas de teste) na nossa app para fazer o uso das rotas.
Aula 6
02/07 às 14h
Formulários e Eventos em React
Aula 5
Roteamento com React Router
Aula 4
Estado e Gerenciamento de Estado
Aula 6
Formulários com React
Formik
Validação de formulários
Eventos
Yup
Desafio
Usamos elementos HTML para coletar dados do usuário
<input>
<textarea>
<select>
Podemos reutilizar elementos trocando os valores de atributos por meio das props
Types para inputs. Alguns exemplos: text, email, number
Types para botões. Alguns exemplos: submit, button
Criamos de funções de manipulação
<form onSubmit={handleSubmit}>
<div>
<label>Nome:</label>
<input
type="text"
value={nome}
onChange={handleNomeChange}
required
/>
</div>
<div>
<label>Profissão:</label>
<select value={profissao} onChange={handleProfissaoChange} required>
<option value="">Selecione uma profissão</option>
<option value="Desenvolvedor">Desenvolvedor</option>
<option value="Designer">Designer</option>
<option value="Engenheiro">Engenheiro</option>
<option value="Professor">Professor</option>
</select>
</div>
<button type="submit">OK</button>
</form>const [nome, setNome] = useState('')
const [profissao, setProfissao] = useState('')
const handleNomeChange = (event) => {
setNome(event.target.value)
}
const handleProfissaoChange = (event) => {
setProfissao(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault()
console.log('Nome:', nome)
console.log('Profissão:', profissao)
}const [nome, setNome] = useState('')
const [profissao, setProfissao] = useState('')
function handleNomeChange(event) {
setNome(event.target.value)
}
function handleProfissaoChange(event) {
setProfissao(event.target.value)
}
function handleSubmit(event) {
event.preventDefault()
console.log('Nome:', nome)
console.log('Profissão:', profissao)
}{onChange}
→ Gerenciamento do estado dos campos de entrada
Manipular e validar os dados de entrada
Manter a UI sincronizada com o estado do aplicativo
import { Formik, Field, Form } from 'formik'
const Componente = () => {
retun (
<div>
<h1>Sign Up</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
}}
onSubmit={(values) => console.log(values)}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field id="firstName" name="firstName" placeholder="Jane" />
<label htmlFor="lastName">Last Name</label>
<Field id="lastName" name="lastName" placeholder="Doe" />
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="jane@acme.com"
type="email"
/>
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
)
}const formValidationSchema = Yup.object({
name: Yup.string().required('O nome é obrigatório.'),
email: Yup.string()
.email('Formato de e-mail inválido.')
.required('O e-mail é obrigatório.'),
age: Yup.number()
.typeError('A idade deve conter apenas números.')
.integer('A idade deve ser um número inteiro.')
.min(1, 'Idade inválida.')
.max(150, 'Idade inválida.'),
cep: Yup.string()
.matches(/^\d{5}-\d{3}$/, 'O CEP deve possuir o formato 00000-000.')
.required('O CEP é obrigatório.'),
message: Yup.string().required('A mensagem é obrigatória.')
})Implementar o nosso formulário com as validações de erro e reutilizar nosso componente de Botão já criado.
Aula 7
03/07 às 10h
Chamadas de API com Axios
Aula 6
Formulários e Eventos
Aula 5
Roteamento com React Router
Aula 7
Axios
GET
POST
Segurança de API's
Tratamentos de erros e async/await
Desafio
Faz requisições HTTP (transferência de dados na web)
Baseada em promises para lidar com requisições assíncronas
Simplifica comunicação com APIs
Tranformações
Cancelamentos
Transforma os dados para JSON
npm install axiosForma como os dados são transferidos na web
Recupera dados do servidor
import axios from 'axios'
axios.get('/api/users')
.then(response => console.log(response.data))
.catch(error => console.error(error))
Envia dados ao servidor
axios.post('/api/users', { name: 'John' })
.then(response => console.log(response.data))
.catch(error => console.error(error))import axios from 'axios'
axios.get('/api/users')
.then(response => console.log(response.data))
.catch(error => {
if (error.response) {
console.error('Error ', error.response.status)
} else {
console.error('Error:', error.message)
}
})async e await
import axios from 'axios'
async function fetchData() {
try {
const response = await axios.get('/api/users')
console.log(response.data)
} catch (error) {
console.error(error)
}
}async function fetchData() {
try {
const response = await fetch('/api/users')
const data = await response.json()
console.log(data)
} catch (error) {
console.error(error)
}
}
Token para autenticação segura
Incluído em headers das requisições
import axios from 'axios'
const token = 'your-token'
axios.get('/api/secure-data', {
headers: {
Authorization: `${token}`
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error))Fazer o get da api ViaCep a partir do CEP inserido no campo do formulário.
Aula 8
03/07 às 15h
Estilização em React
Aula 7
Chamadas de API com Axios
Aula 6
Formulários e Eventos
Aula 8
03/07 às 15h
Estilização em React
Aula 8
CSS em React
Styled Components
Imagens no React
CSS modules
SVG's no React
Desafio
Estiliza os componentes e interfaces
Métodos diversos
CSS tradicional
Bibliotecas (Styled Components 💅)
Pré-processadores (para mixins, nesting e funções)
CSS Modules*
import styled from 'styled-components';
const Button = styled.button`
background: blue;
color: white;
padding: 10px;
`;
function App() {
return <Button>Clique aqui</Button>;
}npm install styled-componentsconst Button = styled.button<{ $primary?: boolean; }>`
background: transparent;
border-radius: 3px;
border: 2px solid #BF4F74;
color: #BF4F74;
margin: 0.5em 1em;
padding: 0.25em 1em;
${props => props.$primary && css`
background: #BF4F74;
color: white;
`}
`;
const Container = styled.div`
text-align: center;
`
render(
<Container>
<Button>Normal Button</Button>
<Button $primary>Primary Button</Button>
</Container>
);import
import whyUsQuality from '../assets/images/why-us-quality.png'
import whyUsServices from '../assets/images/why-us-services.png'
import whyUsSignal from '../assets/images/why-us-signal.png'export const arrow = (className) => {
return (
<svg
className={className}
viewBox='0 0 32 32'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M18 6L28 16M28 16L18 26M28 16H4'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
vectorEffect='non-scaling-stroke'
/>
</svg>
)
}import styles from './Button.module.css';
function App() {
return <button className={styles.button}>Clique aqui</button>;
}O css-modules cria um className único para cada local em que é utilizado
.faqScreen {
composes: screen from global;
background-color: var(--rose);
padding-top: 160px;
color: var(--white);
}
.container {
composes: container from global;
}.container {
max-width: var(--container-width);
width: 100%;
height: 100%;
margin: 0 auto;
padding: 0 var(--container-padding);
}
<div className={isSelected ? ${s.component} ${s.selected} : s.component}></div>Podemos adicionar classes por meio de estruturas condicionais.
Usamos a estrutura de operador ternário:
condition ? expressionIfTrue : expressionIfFalseAula 9
04/07 às 10h
Hooks Avançados
Aula 8
Estilização em React
Aula7
Chamadas de API com Axios
Aula 9
Hooks avançados
useMemo
Performance
Boas práticas
Hooks customizados
Desafio
Melhoram funcionalidade dos componentes funcionais
Simplificam lógica complexa
Promovem reutilização de código
Memoriza valores calculados
Evita cálculos desnecessários
Otimiza o desempenho
const ShoppingCart = ({ items }) => {
const totalPrice = items.reduce((sum, item) => sum + item.price, 0)
return <div>Total Price: {totalPrice}</div>
}import { useMemo } from 'react'
const ShoppingCart = ({ items }) => {
const totalPrice = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0)
}, [items])
return <div>Total Price: {totalPrice}</div>
}useMemomemoização de dadosusar gerenciamento de estado de forma eficientelazy loadingReutilizam lógica entre componentes
Simplificam o código
Promovem a manutenção e legibilidade
Permitem personalizações
Componentização
Nomeação consciente
Gerenciamento de estado
Performance
Padrões de codificação
Criar um hook personalizado para que o Vivinho apareça apenas quando nossa MainScreen estiver no topo da tela.
Aula 10
05/07 às 10h
Introdução ao TypeScript
Aula 9
Hooks Avançados
Aula 8
Estilização em React
Aula 10
O que é o TypeScript?
Por que TypeScript com React?
Componentes funcionais com TypeScript
Tipos básicos e Types
Instruções
Configurando o ambiente de desenvolvimento
Superset de JavaScript
Adiciona tipagem estática
Melhora autocomplete e refatoração
Detecta erros em tempo de desenvolvimento
Compila para JavaScript puro
TypeScript
Websites simples
Sem grande volumes de dados
Projetos individuais
Aplicações mais complexas
SPAs
Projetos grandes
Projetos em equipe
| Característica do sistema de tipos | JS | TS |
|---|---|---|
| Como os tipos são vinculados? | Dinamicamente | Estaticamente |
| Os tipos são convertidos automaticamente? | Sim | Em alguns casos sim |
| Quando os tipos são verificados? | Em tempo de execução | Em dev e em tempo de compilação |
Aumento da produtividade no desenvolvimento
Redução de bugs através da tipagem estática
Facilidade na manutenção de código em projetos grandes
Melhor suporte para trabalho em equipe
| String | let myString: string = "Eu sou uma string" |
| Number | let myNumber: number = 12 |
| Boolean | let myBoolean: boolean = true |
| Array | let myArray: string[] = ["Olá", "Mundo"] |
| Object | let myObject: {name: string, age: number} = {name: "Milena", age: 28} |
| Any | let myAny: any |
Tipo personalizável
function Botao({ type, text, isDisabled }) {
return (
<button type={type} disabled={isDisabled}>
{text}
</button>
)
}type BotaoProps = {
type: 'submit' | 'button' | 'reset'
text: string
isDisabled: boolean
}
function Botao({ type, text, isDisabled }: BotaoProps) {
return (
<button type={type} disabled={isDisabled}>
{text}
</button>
)
}function Apresentacao({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>
}
Qualquer dúvida você pode me procurar o linkedin.
Será um prazer te ajudar!
Materiais gratuitos para consulta
Te desejo muito sucesso e que você sempre tenha força para superar seus desafios!
Um grande abraço,
Milena Emmert.
Aula 10
Introdução o TypeScript com React
Aula 9
Hooks Avançados