Construindo um E-commerce com Flutter: Design System, Isar DB e Testes (Parte 1)

Este artigo irá mostrar como criar um projeto voltado a Comércio de Vendas Online, usando o Flutter como Framework e sua linguagem Dart. A partir disso, demonstrarei os primeiros passos para a criação das primeiras telas, primeiros códigos, utilizando banco de dados (Isar Community), controle de temas (cores, tamanhos e letras) e, no final, demonstrando um teste unitário prático.

Um E-commerce não é apenas uma lista de itens; é sobre experiência de usuário e confiabilidade de dados. Vamos construir isso do jeito certo.

🚀 Passo 1: Setup e Arquitetura

Crie o projeto no terminal:

flutter create shop_app
cd shop_app

Vamos organizar nossas pastas pensando em escalabilidade. Dentro de lib/, crie:

lib/
├── core/
│   ├── theme/       # Nossas cores e fontes (Design System)
│   └── constants/   # Strings e valores fixos
├── data/
│   ├── models/      # Modelos do Isar (ex: Produto)
│   └── services/    # Lógica do Banco de Dados
├── modules/         # As telas do app (Home, Carrinho, Detalhe)
└── main.dart

Adicionando Dependências

No pubspec.yaml, vamos adicionar o Isar (banco), Google Fonts (tipografia) e suporte a testes.

dependencies:
  flutter:
    sdk: flutter
  # UI
  google_fonts: ^6.1.0
  intl: ^0.19.0 # Para formatar dinheiro (R$)
  
  # Banco de Dados
  isar_community: ^3.3.0
  isar_community_flutter_labs: ^3.3.0
  path_provider: ^2.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  isar_community_generator: ^3.3.0
  build_runner: ^2.4.8

Rode flutter pub get.

🎨 Passo 2: Controle de Temas (Design System)

Um E-commerce precisa de uma identidade visual forte. Vamos centralizar isso para não ficar espalhando cores Colors.blue pelo código.

Crie o arquivo lib/core/theme/app_theme.dart.

Aqui definiremos nossa paleta de cores e tipografia usando o Material 3.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class AppTheme {
  // Cores da Marca
  static const Color _primaryColor = Color(0xFF6200EE);
  static const Color _secondaryColor = Color(0xFF03DAC6);
  static const Color _errorColor = Color(0xFFB00020);

  // Definição de Texto (Tamanhos e Fontes)
  static TextTheme _buildTextTheme() {
    return TextTheme(
      displayLarge: GoogleFonts.montserrat(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black87),
      titleLarge: GoogleFonts.poppins(fontSize: 22, fontWeight: FontWeight.w600, color: Colors.black87),
      bodyLarge: GoogleFonts.openSans(fontSize: 16, color: Colors.black54),
      bodyMedium: GoogleFonts.openSans(fontSize: 14, color: Colors.black87),
      labelLarge: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
    );
  }

  // O Tema Final
  static ThemeData get lightTheme {
    return ThemeData(
      useMaterial3: true,
      colorScheme: ColorScheme.fromSeed(
        seedColor: _primaryColor,
        primary: _primaryColor,
        secondary: _secondaryColor,
        error: _errorColor,
        brightness: Brightness.light,
      ),
      textTheme: _buildTextTheme(),
      
      // Estilo padrão dos botões
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
          backgroundColor: _primaryColor,
          foregroundColor: Colors.white,
        ),
      ),
      
      // Estilo dos Cards de Produto
      cardTheme: CardTheme(
        elevation: 4,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        color: Colors.white,
        surfaceTintColor: Colors.white, // Remove o tint do Material 3
      ),
    );
  }
}

Agora, no main.dart, aplicamos esse tema:

import 'package:flutter/material.dart';
import 'core/theme/app_theme.dart';
// import 'modules/home/home_screen.dart'; // Criaremos em breve

