Módulo 01 — Introdução ao Desenvolvimento Mobile e Configuração do Ambiente

Bem-vindo ao primeiro módulo de Sistemas Móveis. Este material foi preparado para que você o estude antes da nossa primeira aula presencial. Quando você chegar à aula, já terá o ambiente configurado, uma visão clara do que é o Flutter e, o mais importante, estará pronto para começar a trabalhar no Projeto Integrador junto com sua equipe. Reserve algumas horas tranquilas para este material: ele é mais denso do que os módulos seguintes, porque estamos construindo o chão sobre o qual tudo o mais vai ficar.


Por Que o Mobile Importa Tanto

Antes de falar sobre Flutter, sobre Dart ou sobre qualquer ferramenta, preciso te convencer de uma coisa: você está prestes a aprender uma das habilidades mais valiosas do mercado de tecnologia. Não digo isso para ser dramático. Os números falam por si sós.

Em 2024, o planeta tinha mais de 6,8 bilhões de smartphones ativos — isso significa que mais de 85% da população mundial carregava no bolso um computador com câmera, GPS, acelerômetro, microfone e conexão à internet. A Google Play Store e a Apple App Store juntas hospedavam mais de 5 milhões de aplicativos. O tempo médio que um adulto passa olhando para a tela do celular já ultrapassa quatro horas por dia e continua crescendo.

Esse cenário se traduz diretamente em oportunidade econômica. O mercado global de aplicativos móveis movimentou mais de 430 bilhões de dólares em 2022, com projeções de dobrar esse valor até 2030. E mais relevante para você, estudante do quinto semestre de Ciência da Computação: a demanda por desenvolvedores mobile qualificados no Brasil e no mundo supera, de longe, a oferta de profissionais com esse perfil.

Mas há algo mais importante do que os números. O desenvolvimento mobile é o campo onde o conhecimento que você acumulou nos semestres anteriores — algoritmos, estruturas de dados, orientação a objetos, banco de dados, engenharia de software — finalmente converge para produzir algo que pessoas reais usam no dia a dia. Quando um usuário abre um aplicativo que você construiu e resolve um problema com ele, o resultado do seu trabalho é palpável de uma forma que poucos campos da computação proporcionam.


A Evolução do Desenvolvimento Mobile: Três Eras

Para entender o Flutter — e entender por que ele existe —, é importante conhecer o caminho que levou até ele. O desenvolvimento mobile passou por três grandes eras, e cada uma delas deixou lições que moldaram as ferramentas que usamos hoje.

timeline
    title Evolução do Desenvolvimento Mobile
    2007 : iPhone lançado pela Apple
         : Era Nativa começa
    2008 : App Store aberta ao público
         : Google lança o Android SDK
    2010 : Apache Cordova criado
         : Era Híbrida começa
    2013 : React Native preview (Facebook)
    2015 : React Native lançado oficialmente
    2017 : Flutter Alpha lançado pelo Google
    2018 : Flutter 1.0 - Era Multiplataforma Moderna
    2020 : Flutter suporte a Web e Desktop
    2021 : Flutter 2.0 - null safety estável
    2023 : Flutter 3.x - Impeller engine

A Primeira Era: Desenvolvimento Nativo Puro

Quando a Apple abriu a App Store em 2008, a única maneira de criar um aplicativo para iPhone era escrever código em Objective-C usando as ferramentas da própria Apple. Para Android, o caminho era Java (e mais tarde Kotlin) com o Android SDK do Google. Cada plataforma tinha seu próprio conjunto de bibliotecas, suas próprias convenções de interface e seu próprio modelo de ciclo de vida.

Os aplicativos nativos tinham, e ainda têm, vantagens claras: desempenho excelente, acesso total aos recursos do dispositivo e integração perfeita com a linguagem visual de cada plataforma. Um botão em um aplicativo iOS nativo parece, comporta-se e anima exatamente como o usuário de iPhone espera. O mesmo vale para Android.

O preço dessa abordagem é igualmente claro: uma empresa que quisesse estar presente em iOS e Android precisava manter duas equipes separadas, dois repositórios distintos e dois ciclos independentes de desenvolvimento e revisão. Para startups e projetos com orçamento limitado, isso era proibitivo.

A Segunda Era: Desenvolvimento Híbrido

O desejo de “escrever uma vez, executar em todos os lugares” foi o motor da segunda era. Ferramentas como Apache Cordova, PhoneGap e, mais tarde, Ionic propunham uma solução: construir a interface do aplicativo com HTML, CSS e JavaScript, e empacotar tudo dentro de uma visualização web nativa — chamada de WebView — que cada plataforma disponibilizava.

O resultado era uma experiência de desenvolvimento muito mais econômica: o mesmo código rodava em iOS e Android. Mas o desempenho sofria. A interface não era renderizada pelos componentes nativos de cada plataforma; era renderizada por um motor de browser embutido. Em animações, transições e listas longas, a diferença era perceptível para o usuário. Além disso, acessar recursos nativos do dispositivo — câmera, GPS, sensores — exigia plugins específicos para cada plataforma, e esses plugins nem sempre estavam disponíveis ou atualizados. O modelo híbrido resolveu o problema do custo, mas criou problemas novos de desempenho e experiência.

A Terceira Era: Multiplataforma Moderna

A terceira era, na qual vivemos hoje, rejeitou as limitações das eras anteriores com uma abordagem diferente. O Flutter, criado pelo Google, não usa WebView e não traduz componentes para widgets nativos de cada plataforma. Ele traz seu próprio motor de renderização gráfica — primeiro o Skia, e mais recentemente o Impeller — e desenha cada pixel da interface diretamente na tela, independentemente do sistema operacional.

Isso é uma mudança conceitual profunda. Um botão no Flutter não é um botão do Android nem um botão do iOS. É um botão desenhado pelo Flutter, com exatamente a aparência e o comportamento que o desenvolvedor definiu, idêntico em todas as plataformas. Isso elimina toda uma categoria de bugs que aparecia no desenvolvimento híbrido por causa de diferenças sutis entre sistemas operacionais.


Entendendo o Flutter por Dentro

O que torna o Flutter diferente de tudo o que veio antes

O Flutter é, em essência, um framework de interface de usuário. Mas para entender por que ele é tão poderoso, você precisa entender como ele funciona internamente. E para isso, precisamos falar sobre três camadas: o Dart, o motor de renderização e a árvore de widgets.

O Motor de Renderização

Todo aplicativo Flutter é compilado para código de máquina nativo usando a compilação AOT (Ahead-of-Time). Isso significa que, antes de ser executado no dispositivo, o código Dart é convertido em instruções nativas para o processador específico daquele dispositivo. O resultado é um aplicativo que executa com velocidade comparável a um aplicativo escrito diretamente em Kotlin ou Swift.

Em tempo de execução, o motor do Flutter é responsável por renderizar a interface. Ele conversa com o sistema operacional apenas para obter uma superfície de desenho — essencialmente, um canvas em branco — e depois usa a GPU do dispositivo para desenhar todos os pixels da interface nessa superfície. O sistema operacional não participa da renderização.

graph TB
    A["Código Dart<br/>(seu aplicativo)"] --> B["Framework Flutter<br/>(widgets, animações, gestos)"]
    B --> C["Motor Flutter<br/>(Skia / Impeller)"]
    C --> D["Plataforma<br/>(Android / iOS / Web / Desktop)"]
    D --> E["Hardware<br/>(GPU, CPU, sensores)"]

    style A fill:#cfe2ff,stroke:#0d6efd
    style B fill:#d1ecf1,stroke:#17a2b8
    style C fill:#d4edda,stroke:#28a745
    style D fill:#fff3cd,stroke:#ffc107
    style E fill:#f5c6cb,stroke:#dc3545

