Jornada APIs e Integração de Sistemas

pessoa = {
    "nome"   : "Cácio Costa",
    "empresa": "Alura",
    "cargo"  : "Instrutor e pagodeiro nas horas vagas"
}

Jornada

  1. Introdução às APIs

  2. Fundamentos do REST

  3. Design de APIs RESTful

  4. Autenticação e Segurança de APIs REST

  5. GraphQL

  6. gRPC

  7. API Gateways e BFF

  8. Resiliência em APIs

  9. Enterprise Integration Patterns

  10. Event-driven Architecture

Seria legal se você soubesse...

  • ... Desenvolvimento Web;

  • ... Spring Boot e o ecossistema do Spring (módulos populares);

  • ... SGBDs e Docker.

Aula 1

Introdução às APIs

1

Estilos de integração

2

Tecnologias de integração

3

CORBA, API e suas lições

5

Desafio

4

Arquiteturas de integração

Integração de sistemas

 Estilos de integração

Estilos de integração

 Considerações

  • Vamos trocar funcionalidades ou apenas dados?
  • Quais dados trocaremos?
  • Qual protocolo utilizaremos?
  • A comunicação será síncrona ou assíncrona?
  • Quais ferramentas/frameworks utilizaremos?
  • etc.

Evolução das tecnologias

Linha do tempo

 Marcos importantes (1960 - 2024)

1960s

RPC

1980s

Mensageria

1990s

Corba e DCOM

2010s

REST

2000s

WebServices SOAP

2020s

GraphQL, gRPC

e Event-Driven

CORBA

 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

CORBA - Caiu atirando...

 Grandes lições

  1. A importância de contratos bem definidos entre sistema

  2. Os desafios de criar abstrações universais

  3. 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

Contratos são importantes!

A base de uma API

Endpoints/operações

O que um cliente é capaz de invocar no sistema remoto

Estruturas de dados

Formato das estruturas trafegadas nas requisições e respostas

Protocolo

O conjunto de regras que rege a comunicação para que os sistemas se entendam.

O que é uma API?

 Elementos fundamentais

Documentação

Detalhamento de como um cliente deve utilizar a API.

Arquiteturas de integração

ESB centralizado

 "One BUS to rule them all!"

Microsserviços

 Defina "micro"!

Como era antes do Flutter?

  • 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

Por que usar Flutter?

  • 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'),
    );
  }
}

O que preciso saber?

Dart

  • Tipos, variáveis e funções

  • Orientação a objetos

  • Estruturas de dados (listas, maps, ...)

  • Assincronismo (Future)

  • Programação funcional*

Ferramentas necessárias

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

+

Ambiente de desenvolvimento

IDE

  • importar o projeto
  • executar

3.

Flutter

  • adicionar no PATH
  • flutter doctor
  • flutter create

1.

SDK da plataforma

  • Xcode
  • iniciar emulador

2.

Mão na massa...

Estrutura do projeto

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

Qual a árvore mais famosa da programação?

A árvore do HTML! 😜

A lógica do Flutter

Assim como um documento HTML é organizado numa árvore de tags, um aplicativo em Flutter é organizado como uma árvore de widgets!"

Árvore de widgets

  • Aplicativo tem um widget raiz como ponto de entrada
  • Alguns widgets suportam só um filho

TUDO É WIDGET!

void main() {
  runApp(
    Text('Hello, World!', textDirection: TextDirection.ltr),
  );
}

Widgets

  • 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),
    ),
  );
}

Widgets com único filho

  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),
  ],
);

Widgets com vários filhos

  Column e Row

Row(
  children: [
    Container(color: Colors.purple, height: 100, width: 100),
    Container(color: Colors.deepPurpleAccent, height: 100, width: 100),
  ],
);

Mão na massa...

Não precisa decorar (tudo)

  1. Escolha alguns sistemas que se integram dentro da Vivo;

  2. Desenhe o fluxo de comunicação entre esses sistemas (ESB, ponto a ponto)

  3. Faça uma engenharia reversa e documente parte dessa API

    • Endpoints

    • Operações

    • Estruturas de dados

    • Protocolo

Desafio

Nossas aulas

Próxima

Aula 1

11/12 às 11h

 

→ Introdução às APIs

 

Atual

Aula 2

11/12 às 15h

 

→ Fundamentos do REST

 

Aula 2

Fundamentos do REST

1

Fundamentos do REST

2

Mergulhar fundo no HTTP

3

Richardson Maturity Model

5

Desafio

4

Mão na massa

Fundamentos REST

 Origem

"REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components."

 

Roy Fielding (2000)

Fundamentos REST

 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.

Conceitos HTTP

Recurso

 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==

Representações

 Mesmo ingrediente, outro sabor: JSON

URL de um livro da Casa do Código

  • https://www.casadocodigo.com.br/products/livro-git-github
{
  "nome": "Controlando versões com Git e GitHub",
  "autores": [
    { "autor": "Alexandre Aquiles" },
    { "autor": "Rodrigo Caneppele" }
  ],
  "paginas": 220,
  "ISBN": "978-85-66250-53-4"
}

Representações

 Mesmo ingrediente, outro sabor: XML

Mesma URL de um livro da Casa do Código

  • https://www.casadocodigo.com.br/products/livro-git-github
<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>

Representações

 Media Types

