Construindo um E-commerce com Flutter: Splash Screen, BLoC e Atualizações (Parte Final)
Este ultimo capitulo 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, irei demonstrar os novos passos e atualizações, como a Tela de Splash Screen (Primeira Tela), a atualização das telas com novos códigos baseados a partir do Flutter 3.41, a refatoração para utilizar o BLoC (Business Logic Component) em todos os fluxos, e uma revisão geral do projeto, utilizando o poder do Material 3.
Vamos finalizar esse projeto com chave de ouro!
📦 Passo 1: Atualizando Dependências e Preparando o Terreno
Para a nossa refatoração, vamos substituir o gerenciamento de estado simples pelo ecossistema do BLoC. Adicione ao seu pubspec.yaml:
dependencies: # ... dependências anteriores (isar, go_router, etc.) flutter_bloc: ^8.1.5 equatable: ^2.0.5 flutter_animate: ^4.5.0 # Para animações fluidas na Splash Screen
Rode flutter pub get para atualizar.
✨ Passo 2: A Tela de Splash Screen (A Primeira Impressão)
A Splash Screen é a primeira tela que o usuário vê. Ela serve para carregar dependências iniciais (como inicializar o Isar ou verificar se o usuário está logado) enquanto exibe a marca do app.
Crie lib/modules/splash/splash_screen.dart:
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
_initializeApp();
}
Future<void> _initializeApp() async {
// Simula o tempo de carregamento de dependências, banco Isar, tokens, etc.
await Future.delayed(const Duration(seconds: 3));
// Após carregar, navega para a Home (ou Login, dependendo do estado da autenticação)
if (mounted) {
context.go('/home');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primary,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.shopping_bag, size: 100, color: Colors.white)
.animate()
.scale(duration: 600.ms, curve: Curves.easeOutBack)
.then()
.shimmer(duration: 1200.ms, color: Colors.white54),
const SizedBox(height: 24),
Text(
'Flutter Shop',
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
).animate().fadeIn(delay: 400.ms).slideY(begin: 0.5, end: 0),
],
),
),
);
}
}
Nota para o app_router.dart: Atualize a initialLocation para '/splash' e crie a rota correspondente para esta tela.
🧠 Passo 3: Refatorando para BLoC (Padrão Ouro do Mercado)
Na Parte 3, usamos um ChangeNotifier simples para o Carrinho. Com o crescimento do app, o BLoC garante uma separação estrita entre UI e Lógica de Negócios, facilitando testes e escalabilidade.
Vamos refatorar o Carrinho de Compras.
3.1 Eventos do Carrinho (cart_event.dart)
O que pode acontecer no carrinho?
import 'package:equatable/equatable.dart';
import '../../data/models/product.dart';
sealed class CartEvent extends Equatable {
const CartEvent();
@override
List<Object> get props => [];
}
class AddProductToCart extends CartEvent {
final Product product;
const AddProductToCart(this.product);
@override
List<Object> get props => [product];
}
class RemoveProductFromCart extends CartEvent {
final int productId;
const RemoveProductFromCart(this.productId);
@override
List<Object> get props => [productId];
}
class ClearCart extends CartEvent {}
3.2 Estados do Carrinho (cart_state.dart)
Como a UI deve reagir? Usando as facilidades do Dart moderno, tornamos o estado imutável.
import 'package:equatable/equatable.dart';
import '../../data/models/product.dart';
// Modelo auxiliar para os itens
class CartItem extends Equatable {
final Product product;
final int quantity;
const CartItem({required this.product, this.quantity = 1});
CartItem copyWith({int? quantity}) {
return CartItem(product: product, quantity: quantity ?? this.quantity);
}
@override
List<Object> get props => [product, quantity];
}
class CartState extends Equatable {
final List<CartItem> items;
final double total;
const CartState({this.items = const [], this.total = 0.0});
CartState copyWith({List<CartItem>? items, double? total}) {
return CartState(
items: items ?? this.items,
total: total ?? this.total,
);
}
@override
List<Object> get props => [items, total];
}
3.3 A Lógica do BLoC (cart_bloc.dart)
import 'package:flutter_bloc/flutter_bloc.dart';
import 'cart_event.dart';
import 'cart_state.dart';
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc() : super(const CartState()) {
on<AddProductToCart>(_onAddProduct);
on<RemoveProductFromCart>(_onRemoveProduct);
on<ClearCart>((event, emit) => emit(const CartState()));
}
void _onAddProduct(AddProductToCart event, Emitter<CartState> emit) {
final List<CartItem> updatedItems = List.from(state.items);
final index = updatedItems.indexWhere((i) => i.product.id == event.product.id);
if (index >= 0) {
updatedItems[index] = updatedItems[index].copyWith(quantity: updatedItems[index].quantity + 1);
} else {
updatedItems.add(CartItem(product: event.product));
}
final newTotal = _calculateTotal(updatedItems);
emit(state.copyWith(items: updatedItems, total: newTotal));
}
void _onRemoveProduct(RemoveProductFromCart event, Emitter<CartState> emit) {
final updatedItems = state.items.where((i) => i.product.id != event.productId).toList();
emit(state.copyWith(items: updatedItems, total: _calculateTotal(updatedItems)));
}
double _calculateTotal(List<CartItem> items) {
return items.fold(0, (sum, item) => sum + (item.product.price * item.quantity));
}
}
⚡ Passo 4: Atualizações Flutter 3.41+ e Integração da UI
As versões mais recentes do Flutter e Dart trazem otimizações no motor gráfico (Impeller) e sintaxes mais curtas (como o Pattern Matching com switch expressions). Vamos atualizar nossa main.dart para injetar o BLoC globalmente usando o Material 3.
Atualizando o main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'core/theme/app_theme.dart';
import 'router/app_router.dart';
import 'blocs/cart/cart_bloc.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Inicialização do Isar ocorreria aqui...
runApp(
// MultiBlocProvider permite adicionar mais BLoCs no futuro (ex: AuthBloc, ThemeBloc)
MultiBlocProvider(
providers: [
BlocProvider<CartBloc>(create: (_) => CartBloc()),
],
child: const ShopApp(),
),
);
}
class ShopApp extends StatelessWidget {
const ShopApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter E-commerce',
theme: AppTheme.lightTheme, // Nosso Design System Material 3
routerConfig: appRouter,
debugShowCheckedModeBanner: false,
);
}
}
Atualizando a StoreHomeScreen (Ouvindo o BLoC):
Em vez de usar o antigo ListenableBuilder, utilizamos os poderosos widgets do flutter_bloc. O Flutter moderno preza por código declarativo e limpo.
// Na StoreHomeScreen, atualize a seção da SliverAppBar:
SliverAppBar.large(
title: const Text('Discover Shop'),
actions: [
// BlocBuilder reconstrói apenas o ícone do carrinho quando o estado muda
BlocBuilder<CartBloc, CartState>(
builder: (context, state) {
return Badge(
label: Text('${state.items.length}'),
isLabelVisible: state.items.isNotEmpty,
child: IconButton(
icon: const Icon(Icons.shopping_cart_outlined),
onPressed: () => context.push('/checkout'),
),
);
},
),
IconButton(
icon: const Icon(Icons.person_outline),
onPressed: () => context.push('/auth/login'),
),
],
),
Atualizando a Ação de Compra (Exemplo no Detalhe do Produto):
// Dentro do ProductDetailScreen, ao clicar em "Adicionar ao Carrinho":
FilledButton.icon(
onPressed: () {
// Dispara o Evento para o BLoC
context.read<CartBloc>().add(AddProductToCart(product));
// SnackBar nativo do M3
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${product.name} adicionado ao carrinho!'),
behavior: SnackBarBehavior.floating, // Estilo moderno
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
);
},
icon: const Icon(Icons.shopping_bag),
label: const Text("ADICIONAR AO CARRINHO"),
)
🏁 Revisão Geral do Projeto (Finalizando)
Chegamos ao fim da nossa série de E-commerce. Vamos olhar para a arquitetura sólida que construímos:
-
Fundação: Usamos Isar para um banco de dados local ultrarrápido (que pode ser facilmente espelhado para a nuvem futuramente).
-
Roteamento: Implementamos o GoRouter, lidando com Deep Links, abas persistentes (Menu Lateral) e passagens de rotas complexas.
-
Interface de Usuário: Aplicamos rigorosamente as diretrizes do Material 3, utilizando
Slivers, animaçõesHero, e uma tipografia unificada com Google Fonts. -
Autenticação e Permissões: Separamos a visão do lojista (Admin) da visão do consumidor final.
-
Gerenciamento de Estado Sênior: Finalizamos migrando toda a lógica de negócio para o BLoC, padrão amplamente exigido em vagas de nível Pleno e Sênior no mercado.
Próximos Passos (Para Você)
O código está pronto e estruturado. Se você deseja evoluir este projeto para seu portfólio ou para produção, os próximos passos ideais seriam:
-
Integrar uma API de Pagamento Real (Stripe ou Mercado Pago).
-
Adicionar chamadas HTTP (usando o pacote
dio) se quiser separar o catálogo em um backend Node.js, Python ou Supabase. -
Escrever testes unitários para o
CartBlocusando a bibliotecabloc_test.
Desenvolver em Flutter é como montar um quebra-cabeça perfeito, onde a performance nativa encontra um design espetacular.
Obrigado por acompanhar esta série técnica! Qual será o seu próximo grande aplicativo? 🚀