Durante o desenvolvimento, o Flutter usa um modo de compilação diferente chamado JIT (Just-in-Time). É nesse modo que funciona o famoso hot reload: você altera o código, salva o arquivo e, em menos de um segundo, vê as mudanças refletidas no emulador sem reiniciar o aplicativo. O hot reload é possível porque no modo JIT o Dart consegue substituir partes do código em execução sem recomeçar do zero. Quando você gera o build final do aplicativo para publicar na loja, o Flutter usa compilação AOT, produzindo um binário otimizado e sem dependências externas.

A Filosofia de Widgets

No Flutter, tudo é um widget. O texto na tela é um widget. O espaço entre dois elementos é um widget. O próprio fundo da tela é um widget. Isso pode parecer exagerado no início, mas essa uniformidade é exatamente o que torna o Flutter tão poderoso e previsível.

Um widget no Flutter é, fundamentalmente, uma descrição imutável de uma parte da interface. Você não diz ao Flutter “mude a cor deste texto para vermelho”. Você diz “reconstrua esta parte da interface com um texto vermelho no lugar do azul”. O Flutter é responsável por calcular o que mudou e atualizar somente o que precisa ser atualizado na tela, de forma eficiente.

Essa filosofia — interfaces descritas como composição de widgets imutáveis — é declarativa, não imperativa. Em vez de escrever comandos que modificam a interface passo a passo, você descreve como a interface deve ser em cada estado, e o framework cuida da renderização. Essa distinção vai se tornar cada vez mais natural à medida que você avançar no semestre.

Dart: A Linguagem do Flutter

O Flutter usa exclusivamente a linguagem Dart. Isso pode parecer uma limitação — por que não JavaScript, que todo mundo já conhece? —, mas é, na prática, uma vantagem significativa.

Dart é uma linguagem orientada a objetos fortemente tipada, com uma sintaxe que será familiar para quem já programou em Java, C# ou Kotlin. Ela tem características que a tornam especialmente adequada para o Flutter: compilação AOT para desempenho nativo, compilação JIT para hot reload durante o desenvolvimento, e um sistema de tipos com null safety que torna o código mais seguro ao obrigar o desenvolvedor a tratar explicitamente a possibilidade de valores nulos.

Vamos ver um exemplo simples para você ter o primeiro contato com a sintaxe Dart. O contexto é o aplicativo que você vai construir no Projeto Integrador: um catálogo de produtos com gerenciamento de pedidos. Não se preocupe em entender tudo agora — o Módulo 02 é dedicado inteiramente à linguagem. Por ora, observe a familiaridade da estrutura:

// Uma classe simples que representa um produto no catálogo do aplicativo.
// Note o uso de 'final' para indicar que os atributos não mudam após
// a criação do objeto — uma boa prática para objetos de dados.

class Produto {
  // Atributos com tipos explícitos
  final String nome;
  final double preco;
  final String? descricao; // O '?' indica que este campo pode ser nulo

  // Construtor nomeado com parâmetros nomeados.
  // 'const' indica que o objeto pode ser criado em tempo de compilação,
  // o que melhora o desempenho no Flutter.
  const Produto({
    required this.nome,   // 'required' torna o campo obrigatório
    required this.preco,
    this.descricao,       // Campo opcional: pode ser null se não fornecido
  });

  // Sobrescrita de toString para facilitar a leitura durante debug
  @override
  String toString() {
    return 'Produto(nome: $nome, preco: $preco)';
  }
}

// Função principal — ponto de entrada de qualquer programa Dart
void main() {
  // Criando um produto com o construtor nomeado
  const produto = Produto(
    nome: 'Café Especial',
    preco: 18.90,
    descricao: 'Grãos selecionados da Serra da Mantiqueira',
  );

  // String interpolation: '$' insere o valor da variável no texto
  print('Produto criado: $produto');
  print('Nome: ${produto.nome}');
  print('Preço: R\$ ${produto.preco.toStringAsFixed(2)}');

  // O null safety exige verificar se o campo opcional tem valor antes de usá-lo
  if (produto.descricao != null) {
    print('Descrição: ${produto.descricao}');
  }
}
class Produto {
  final String nome;
  final double preco;
  final String? descricao;

  const Produto({
    required this.nome,
    required this.preco,
    this.descricao,
  });

  @override
  String toString() => 'Produto(nome: $nome, preco: $preco)';
}

void main() {
  const produto = Produto(
    nome: 'Café Especial',
    preco: 18.90,
    descricao: 'Grãos selecionados da Serra da Mantiqueira',
  );

  print('Produto criado: $produto');
  print('Nome: ${produto.nome}');
  print('Preço: R\$ ${produto.preco.toStringAsFixed(2)}');
  if (produto.descricao case final desc?) print('Descrição: $desc');
}

Comparando as Alternativas: Flutter, React Native e Outros

Uma pergunta que você certamente vai fazer: por que Flutter e não outra tecnologia? Essa é uma questão legítima, e a resposta honesta não é “porque Flutter é melhor que tudo”. A resposta é que Flutter é a melhor escolha para os objetivos desta disciplina, e é uma escolha sólida para o mercado também.

A tabela a seguir compara as principais tecnologias multiplataforma segundo critérios relevantes para você como desenvolvedor:

Critério Flutter React Native Ionic Xamarin
Linguagem Dart JavaScript / TypeScript JavaScript / TypeScript C#
Renderização Própria (Skia / Impeller) Componentes nativos WebView Componentes nativos
Desempenho Excelente Muito bom Razoável Muito bom
Curva de aprendizagem Moderada Menor (para quem já sabe JS) Baixa Moderada
Tamanho da comunidade (2024) Grande e crescente Grande e madura Média Pequena
Suporte a Desktop / Web Sim (desde v2) Parcial Web Sim
Empresas usuárias Google, Alibaba, eBay Meta, Airbnb, Shopify Vários Microsoft, Cisco

O React Native tem a maior base instalada de desenvolvedores, especialmente entre quem já conhece JavaScript e o ecossistema React. Se você programou em React, a curva de migração para React Native é natural. No entanto, para aprendizado em um semestre, o Flutter oferece vantagens que importam muito no contexto pedagógico.

A primeira é a consistência visual. Como o Flutter renderiza sua própria interface, o aplicativo tem a mesma aparência em todas as plataformas, eliminando toda uma categoria de problemas que surgem no React Native quando o comportamento difere entre Android e iOS. A segunda é o hot reload, que no Flutter é excepcionalmente rápido e confiável, tornando o ciclo de desenvolvimento mais ágil durante o aprendizado. A terceira é a documentação oficial, amplamente considerada uma das melhores da indústria, com tutoriais, exemplos interativos e referências de API muito bem organizados. Finalmente, o mercado brasileiro tem adotado o Flutter em ritmo acelerado — empresas como iFood, Nubank e diversas fintechs têm times Flutter ativos.


A Arquitetura Completa que Você Vai Construir

Ao longo do semestre, você construirá um sistema completo, não apenas um aplicativo. É importante que você tenha uma visão do sistema inteiro desde o início, para entender onde cada tecnologia que vai aprender se encaixa.