Tipos comuns:

  • text/html                          -> HTML
  • text/plain                         -> texto puro
  • image/png                          -> imagens PNG
  • application/json                   -> JSON
  • application/xml                    -> XML
  • application/pdf                                              -> PDF
  • application/epub+zip               -> ebooks EPUB
  • application/vnd.amazon.mobi8-ebook  -> ebooks MOBI
  • application/vnd.ms-excel                         -> arquivos .xls Excel

https://www.vivo.com.br/faturas/2024-12

 

O que o servidor fará se você fizer uma requisição

para a URL acima?

🤔

GET

Usado para obter uma representação de um recurso em uma determinada URL.

POST

Cria um novo recurso no servidor, com sua própria URL.

Métodos

 O que deve-se fazer com o recurso

PUT

Atualiza ou cria um recurso na URL informada.

DELETE

O recurso da URL informada é removido do servidor.

PATCH

Atualiza uma representação parcial do recurso da URL informada. 

HEAD

Usado para obter os metadados de um recurso (sem a sua representação).

OPTIONS

Retorna os métodos HTTP suportados por uma URL.

TRACE

Repete o request, para verificar se alguma alteração foi feita por intermediário.

CONNECT

Request vira um túnel TCP/IP para permitir comunicação por um proxy.

GET https://www.vivo.com.br/faturas/2024-12

 

Como dizer para o servidor que eu quero

a representação em XML e não em JSON?

🤔

Accept

Enumera os formatos aceitos como resposta.

Access-Control-Allow-Origin

Enviado na resposta para indicar quais origins podem acessar um recurso.

Cabeçalhos

 Guiando a conversa entre cliente e servidor

Authorization

Transporta credenciais de autenticação na requisição.

Content-type

Indica a representação usada na requisição e na resposta

Location

Indica, na resposta, para indicar redirecionamento ou localização de recurso.

POST https://www.vivo.com.br/faturas

 

Como o cliente vai saber que foi criada uma nova fatura?

🤔

1XX (Informational)

A requisição foi recebida e o processamento continua

Status code

 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.

Juntando tudo...

 Comunicação completa

Richardson Maturity Model

Avaliando o quanto uma API está aderente ao REST

Nível 0

 Pântano de POX (Plain Old XML)

Nível 1

 Recursos

Nível 2

 Métodos HTTP

Nível 3

 Controle de Hypermedia (hyperlinks)

Assunto para a próxima 😎

Mão na massa...

O que é Dart

  O que é o Dart?

  • Desenvolvida pelo Google
  • Otimizada para o desenvolvimento web e móvel
  • Compilação Ahead-of-Time (AOT)
    • Desempenho Consistente, inicialização rápida e eficiência de memória
  • Just-in-Time (JIT)
    • Otimizações dinâmicas, ideal para desenvolvimento
  • Multiparadigma
    • ​Orientação a objetos e funcional

Curiosidade: Foi criada para superar as limitações do JavaScript e melhorar a performance das aplicações web.

Sintaxe básica

  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.');
}

Sintaxe básica

  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
  }

}

Condicionais e laços

  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  

Condicionais e laços

  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.');
}

Null Safety

  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

Null Safety

  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 nulo

Funções

  Definiçã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

Funções

  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ções

  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 =>)

Orientação a objetos

  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); // Premium
  • Atributos 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

Orientação a objetos

  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)

Orientação a objetos

  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);

Processamento assíncrono

  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');
    });
}
  • Assincronismo permite realizar operações sem bloquear o fluxo principal do programa
  • Future representa um valor ou erro que estará disponível no futuro
    • quando o valor estiver disponível, invoca o callback passado no then

Processamento assíncrono

  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');
}
  • Async/await facilitam a escrita de códigos que possuem assincronismo
  1. Escolha alguns sistemas que se integram dentro da Vivo;

  2. Desenhe o fluxo de comunicação entre esses sistemas (ESB, ponto a ponto)

  3. Faça uma engenharia reversa e documente parte dessa API

    • Endpoints

    • Operações

    • Estruturas de dados

    • Protocolo

Desafio

Nossas aulas

Anterior

Próxima

Aula 3

12/12 às 10h

 

Design de APIs RESTful

Atual

Aula 1

11/12 às 11h

 

→ Introdução às APIs

 

Aula 2

11/12 às 15h

 

→ Fundamentos do REST

 

Aula 3

Design de APIs RESTful

1

HATEOAS

(Hypermedia)

2

Boas práticas

3

Documentação e Swagger

5

Desafio

4

Mão na massa

O poder dos links

Navegação

 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

Máquina de estado

 Links de transição

HATEOAS

{
  "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

Mão na massa...

Boas práticas

Boas práticas de design

 Princípios

  • Consistência
  • Simplicidade
  • Flexibilidade
  • Segurança

 

"A good API is not just a set of endpoints, but a carefully crafted interface that empowers developers."
Autor desconhecido

Nomenclatura de recursos

 Recursos são coisas

Ruim Bom
/getUsers /users
/article_by_id?id=123 /articles/{id}
/get-pending-orders /orders?status=pending
  • Coleções no plural
  • Evita-se verbos na URL
    • descreve recurso, não a ação
  • Filtros via query string

Métodos e Status

 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

Versionamento

Estratégias e semântica

 Prós e contras

Local da versão:

  • URI: /v1/users
  • Query Param: /users?version=1
  • Header: Accept: application/vnd.company.v1+json

 

Semântica:

  • 1.0.0 -> 1.0.1 (correção de bug)
  • 1.0.0 -> 1.1.0 (nova funcionalidade compatível)
  • 1.0.0 -> 2.0.0 (mudanças incompatíveis)

Documentação

OpenApi e Swagger

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

  • Documentação interativa
  • Gerada automaticamente

Mão na massa...

void main() {
  runApp(
    MaterialApp(
      home: Text('Hello World'),
    ),
  );
}

MaterialApp

  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')
        ),
      )
    ),
  );
}

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)),
          ),
        ),
      ),
    ),
  );
}

