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:
-
Definimos uma Arquitetura Limpa de pastas.
-
Criamos um Design System centralizado em
AppTheme. -
Configuramos o banco Isar.
-
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! 🚀🛍️