graph LR
    subgraph Dispositivo["Dispositivo do Usuário"]
        APP["Aplicativo Flutter<br/>(Dart)"]
    end

    subgraph AWS["Amazon Web Services"]
        APIGW["API Gateway<br/>(REST)"]
        LAMBDA["Lambda Functions<br/>(Python / Node.js)"]
        RDS["RDS PostgreSQL<br/>(Banco de Dados)"]
        SQS["SQS<br/>(Fila de Mensagens)"]
    end

    subgraph Google["Google Firebase"]
        FCM["Firebase Cloud<br/>Messaging (FCM)"]
    end

    APP <-->|"HTTPS / JSON"| APIGW
    APIGW --> LAMBDA
    LAMBDA <--> RDS
    LAMBDA --> SQS
    SQS --> LAMBDA
    LAMBDA --> FCM
    FCM -->|"Push Notification"| APP

    style Dispositivo fill:#cfe2ff,stroke:#0d6efd
    style AWS fill:#fff3cd,stroke:#ffc107
    style Google fill:#d4edda,stroke:#28a745

O aplicativo Flutter é o que o usuário vê e toca. Ele é responsável pela interface, pela navegação, pela validação de formulários, pelo gerenciamento do estado local e pela comunicação com o backend. Internamente, o aplicativo usa Provider para gerenciamento de estado, go_router para navegação e http para chamadas à API.

O backend na AWS é o servidor que processa as requisições do aplicativo. O API Gateway recebe as chamadas HTTP, distribui para as Lambda Functions correspondentes, que leem e escrevem no banco de dados PostgreSQL hospedado no RDS. A fila SQS é usada para processar tarefas assíncronas — por exemplo, enviar uma confirmação por email ou disparar uma notificação push — sem bloquear a resposta ao usuário.

As notificações push chegam ao dispositivo via Firebase Cloud Messaging. O backend, ao detectar um evento relevante, envia uma mensagem ao FCM, que a entrega ao dispositivo do usuário, independentemente de o aplicativo estar aberto ou não.

Você não precisa entender cada detalhe desta arquitetura agora. O ponto é ter a imagem completa: cada módulo do semestre vai adicionar uma camada a esse sistema. No final, você terá construído, de verdade, um sistema com todas essas partes funcionando juntas.


Configurando o Ambiente de Desenvolvimento

Atenção antes de começar

A configuração do ambiente é a parte mais propensa a problemas de todo este módulo. Seguir os passos na ordem correta é importante. Se algo não funcionar como esperado, não pule para o próximo passo: resolva o problema antes de avançar. Um ambiente com problema silencioso vai gerar confusão semanas depois.

O ambiente de desenvolvimento é composto de cinco ferramentas: Java Development Kit (JDK), Android Studio (com emuladores Android), Flutter SDK, Visual Studio Code (com extensões) e Git. Elas precisam ser instaladas nesta ordem específica, porque cada uma delas depende das anteriores.

Passo 1: Java Development Kit

O Android SDK, que o Flutter usa para compilar aplicativos Android, depende do Java. A versão recomendada é o JDK 17 (LTS), disponível no site da Oracle ou na distribuição OpenJDK.

Após instalar, abra um terminal e verifique:

java -version

A saída deve mostrar algo como java version "17.x.x". Se aparecer um erro ou uma versão diferente, verifique se o JDK foi adicionado ao PATH do sistema. No Windows, isso é feito em: Painel de Controle > Sistema > Configurações Avançadas do Sistema > Variáveis de Ambiente > PATH.

Passo 2: Android Studio e Emuladores

O Android Studio é o IDE oficial do Android. Mesmo que você use o Visual Studio Code para escrever código Flutter, o Android Studio é necessário porque instala o Android SDK e as ferramentas de linha de comando que o Flutter precisa para compilar aplicativos Android.

Após instalar o Android Studio, abra-o e siga o assistente de configuração inicial. Ele vai baixar o Android SDK, o Android Emulator e as ferramentas de build. Esse download pode ser lento dependendo da sua conexão — planeje ter pelo menos 10 GB livres no disco.

Após a instalação, crie um Virtual Device (emulador) via Tools > Device Manager > Create Device. Escolha um dispositivo da categoria “Phone” — o Pixel 6 é uma boa escolha por representar bem o hardware Android moderno — e selecione a imagem de sistema mais recente (API 35 ou superior).

Em computadores com processadores AMD ou em máquinas virtuais, o emulador pode ter problemas de desempenho por falta de suporte à aceleração de hardware (HAXM ou Hyper-V). Se você tiver esse problema, considere usar um dispositivo físico Android conectado por USB para o desenvolvimento. O Flutter detecta automaticamente dispositivos físicos conectados e funciona da mesma forma que com o emulador.

Passo 3: Flutter SDK

Acesse a documentação oficial do Flutter (docs.flutter.dev) e siga as instruções de instalação para Windows. O processo consiste em baixar o arquivo ZIP do Flutter SDK, extraí-lo em um diretório de sua preferência — evite caminhos com espaços, como C:\Program Files —, e adicionar o subdiretório flutter\bin ao PATH do sistema.

Após configurar o PATH, feche e reabra o terminal, depois execute:

flutter doctor

O flutter doctor é a ferramenta de diagnóstico do Flutter. Ele verifica se tudo está instalado corretamente e mostra exatamente o que ainda precisa ser feito. A saída será algo como:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.x.x)
[✓] Windows Version (Windows 11)
[✓] Android toolchain - develop for Android devices
[!] Chrome - develop for the web (CHROME_EXECUTABLE not found)
[✓] Android Studio (version 2023.x)
[✓] VS Code (version 1.9x)
[✓] Connected device (1 available)
[✓] Network resources

Cada [✓] significa que aquele componente está correto. Cada [!] indica um problema ou configuração faltante. Você precisa resolver todos os problemas relevantes antes de continuar. O [!] referente ao Chrome pode ser ignorado por ora, pois não desenvolveremos para a web neste módulo.

Um problema comum no Windows é o Flutter pedir que você aceite as licenças do Android SDK. Para isso, execute:

flutter doctor --android-licenses

Responda y (yes) para cada licença apresentada.

Passo 4: Visual Studio Code e Extensões

O Visual Studio Code é o editor que usaremos para escrever código Dart e Flutter. Após instalá-lo, adicione as seguintes extensões pelo Marketplace (Ctrl + Shift + X):

A extensão Flutter (publicada pela equipe Flutter/Dart) inclui suporte completo ao Dart, realce de sintaxe, autocompletar, snippets e integração com o flutter doctor. A extensão Dart é instalada automaticamente junto com a extensão Flutter. A extensão GitLens (opcional, mas muito útil) adiciona visualizações avançadas do histórico Git dentro do editor. A extensão Pubspec Assist (opcional) facilita a adição de dependências ao arquivo pubspec.yaml.

Para verificar que o VS Code está funcionando corretamente com o Flutter, abra o Command Palette com Ctrl + Shift + P e digite Flutter: Run Flutter Doctor. O resultado deve ser idêntico ao que você viu no terminal.

Passo 5: Git e Configuração do GitHub

Git é o sistema de controle de versão que você usará ao longo de todo o semestre, tanto individualmente quanto em equipe. Se você já tem Git instalado de semestres anteriores, verifique se é a versão 2.40 ou superior:

git --version

Caso precise instalar, acesse git-scm.com e baixe o instalador para Windows. Durante a instalação, mantenha as configurações padrão — em especial, permita que o Git adicione os executáveis ao PATH.

Após instalar, configure seu nome e email. Esses dados aparecem em cada commit e são usados pelo GitHub para identificar os autores das contribuições:

git config --global user.name "Seu Nome Completo"
git config --global user.email "seu.email@exemplo.com"

