Como Integrar Flutter Modular com Flutter Bloc (Cubit) de Forma Simples e Usando Estados Customizados (Classes)
O mundo das libs do Flutter está em constante evolução. Mas, e se pudéssemos combinar o poder do Flutter Modular com a simplicidade do Flutter Bloc (Cubit)? Neste artigo, vamos explorar exatamente isso e desvendar os segredos dessa integração poderosa.
Flutter Modular
Flutter Modular é um pacote que simplifica a organização e a modularização de projetos Flutter. Ele oferece uma estrutura baseada em módulos. Além de proporcionar a injeção de dependencias e a navegação por rotas.
Cubit
Cubit é uma implementação do padrão de design Bloc (Business Logic Component) no Flutter, desenvolvido pela comunidade. Ele simplifica o gerenciamento de estado em aplicativos Flutter, tornando-o mais fácil de entender e manter. Se você quiser mergulhar mais fundo no Cubit, confira meu outro artigo aqui.
Instalando as libs
Este processo é essencial para garantir que você tenha acesso às funcionalidades do Cubit e do Modular no seu aplicativo.
flutter pub add flutter_modular flutter_bloc
Integração do Modular + Cubit
Para integrar o Flutter Modular com o Flutter Bloc (Cubit), adotaremos a estrutura de pastas padrão do Modular que promove uma separação clara dos modulos do projeto.
Nossa estrutura de pastas foi organizada da seguinte forma:
| lib
|— main.dart
|— app/
| — — app_module.dart
| — — app_widget.dart
| — — home/
| — — — home_cubit.dart
| — — — home_module.dart
| — — — home_page.dart
Agora para explicar melhor irei apresentar os trechos de código e explicar os pontos mais importantes.
// main.dart
void main() {
runApp(
ModularApp(
module: AppModule(),
child: const AppWidget(),
),
);
}
// app_module.dart
class AppModule extends Module {
@override
List<Module> get imports => const [];
@override
void binds(Injector i) {}
@override
void exportedBinds(Injector i) {}
@override
void routes(RouteManager r) {
r.module('/', module: HomeModule());
}
}
A versão do
flutter_modular
usada nesse artigo é a6.3.3
e a forma de herdar os métodos da classe Module é dessa forma apresentada acima.
O Module
é uma classe do pacote flutter_modular
que ajuda na organização e modularização de projetos Flutter.
imports
: retorna uma lista de módulos que este módulo importa. Neste caso, não há nenhum módulo importado ([]
).
binds
: é usado para definir as dependências (bindings) do módulo. Aqui, estamos deixando-o vazio, o que significa que não estamos definindo nenhuma dependência neste módulo específico.
exportedBinds
: é usado para definir as dependências que serão exportadas para outros módulos. Assim como o binds
, estamos deixando-o vazio neste exemplo.
routes
: O método routes
é usado para definir as rotas do módulo. Uma rota é uma URL que direciona para uma determinada parte do aplicativo. Aqui, estamos definindo a rota raiz (/
) para apontar para o módulo HomeModule
.
Em resumo, o arquivo app_module.dart
está definindo o módulo principal do aplicativo (AppModule
). Ele importa outros módulos, define dependências, exporta dependências e configura as rotas do aplicativo. Essas configurações são importantes para organizar e estruturar o projeto.
// app_widget.dart
class AppWidget extends StatelessWidget {
const AppWidget({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
theme: ThemeData.light(),
title: 'Modular Cubit Example',
routeInformationParser: Modular.routeInformationParser,
routerDelegate: Modular.routerDelegate,
);
}
}
o AppWidget
configura e inicializa o aplicativo Flutter, fornecendo temas, títulos e configurações de roteamento personalizadas usando o MaterialApp.router
. Isso é essencial para integrar o Flutter Modular com o Flutter Bloc (Cubit) e garantir uma experiência de usuário suave e consistente.
// home_module.dart
class HomeModule extends Module {
@override
List<Module> get imports => const [];
@override
void binds(Injector i) {}
@override
void exportedBinds(Injector i) {}
@override
void routes(RouteManager r) {
r.child(
'/',
child: (_) => BlocProvider(
create: (_) => HomeCubit(),
child: const HomePage(),
),
);
}
}
o HomeModule
define as configurações específicas do módulo de Home do aplicativo, incluindo a definição das rotas e a configuração do cubit HomeCubit
para a página inicial (HomePage
).
Vale ressaltar que dentro de r.child
existe um BlocProvider
ele está fornecendo o HomeCubit()
para a árvore de widgets dessa rota. Isso é útil quando você precisa que uma classe cubit esteja disponível para os widgets dentro de uma determinada rota, mas não globalmente em todo o aplicativo.
No exemplo do home_module.dart
, o BlocProvider
está envolvendo a HomePage
. Isso significa que qualquer widget dentro de HomePage
que precisar acessar o HomeCubit
terá acesso a ele.
// home_page.dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final HomeCubit homeCubit = BlocProvider.of<HomeCubit>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<HomeCubit, CounterState>(
builder: (context, state) {
return Text('Counter: ${state.count}');
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FloatingActionButton(
onPressed: () => homeCubit.increment(),
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => homeCubit.decrement(),
child: const Icon(Icons.remove),
),
],
),
],
),
),
);
}
}
Dentro do método build
, usamos BlocProvider.of<HomeCubit>(context)
para obter a instância do HomeCubit
fornecida pelo BlocProvider
. Isso nos permite acessar os métodos do cubit para interagir com o estado.
BlocBuilder
é usado para reconstruir seus filhos toda vez que o estado do cubit fornecido muda. Aqui, estamos exibindo o valor atual do contador no texto.
FloatingActionButton
define dois botões flutuantes: um para incrementar o contador quando pressionado e outro para decrementar o contador, ambos chamando seus métodos respectivos implemetados no cubit.
// home_cubit.dart
class HomeCubit extends Cubit<CounterState> {
HomeCubit() : super(CounterInitialState());
void increment() {
emit(CounterIncrementedState(state.count + 1));
}
void decrement() {
emit(CounterDecrementedState(state.count - 1));
}
}
// states
abstract class CounterState {
final int count;
CounterState(this.count);
}
class CounterInitialState extends CounterState {
CounterInitialState() : super(0);
}
class CounterIncrementedState extends CounterState {
CounterIncrementedState(int count) : super(count);
}
class CounterDecrementedState extends CounterState {
CounterDecrementedState(int count) : super(count);
}
HomeCubit
é uma classe que estende Cubit<CounterState>
. Cubit é uma classe do pacote flutter_bloc
que gerencia o estado e a lógica de negócios em um aplicativo Flutter. Aqui, HomeCubit
gerencia o estado do contador.
O construtor HomeCubit()
chama o construtor da classe pai Cubit<CounterState>
e passa CounterInitialState()
como o estado inicial do cubit. Isso significa que o estado inicial do contador é CounterInitialState
.
increment()
e decrement()
são métodos usados para aumentar e diminuir o contador, respectivamente. Eles usam o método emit
para emitir novos estados do contador com base no estado atual. Por exemplo, increment()
emite um novo estado CounterIncrementedState
com o contador incrementado em 1 em relação ao estado atual.
Os estados do contador são representados por classes que estendem CounterState
, que é uma classe abstrata. Cada classe de estado contém um campo count
, que representa o valor atual do contador.
CounterInitialState
representa o estado inicial do contador, onde o contador começa com o valor 0 e sendo inicializada junta com o construtor conforme abaixo.
HomeCubit() : super(CounterInitialState());
CounterIncrementedState
representa o estado do contador após a operação de incremento, onde o contador é aumentado em 1 em relação ao estado anterior.
CounterDecrementedState
representa o estado do contador após a operação de decremento, onde o contador é diminuído em 1 em relação ao estado anterior.
O arquivo home_cubit.dart
define a classe cubit HomeCubit
responsável por gerenciar o estado do contador e os estados relacionados ao contador, incluindo o estado inicial, o estado após o incremento e o estado após o decremento. Ele fornece métodos para alterar o estado do contador de forma reativa e eficiente.
Usar classes para representar os estados como é feito no exemplo fornecido, traz várias vantagens e é considerado uma prática recomendada. Aqui estão algumas razões pelas quais é importante usar classes como estado:
- Clareza e organização: Permite organizar e estruturar o código de forma clara e compreensível. Cada estado é representado por uma classe separada, o que torna mais fácil entender e manter o código, especialmente em aplicativos com estados complexos.
- Encapsulamento de dados: As classes de estado encapsulam os dados relacionados a um determinado estado, tornando-os mais fáceis de gerenciar e manipular. Isso promove a coesão e reduz o acoplamento entre os diferentes componentes do aplicativo.
- Reutilização de código: Você pode reutilizar essas classes em diferentes partes do seu aplicativo, facilitando a consistência e a reutilização de código. Por exemplo, se você tiver um estado de contador em várias telas do seu aplicativo, poderá reutilizar a mesma classe de estado em todas elas.
- Tipagem estática: O uso de classes como estado promove a tipagem estática, o que ajuda a detectar erros de tipo em tempo de compilação. Isso proporciona mais segurança e robustez ao código, reduzindo a probabilidade de erros durante a execução.
- Facilidade de teste: As classes de estado são objetos simples e independentes que podem ser facilmente testados de forma isolada. Isso facilita a escrita de testes unitários para validar o comportamento do aplicativo em diferentes estados.
- Extensibilidade: Você pode facilmente adicionar novos estados e funcionalidades ao seu aplicativo, simplesmente criando novas classes de estado e atualizando a lógica de acordo. Isso torna o aplicativo mais flexível e adaptável a mudanças futuras.
Conclusão
Integrar o Flutter Modular com o Flutter Bloc (Cubit) pode parecer assustador à primeira vista, mas com a abordagem certa e as ferramentas certas, é uma tarefa alcançável. Espero que este artigo tenha fornecido insights valiosos sobre como realizar essa integração. Continue explorando e experimentando para aprimorar suas habilidades de desenvolvimento Flutter!
Para aprender mais sobre Flutter Modular, confira a documentação oficial aqui e para descobrir mais sobre o Cubit leia a documentação aqui ou meu outro artigo aqui.
Código fonte: https://github.com/rkpontes/flutter_modular_cubit