Card e ListTile

  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 (e Row)

  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
    ],
  ),
);

Overflow e SingleChildScrollView

  Column

void main() {
  runApp(
    Center(
      child: Text(
        'Olá, Mundo Flutter!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

Widgets Próprios

  • 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),
    );
  }

}

StatelessWidget

  • 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

StatelessWidget

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)),
      ),
    );
  }
}

Mão na massa...

StatefulWidget

  • 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

StatefulWidget

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}';
    });
  }
}

Dúvida! Quem atualiza? A tela ou o dado? 🤔

A tela pode se atualizar com widgets stateless.

Pode ser stateless, reaproveitado e evitar código duplicado.

StatefulWidget + StatelessWidget

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,
          ),
        ),
      ),
    );
  }
}
  • Crie um serviço simples para gerenciar os planos da Vivo
  • Integre o serviço de fatura com o serviço de Plano para pegar detalhes do nome e do valor

Desafio

Nossas aulas

Anterior

Próxima

Aula

12/12 às 15h

 

Autenticação e Segurança de APIs REST​

Aula 3

12/12 às 10h

 

Design de APIs RESTful

Atual

Aula 2

11/12 às 15h

 

→ Fundamentos do REST

 

Aula 4

Autenticação e Segurança de APIs REST

1

Segurança em camadas

2

Zero Trust

3

Cuidados nas APIs

5

Desafio

4

Cuidados no ambiente de execução

Malha de serviços

 Visão geral das integrações

Tradicional

  • Confia no perímetro
  • Verifica uma vez
  • IP interno = confiável
  • Acesso amplo

Zero Trust Model

 Mudando a mentalidade

Zero Trust

  • Não confia em nada
  • Verifica continuamente
  • Localização irrelevante
  • Menor privilégio possível

Zero Trust Model

 Checklist da aplicação

Integração com OAuth e JWT

  • OAuth permite autorização granular
  • JWTs com claims específicos para autorização
  • Tokens com vida útil curta

 

Considerações da implementação

  • Evitar cookies para autenticação
  • Habilitar CORS
  • Implementar rigoroso controle de acesso
  • Validar dados de entrada

Mão na massa...

Cuidados da aplicação

Exemplos de ataques

 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:

  • Controle completo do servidor.
  • Roubo de dados, modificação de arquivos, ou ataques adicionais à infraestrutura.

Resultado:

Arquivos críticos do sistema podem ser deletados.

ENTRADA: & rm -rf /

Exemplos de ataques

 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:

  • Roubo de sessões e credenciais do usuários
  • Execução de scripts maliciosos no navegador do usuário.
  • Redirecionamento para sites maliciosos.

Resultado:

O script é executado no navegador de outro usuário.

<script>alert('Hacked');</script>

Exemplos de ataques

 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

  • Controle completo do servidor.
  • Escalação de privilégios.
  • Uso do servidor para atividades maliciosas (ex.: mineração de criptomoedas, envio de spam).
GET https://server.com?controller=TarefasController&acao=listaTarefasDoUsuario

<?php

$controller = new $_GET['controller'];
$metodo = $_GET['acao']

$controller->$metodo()

Exemplos de ataques

 Path Injection

Descrição:

Ocorre quando um caminho (path) fornecido pelo usuário é concatenado diretamente para manipular arquivos ou URLs.

Impacto:

  • Modificação de dados sensíveis.
  • Exposição de arquivos internos.
ENTRADA: /var/data/../../etc/passwd

Exemplos de ataques

 JSON/XML Injection

Descrição:

Ocorre quando entradas maliciosas são usadas para manipular o parser de JSON ou XML da API.

Impacto:

  • Corrupção ou manipulação de dados.
  • Acesso a arquivos locais ou requisições para servidores internos.

Resultado:

O conteúdo de /etc/passwd é exposto ao atacante.

<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<data>&xxe;</data>

Validações de dados de entrada

 Diretrizes

  • Validação e Sanitização de Dados:

    • Valide sempre o tipo, formato e tamanho das entradas.
    • Remova caracteres especiais ou use codificação segura.
  • Use Bibliotecas Confiáveis:

    • Utilize bibliotecas de framework para proteção contra injeções (ex.: Prepared Statements para SQL).
  • Limite o Tamanho da Requisição:

    • Configure limites de tamanho para o corpo das requisições.
  • Restrinja Diretórios e Caminhos:

    • Nunca concatene diretamente caminhos fornecidos pelo usuário.
  • Desabilite Recursos Inseguros:

    • Desabilite suporte a entidades externas em parsers XML.
  • Log e Monitoramento:

    • Registre tentativas de entrada maliciosa e monitore padrões incomuns.
  • Validação de Tipo e Estrutura:

    • Use schemas para validar JSON/XML, como JSON Schema ou ferramentas de validação de contratos.

