Jornada Flutter
void main() => runApp(
PessoaApp(
nome: 'Cácio Costa',
empresa: 'Alura',
cargo: 'Instrutor e pagodeiro nas horas vagas',
),
);Jornada
-
Introdução ao Flutter e configuração de ambiente
-
Fundamentos do Dart
-
Widgets fundamentais do Flutter
-
Navegação e rotas
-
Leiautes e media
-
Gerenciamento de estado
-
Integração com APIs e armazenamento local
-
Animações e transições
-
Testes, depuração e melhores práticas
-
Desenvolvimento cross-platform com Flutter

Seria legal se você...
-
... programa orientado a objetos (JavaScript melhor ainda).
-
... entende o protocolo HTTP e API REST.
-
... tem noção de SQL.
-
... conhece HTML e CSS.

Aula 1
Introdução ao Flutter
1
O que é o
Flutter
2
Ferramentas e ambiente de desenvolvimento
3
Como criar e organizar um projeto
5
Desafio
4
Árvore de Widgets
O que é o Flutter?

-
Framework open source para a construção de (não somente) aplicativos
-
Baseado em widgets
-
Permite criar aplicativos para Android e iOS
-
Criado pelo Google em 2017
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!
Biblioteca de widgets: https://docs.flutter.dev/ui/widgets

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)
Biblioteca de widgets: https://docs.flutter.dev/ui/widgets
-
Montar ambiente e implementar a tela ao lado sabendo que:
-
O primeiro quadrado tem lado 150 e a cor é deepPurple;
-
O segundo quadrado tem lado 120 e a cor é deepPurpleAccent;
-
O terceiro quadrado tem lado 150 e a cor é purple.
-
-
Pesquise pelo widget Stack no catálogo de widgets do Flutter.
Desafio

Nossas aulas
Próxima
Aula 1
24/06 às 10h
→ Introdução ao Flutter e Configuração do Ambiente
Atual
Aula 2
24/06 às 14h
→ Fundamentos do Dart
Aula 2
Fundamentos do Dart
1
Características do
Dart
2
Sintaxe
básica
3
Null
Safety
5
Orientação a
Objetos
4
Funções e
parâmetros
6
Assincronismo
7
Desafio
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 nuloFunçõ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
-
Crie uma classe
PlanoInternetcom os seguintes atributos privados:nome,velocidade e preco; -
Implemente um construtor para inicializar esses atributos.
-
Crie métodos para:
-
Exibir as informações do plano
-
-
Utilize getters e setters para encapsular os dados.
Desafio
Nossas aulas
Anterior
Próxima
Aula 3
25/07 às 10h
Widgets fundamentais do Flutter
Atual
Aula 2
Fundamentos do Dart
Aula 1
Introdução ao Flutter
Aula 3
Widgets fundamentais do Flutter
1
Tour por vários
widgets
2
Stateless
Widget
3
Stateful
Widget
4
Desafio
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 os cards da seção Acesso rápido da tela inicial.
- Faça tanto o card maior quanto o menor
Desafio

Aula 4
Navegação e rotas
1
Estrutura de navegação do Flutter
2
Formas de
navegação
3
Passagem de parâmetros
4
Desafio
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);
- Implementar a navegação do botão "Central de Faturas" para a central.
- OBS: Não precisa implementar toda a tela.
Desafio


Aula 5
Leiautes e media
1
Como usar
imagens no Flutter
2
Temas e
estilos
3
Componentes de leiaute avançados
4
Desafio
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.jpgImage.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

- Criar tema para o aplicativo
Desafio



Aula 6
Gerenciamento de Estado
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


Aula 7
Interação com APIs e Armazenamento Local
1
Integração via HTTP
2
Persistência com SharedPreferences
3
Desafio
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
Animações e Transições
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:createterminal
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
Testes e boas práticas
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 10
Desenvolvimento Cross-Platform com Flutter
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"})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


Criando uma aplicação React com Vite (artigo)
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:
componentDidMountcomponentDidUpdatecomponentWillUnmount
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
useStateouthis.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

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
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
promisespara lidar com requisições assíncronas -
Simplifica comunicação com APIs
-
Tranformações
-
Cancelamentos
-
-
Transforma os dados para JSON


npm install axiosRequisiçõ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
asynceawait
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 : expressionIfFalseNossas 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
useMemomemoização de dadosusar gerenciamento de estado de forma eficientelazy 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
Materiais gratuitos para consulta
- O que é React JS? #HipstersPontoTube (youtube.com)
- O que saber antes de começar a aprender react.js? (youtube.com)
- Guia do iniciante em React - Hipsters #209
- React: o que é, como funciona e um Guia da biblioteca JS
- React Hooks: o que é e como funcionam?
- Estado do ecossistema React 2023 - Hipsters Ponto Tech #354
- Componentes com Styled Components no React JS
- Prop Drilling: o que é?
- React e Context API: testes de unidade
- Requisições HTTP utilizando o AXIOS
- Boas práticas ao escrever código em React js
- GitHub - braziljs/eloquente-javascript: Tradução do livro Eloquent JavaScript - 2ª edição.
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
Jornada Flutter
By cacio-costa
Jornada Flutter
- 105