Para conectar ao GitHub sem precisar digitar senha toda vez, configure uma chave SSH. O GitHub aceita o formato Ed25519, que é mais seguro e gera chaves mais curtas do que o RSA:

# Gera um par de chaves SSH
# Pressione Enter em todas as perguntas para aceitar os valores padrão
ssh-keygen -t ed25519 -C "seu.email@exemplo.com"

# Exibe a chave pública para copiar
cat ~/.ssh/id_ed25519.pub

Copie o conteúdo exibido e adicione em: GitHub > Settings > SSH and GPG keys > New SSH key. Para verificar se a conexão SSH está funcionando:

ssh -T git@github.com

A resposta esperada é algo como Hi seu-usuario! You've successfully authenticated...


Criando o Primeiro Projeto Flutter

Com o ambiente configurado, chegou o momento de criar seu primeiro projeto Flutter e entender a estrutura de diretórios gerada automaticamente.

Criando o Projeto

Abra um terminal no diretório onde você deseja criar seus projetos e execute:

flutter create meu_primeiro_app

O Flutter vai criar um diretório chamado meu_primeiro_app com toda a estrutura do projeto. Abra esse diretório no VS Code:

cd meu_primeiro_app
code .

Entendendo a Estrutura de Diretórios

A primeira coisa que você verá no VS Code é a árvore de diretórios do projeto. Vamos entender o que cada pasta significa:

meu_primeiro_app/
├── android/          # Configurações nativas do Android (raramente editadas diretamente)
├── ios/              # Configurações nativas do iOS (não usaremos neste curso)
├── lib/              # Todo o seu código Dart e Flutter fica aqui
│   └── main.dart     # Ponto de entrada da aplicação
├── test/             # Testes automatizados
├── web/              # Suporte a Web (não usaremos neste módulo)
├── pubspec.yaml      # Manifesto do projeto: nome, versão, dependências
└── README.md         # Documentação inicial do projeto

A pasta mais importante é lib/. É lá que você vai trabalhar durante todo o semestre. A pasta android/ contém configurações nativas que raramente precisam ser tocadas diretamente — o Flutter gera e mantém a maior parte dessas configurações automaticamente. O arquivo pubspec.yaml é o coração do projeto: ele define o nome do aplicativo, a versão, as dependências de pacotes externos e os assets como imagens e fontes.

O Arquivo pubspec.yaml

Abra o pubspec.yaml do projeto. Ele se parece com isto:

name: meu_primeiro_app
description: "A new Flutter project."
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.6

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0

flutter:
  uses-material-design: true

O campo dependencies lista as bibliotecas externas que seu projeto usa em produção. O dev_dependencies lista ferramentas usadas apenas durante o desenvolvimento, como frameworks de teste e analisadores de código. Ao longo do semestre, você vai adicionar diversas dependências a este arquivo — todas as que estão definidas na ementa da disciplina, como provider, go_router, http, sqflite e outras.

O campo version tem o formato 1.0.0+1, onde 1.0.0 é o nome da versão e +1 é o código de versão numérico. No Android, o código de versão corresponde ao versionCode do Gradle; no iOS, ao CFBundleVersion. O Flutter usa esse campo automaticamente quando você gera um build.

O Arquivo main.dart

Abra o arquivo lib/main.dart. Ele contém o aplicativo de contador padrão que o Flutter gera como exemplo. Vamos analisá-lo para entender a estrutura básica de qualquer aplicativo Flutter:

// O Flutter usa o pacote 'material' para os componentes de interface.
// 'material' se refere ao Material Design, o sistema de design do Google.
import 'package:flutter/material.dart';

// Todo aplicativo Dart começa pela função 'main'.
// 'runApp' diz ao Flutter qual widget deve ocupar a tela inteira.
void main() {
  runApp(const MeuAplicativo());
}

// 'StatelessWidget' é um widget sem estado interno.
// 'MeuAplicativo' é a raiz da árvore de widgets do aplicativo.
class MeuAplicativo extends StatelessWidget {
  const MeuAplicativo({super.key});

  // 'build' descreve a interface deste widget.
  // É chamado pelo Flutter sempre que o widget precisa ser renderizado.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Meu Primeiro App',
      // 'ThemeData' define o tema visual do aplicativo inteiro
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      // 'home' define qual tela é exibida quando o app abre
      home: const TelaPrincipal(titulo: 'Meu Primeiro App'),
    );
  }
}

// 'StatefulWidget' é um widget que pode ter estado que muda com o tempo.
// A tela principal tem um contador que muda quando o botão é pressionado.
class TelaPrincipal extends StatefulWidget {
  const TelaPrincipal({super.key, required this.titulo});

  // Atributos do StatefulWidget são definidos na classe do widget
  final String titulo;

  // Todo StatefulWidget precisa de um método 'createState' que
  // retorna o objeto de estado correspondente
  @override
  State<TelaPrincipal> createState() => _TelaPrincipalState();
}

// O estado fica separado do widget: '_TelaPrincipalState' guarda o
// contador e é quem decide o que aparece na tela
class _TelaPrincipalState extends State<TelaPrincipal> {
  // Variável de estado: quando ela muda, o Flutter reconstrói a tela
  int _contador = 0;

  void _incrementarContador() {
    // 'setState' avisa o Flutter que o estado mudou e que a tela
    // precisa ser reconstruída com os novos valores
    setState(() {
      _contador++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // AppBar é a barra superior da tela
      appBar: AppBar(
        title: Text(widget.titulo),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      // 'body' é o conteúdo principal da tela
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Você pressionou o botão:'),
            // '$_contador' insere o valor do contador no texto
            Text(
              '$_contador',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      // Botão flutuante no canto inferior direito da tela
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementarContador,
        tooltip: 'Incrementar',
        child: const Icon(Icons.add),
      ),
    );
  }
}
import 'package:flutter/material.dart';

void main() => runApp(const MeuAplicativo());

class MeuAplicativo extends StatelessWidget {
  const MeuAplicativo({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Meu Primeiro App',
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
      useMaterial3: true,
    ),
    home: const TelaPrincipal(titulo: 'Meu Primeiro App'),
  );
}

class TelaPrincipal extends StatefulWidget {
  const TelaPrincipal({super.key, required this.titulo});
  final String titulo;

  @override
  State<TelaPrincipal> createState() => _TelaPrincipalState();
}

class _TelaPrincipalState extends State<TelaPrincipal> {
  int _contador = 0;

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: Text(widget.titulo),
      backgroundColor: Theme.of(context).colorScheme.inversePrimary,
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Você pressionou o botão:'),
          Text('$_contador',
              style: Theme.of(context).textTheme.headlineMedium),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () => setState(() => _contador++),
      tooltip: 'Incrementar',
      child: const Icon(Icons.add),
    ),
  );
}

Executando o Aplicativo

Com o emulador Android iniciado — você pode iniciá-lo pelo Device Manager do Android Studio ou pelo ícone na barra de status do VS Code —, execute o aplicativo:

flutter run

Você verá o processo de compilação no terminal, e em alguns segundos o aplicativo de contador aparecerá no emulador. Agora experimente uma das funcionalidades mais marcantes do Flutter: o hot reload.

Sem fechar o aplicativo, vá ao arquivo main.dart e mude o texto 'Você pressionou o botão:' para 'Contador atual:'. Salve o arquivo com Ctrl + S. Observe o emulador: em menos de um segundo, o texto na tela vai mudar, sem o aplicativo reiniciar. Isso é o hot reload em ação — uma das experiências que torna o desenvolvimento com Flutter significativamente mais ágil do que em outras plataformas.