Cuidados com o ambiente de execução

Práticas de segurança

 Zero Trust na Infra

 Use Imagens de Container Seguras:

  • Sempre use imagens oficiais ou de fontes confiáveis.
  • Digitalize imagens antes de usá-las para verificar vulnerabilidades.
  • Mantenha as imagens atualizadas regularmente.

Ferramentas:

  • Docker Security Scan
  • Trivy
  • Clair.

Práticas de segurança

 Zero Trust na Infra

 Restringir Privilégios do Container:

  • Execute containers com o menor privilégio possível (non-root container):
    • Não use o usuário root dentro do container.
    • Use a flag --user para definir um usuário não privilegiado.
  • Desative capacidades perigosas do Linux, como CAP_SYS_ADMIN.

Exemplo:

securityContext:
  runAsUser: 1000
  runAsGroup: 1000
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

Práticas de segurança

 Zero Trust na Infra

Gerencie Segredos com Segurança:

  • Não armazene segredos diretamente dentro do container.
  • Use gerenciadores de segredos, como AWS Secrets Manager, HashiCorp Vault, ou Kubernetes Secrets.
  • Sempre criptografe segredos em trânsito e em repouso.

Exemplo:

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
data:
  username: dXNlcm5hbWU=
  password: cGFzc3dvcmQ=

Práticas de segurança

 Zero Trust na Infra

Isolamento Adequado:

  • Use namespaces e control groups (cgroups) para isolar containers no nível do sistema operacional.

Ferramentas:

  • PodSecurityPolicies

Validação de Configurações:

  • Verifique configurações do Docker e Kubernetes com ferramentas como kube-bench ou docker-bench-security.

Práticas de segurança

 Zero Trust na Infra

Implementar Logs e Monitoramento adequados:

  • Configure o logging adequado para monitorar atividades anômalas.
  • Monitore integração de serviços com OpenTracing

Ferramentas:

  • Fluentd
  • Prometheus
  • Grafana
  • ELK Stack
  • Zipkin

E não para por aqui...

Como é a navegacão no Flutter?

  Column

Tela 1

Tela 2

Tela 3

Tela N

push

push

push

pop

pop

pop

popUntil

Funciona como uma pilha!

Eu vejo Futures ao

mudar de tela

Com que frequência?

O tempo todo!

Navegação anônima

Mais simples e imperativas.

Formas de navegação

Navegação nomeada

Declaradas no atributo `router` de MaterialApp.

Navegação 2.0

Mais complexa e completa. Ideal para aplicativos web.

Rotas anônimas

  • Mais fáceis de passar parâmetros
    na navegação
  • Rotas ficam "escondidas" e
    espalhadas pelo código
  • Usam MaterialPageRoute para
    fazer a transição

Navegação

