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

1. O Que Você Vai Aprender


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

100%
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
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.
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
💡 Dica: O ShopApp escolheu Riverpod porque tem menos código, a geração de código reduz boilerplate, e a curva de aprendizado é moderada.


5. Camadas Clean Architecture

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

(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


6. Modelagem de Dados

(1) Entidades Centrais

▶ Exemplo

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.

: Definições de Entidade de Domínio

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

(2) Design do Firestore Schema

TEXT
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

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

8. Exemplo Completo: Estrutura de Diretórios do Projeto

TEXT
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


📝 Exercícios

  1. Básico (dificuldade ⭐): Escreva 10 histórias de usuário para o ShopApp, categorizadas por prioridade P0/P1/P2.
  2. Intermediário (dificuldade ⭐⭐): Design uma estrutura de diretórios de quatro camadas Clean Architecture, definindo entidades Product/Cart/Order e interfaces Repository correspondentes.
  3. 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

DART
final Map<String, List<String>> moduleDeps = {
  'core': [],
  'cart': ['core'],
  'checkout': ['core', 'cart'],
  'profile': ['core'],
  'catalog': ['core'],
};

Saída:

TEXT
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

DART
final Map<String, String> routes = {
  '/': 'CatalogPage',
  '/cart': 'CartPage',
  '/checkout': 'CheckoutPage',
  '/profile': 'ProfilePage',
};

Saída:

TEXT
/         -> CatalogPage
/cart     -> CartPage
/checkout -> CheckoutPage
/profile  -> ProfilePage
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%