Existem dois comandos relacionados que você vai usar frequentemente. O hot reload (r no terminal enquanto o app está rodando, ou Ctrl + F5 no VS Code) injeta o código novo no Dart VM e reconstrói a árvore de widgets, mantendo o estado atual. O hot restart (R maiúsculo no terminal, ou Shift + F5 no VS Code) reinicia completamente o Dart VM, perdendo o estado, mas sendo necessário quando você adiciona novas dependências ou muda a lógica de inicialização.


Controle de Versão com Git e GitHub

O controle de versão não é uma ferramenta opcional no desenvolvimento moderno; ele é a infraestrutura básica de qualquer projeto de software sério. Ao longo deste semestre, você vai usar Git e GitHub tanto para versionar seu próprio código quanto para colaborar com sua equipe no Projeto Integrador. Esta seção apresenta os conceitos e comandos fundamentais que você precisará dominar.

Conceitos Fundamentais

O Git é um sistema de controle de versão distribuído. “Distribuído” significa que cada desenvolvedor tem uma cópia completa do histórico do repositório em sua máquina local. Você não depende de uma conexão com o servidor para trabalhar; você trabalha localmente e, quando quiser compartilhar suas mudanças, faz um push para o repositório remoto no GitHub.

sequenceDiagram
    participant WD as Área de Trabalho
    participant Stage as Staging Area
    participant Local as Repositório Local
    participant Remote as GitHub (Remoto)

    WD->>Stage: git add arquivo.dart
    Stage->>Local: git commit -m "mensagem"
    Local->>Remote: git push origin main
    Remote->>Local: git pull (fetch + merge)
    Local->>WD: arquivos atualizados

Os três estados de um arquivo no Git são: modificado (você alterou o arquivo, mas ainda não disse ao Git), staged (você adicionou o arquivo à área de preparação com git add) e commitado (você confirmou as mudanças no repositório local com git commit). Compreender esses três estados é a base para entender tudo o que o Git faz.

Fluxo de Trabalho Básico

Após criar seu projeto Flutter, inicializar o Git dentro dele é simples:

# Inicializa o repositório Git dentro da pasta do projeto
git init

# Verifica o status atual dos arquivos
git status

# Adiciona todos os arquivos ao staging
git add .

# Cria o primeiro commit com uma mensagem descritiva
git commit -m "feat: cria estrutura inicial do projeto Flutter"

# Conecta ao repositório remoto já criado no GitHub
git remote add origin git@github.com:seu-usuario/nome-do-repositorio.git

# Envia o commit para o GitHub e define o branch upstream
git push -u origin main

A convenção de mensagens de commit mais usada na indústria é chamada de Conventional Commits. Cada mensagem começa com um tipo: feat: para novas funcionalidades, fix: para correções de bugs, docs: para alterações na documentação, refactor: para refatorações de código sem mudança de comportamento, e test: para adição ou modificação de testes. Adotar essa convenção desde o início vai tornar o histórico do seu projeto muito mais legível para você, para sua equipe e para o professor.

O Arquivo .gitignore

Nem todos os arquivos de um projeto Flutter devem ser versionados. O diretório build/ contém artefatos de compilação que podem ser regenerados a qualquer momento; as pastas .dart_tool/ e .flutter-plugins contêm configurações geradas automaticamente; arquivos .env (se você usar um) podem conter senhas e chaves de API que nunca devem ser publicadas.

O Flutter já cria um .gitignore padrão quando você usa flutter create. Ele já exclui os diretórios mais comuns. Mas ao longo do semestre, você precisará adicionar outros padrões — em especial, qualquer arquivo que contenha chaves de API da AWS, do Firebase ou outras credenciais sensíveis. Nunca commite credenciais no repositório. Uma vez publicados, eles ficam acessíveis para sempre no histórico do Git, mesmo que sejam removidos em commits posteriores.

Branches e Colaboração em Equipe

Quando você trabalha em equipe, cada membro deve trabalhar em um branch separado para cada funcionalidade ou tarefa. Isso evita que o trabalho de um membro interfira no trabalho dos outros.

# Cria um novo branch para trabalhar em uma funcionalidade específica
git checkout -b feature/tela-inicial

# ... trabalha, faz commits normalmente ...

# Volta para o branch principal quando a funcionalidade estiver pronta
git checkout main

# Incorpora as mudanças do branch de feature ao branch principal
git merge feature/tela-inicial

# Exclui o branch local após o merge
git branch -d feature/tela-inicial

O fluxo mais comum em equipes é o GitHub Flow: o branch main sempre contém o código funcional e pronto; cada nova funcionalidade é desenvolvida em um branch separado; quando a funcionalidade está pronta, um Pull Request (PR) é aberto no GitHub para revisão antes de ser incorporado ao main. Esse fluxo é o que o seu grupo deve adotar durante o Projeto Integrador.


A Estrutura do Projeto Integrador

Conectando o que você aprendeu com o que vai construir

Tudo o que você viu até aqui — o ambiente, o Flutter, o Git — converge para o Projeto Integrador. A partir de agora, vamos falar especificamente sobre o que você vai construir durante o semestre, como o repositório deve ser organizado e o que é esperado do seu grupo ao final deste primeiro módulo.

Por Que a Estrutura de Pastas Importa

Um projeto Flutter sem organização adequada parece funcionar bem nas primeiras semanas. Mas à medida que o número de arquivos cresce — e vai crescer muito —, a falta de organização começa a cobrar seu preço: arquivos difíceis de encontrar, dependências circulares entre módulos, lógica de negócio misturada com código de interface. Esses problemas são evitáveis se você começa com uma estrutura bem pensada desde o primeiro dia.

A estrutura que adotaremos ao longo da disciplina é baseada em dois princípios complementares: Feature-First e Arquitetura Hexagonal.

O princípio Feature-First organiza os arquivos por funcionalidade, não por tipo de arquivo. Em vez de ter uma pasta models/ com todos os modelos do projeto, uma pasta widgets/ com todos os widgets e uma pasta providers/ com todos os providers, cada funcionalidade tem sua própria pasta contendo seus modelos, widgets e providers específicos. Isso facilita imensamente a localização de código relacionado a uma determinada parte do aplicativo. Quando você está trabalhando na tela de autenticação, todos os arquivos relevantes estão em features/autenticacao/, e não espalhados em diversas pastas pelo projeto.

A Arquitetura Hexagonal (também conhecida como Ports and Adapters) separa o código em três camadas bem definidas. A camada de domínio contém as classes de dados e a lógica de negócio pura, sem dependências de Flutter, HTTP ou banco de dados. A camada de dados contém a implementação concreta de acesso a dados — chamadas HTTP, queries SQL, leitura de arquivos. A camada de apresentação contém os widgets Flutter e os providers que gerenciam o estado da interface.

graph TB
    subgraph feature["Feature: Catálogo de Produtos"]
        subgraph domain["Domínio (puro Dart)"]
            D1["Produto (model)"]
            D2["ProdutoRepository (interface)"]
        end
        subgraph data["Dados (implementações)"]
            DA1["ProdutoRepositoryImpl"]
            DA2["ProdutoService (HTTP)"]
        end
        subgraph presentation["Apresentação (Flutter)"]
            P1["CatalogoProvider"]
            P2["CatalogoScreen"]
            P3["ProdutoCard (widget)"]
        end
    end

    P2 --> P1
    P1 --> D2
    DA1 --> D2
    DA1 --> DA2

    style domain fill:#d4edda,stroke:#28a745
    style data fill:#fff3cd,stroke:#ffc107
    style presentation fill:#cfe2ff,stroke:#0d6efd

