Jornada Flutter

void main() => runApp(
  PessoaApp(
    nome: 'Cácio Costa',
    empresa: 'Alura',
    cargo: 'Instrutor e pagodeiro nas horas vagas',
  ),
);

Jornada

  1. Introdução ao Flutter e configuração de ambiente

  2. Fundamentos do Dart

  3. Widgets fundamentais do Flutter

  4. Navegação e rotas

  5. Leiautes e media

  6. Gerenciamento de estado

  7. Integração com APIs e armazenamento local

  8. Animações e transições

  9. Testes, depuração e melhores práticas

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

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

  2. ​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 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
  • Crie uma classe PlanoInternet com 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.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

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

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

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

Jornada Flutter

By cacio-costa

Jornada Flutter

  • 105