void main() {
  runApp(const ShopApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Shop',
      theme: AppTheme.lightTheme, // Aplica nosso Design System
      home: const Scaffold(body: Center(child: Text('Loja Iniciando...'))), // Placeholder
      debugShowCheckedModeBanner: false,
    );
  }
}

💾 Passo 3: Banco de Dados (Isar) e Modelagem

Vamos criar o modelo Product. Um produto precisa de preço, nome, descrição e uma URL de imagem.

Crie lib/data/models/product.dart:

import 'package:isar/isar.dart';

part 'product.g.dart';

@collection
class Product {
  Id id = Isar.autoIncrement;

  late String name;
  late String description;
  late double price;
  late String imageUrl;
  
  @Index() // Para buscas rápidas
  late String category;

  bool isFavorite = false;

  // Método auxiliar para formatar preço (lógica de negócio simples)
  String get formattedPrice {
    return 'R\$ ${price.toStringAsFixed(2).replaceAll('.', ',')}';
  }
}

Rode o gerador:

dart run build_runner build

🖥️ Passo 4: A Primeira Tela (Home Skeleton)

Vamos criar uma tela simples apenas para visualizar nosso Tema e Fonte aplicados.

Crie lib/modules/home_screen.dart:

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Ofertas do Dia', style: Theme.of(context).textTheme.titleLarge),
        actions: [
          IconButton(onPressed: () {}, icon: const Icon(Icons.shopping_cart_outlined))
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Destaques',
              style: Theme.of(context).textTheme.displayLarge?.copyWith(fontSize: 24),
            ),
            const SizedBox(height: 10),
            // Exemplo de Card usando nosso Tema
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    const Icon(Icons.image, size: 80, color: Colors.grey),
                    const SizedBox(height: 10),
                    Text('Tênis Esportivo', style: Theme.of(context).textTheme.titleLarge),
                    Text('Conforto para sua corrida', style: Theme.of(context).textTheme.bodyLarge),
                    const SizedBox(height: 10),
                    ElevatedButton(
                      onPressed: () {},
                      child: const Text('COMPRAR AGORA'),
                    )
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Atualize o main.dart para chamar home: const HomeScreen().

🧪 Passo 5: Testes Unitários

Qualidade é essencial. Vamos criar um teste unitário para garantir que nossa lógica de formatação de preço no modelo Product está correta. Testar lógica de modelo é o primeiro passo para TDD (Test Driven Development).

Crie (ou edite) o arquivo test/product_test.dart:

import 'package:flutter_test/flutter_test.dart';
import 'package:shop_app/data/models/product.dart';

void main() {
  group('Product Model Test', () {
    test('Deve formatar o preço corretamente para o padrão BRL', () {
      // 1. Arrange (Preparação)
      final product = Product()
        ..name = 'Camiseta'
        ..description = 'Algodão'
        ..price = 199.90
        ..imageUrl = ''
        ..category = 'Roupas';

      // 2. Act (Ação)
      final result = product.formattedPrice;

      // 3. Assert (Verificação)
      expect(result, 'R\$ 199,90');
    });

    test('Deve formatar valor inteiro corretamente', () {
      final product = Product()..price = 50.0;
      expect(product.formattedPrice, 'R\$ 50,00');
    });
  });
}

Para rodar o teste, execute no terminal:

flutter test

Se você vir All tests passed!, parabéns! Você configurou seu ambiente, banco de dados, tema e ainda garantiu a qualidade do código.

✅ Conclusão

Neste primeiro passo, não saímos codando telas aleatórias. Nós:

  1. Definimos uma Arquitetura Limpa de pastas.

  2. Criamos um Design System centralizado em AppTheme.

  3. Configuramos o banco Isar.

  4. Escrevemos Testes Unitários para a lógica de negócios.

No próximo artigo, vamos popular o banco de dados com produtos reais, criar a Grid de Produtos e implementar a navegação para a tela de detalhes.

Gostou da abordagem profissional? Deixe seu feedback! 🚀🛍️

Please follow and like us:
error0
fb-share-icon
Tweet 20
fb-share-icon20