A estrutura de pastas completa do projeto segue este padrão:

frontend/
└── lib/
    ├── core/              # Código compartilhado por todas as features
    │   ├── theme/         # Cores, tipografia, espaçamentos
    │   ├── widgets/       # Widgets genéricos reutilizáveis
    │   ├── utils/         # Funções utilitárias
    │   └── di/            # Injeção de dependência com GetIt
    └── features/          # Uma pasta para cada funcionalidade
        ├── autenticacao/
        │   ├── domain/
        │   │   └── models/
        │   ├── data/
        │   │   └── repositories/
        │   └── presentation/
        │       ├── providers/
        │       └── screens/
        └── catalogo/
            ├── domain/
            ├── data/
            └── presentation/

Você não precisa entender cada detalhe desta estrutura agora. O que importa neste módulo é inicializar o projeto com as pastas core/ e features/ criadas, mesmo que ainda vazias. Nos módulos seguintes, você vai preenchê-las gradualmente e entender o propósito de cada decisão à medida que o projeto cresce.

O que é Esperado ao Final do Módulo 01

Entregáveis do Módulo 01

Ao final das três aulas práticas deste módulo, o seu grupo deve ter entregue ao professor os seguintes itens. Cada item é verificado diretamente no repositório GitHub ou durante a aula.

O primeiro entregável é a proposta aprovada do projeto. Trata-se de um documento de uma página — pode ser um arquivo docs/proposta.md no repositório — descrevendo o aplicativo que o grupo vai construir: o nome, o problema que resolve, o público-alvo, uma lista inicial das funcionalidades principais e quais recursos técnicos serão utilizados (câmera, GPS, etc.). Essa proposta precisa ser aprovada pelo professor antes do grupo avançar para o Módulo 02. Não existe proposta certa ou errada; existe proposta viável — um aplicativo suficientemente rico para cobrir todos os conteúdos dos 15 módulos.

O segundo entregável é o repositório configurado e acessível. O repositório deve estar público ou com o professor adicionado como colaborador. Ele deve ter a estrutura de pastas correta (com lib/core/ e lib/features/ criadas), um .gitignore adequado para projetos Flutter e ao menos dois commits com mensagens descritivas no formato Conventional Commits.

O terceiro entregável é o projeto Flutter executando no emulador. Todos os membros do grupo devem conseguir clonar o repositório, executar flutter run e ver o aplicativo funcionar no emulador sem erros. Este é um pré-requisito para tudo o que vem nos módulos seguintes.

O quarto entregável é o esboço das telas principais. Pode ser um papel digitalizado, uma foto de um post-it, ou um screenshot de uma ferramenta de prototipação como Figma ou Excalidraw. O esboço deve mostrar as três ou quatro telas principais do aplicativo e o fluxo de navegação entre elas. Esse arquivo deve ser salvo no repositório, dentro de docs/esbocos/.

👉 No moodle, entregue apenas o número do commit que contém o trabalho realizado.


Aprofundamento: Como o Flutter Gerencia as Três Árvores

Esta seção é um aprofundamento para quem quer entender os bastidores do Flutter com mais profundidade. Ela não é necessária para o Projeto Integrador deste módulo, mas vai ajudar a entender decisões de arquitetura que você encontrará nos módulos seguintes.

Quando você escreve código Flutter, você está descrevendo uma árvore de widgets. Mas o Flutter, internamente, mantém três árvores em paralelo: a Widget Tree, a Element Tree e a Render Tree.

A Widget Tree é o que você escreve. Ela é imutável: cada vez que o estado muda, a Widget Tree é reconstruída — completamente ou em parte. Como os widgets são objetos leves (são apenas configurações), essa reconstrução é extremamente rápida.

A Element Tree é o que o Flutter mantém internamente para rastrear a identidade de cada widget ao longo do tempo. Quando a Widget Tree é reconstruída, o Flutter compara os novos widgets com os elementos existentes e decide quais elementos precisam ser atualizados, criados ou destruídos. Essa comparação é chamada de reconciliação e é o mecanismo que torna o Flutter eficiente — em vez de redesenhar a tela inteira a cada mudança de estado, o Flutter atualiza somente o que precisa ser atualizado.

A Render Tree é a que realmente ocupa espaço na tela. Cada RenderObject na Render Tree sabe seu tamanho, sua posição e como se desenhar. O Flutter percorre a Render Tree em duas fases: o layout (calculando o tamanho e a posição de cada elemento) e o paint (desenhando os pixels finais na tela).

graph TB
    subgraph WT["Widget Tree (você escreve)"]
        W1["MaterialApp"] --> W2["Scaffold"]
        W2 --> W3["AppBar"]
        W2 --> W4["Center"]
        W4 --> W5["Text"]
    end

    subgraph ET["Element Tree (Flutter gerencia)"]
        E1["StatelessElement"] --> E2["StatefulElement"]
        E2 --> E3["StatelessElement"]
        E2 --> E4["StatelessElement"]
        E4 --> E5["StatelessElement"]
    end

    subgraph RT["Render Tree (desenhada na GPU)"]
        R1["RenderView"] --> R2["RenderFlex"]
        R2 --> R3["RenderBox (AppBar)"]
        R2 --> R4["RenderPositionedBox"]
        R4 --> R5["RenderParagraph"]
    end

    WT -.->|"reconciliação"| ET
    ET -.->|"atualiza"| RT

Para você como desenvolvedor, o que importa na prática é o seguinte: widgets são baratos. Criar muitos deles não é um problema de desempenho, porque o Flutter foi projetado para reconstruir widgets com frequência. O que é computacionalmente caro é a fase de layout e paint na Render Tree, e o Flutter faz um trabalho excelente de minimizar o que precisa ser recalculado a cada frame.

Esse entendimento vai se tornar relevante a partir do Módulo 03, quando você começar a construir interfaces mais complexas e precisar tomar decisões sobre onde posicionar o gerenciamento de estado.


Entendendo o pubspec.yaml com as Dependências da Disciplina

Ao longo do semestre, você vai adicionar ao pubspec.yaml um conjunto de dependências específicas. É útil ter uma visão geral dessas dependências desde o início, para entender quais problemas cada uma resolve. A tabela a seguir apresenta as principais dependências e o módulo em que cada uma será introduzida:

Dependência Versão Propósito Módulo
provider ^6.1.5+1 Gerenciamento de estado 07
go_router ^17.0.1 Navegação declarativa 05
http ^1.6.0 Chamadas à API REST 09
sqflite ^2.4.2 Banco de dados SQLite local 08
shared_preferences ^2.5.4 Preferências simples 08
flutter_secure_storage ^10.0.0 Armazenamento seguro 11
oauth2_client ^4.2.3 Autenticação OAuth 2.0 11
get_it ^9.2.0 Injeção de dependência 07
local_auth ^3.0.0 Autenticação biométrica 11
path_provider ^2.1.5 Caminhos de sistema de arquivos 08
permission_handler ^12.0.1 Solicitação de permissões 13
loading_overlay ^0.5.0 Indicador de carregamento 06
i18n_extension ^15.1.0 Internacionalização 14
firebase_messaging ^16.1.1 Notificações push 12
firebase_core ^4.4.0 SDK base do Firebase 12

Neste primeiro módulo, você não vai adicionar nenhuma dessas dependências ao projeto. O foco é garantir que a estrutura base está correta e que o ambiente funciona. As dependências serão adicionadas gradualmente conforme o conteúdo de cada módulo for introduzido.


Resumo do Módulo 01

O que você aprendeu neste módulo