/**
 * 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();

Rotas nomeadas

  • Rotas declaradas no MaterialApp
  • São facilmente encontradas
  • Passagem de parâmetros exige
    casting
  • Não precisa do MaterialPageRoute
    na navegação

Navegação

// Navegação mais simplificada
Navigator.of(context).pushNamed('/fatura');

Navigator.of(context).pop();
MaterialApp(
  routes: {
    '/': (context) => TelaInicial(),
    '/faturas': (context) => ListagemDeFaturas(),
    '/faturas/cobranca': (context) => CobrancaDeFatura(),
  },
);

Rotas anônimas

  • Diretamente no construtor da
    nova tela
  • No método `pop` ao voltar para
    a tela anterior

Passagem de parâmetros

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);

Rotas nomeadas

  • Diretamente no Navigator (Object)
  • Parâmetros disponíveis em
    ModalRoute
  • Precisa de casting

Passagem de parâmetros

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);
  • Execute o keycloak e import o realm do arquivo "realm-desenvolvimento.json"
  • Configure os serviços para validarem tokens

Desafio

Nossas aulas

Anterior

Próxima

Aula

16/12 às 10h

 

GraphQL

Atual

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

GraphQL

Linha do tempo

2012

Criada internamente no Facebook

2015

Projeto

OpenSource

2018

GraphQL

Foundation

2024

Adoção em massa

Todo poder emana do cliente!

GraphQL vs REST

Demo rápida

Schema, tipos e operações

 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  

Anatomia de um comando

 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  

Resolvers e Netflix DGS

 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  

Mão na massa...

Imagens

Imagens

  • Carrega imagens da internet via URL
  • Problema se estiver sem conexão
  • Bom para ambiente de desenvolvimento

Image.network

Image.network(
  'https://pbs.twimg.com/media/Fwvbm_NWAAIpIOV?format=jpg&name=large',
  width: 300,
  height: 450,
);

Imagens

  • Meio mais seguro
  • Deixa o app maior

Image.asset

flutter:

  # Tema que descomentar essa linha no 
  # adicionar os recursos
  assets:
    - assets/images/flu_serie_b.jpg
Image.asset('assets/images/flu_serie_b.jpg');

pubspec.yaml  

Imagens

  • Carrega arquivo local do dispositivo
  • Ideal para abrir da galeria de fotos ou
    captura de câmera
  • Requer permissão
  • Tipo `File

Image.file

  • Exibe imagens carregadas em bytes na
    memória
  • Útil para exibir imagens recuperadas de APIs
  • Tipo `Uint8List`

Image.memory

Imagens

  • Carrega arquivo local do dispositivo
  • Ideal para abrir da galeria de fotos ou
    captura de câmera
  • Requer permissão
  • Tipo `File

Image.file

  • Exibe imagens carregadas em bytes na
    memória
  • Útil para exibir imagens recuperadas de APIs
  • Tipo `Uint8List`

Image.memory

Temas

Estrutura do tema (M3)

MaterialApp(
  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

Principais componentes

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

Principais componentes

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.

Principais componentes

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...

Principais componentes

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

Usando tema nos widgets

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.

Leiautes avançados

Canais do youtube

  • No serviço de pagamento:
    • Criar enum para status do Pagamento
    • Queries:
      • listar todos os pagamentos
      • pesquisar pagamentos por status
    • Mutations:
      • cancelar pagamento

Desafio

Nossas aulas

Anterior

Próxima

Aula

16/12 às 14h

 

gRPC

Aula

16/12 às 10h

 

GraphQL

Atual

Aula

12/12 às 15h

 

Autenticação e Segurança de APIs REST​

Aula 6

gRPC e Protocolos Binários

Protocolos binários

 Comparação com HTTP1 - Cenário 1

Serializar um objeto Curso com 5 objetos Pessoa e com um objeto Telefone associados, mostra o tamanho:

  • Protocol Buffers: 250
  • Thrift TCompactProcotol: 278
  • Thrift TBinaryProtocol: 460
  • HTTP/JSON: 559
  • HTTP/XML: 836
  • RMI: 905

Protocolos binários

 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:

  • Thrift TCompactProcotol: 01 min 05 s
  • Thrift TBinaryProtocol: 01 min 13 s
  • Protocol Buffers: 01 min 19 s
  • RMI: 02 min 14 s
  • HTTP/JSON: 04 min 44 s
  • HTTP/XML: 05 min 27 s

Protocolos binários

 Comparação com HTTP1 - Cenário 2 (continuação)

CPU no servidor:

  • Thrift TBinaryProtocol: 33 %
  • Thrift TCompactProcotol: 30 %
  • Protocol Buffers: 30 %
  • HTTP/JSON: 20 %
  • RMI: 16 %
  • HTTP/XML: 12 %

CPU no cliente:

  • Thrift TCompactProcotol: 22.5 %
  • Thrift TBinaryProtocol: 21 %
  • Protocol Buffers: 37.75 %
  • RMI: 46.5 %
  • HTTP/JSON: 75 %
  • HTTP/XML: 80.75 %

gRPC

 Arquitetura

Tipos de canais

 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.

Protocol Buffer

 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;
}

Implementando o serviço

 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();
    }
}

Boas práticas

 Considerações importantes

  • Versionamento de serviços
    • Compartilhamento dos arquivos .proto
  • Tratamento de erros
  • Deadlines e timeouts
  • Monitoramento e tracing

"Mão na massa..."

😅

1

Ciclo de vida 

do Stateful

2

Instalação de

dependências

3

Provider

4

Desafio

Permissão para mudar a rota...

🫡

Formulário

  • 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

Formulário de chamados

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,
    );
  }
}

StatefulWidget++

Ciclo de vida

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() { ... }
}

Mão-na-massa

FormularioDeLogin

Home

AcessoRapido

ListaDeChamados

FormularioNovoChamado

Prop drilling

Estado centralizado

Prop drilling

Estado centralizado

Provider

Instalação

ChangeNotifier

  • Notifica interessados de que algo mudou
  • Armazena o estado

Conceitos

ChangeNotifierProvider

  • Provê acesso ao ChangeNotifier
  • Deve ser colocado no último ancestral comum de onde o estado é necessário (geralmente é o APP)

Consumer

  • Permite reconstruir um widget com base nos valores modificados do provider
  • Deve ficar o mais baixo possível na árvore de widgets

watch e read

  • Outra forma de acessar ler um estado ou reagir a mudanças nele

ChangeNotifier

class 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)

ChangeNotifierProvider

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.

Context.watch

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),
      )
    );
  }
}

Consumer<T>

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),
      )
    );
  }
}

Context.read

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'),
)

Mão-na-massa

  • Implementar formulário de login e disponibilizar usuário com provider.
  • OBS: dados de login devem ser fictícios.

Desafio

Nossas aulas

Anterior

Próxima

Aula

17/12 às 15h

 

API Gateways e Backend For Frontend (BFF)

Aula

16/12 às 10h

 

GraphQL

Aula

16/12 às 14h

 

gRPC

Atual

Aula 7

API Gateways e Backend for Frontend (BFF)

Integração via HTTP

Obtendo dados da API

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

Convertendo em JSON

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

Enviando dados para a API

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

Listagem com API

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

Cadastrando no formulário

// 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

Armazenamento local

com SharedPreferences

Shared Preferences

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

Shared Preferences

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

Mão-na-massa

  • Implementar a listagem de faturas usando o pacote http.
  • Use dados fictícios e simples

Desafio

Aula 8

Resiliência em APIs

1

Splash Screen

2

Conceitos elementais de animação

3

Animações

implícitas

4

Transição de

telas customizadas

5

Desafio

Splash Screen

Splash Screen

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

Native Splash Screen

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:create

terminal  

Animações

Princípios básicos

A

B

Interpolação

(movimento de A para B)

(class Tween)

Princípios básicos

Animações implícitas

O que são?

  • 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...

AnimatedContainer

/**
 * 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

AnimatedOpacity

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

AnimatedAlign

Outros exemplos...

AnimatedCrossFade

AnimatedSize

AnimatedList

Hero

...

Transições de tela

Transição de tela

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

Transição de tela

context.read<UsuarioProvider>()
  .login(usuario)
  .then((_) {
  
    Navigator.of(context)
      .pushReplacement(
        rotaDeslizante(const Home())
      );
      
  });

widgets/util.dart  

Usando o PageRouteBuilder

  • Implementar a seção Especiais pra você com o o pacote carousel_slider.
  • Use a mesma imagem e chamada; o foco é a animação. 😉

Desafio

Aula 9

Enterprise Integration Patterns

1

Tipos de teste

no Flutter

2

Escrever

testes

3

Boas (más) práticas

4

Desafio

Quem nunca fez um teste automatizado, levanta a mão. 🤚🏼

Testes de unidade

  • Não dependem de recursos externos
  • Mais fáceis de escrever e rápidos de executar

Testes no Flutter

Testes de Widgets

  • Flutter oferece mecanismos para controlar (e interagir com) widgets
  • Mais lentos e difíceis de criar
  • Pacote flutter_test

Testes de integração

  • Foco em testar o aplicativo como um todo (GUI testing)
  • É possível executar no target device e no host machine
  • Pacote integration_test

Teste de unidade

Teste de widget

Boas práticas

  • Implemente um caso de teste para a Central de faturas que verifique se tem uma fatura aberta, uma atrasada e duas pagas.

Desafio

Aula 9

Event-driven Architecture

É "só" mensageria com outra mentalidade

Revisão a jato

Tipos de mensagem

 Semântica faz toda diferença

Domain events

 Reagindo a acontecimentos importante

Domain Event:

Uma ocorrência significativa em termos de negócio em um determinado Bounded Context.

PedidoRealizado

FaturaPaga

CancelamentoEfetuado

PagamentoRecusado

Event-driven Architecture

Conceitos-chave

 Natureza da comunicação

1

Desafios

2

Considerações

sobre interface

3

Interação com 

código nativo

4

Estado atual das

novas plataformas

Vantagens e desafios

Vantagens

Alcance de Público Amplo

Consistência de Experiência do Usuário

Economia de Tempo e Recursos

Desafios

Compatibilidade e Adaptação

Limitações de Desempenho

Limitações de API nativa

Considerações

Adaptação de Interface: Consistência entre iOS, Android e Web

 Usar elementos específicos da plataforma

Experiência direcionada

Cupertino

Material

 Padronização + interação + feedback

Consistência de design

  • 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)

Gerenciamento de

Dependências

Method Channel

Dependências nativas

  • Habilita aplicativo interagir diretamente com código nativo
  • Possibilita instalar e usar dependências nativas

1 - Dependência da plataforma

Method channel - Etapas

// build.gradle
dependencies {
    implementation 'com.example:your-library:1.0.0'
}
# Podfile
pod 'YourLibrary', '~> 1.0'

2 - Código nativo

Method channel - Etapas

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

Method channel - Etapas

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

Method channel - Etapas

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

Method Channels

  • 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 🏋🏼

Web e Desktop com Flutter

Panorama

Web e Desktop

  •  

    • 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

Obrigado

(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

Mega-revisão

O que é o React

  • 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

Funcionamento dos websites

  Server Side Rendering

Funcionamento dos websites

  Client Side Rendering

Animação de loading no site da Coca-Cola

Por que usar React

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

Fundamentos

Fluxo de dados unidirecional

Virtual DOM

JSX

Ecossistema React

Conceitos

Props

State

Renderização

condicional

Componentes

Recursos 

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.">

O que preciso saber?

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'
  }
]

O que preciso saber?

JavaScript

  • Arrays e objetos

  • Métodos para manipulação de arrays

  • Funções

Por quê?

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.

Ferramentas necessárias

Navegador

Node.js

VS Code

Ambiente de desenvolvimento

Build e transpilers

  • Webpack
  • Babel

3.

Servidor

  • create-react-app
  • vite

1.

Gerenciador de pacotes

  • npm
  • yarn

2.

Airbnb

Empresas que utilizam

Empresas que utilizam

{concorrentes}

export function Saudacao(props) {
  return <h1>Olá, {props.nome}!</h1>;
}

function App() {
  return (
    <div>
	  <Header />
      <Saudacao nome="Fulana" />
    </div>
  )
}

export default App

Componentes

  • 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?"

Desafio

Nossas aulas

null

Anterior

Próxima

Aula 1

24/06 às 15h

 

→ Introdução ao React

 

Atual

Aula 2

25/06 às 14h

 

→ JXS e Elementos React

 

JSX e Elementos React

Aula 2

1

O que é o JSX

2

Renderização de elementos

3

Componentes

5

Eventos

4

Props

6

Desafio

O que é o JSX

  • 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>
  )
}

Renderização de elementos

  • 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 />
)

Renderização de elementos

  • Blocos de construção

  • São reutilizáveis

  • Encapsulam lógica e estilo

  • Podem ser funcionais ou de classe

Componentes

function Botao(props) {
  const handleClick = () => {
    props.onClick()
  }

  return (
    <button className={`btn ${props.className}`} onClick={handleClick}>
      {props.texto}
    </button>
  )
}

Props

  • 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

Props

// 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>
  )
}

Eventos

  • Sintaxe similar ao HTML

  • Funções de evento como props

  • Usados para dinamismo e interatividade

Ações que acontecem no navegador

Eventos

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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 3

26/06 às 10h

 

Componentes de Classe vs. Componentes Funcionais

Atual

Aula 2

 

JSX e Elementos React

Aula 1

 

Introdução ao React

Aula 3

Componentes de Classe vs. Componentes Funcionais

1

Class components

2

Estado e ciclo de vida

3

Function components

4

Hooks em componentes funcionais

6

Desafio

Formato dos componentes

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 Components

class Welcome extends React.Component { 
   render() { 
      return <h1>Hello, Vivo!</h1> 
   }
}

Class Components

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:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Class Components

→ Itens de Class e OO, além da classe

  • Constructor
  • Herança
  • Instância
  • Encapsulamento
  • Ciclo de Vida

Estado em class components

No constructor

Armazena dados mutáveis

Definição

Usa this.setState()

Aceita objeto ou função

Atualização

Mudanças no estado re-renderizam o componente

Reactividade

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

Fases do ciclo de vida

  • 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

Components

function OlaMundo() { 
  return <h1>Olá, mundo!</h1>
}
function Botao(props) {
  return (
    <button onClick={props.onClick}>
      {props.texto}
    </button>
  )
}

export default Botao

Vantagens e desvantagens

Suporte a ciclo de vida

Class

Mais verbosos

Ciclo de vida mais complexo

Function

Mais simples, menos código

Hooks para estado e efeitos

Sem estado antes dos Hooks

Hooks em Componentes Funcionais

  • 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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 4

26/06 às 15h

 

Estado e Gerenciamento de Estado

Atual

Aula 3

 

Componentes de Classe vs. Componentes Funcionais

Aula 2

 

JSX e Elementos React

Aula 4

Estado e Gerenciamento de Estado

1

useState e useEffect

2

O que é o Gerenciamento de Estado?

5

useContext

4

6

Desafio

Context API

Flux vs. Redux

3

useState

  • 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

useState

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>
  )
}

useEffect

  • 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!

useEffect

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>
  )
}

O que é gerenciamento de Estado?

  • Gerenciar o estado com muitas informações que podem mudar ao longo do tempo e afetar a renderização do componente
  • Facilita a manutenção e previsibilidade do comportamento do aplicativo, assegurando que a interface do usuário esteja sempre sincronizada com os dados

Tipos de Estado

  • Estado Local: Gerenciado dentro de um único componente usando useState ou this.state
  • Estado Global: Compartilhado entre múltiplos componentes (Context API, Redux etc.)

Flux vs. Redux

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.

Redux

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

Flux

Context API

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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 5

28/06 às 10h

 

Roteamento com React Router

Atual

Aula 4

 

Estado e Gerenciamento de Estado

Aula 3

 

Componentes de Classe vs. Componentes Funcionais

Aula 5

Roteamento com React Router

1

O que são Rotas

2

O que é a React Router

4

Navegação e parâmetros

6

Desafio

Proteção de Rotas

5

Configuração inicial

3

O que são rotas

Organização e navegação das páginas, dentro do contexto de SPA

{Função das rotas nas SPAs}

  • 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

O que é a React Router

  • 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

Configuração

A React Router Dom não é uma lib nativa, portanto precisamos importá-la.

Importar

npm install react-router-dom

Adicionando uma Rota

Configuraçã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 Router

import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { App } from './app'

ReactDOM.createRoot(document.getElementById('root')).render(
  <BrowserRouter>
     <App />
  </BrowserRouter>
)

Navegação

Pode substituir a tag <a> ou pode ser usado para navegação interna

NavLink

Quando a navegação precisa ser ativada programaticamente, como em eventos ou lógica interna do componente

Navigate ou useNavigate

import { 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>
  )
}

Route

Rotas individuais

Route e Routes

import { Route } from 'react-router-dom'

<Route path="/about" component={About} />

Routes

Agrupa 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>

Route e Routes

<Routes>
  <Route path='/' element={<MainScreen />} />
  <Route path='/planos' element={<InternetScreen />} />
  <Route path='/duvidas' element={<FaqScreen />} />
</Routes>

Proteção das Rotas

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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 6

02/07 às 14h

 

Formulários e Eventos em React

Atual

Aula 5

 

Roteamento com React Router

Aula 4

 

Estado e Gerenciamento de Estado

Aula 6

Formulários e Eventos em React

1

Formulários com React

2

Formik

3

Validação de formulários

5

Eventos

4

Yup

6

Desafio

Formulários com React

Formulários com React

  • 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)
}

<input /> nos Formulários

{onChange}

→ Gerenciamento do estado dos campos de entrada

  • Manipular e validar os dados de entrada

  • Manter a UI sincronizada com o estado do aplicativo

Formik

  • Biblioteca para gerenciamento de formulários
  • Gerencia
    • Estado
    • Validação
    • Envio

Formik

npm install formik
 import { Formik } from 'formik'
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>
	)
}

Formik

  • Biblioteca para validação
  • Define e valida formatos de dados
  • Integração com Formik para validação de formulários

Yup

npm i yup
import * as yup from 'yup'
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.')
 })

Yup

Implementar o nosso formulário com as validações de erro e reutilizar nosso componente de Botão já criado.

Desafio

Nossas aulas

Anterior

Próxima

Aula 7

03/07 às 10h

 

Chamadas de API com Axios

Atual

Aula 6

 

Formulários e Eventos

Aula 5

 

Roteamento com React Router

Aula 7

Chamadas de API com Axios

1

Axios

2

GET

3

POST

5

Segurança de API's

4

Tratamentos de erros e async/await

6

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 axios

Requisições HTTP

Forma como os dados são transferidos na web

GET

Recupera dados do servidor

import axios from 'axios'

axios.get('/api/users')
  .then(response => console.log(response.data))
  .catch(error => console.error(error))

POST

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)
   }
 })

Possíveis erros

Async/Await

  • Sintaxe assíncrona para requisições
  • Usa 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)
  }
}

Segurança de APIs

  • 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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 8

03/07 às 15h

 

Estilização em React

Atual

Aula 7

 

Chamadas de API com Axios

Aula 6

 

Formulários e Eventos

Anterior

Próxima

Aula 8

03/07 às 15h

 

Estilização em React

Atual

Aula 8

Estilização com React

1

CSS em React

2

Styled Components

3

Imagens no React

5

CSS modules

4

SVG's no React

6

Desafio

CSS em React

  • Estiliza os componentes e interfaces

  • Métodos diversos

    • CSS tradicional

    • Bibliotecas (Styled Components 💅)

    • Pré-processadores (para mixins, nesting e funções)

    • CSS Modules*

Styled Components

  • Biblioteca para CSS-in-JS
  • Escreve estilos com JavaScript
  • Estilos escopados por componente
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-components
const 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>
);

Imagens no React

  • Utilizamos o 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'

SVG's no React

  • Função que retorna elemento SVG num JSX
  • Importe os componentes SVG onde necessário
  • Utilize-os como componentes JSX normais

SVG

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>;
}

CSS Modules

  • Escopo local por padrão.
  • Estilos importados como módulos
  • Evita conflitos de nome de classes

CSS Modules

O css-modules cria um className único para cada local em que é utilizado

CSS Modules e as classes globais

.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);
}

Classes dinâmicas

<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 : expressionIfFalse

Nossas aulas

Anterior

Próxima

Aula 9

04/07 às 10h

 

Hooks Avançados

Atual

Aula 8

 

Estilização em React

Aula7

 

Chamadas de API com Axios

Aula 9

Hooks Avançados

1

Hooks avançados

2

useMemo

3

Performance

5

Boas práticas

4

Hooks customizados

6

Desafio

Hooks Avançados

  • Melhoram funcionalidade dos componentes funcionais

  • Simplificam lógica complexa

  • Promovem reutilização de código

useMemo

  • Memoriza valores calculados

  • Evita cálculos desnecessários

  • Otimiza o desempenho

useMemo

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>
}

Performance

  • useMemo
  • memoização de dados
  • usar gerenciamento de estado de forma eficiente
  • lazy loading

hooks customizados

  • Reutilizam lógica entre componentes

  • Simplificam o código

  • Promovem a manutenção e legibilidade

  • Permitem personalizações

Boas práticas

  • 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.

Desafio

Nossas aulas

Anterior

Próxima

Aula 10

05/07 às 10h

 

Introdução ao TypeScript

Atual

Aula 9

 

Hooks Avançados

Aula 8

 

Estilização em React

Aula 10

Introdução ao TypeScript com React

1

O que é o TypeScript?

2

Por que TypeScript com React?

5

Componentes funcionais com TypeScript

4

Tipos básicos e Types

6

Instruções

Configurando o ambiente de desenvolvimento

3

O que é TS?

  • Superset de JavaScript

  • Adiciona tipagem estática

  • Melhora autocomplete e refatoração

  • Detecta erros em tempo de desenvolvimento

  • Compila para JavaScript puro

Por que TS?

TypeScript

  • Websites simples

  • Sem grande volumes de dados

  • Projetos individuais

  • Aplicações mais complexas

  • SPAs

  • Projetos grandes

  • Projetos em equipe

JavaScript x TypeScript

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

Por que React com TS?

  • 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

Configurando o Ambiente de Desenvolvimento

Tipos básicos no TS

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

Types

  • 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>
  )
}

Componentes funcionais com TS

function Apresentacao({ name }: { name: string }) {
  return <h1>Hello, {name}!</h1>
}
  • Para fazer o projeto da nossa LP, acesse o Figma disponibilidado nesse slide.
  • Todos os nossos encontros (exceto o encontro 8), foram propostos desafios de acordo com o nível do conteúdo apresentado. Esses desafios estão no final de cada aula, nesses slides.
  • Nosso projeto final também pode ser acessado no GitHub, deixei o código comentado com as explicações referentes aos desafios.

 

Qualquer dúvida você pode me procurar o linkedin.

Será um prazer te ajudar!

Instruções

Nossas aulas chegaram ao fim 😢

Anterior

❤️

Te desejo muito sucesso e que você sempre tenha força para superar seus desafios!

 

Um grande abraço,

Milena Emmert.

Atual

Aula 10

 

Introdução o TypeScript com React

Aula 9

 

Hooks Avançados

Made with Slides.com