Gerenciamento de Estado Avançado com Cubit e Sealed Classes no Flutter

Introdução

O Cubit, parte do pacote flutter_bloc, é uma solução simples e poderosa para gerenciar estado em Flutter. Ele permite criar estados reativos e previsíveis com uma curva de aprendizado menor que o BLoC. Combinado com Sealed Classes, o Dart oferece um sistema de tipos mais robusto para estruturar estados complexos, garantindo mais segurança e legibilidade no código.

Neste tutorial, você aprenderá a implementar o gerenciamento de estado usando Cubit e Sealed Classes no Dart, explorando os seguintes tópicos:

  • Configuração inicial do projeto
  • Criação de estados usando Sealed Classes
  • Implementação de um Cubit para gerenciar estados
  • Integração com widgets do Flutter
  • Boas práticas para arquitetura e testes

1. Configurando o Projeto

Adicione o pacote flutter_bloc ao seu projeto:


dependencies:
  flutter_bloc: ^8.0.0

Certifique-se de que está usando uma versão recente do Dart que suporte Sealed Classes (Dart 3.0 ou superior).

2. Criando Estados com Sealed Classes

Sealed Classes permitem criar hierarquias de estados que são fáceis de manter e garantem que todas as possibilidades sejam tratadas. Aqui está um exemplo para um sistema de autenticação:


sealed class AuthState {}

class AuthInitial extends AuthState {}

class AuthLoading extends AuthState {}

class Authenticated extends AuthState {
  final String username;

  Authenticated(this.username);
}

class AuthError extends AuthState {
  final String message;

  AuthError(this.message);
}

Com essa estrutura, é possível representar os estados iniciais, de carregamento, autenticado e erro de forma clara e imutável.

3. Criando o Cubit

O Cubit é responsável por gerenciar as transições entre os estados definidos. Aqui está um exemplo:


import 'package:flutter_bloc/flutter_bloc.dart';

class AuthCubit extends Cubit {
  AuthCubit() : super(AuthInitial());

  Future login(String username, String password) async {
    emit(AuthLoading());
    try {
      // Simulação de autenticação
      await Future.delayed(Duration(seconds: 2));
      if (username == "admin" && password == "1234") {
        emit(Authenticated(username));
      } else {
        emit(AuthError("Credenciais inválidas"));
      }
    } catch (e) {
      emit(AuthError("Erro inesperado"));
    }
  }

  void logout() {
    emit(AuthInitial());
  }
}

O método login simula uma autenticação e altera os estados com base no sucesso ou falha da operação.

4. Consumindo o Cubit no Flutter

Com o Cubit configurado, você pode usá-lo em widgets Flutter. Aqui está um exemplo de integração:


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

class AuthScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AuthCubit(),
      child: Scaffold(
        appBar: AppBar(title: Text("Cubit + Sealed Classes")),
        body: BlocConsumer(
          listener: (context, state) {
            if (state is Authenticated) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text("Bem-vindo, \${state.username}!")),
              );
            } else if (state is AuthError) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text(state.message)),
              );
            }
          },
          builder: (context, state) {
            if (state is AuthInitial) {
              return _buildLoginForm(context);
            } else if (state is AuthLoading) {
              return Center(child: CircularProgressIndicator());
            } else if (state is Authenticated) {
              return _buildAuthenticatedScreen(context, state);
            } else if (state is AuthError) {
              return _buildLoginForm(context, errorMessage: state.message);
            }
            return Container();
          },
        ),
      ),
    );
  }

  Widget _buildLoginForm(BuildContext context, {String? errorMessage}) {
    final usernameController = TextEditingController();
    final passwordController = TextEditingController();

    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          if (errorMessage != null) ...[
            Text(errorMessage, style: TextStyle(color: Colors.red)),
            SizedBox(height: 8),
          ],
          TextField(
            controller: usernameController,
            decoration: InputDecoration(labelText: "Usuário"),
          ),
          TextField(
            controller: passwordController,
            decoration: InputDecoration(labelText: "Senha"),
            obscureText: true,
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {
              context.read().login(
                    usernameController.text,
                    passwordController.text,
                  );
            },
            child: Text("Entrar"),
          ),
        ],
      ),
    );
  }

  Widget _buildAuthenticatedScreen(BuildContext context, Authenticated state) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text("Bem-vindo, \${state.username}!", style: TextStyle(fontSize: 24)),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: () => context.read().logout(),
            child: Text("Sair"),
          ),
        ],
      ),
    );
  }
}

Conclusão

Combinando o poder do Cubit e das Sealed Classes, você pode criar sistemas de gerenciamento de estado robustos, escaláveis e fáceis de manter. Este tutorial demonstrou como estruturar estados complexos e integrá-los eficientemente em widgets Flutter. Para projetos maiores, considere usar BLoC, que é uma extensão natural do Cubit, adicionando suporte completo a eventos.