Você chegou ao final do primeiro módulo. Vamos consolidar o que foi coberto para que você chegue à aula presencial com as ideias organizadas.

Você aprendeu que o desenvolvimento mobile passou por três grandes eras — nativo, híbrido e multiplataforma moderna —, e que o Flutter pertence a esta terceira era com uma abordagem diferente de todos os seus predecessores: um motor de renderização próprio que desenha cada pixel da interface, independentemente da plataforma.

Você entendeu a arquitetura interna do Flutter em três camadas: o código Dart que você escreve, o motor de renderização que transforma widgets em pixels, e a plataforma nativa que fornece a superfície de desenho e acesso ao hardware. E entendeu que o Flutter mantém três árvores internas — Widget, Element e Render — para gerenciar a interface com eficiência.

Você configurou um ambiente de desenvolvimento completo: JDK, Android Studio com emulador, Flutter SDK, Visual Studio Code com extensões Flutter e Dart, e Git com integração ao GitHub via SSH.

Você criou seu primeiro projeto Flutter, executou-o no emulador e experimentou o hot reload. Você entendeu a estrutura de diretórios do projeto, o papel do pubspec.yaml e os dois modos de compilação do Dart (JIT para desenvolvimento, AOT para produção).

Você aprendeu o fluxo básico de trabalho com Git, a convenção Conventional Commits para mensagens, a importância do .gitignore para proteger credenciais, e como trabalhar em equipe com branches e Pull Requests no GitHub Flow.

E você conheceu a estrutura de pastas do Projeto Integrador, baseada nos princípios Feature-First e Arquitetura Hexagonal, que guiará a organização de código durante todo o semestre.

Antes da aula presencial, certifique-se de que o flutter doctor retorna sem erros relevantes, que você consegue criar e executar um projeto Flutter no emulador e que seu repositório GitHub está configurado e acessível. Se tiver dificuldades com a configuração, acesse o fórum da disciplina antes da aula: resolver esse problema é a prioridade do seu grupo no Módulo 01.


Leitura Complementar e Recursos

Os recursos abaixo são sugestões para quem quiser se aprofundar nos temas deste módulo. Nenhum deles é obrigatório, mas todos são úteis se você encontrar dificuldades específicas ou quiser ir além do que este material cobre.

A documentação oficial do Flutter (docs.flutter.dev) é o recurso mais completo e atualizado disponível. Em particular, a seção de codelabs — tutoriais interativos com código — é uma excelente introdução hands-on ao framework. O Flutter Cookbook, também na documentação oficial, é uma coleção de receitas para tarefas comuns que você vai consultar com frequência ao longo do semestre.

Para aprofundamento na arquitetura interna do Flutter, a palestra “How Flutter renders widgets”, disponível no canal oficial Flutter no YouTube, explica com animações como as três árvores funcionam juntas. É um conteúdo de cerca de 20 minutos que vai solidificar muito do que foi apresentado neste módulo.

Para Git e GitHub, o livro “Pro Git” de Scott Chacon e Ben Straub está disponível gratuitamente em git-scm.com/book/pt-br/v2, em português, e é a referência definitiva sobre o assunto. Os capítulos 1, 2 e 3 cobrem tudo o que você precisa para este semestre.


Aprofundamento: O Ciclo Completo de Desenvolvimento Flutter

Compreender o ciclo completo de desenvolvimento — desde a edição do código até a execução no dispositivo — vai ajudá-lo a trabalhar de forma mais produtiva desde o primeiro dia. Cada etapa desse ciclo tem implicações práticas que você vai encontrar constantemente ao longo do semestre.

Edição e Análise Estática

Quando você abre um arquivo Dart no VS Code com a extensão Flutter instalada, o editor imediatamente começa a analisar o código. Essa análise estática é feita pelo Dart Analyzer, uma ferramenta que examina seu código sem executá-lo e identifica problemas como variáveis não usadas, tipos incompatíveis, possíveis erros de null safety e violações das regras de estilo definidas pelo flutter_lints.

Os problemas encontrados pelo analisador aparecem como sublinhados coloridos no editor: vermelho para erros que impedem a compilação, amarelo para avisos sobre código potencialmente problemático e azul para sugestões de melhoria. No painel “Problems” do VS Code (Ctrl + Shift + M), você vê todos os problemas do projeto de uma vez.

Isso significa que você obtém feedback sobre seu código em tempo real, sem precisar compilar e executar para descobrir erros simples. Em linguagens dinâmicas como Python ou JavaScript, muitos erros só aparecem em tempo de execução. Em Dart, a maioria dos erros é detectada antes mesmo de você apertar Run.

O Processo de Build

Quando você executa flutter run, o Flutter realiza uma sequência de passos que vale entender:

O primeiro passo é a resolução de dependências: o Flutter lê o pubspec.yaml, verifica se todas as dependências declaradas estão disponíveis no cache local e, se não estiverem, as baixa do repositório pub.dev. Isso é feito automaticamente, mas você pode forçar explicitamente com flutter pub get.

O segundo passo é a geração de código, se você usar pacotes que dependem dela (como injectable ou freezed, que você vai encontrar mais adiante). Para os módulos iniciais do semestre, não haverá geração de código.

O terceiro passo é a compilação do Dart. No modo debug (que é o padrão de flutter run), o Dart usa compilação JIT para permitir o hot reload. No modo release (flutter run --release), a compilação é AOT.

O quarto passo é o empacotamento: o Flutter combina o código compilado com os assets declarados no pubspec.yaml (imagens, fontes, arquivos de configuração) e com as configurações nativas do Android para gerar o APK (Android Package). Esse APK é então instalado no emulador ou dispositivo conectado.

Debug e Ferramentas de Inspeção

Uma das ferramentas mais úteis do ecossistema Flutter é o Flutter DevTools. Você pode acessá-lo enquanto o aplicativo está em execução: no VS Code, clique no ícone do DevTools na barra de status ou abra-o pelo Command Palette com Flutter: Open DevTools.

O DevTools oferece várias abas:

A Widget Inspector é a mais útil para o aprendizado. Ela mostra a árvore de widgets do aplicativo em execução, permitindo que você clique em qualquer widget na tela e veja imediatamente onde ele está na hierarquia, quais são suas propriedades e qual é o seu tamanho e posição. Isso é extremamente valioso quando um layout não se comporta como esperado: em vez de adivinhar, você inspeciona.

A aba Performance mostra o tempo de renderização de cada frame. Um aplicativo que renderiza a 60 quadros por segundo tem 16 milissegundos por frame para completar todo o trabalho de build, layout e paint. A aba Performance ajuda a identificar quando e por que esse orçamento de tempo está sendo ultrapassado.

A aba Memory mostra o consumo de memória ao longo do tempo e pode ajudar a identificar vazamentos de memória — situações em que objetos que deveriam ser descartados continuam ocupando memória.

Você não precisa dominar todas essas ferramentas agora. Mas saber que elas existem e entender para que servem vai poupar horas de trabalho ao longo do semestre.


Aprofundamento: Null Safety em Dart

O null safety é uma das características mais importantes do Dart moderno e merece atenção especial desde o primeiro módulo, porque ele influencia a forma como você escreve todo o seu código.

Em linguagens sem null safety, qualquer variável pode conter o valor nulo (null) a qualquer momento, a menos que o desenvolvedor tome cuidados especiais. Isso leva ao erro mais clássico da programação: o NullPointerException (ou Null reference exception, dependendo da linguagem). Você tenta usar um objeto que não existe, e o programa trava.

