🛍️ E-commerce App Example
Exemplo completo de uma aplicação de e-commerce usando GoRouter Modular com arquitetura modular.
🏗️ Estrutura da Aplicação
e_commerce_app/
├── lib/
│ ├── main.dart
│ ├── app_module.dart
│ └── modules/
│ ├── auth/
│ │ ├── auth_module.dart
│ │ └── pages/
│ ├── products/
│ │ ├── product_module.dart
│ │ └── pages/
│ ├── cart/
│ │ ├── cart_module.dart
│ │ └── pages/
│ └── checkout/
│ ├── checkout_module.dart
│ └── pages/
🚀 Configuração Principal
// main.dart
import 'package:flutter/material.dart';
import 'package:go_router_modular/go_router_modular.dart';
import 'app_module.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GoRouterModular.configure(
appModule: AppModule(),
initialRoute: '/home',
debugLogEventBus: true,
);
runApp(ECommerceApp());
}
class ECommerceApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'E-commerce Modular',
routerConfig: GoRouterModular.routerConfig,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
);
}
}
📦 Módulo Principal
// app_module.dart
import 'package:go_router_modular/go_router_modular.dart';
import 'modules/auth/auth_module.dart';
import 'modules/products/product_module.dart';
import 'modules/cart/cart_module.dart';
import 'modules/checkout/checkout_module.dart';
class AppModule extends Module {
@override
List<ModularRoute> get routes => [
// Rota inicial
ChildRoute('/', child: (context, state) => SplashPage()),
ChildRoute('/home', child: (context, state) => HomePage()),
// MĂłdulos de funcionalidades
ModuleRoute('/auth', module: AuthModule()),
ModuleRoute('/products', module: ProductModule()),
ModuleRoute('/cart', module: CartModule()),
ModuleRoute('/checkout', module: CheckoutModule()),
];
@override
void binds(Injector i) {
// Serviços globais
i.addSingleton<ApiService>(() => ApiService());
i.addSingleton<AuthService>(() => AuthService());
i.addSingleton<CartService>(() => CartService());
i.addSingleton<NavigationService>(() => NavigationService());
}
}
đź›’ MĂłdulo de Produtos
// modules/products/product_module.dart
import 'package:go_router_modular/go_router_modular.dart';
class ProductModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => ProductListPage()),
ChildRoute('/:id', child: (context, state) => ProductDetailPage(
productId: state.pathParameters['id']!,
)),
ChildRoute('/category/:category', child: (context, state) => CategoryPage(
category: state.pathParameters['category']!,
)),
];
@override
void binds(Injector i) {
i.addSingleton<ProductRepository>(() => ProductRepository());
i.addFactory<ProductBloc>(() => ProductBloc());
}
@override
void listen() {
// Escuta eventos de outros mĂłdulos
on<UserLoginEvent>((event, context) {
_loadUserRecommendations(event.userId);
});
on<AddToCartEvent>((event, context) {
_updateProductMetrics(event.productId);
});
}
void _loadUserRecommendations(String userId) {
final productRepo = Modular.get<ProductRepository>();
productRepo.loadRecommendations(userId);
}
void _updateProductMetrics(String productId) {
// Atualizar métricas do produto
ModularEvent.fire(ProductViewedEvent(
productId: productId,
timestamp: DateTime.now(),
));
}
}
🛍️ Módulo do Carrinho
// modules/cart/cart_module.dart
import 'package:go_router_modular/go_router_modular.dart';
class CartModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => CartPage()),
ChildRoute('/summary', child: (context, state) => CartSummaryPage()),
];
@override
void binds(Injector i) {
i.addFactory<CartBloc>(() => CartBloc());
}
@override
void listen() {
on<AddToCartEvent>((event, context) {
_addItemToCart(event.productId, event.quantity);
});
on<RemoveFromCartEvent>((event, context) {
_removeItemFromCart(event.itemId);
});
on<UserLogoutEvent>((event, context) {
_clearCart();
});
}
void _addItemToCart(String productId, int quantity) {
final cartService = Modular.get<CartService>();
cartService.addItem(productId, quantity);
// Notificar outros mĂłdulos
ModularEvent.fire(CartUpdatedEvent(
itemCount: cartService.itemCount,
total: cartService.total,
));
}
void _removeItemFromCart(String itemId) {
final cartService = Modular.get<CartService>();
cartService.removeItem(itemId);
ModularEvent.fire(CartUpdatedEvent(
itemCount: cartService.itemCount,
total: cartService.total,
));
}
void _clearCart() {
final cartService = Modular.get<CartService>();
cartService.clear();
ModularEvent.fire(CartClearedEvent());
}
}
đź’ł MĂłdulo de Checkout
// modules/checkout/checkout_module.dart
import 'package:go_router_modular/go_router_modular.dart';
class CheckoutModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => CheckoutPage()),
ChildRoute('/payment', child: (context, state) => PaymentPage()),
ChildRoute('/confirmation', child: (context, state) => OrderConfirmationPage()),
];
@override
void binds(Injector i) {
i.addSingleton<PaymentService>(() => PaymentService());
i.addSingleton<OrderService>(() => OrderService());
i.addFactory<CheckoutBloc>(() => CheckoutBloc());
}
@override
void listen() {
on<ProcessPaymentEvent>((event, context) {
_processPayment(event.paymentData);
});
on<OrderCreatedEvent>((event, context) {
_handleOrderCreated(event.orderId);
});
}
Future<void> _processPayment(PaymentData paymentData) async {
try {
final paymentService = Modular.get<PaymentService>();
final result = await paymentService.processPayment(paymentData);
if (result.success) {
ModularEvent.fire(PaymentSuccessEvent(
transactionId: result.transactionId,
amount: paymentData.amount,
));
// Criar pedido
await _createOrder(paymentData);
} else {
ModularEvent.fire(PaymentFailedEvent(
error: result.error,
amount: paymentData.amount,
));
}
} catch (error) {
ModularEvent.fire(PaymentErrorEvent(
error: error.toString(),
));
}
}
Future<void> _createOrder(PaymentData paymentData) async {
final orderService = Modular.get<OrderService>();
final cartService = Modular.get<CartService>();
final order = await orderService.createOrder(
items: cartService.items,
paymentData: paymentData,
);
ModularEvent.fire(OrderCreatedEvent(
orderId: order.id,
total: order.total,
));
// Limpar carrinho apĂłs pedido criado
cartService.clear();
}
void _handleOrderCreated(String orderId) {
// Navegar para confirmação
Modular.to.pushNamed('/checkout/confirmation', arguments: orderId);
// Notificar outros mĂłdulos
ModularEvent.fire(CartClearedEvent());
}
}
🎠Eventos da Aplicação
// events/app_events.dart
// Eventos de Autenticação
class UserLoginEvent {
final String userId;
final String email;
final DateTime loginTime;
UserLoginEvent({
required this.userId,
required this.email,
required this.loginTime,
});
}
class UserLogoutEvent {
final String userId;
final DateTime logoutTime;
UserLogoutEvent({
required this.userId,
required this.logoutTime,
});
}
// Eventos de Carrinho
class AddToCartEvent {
final String productId;
final int quantity;
AddToCartEvent({
required this.productId,
required this.quantity,
});
}
class RemoveFromCartEvent {
final String itemId;
RemoveFromCartEvent({required this.itemId});
}
class CartUpdatedEvent {
final int itemCount;
final double total;
CartUpdatedEvent({
required this.itemCount,
required this.total,
});
}
class CartClearedEvent {}
// Eventos de Produtos
class ProductViewedEvent {
final String productId;
final DateTime timestamp;
ProductViewedEvent({
required this.productId,
required this.timestamp,
});
}
// Eventos de Pagamento
class ProcessPaymentEvent {
final PaymentData paymentData;
ProcessPaymentEvent({required this.paymentData});
}
class PaymentSuccessEvent {
final String transactionId;
final double amount;
PaymentSuccessEvent({
required this.transactionId,
required this.amount,
});
}
class PaymentFailedEvent {
final String error;
final double amount;
PaymentFailedEvent({
required this.error,
required this.amount,
});
}
class PaymentErrorEvent {
final String error;
PaymentErrorEvent({required this.error});
}
// Eventos de Pedidos
class OrderCreatedEvent {
final String orderId;
final double total;
OrderCreatedEvent({
required this.orderId,
required this.total,
});
}
🏠Home Page com Navegação
// pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:go_router_modular/go_router_modular.dart';
class HomePage extends StatefulWidget {
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _cartItemCount = 0;
@override
void initState() {
super.initState();
_listenToCartUpdates();
}
void _listenToCartUpdates() {
// Escutar atualizações do carrinho via EventModule
final homeModule = Modular.get<HomeModule>();
homeModule.on<CartUpdatedEvent>((event, context) {
setState(() {
_cartItemCount = event.itemCount;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('E-commerce App'),
actions: [
IconButton(
icon: Stack(
children: [
Icon(Icons.shopping_cart),
if (_cartItemCount > 0)
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
constraints: BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
'$_cartItemCount',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
textAlign: TextAlign.center,
),
),
),
],
),
onPressed: () => context.go('/cart'),
),
],
),
body: GridView.builder(
padding: EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.8,
),
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
return CategoryCard(
category: category,
onTap: () => context.go('/products/category/${category.id}'),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.go('/products'),
child: Icon(Icons.search),
tooltip: 'Ver todos os produtos',
),
);
}
}
class CategoryCard extends StatelessWidget {
final Category category;
final VoidCallback onTap;
const CategoryCard({
required this.category,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
image: DecorationImage(
image: NetworkImage(category.imageUrl),
fit: BoxFit.cover,
),
),
),
),
Padding(
padding: EdgeInsets.all(8),
child: Text(
category.name,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
🔄 Fluxo de Navegação
- Splash Screen → Home Page
- Home Page → Product Categories → Product List → Product Detail
- Product Detail → Add to Cart → Cart Page
- Cart Page → Checkout → Payment → Order Confirmation
âś… BenefĂcios desta Arquitetura
- âś… Modularidade: Cada funcionalidade em seu prĂłprio mĂłdulo
- ✅ Comunicação: EventModule permite comunicação entre módulos
- ✅ Escalabilidade: Fácil adicionar novas funcionalidades
- âś… Testabilidade: Cada mĂłdulo pode ser testado independentemente
- âś… Manutenibilidade: CĂłdigo organizado e bem estruturado
- ✅ Performance: Lazy loading de módulos conforme necessário
đź§Ş Testes
// test/modules/cart/cart_module_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router_modular/go_router_modular.dart';
void main() {
group('CartModule Tests', () {
late CartModule cartModule;
setUp(() {
cartModule = CartModule();
// Setup de dependĂŞncias para teste
});
test('should add item to cart when AddToCartEvent is fired', () {
// Arrange
final event = AddToCartEvent(productId: 'product123', quantity: 2);
// Act
ModularEvent.fire(event);
// Assert
// Verificar se o item foi adicionado ao carrinho
});
test('should update cart count when item is added', () {
// Teste de atualização do contador
});
});
}
Este exemplo demonstra uma aplicação completa de e-commerce usando GoRouter Modular, mostrando como organizar código em módulos, comunicação via eventos e navegação type-safe.