Flutter: Design do Projeto
Desenhe a planta antes de construir — o design de arquitetura determina onde estão as "paredes portantes" do seu código.
📋 Pré-requisitos: Você deve ter dominado o seguinte primeiro
- Aula 1: Introdução ao Flutter e Configuração do Ambiente
- Aula 2: Curso Intensivo da Linguagem Dart
- Aula 3: Fundamentos de Widget
- Aula 4: Sistema de Layout
- Aula 5: StatefulWidget e Interação
- Aula 6: Material Design e Componentes Comuns
- Aula 7: Prática Fase 1 — ShopApp Inicial
- Aula 8: Navegação e Roteamento
- Aula 9: Formulário e Entrada
- Aula 10: Lista e Rolagem
- Aula 11: Rede e API REST
- Aula 12: Gerenciamento de Estado — Riverpod
- Aula 13: Armazenamento Local
- Aula 14: Prática Fase 2 — ShopApp Núcleo
- Aula 15: Sistema de Animação
- Aula 16: Tema e Estilização
- Aula 17: Canais de Plataforma e Interoperabilidade Nativa
- Aula 18: Integração Firebase
- Aula 19: Internacionalização e Localização
- Aula 20: Pintura Personalizada e CustomPainter
- Aula 21: Testes — Unit/Widget/Integration
- Aula 22: Otimização de Desempenho
- Aula 23: Implantação Multiplataforma Web e Desktop
- Aula 24: Automação CI/CD
- Aula 25: Publicação Multiplataforma e Revisão de Loja
1. O Que Você Vai Aprender
- Análise de requisitos: histórias de usuário (Alice navegando e pedindo / Bob gerenciamento de mercadorias / Charlie pagamento transfronteiriço)
- Seleção de tecnologia: Flutter 3.x + Riverpod + GoRouter + Dio + Firebase + Hive
- Design de arquitetura: camadas Clean Architecture (Domain / Data / Presentation)
- Modelagem de dados: entidades Product / Cart / Order / User e Firestore Schema
- Design UI/UX: wireframes + planejamento de biblioteca de componentes + sistema de design tokens
2. Uma História de um Projeto Sem Arquitetura
(1) A Dor: Requisitos Vagos + Arquitetura Caótica
Quando Bob recebeu o projeto ShopApp, o único requisito era: "Construa um aplicativo de e-commerce transfronteiriço." Ele começou a codificar imediatamente, e 3 meses depois descobriu: as histórias de usuário eram imprecisas (funcionalidades de gerenciamento de mercadorias estavam faltando), os modelos de dados mudavam constantemente (Order foi reescrito 3 vezes), e a arquitetura era fortemente acoplada (mudar o pagamento afetava a lista de produtos). O projeto foi atrasado em 2 meses.
(2) Design Primeiro, Código Depois
A fase de design do projeto esclarece requisitos, arquitetura, modelos de dados e planejamento de UI antecipadamente — codificar então se torna "construir a partir da planta."
(3) O Resultado: Tempo de Codificação Reduzido pela Metade + Custo de Mudança Reduzido em 80%
Depois de gastar 2 semanas em design, Bob completou a codificação em 4 semanas (em vez dos anteriores 3+2 meses), e mudanças de requisitos exigiam apenas modificar a camada correspondente sem afetar outros módulos.
3. Análise de Requisitos
(1) Histórias de Usuário
graph TD
subgraph User Stories
ALICE[Alice: Navegar → Pedir → Pagar $299.99]
BOB[Bob: Gerenciar Produtos → Ver Pedidos]
CHARLIE[Charlie: Transfronteiriço → JPY → Pagamento Internacional]
end
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
| Papel | História de Usuário | Prioridade |
|---|---|---|
| Alice (Consumidora) | Navegar lista de milhões de produtos | P0 |
| Alice | Buscar/filtrar/navegar por categoria | P0 |
| Alice | Ver detalhes do produto + avaliações | P0 |
| Alice | Adicionar ao carrinho + gerenciar quantidades | P0 |
| Alice | Liquidação multi-moeda USD/CNY/JPY | P1 |
| Alice | Ver status do pedido + rastreamento logístico | P1 |
| Bob (Mercador) | Gerenciar publicação/despublicação de produtos + estoque | P1 |
| Bob | Ver relatórios de dados de vendas | P2 |
| Charlie (Usuário Transfronteiriço) | Compra transfronteiriça + pagamento internacional | P2 |
| Charlie | Troca de multi-idioma (EN/ZH/JA) | P1 |
4. Seleção de Tecnologia
(1) Decisões de Stack Tecnológico
| Domínio | Seleção | Justificativa |
|---|---|---|
| Framework UI | Flutter 3.x (Material 3) | Multiplataforma + motor de pintura personalizado |
| Gerenciamento de Estado | Riverpod 2.x (@riverpod) | Tipagem segura + geração de código |
| Roteamento | GoRouter | Declarativo + suporte a URL Web |
| Rede | Dio + Interceptors | Interceptors + refresh de Token |
| Backend | Firebase (Auth+FS+Storage) | Zero ops + sincronização em tempo real |
| Cache | Hive | NoSQL leve + offline |
| Armazenamento Criptografado | flutter_secure_storage | Armazenamento seguro de tokens |
| Serialização | json_serializable + freezed | Tipagem segura + imutável |
| Internacionalização | flutter_localizations + intl | Fluxo ARB |
| Testes | flutter_test + mocktail | Unit/Widget/Integration |
| CI/CD | GitHub Actions | Plano gratuito + multi-matriz |
(2) Comparação Riverpod vs BLoC
| Dimensão | Riverpod | BLoC |
|---|---|---|
| Curva de aprendizado | Média | Alta |
| Boilerplate | Menos (@riverpod) | Mais (Event/State/Bloc) |
| Segurança de tipos | Forte | Forte |
| Testes | Simples | Simples |
| Geração de código | ✅ | ❌ |
| Melhor para | Projetos pequenos a médios | Projetos de equipes grandes |
5. Camadas Clean Architecture
graph TD
subgraph Presentation
PAGE[Pages/Widgets]
NOTI[Notifiers/Providers]
end
subgraph Domain
ENT[Entities]
REPO_I[Repository Interfaces]
USECASE[Use Cases]
end
subgraph Data
REPO_IMPL[Repository Impl]
DS[Data Sources]
DTO2[DTOs / Models]
end
PAGE --> NOTI
NOTI --> USECASE
USECASE --> REPO_I
REPO_I -.-> REPO_IMPL
REPO_IMPL --> DS
DS --> DTO2
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
(1) Responsabilidades das Camadas
| Camada | Diretório | Responsabilidade | Direção de Dependência |
|---|---|---|---|
| Presentation | screens/, widgets/ |
UI + interação do usuário | → Application |
| Application | notifiers/, usecases/ |
Lógica de negócio + estado | → Domain |
| Domain | entities/, repositories/ |
Regras de negócio centrais | Sem dependências externas |
| Data | repositories/impl/, datasources/ |
Busca de dados + persistência | → Domain |
(2) Regras de Dependência
- Camada Domain: Dart puro, sem dependências Flutter/terceiros
- Camada Data: Implementa as interfaces Repository do Domain
- Camada Application: Coordena Domain e Presentation
- Camada Presentation: Depende apenas da camada Application
6. Modelagem de Dados
(1) Entidades Centrais
▶ Exemplo
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
: Definições de Entidade de Domínio
// domain/entities/product.dart
// Classe Dart pura, sem dependências externas
class Product {
final int id;
final String name;
final double price;
final String imageUrl;
final String category;
final double rating;
final int reviewCount;
final int stock;
final String currency;
const Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
this.category = 'General',
this.rating = 0.0,
this.reviewCount = 0,
this.stock = 0,
this.currency = 'USD',
});
bool get inStock => stock > 0;
bool get hasDiscount => false; // Extendido em cenário de promoção
}
// domain/entities/cart_item.dart
class CartItem {
final Product product;
final int quantity;
final String? selectedSize;
final String? selectedColor;
const CartItem({
required this.product,
required this.quantity,
this.selectedSize,
this.selectedColor,
});
double get lineTotal => product.price * quantity;
CartItem copyWith({int? quantity, String? selectedSize, String? selectedColor}) =>
CartItem(product: product, quantity: quantity ?? this.quantity,
selectedSize: selectedSize ?? this.selectedSize,
selectedColor: selectedColor ?? this.selectedColor);
}
// domain/entities/address.dart
class Address {
final String name;
final String street;
final String city;
final String state;
final String zip;
final String country;
const Address({
required this.name,
required this.street,
required this.city,
required this.state,
required this.zip,
this.country = 'US',
});
String get fullAddress => '$street, $city, $state $zip, $country';
Map<String, dynamic> toJson() => {
'name': name, 'street': street, 'city': city,
'state': state, 'zip': zip, 'country': country,
};
}
// domain/entities/order.dart
enum OrderStatus { pending, confirmed, shipped, delivered, cancelled }
class Order {
final String id;
final String userId;
final List<CartItem> items;
final double total;
final String currency;
final OrderStatus status;
final DateTime createdAt;
final Address shippingAddress;
const Order({
required this.id,
required this.userId,
required this.items,
required this.total,
this.currency = 'USD',
this.status = OrderStatus.pending,
required this.createdAt,
required this.shippingAddress,
});
}
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
(2) Design do Firestore Schema
firestore/
├── products/{productId}
│ ├── name: string
│ ├── price: number
│ ├── stock: number
│ ├── category: string
│ ├── rating: number
│ ├── imageUrl: string
│ └── currency: string
├── users/{userId}
│ ├── email: string
│ ├── name: string
│ ├── preferences: map
│ │ ├── theme: string
│ │ ├── currency: string
│ │ └── locale: string
│ └── addresses: array
├── orders/{orderId}
│ ├── userId: string
│ ├── items: array<{productId, quantity, price}>
│ ├── total: number
│ ├── currency: string
│ ├── status: string
│ └── createdAt: timestamp
└── reviews/{reviewId}
├── productId: string
├── userId: string
├── rating: number
└── comment: string
```text
```text
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
7. Planejamento UI/UX
(1) Inventário de Páginas
| Página | Rota | Componentes Centrais |
|---|---|---|
| Home | / |
SliverAppBar + GridView + CategoryChips |
| Busca | /search |
SearchBar + FilterSheet + ProductList |
| Detalhes do Produto | /product/:id |
Hero + SliverAppBar + BottomSheet |
| Carrinho | /cart |
CartList + QuantitySelector + TotalBar |
| Checkout | /checkout |
AddressForm + PaymentSelector + OrderSummary |
| Confirmação do Pedido | /order/:id |
OrderTimeline + TrackingMap |
| Perfil | /profile |
UserCard + OrderHistory + Settings |
| Login | /login |
EmailForm + GoogleButton + AppleButton |
(2) Design Tokens
import 'package:flutter/material.dart';
// Design tokens: constantes unificadas de cor/espaçamento/raio/fonte, carregadas por ThemeExtension
class ShopDesignTokens {
// Cores
static const primary = Color(0xFF0066CC);
static const secondary = Color(0xFFFF6B35);
static const sale = Color(0xFF4CAF50);
static const discount = Color(0xFFEF5350);
// Espaçamento
static const xs = 4.0;
static const sm = 8.0;
static const md = 16.0;
static const lg = 24.0;
static const xl = 32.0;
// Raios
static const cardRadius = 12.0;
static const buttonRadius = 8.0;
static const inputRadius = 8.0;
// Tipografia
static const headlineSize = 24.0;
static const titleSize = 18.0;
static const bodySize = 14.0;
static const captionSize = 12.0;
}
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
8. Exemplo Completo: Estrutura de Diretórios do Projeto
shop_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── core/
│ │ ├── router/
│ │ │ └── app_router.dart
│ │ ├── network/
│ │ │ ├── dio_client.dart
│ │ │ └── api_exception.dart
│ │ ├── storage/
│ │ │ ├── secure_storage.dart
│ │ │ ├── hive_cache.dart
│ │ │ └── shared_prefs.dart
│ │ ├── theme/
│ │ │ ├── app_theme.dart
│ │ │ └── brand_tokens.dart
│ │ └── platform/
│ │ └── platform_helper.dart
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── product.dart
│ │ │ ├── cart_item.dart
│ │ │ ├── order.dart
│ │ │ └── user.dart
│ │ └── repositories/
│ │ ├── product_repository.dart
│ │ ├── auth_repository.dart
│ │ └── order_repository.dart
│ ├── data/
│ │ ├── repositories/
│ │ │ ├── product_repository_impl.dart
│ │ │ ├── auth_repository_impl.dart
│ │ │ └── order_repository_impl.dart
│ │ ├── datasources/
│ │ │ ├── firestore_datasource.dart
│ │ │ └── hive_datasource.dart
│ │ └── models/
│ │ ├── product_dto.dart
│ │ └── order_dto.dart
│ ├── application/
│ │ ├── notifiers/
│ │ │ ├── auth_notifier.dart
│ │ │ ├── product_notifier.dart
│ │ │ └── cart_notifier.dart
│ │ └── providers/
│ │ └── infrastructure_providers.dart
│ ├── presentation/
│ │ ├── screens/
│ │ │ ├── home_page.dart
│ │ │ ├── detail_page.dart
│ │ │ ├── cart_page.dart
│ │ │ ├── checkout_page.dart
│ │ │ └── login_page.dart
│ │ └── widgets/
│ │ ├── product_card.dart
│ │ ├── quantity_selector.dart
│ │ ├── cart_badge.dart
│ │ └── price_tag.dart
│ └── l10n/
│ ├── app_en.arb
│ ├── app_zh.arb
│ └── app_ja.arb
├── test/
│ ├── domain/
│ ├── application/
│ └── presentation/
├── integration_test/
├── android/
├── ios/
├── web/
├── windows/
├── macos/
├── pubspec.yaml
├── l10n.yaml
└── .github/workflows/
```text
```text
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
❓ Perguntas Frequentes
P: Preciso de todas as camadas do Clean Architecture? R: Para projetos pequenos a médios, as camadas Application e Domain podem ser mescladas em uma única camada Application. A camada Data é sempre necessária (abstração de fonte de dados).
P: Qual a diferença entre Entity e DTO? R: Uma Entity é um conceito de domínio (usada na lógica de negócio); um DTO é um objeto de transferência de dados (usado para API/banco de dados). Entities não dependem de externos; DTOs podem incluir anotações de serialização JSON.
P: É significativo colocar interfaces Repository na camada Domain? R: Sim. Este é o Princípio da Inversão de Dependência: Domain define a interface, a camada Data a implementa. Assim a camada Domain não depende de bibliotecas externas como Firebase/Dio.
P: Quanto tempo deve levar a fase de design do projeto? R: Projetos pequenos a médios: 1-2 semanas. Projetos grandes: 2-4 semanas. Investir 20% do tempo em design economiza 50% do tempo de desenvolvimento.
P: Como devo otimizar o design de um Firestore Schema? R: 1) Design documentos por relações 1:1 ou 1:N; 2) Evite aninhamento profundo; 3) Desnormalize dados frequentemente lidos (armazene nomes de forma redundante); 4) Normalize dados frequentemente escritos.
P: Como garanto que o design de arquitetura seja realmente seguido? R: 1) Use regras de lint para impor a direção de dependência; 2) Code Review verifica conformidade de camadas; 3) Testes unitários cobrem cada fronteira de camada.
📖 Resumo
- Histórias de usuário direcionam a análise de requisitos, organizadas por papel e prioridade
- Seleção de tecnologia requer equilibrar capacidade da equipe, escala do projeto e maturidade do ecossistema
- Quatro camadas do Clean Architecture: Presentation → Application → Domain → Data
- A camada Domain é Dart puro sem dependências externas; interfaces Repository são definidas no Domain
- Design tokens unificam cor/espaçamento/raio/fonte, carregados por ThemeExtension
📝 Exercícios
- Básico (dificuldade ⭐): Escreva 10 histórias de usuário para o ShopApp, categorizadas por prioridade P0/P1/P2.
- Intermediário (dificuldade ⭐⭐): Design uma estrutura de diretórios de quatro camadas Clean Architecture, definindo entidades Product/Cart/Order e interfaces Repository correspondentes.
- Desafio (dificuldade ⭐⭐⭐): Complete um documento de design de projeto completo: histórias de usuário + tabela comparativa de seleção de tecnologia + diagrama de camadas Clean Architecture + Firestore Schema + inventário de páginas + design tokens.
← Aula Anterior | Próxima Aula →
(2) Exemplo: Grafo de Dependência de Módulos do ShopApp
final Map<String, List<String>> moduleDeps = {
'core': [],
'cart': ['core'],
'checkout': ['core', 'cart'],
'profile': ['core'],
'catalog': ['core'],
};
Saída:
core: 0 dependências diretas
cart: depende de core
checkout: depende de core, cart
profile: depende de core
catalog: depende de core
(3) Exemplo: Tabela de Rotas
final Map<String, String> routes = {
'/': 'CatalogPage',
'/cart': 'CartPage',
'/checkout': 'CheckoutPage',
'/profile': 'ProfilePage',
};
Saída:
/ -> CatalogPage
/cart -> CartPage
/checkout -> CheckoutPage
/profile -> ProfilePage