O Dart resolve isso com uma distinção de tipos no próprio sistema de tipos: um tipo sem ? nunca pode ser nulo, e o compilador garante isso; um tipo com ? pode ser nulo, mas o compilador exige que você trate essa possibilidade antes de usar o valor. Essa distinção é verificada em tempo de compilação, não em tempo de execução.

// Exemplo de null safety no contexto do aplicativo:
// um usuário pode ou não ter uma foto de perfil configurada

class Usuario {
  final String nome;       // Nunca pode ser null — campo obrigatório
  final String email;      // Nunca pode ser null — campo obrigatório
  final String? fotoPerfil; // Pode ser null — campo opcional

  const Usuario({
    required this.nome,
    required this.email,
    this.fotoPerfil,  // Se não fornecido, será null por padrão
  });
}

void exibirInformacoesUsuario(Usuario usuario) {
  // 'nome' e 'email' podem ser usados diretamente — nunca são null
  print('Nome: ${usuario.nome}');
  print('Email: ${usuario.email}');

  // 'fotoPerfil' pode ser null, então precisamos verificar antes de usar.
  // O compilador vai recusar o código se tentarmos usar sem verificar.

  // Opção 1: verificação com if
  if (usuario.fotoPerfil != null) {
    print('Foto: ${usuario.fotoPerfil}');
  }

  // Opção 2: operador '??' (null coalescing) — usa valor padrão se for null
  String foto = usuario.fotoPerfil ?? 'imagem_padrao.png';
  print('Foto ou padrão: $foto');

  // Opção 3: operador '?.' (null-safe member access) — não chama o método
  // se o objeto for null; retorna null em vez de lançar exceção
  int? tamanhoFoto = usuario.fotoPerfil?.length;
  print('Tamanho do nome da foto: $tamanhoFoto');

  // Opção 4: operador '!' (non-null assertion) — use com cuidado!
  // Diz ao compilador: "eu sei que este valor não é null neste ponto"
  // Se o valor for null mesmo assim, o programa vai lançar uma exceção em runtime
  if (usuario.fotoPerfil != null) {
    // Aqui é seguro usar '!' porque já verificamos acima
    int tamanho = usuario.fotoPerfil!.length;
    print('Tamanho: $tamanho');
  }
}
class Usuario {
  final String nome;
  final String email;
  final String? fotoPerfil;

  const Usuario({
    required this.nome,
    required this.email,
    this.fotoPerfil,
  });
}

void exibirInformacoesUsuario(Usuario usuario) {
  print('Nome: ${usuario.nome}');
  print('Email: ${usuario.email}');

  final foto = usuario.fotoPerfil ?? 'imagem_padrao.png';
  print('Foto: $foto');

  final tamanho = usuario.fotoPerfil?.length;
  if (tamanho != null) print('Tamanho da foto: $tamanho');
}

O null safety do Dart elimina uma categoria inteira de erros em tempo de execução, transferindo a responsabilidade para o tempo de compilação. Isso é muito mais seguro e mais eficiente: em vez de descobrir que algo é nulo quando o usuário usa o aplicativo (no pior momento possível), você descobre enquanto está escrevendo o código (no melhor momento possível).


Aprofundamento: Organização do Código com Conveções Dart

O Dart tem um conjunto de convenções de estilo bem estabelecido, documentado no “Effective Dart” (dart.dev/effective-dart). Seguir essas convenções desde o início vai tornar seu código mais legível para qualquer desenvolvedor Dart, incluindo seu professor e seus colegas de equipe.

As convenções mais importantes para este primeiro módulo são as seguintes. Nomes de classes usam UpperCamelCase: Produto, TelaPrincipal, AutenticacaoProvider. Nomes de variáveis, funções e parâmetros usam lowerCamelCase: nomeProduto, buscarProdutos(), precoMaximo. Nomes de arquivos usam snake_case (tudo minúsculo, palavras separadas por underscore): produto.dart, tela_principal.dart, autenticacao_provider.dart. Constantes privadas a uma classe usam lowerCamelCase com um underscore inicial se forem membros privados: _contador, _carregando.

A ferramenta dart format formata automaticamente seu código de acordo com as convenções de estilo. No VS Code com a extensão Dart, você pode configurar a formatação automática ao salvar: Settings > Format On Save. Recomendo fortemente ativar isso desde o início — o código vai sempre estar consistentemente formatado sem esforço.


Uma Nota sobre o Projeto do Professor

Ao longo do semestre, você terá acesso a um recurso adicional: o Projeto do Professor. Esse é um aplicativo real, construído pelo professor em paralelo ao seu Projeto Integrador, usando exatamente as mesmas tecnologias e a mesma arquitetura que você está aprendendo. A cada módulo, o professor vai disponibilizar o código correspondente àquele módulo.

O Projeto do Professor serve como referência de qualidade, não como gabarito. Você não precisa replicar o que ele fez; você precisa construir seu próprio projeto. Mas quando você estiver com dúvidas sobre como organizar um arquivo, como nomear uma classe, ou como implementar um determinado padrão arquitetural, o Projeto do Professor é o primeiro lugar para olhar.

Uma palavra de aviso: é tentador copiar o código do Projeto do Professor sem entender o que ele faz. Resista a essa tentação. O aprendizado acontece quando você luta com o problema, não quando você copia a solução. O projeto do professor é uma bússola, não um atalho.


Perguntas Frequentes deste Módulo

Ao longo dos semestres anteriores, algumas perguntas aparecem com muita frequência durante a configuração do ambiente e os primeiros passos com Flutter. Respondo aqui as mais comuns para economizar o seu tempo.

O flutter doctor mostra um aviso sobre o Visual Studio para desenvolvimento Windows. Preciso instalar o Visual Studio? Não, para o desenvolvimento Android (que é o foco desta disciplina) o Visual Studio não é necessário. O flutter doctor mostra esse aviso porque o Flutter também suporta o desenvolvimento de aplicativos desktop para Windows, o que exige o Visual Studio. Pode ignorar esse aviso com segurança.

Meu emulador está muito lento. O que fazer? Verifique se a aceleração de hardware está habilitada. No Windows com processador Intel, o HAXM (Hardware Accelerated Execution Manager) precisa estar instalado — o Android Studio geralmente o instala automaticamente. Com processadores AMD, o Android Emulator usa o Hyper-V do Windows. Se mesmo com aceleração o emulador for lento, conectar um dispositivo Android físico via USB é a alternativa mais eficaz.

Posso usar o iOS para testar o aplicativo? Para desenvolver para iOS é necessário um Mac com Xcode instalado. Em Windows, não é possível compilar para iOS. Nesta disciplina, o desenvolvimento é direcionado ao Android, mas a arquitetura do Flutter garante que um projeto Android bem desenvolvido roda corretamente no iOS sem mudanças significativas.

O hot reload não está funcionando. O que verificar? O hot reload funciona apenas em modo debug. Verifique se você executou o app com flutter run (não com flutter run --release). Alguns tipos de mudança não são suportados pelo hot reload — como mudanças na função main() ou em constantes de nível superior — e nesses casos o hot restart é necessário. Se nada funcionar, feche e reabra o terminal e reinicie o flutter run.

Qual é a diferença entre flutter pub get e flutter pub upgrade? O comando flutter pub get baixa as versões das dependências especificadas no pubspec.lock (ou as mais recentes compatíveis com os intervalos declarados no pubspec.yaml, se o lock não existir ainda). O flutter pub upgrade atualiza todas as dependências para as versões mais recentes compatíveis com os intervalos declarados e recria o pubspec.lock. Use pub get no dia a dia; use pub upgrade quando quiser deliberadamente atualizar as dependências.