modugo 4.2.0
modugo: ^4.2.0 copied to clipboard
Modular Routing and Dependency Injection for Flutter with GetIt and GoRouter.
Modugo #
Modugo é um sistema modular para Flutter inspirado em Flutter Modular e Go Router Modular. Ele organiza sua aplicação em módulos, rotas e injeção de dependências de forma clara e escalável. Diferente de outros frameworks, o Modugo não gerencia descarte automático de modulos.
📖 Sumário #
- 🚀 Visão Geral
- 📦 Instalação
- 🏗️ Estrutura de Projeto
- ▶️ Primeiros Passos
- 🧭 Navegação
Construtor DeclarativoChildRouteModuleRouteShellModuleRouteStatefulShellModuleRouteAliasRoute
- 🔒 Guards
- 🛠️ Injeção de Dependência
- ⏳ AfterLayoutMixin
- 🔎 Regex e Matching
- 📡 Sistema de Eventos
- 📝 Logging e Diagnóstico
- 📚 Documentação MkDocs
- 🤝 Contribuições
- 📜 Licença
🚀 Visão Geral #
- Usa GoRouter para navegação.
- Usa GetIt para injeção de dependências.
- Dependências são registradas uma única vez ao iniciar.
- Não há descarte automático — as dependências de modulos vivem até o app encerrar.
- Projetado para fornecer arquitetura modular desacoplada.
⚠️ Atenção: Diferente das versões <3.x, o Modugo não descarta dependências de modulos automaticamente.
📦 Instalação #
dependencies:
modugo: ^x.x.x
🏗️ Estrutura de Projeto #
/lib
/modules
/home
home_page.dart
home_module.dart
/profile
profile_page.dart
profile_module.dart
/chat
chat_page.dart
chat_module.dart
app_module.dart
app_widget.dart
main.dart
▶️ Primeiros Passos #
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Modugo.configure(module: AppModule(), initialRoute: '/');
runApp(const AppWidget());
}
🧭 Navegação #
🧩 Construtor Declarativo de Rotas do Modugo #
O Modugo introduz uma API limpa e declarativa para criar rotas dentro do seu Module. Ele elimina a repetição de código ao definir rotas, tornando a configuração dos módulos mais expressiva, legível e consistente.
🚀 Visão Geral #
Tradicionalmente, você definia rotas do Modugo assim:
List<IRoute> routes() => [
ChildRoute(path: '/', child: (_, _) => const HomePage()),
ModuleRoute(path: '/auth', module: AuthModule()),
];
De forma declarativa, você pode escrever:
List<IRoute> routes() => [
route('/', child: (_, _) => const HomePage()),
module('/auth', AuthModule()),
];
Dessa forma melhoramos a experiência de desenvolvimento sem alterar nenhuma lógica interna do Modugo.
🧱 Métodos Disponíveis #
route() — Cria uma ChildRoute
Use para rotas simples que apontam diretamente para um widget.
route(
'/',
child: (_, _) => const HomePage(),
guards: [AuthGuard()],
transition: TypeTransition.fade,
);
module() — Cria uma ModuleRoute
Conecta submódulos, permitindo uma arquitetura modular e hierárquica.
module('/auth', AuthModule());
alias() — Cria uma AliasRoute
Permite criar apelidos (caminhos alternativos) para rotas existentes sem duplicar lógica.
alias(from: '/cart/:id', to: '/order/:id');
Isso faz com que /cart/:id e /order/:id apontem para a mesma tela.
shell() — Cria uma ShellModuleRoute
Agrupa várias rotas sob um layout ou container compartilhado (ex: abas, menus laterais).
shell(
builder: (_, _, child) => AppScaffold(child: child),
routes: [
route('/feed', child: (_, _) => const FeedPage()),
route('/settings', child: (_, _) => const SettingsPage()),
],
);
statefulShell() — Cria uma StatefulShellModuleRoute
Usado para estruturas com abas ou navegação inferior onde cada aba mantém seu próprio histórico de navegação.
statefulShell(
builder: (_, _, shell) => BottomBarWidget(shell: shell),
routes: [
module('/home', HomeModule()),
module('/profile', ProfileModule()),
],
);
💡 Exemplo Completo de Módulo #
final class AppModule extends Module {
@override
List<IRoute> routes() => [
route('/', child: (_, _) => const HomePage()),
module('/auth', AuthModule()),
alias(from: '/cart/:id', to: '/order/:id'),
shell(
builder: (_, _, child) => MainShell(child: child),
routes: [
route('/dashboard', child: (_, _) => const DashboardPage()),
route('/settings', child: (_, _) => const SettingsPage()),
],
),
statefulShell(
builder: (_, _, shell) => BottomBarWidget(shell: shell),
routes: [
module('/feed', FeedModule()),
module('/profile', ProfileModule()),
],
),
];
}
📚 Resumo #
| Helper | Retorna | Uso Principal |
|---|---|---|
route() |
ChildRoute |
Telas simples |
module() |
ModuleRoute |
Submódulos |
alias() |
AliasRoute |
Caminhos alternativos |
shell() |
ShellModuleRoute |
Containers e layouts compartilhados |
statefulShell() |
StatefulShellModuleRoute |
Navegação com múltiplas pilhas |
✨ Desta forma transformamos suas definições de rota em uma DSL fluente e legível — mantendo seus módulos Modugo elegantes e escaláveis.
🔹 route() -> ChildRoute #
route(
path: '/home',
child: (_, _) => const HomePage(),
)
🔹 module() -> ModuleRoute #
module(
path: '/profile',
module: ProfileModule(),
)
🔹 shell() -> ShellModuleRoute #
Útil para criar áreas de navegação em parte da tela, como menus ou abas.
shell(
builder: (context, state, child) => Scaffold(body: child),
routes: [
route(path: '/user', child: (_, _) => const UserPage()),
route(path: '/config', child: (_, _) => const ConfigPage()),
],
)
🔹 statefulShell() -> StatefulShellModuleRoute #
Ideal para apps com BottomNavigationBar ou abas preservando estado.
statefulShell(
builder: (context, state, shell) => BottomBarWidget(shell: shell),
routes: [
module(path: '/', module: HomeModule()),
module(path: '/profile', module: ProfileModule()),
],
)
🔹 alias() -> AliasRoute #
O AliasRoute é um tipo especial de rota que funciona como um apelido (alias) para uma ChildRoute já existente. Ele resolve o problema de URLs alternativas para a mesma tela, sem precisar duplicar lógica ou cair nos loops comuns de RedirectRoute.
📌 Quando usar?
- Para manter compatibilidade retroativa com URLs antigas.
- Para expor uma mesma tela em múltiplos caminhos semânticos (ex:
/carte/order).
✅ Exemplo simples
child(
path: '/order/:id',
child: (_, state) => OrderPage(id: state.pathParameters['id']!),
),
alias(
from: '/cart/:id',
to: '/order/:id',
),
➡️ Nesse caso, tanto /order/123 quanto /cart/123 vão renderizar a mesma tela OrderPage.
⚠️ Limitações
-
O
AliasRoutesó funciona parachild.- Ele não pode apontar para
moduleoushell. - Essa limitação é intencional, pois módulos inteiros ou shells representam estruturas de navegação maiores e complexas.
- Ele não pode apontar para
-
O alias precisa apontar para uma
childexistente dentro do mesmo módulo.-
Caso contrário, será lançado um erro em tempo de configuração:
Alias Route points to /cart/:id, but there is no corresponding Child Route.
-
-
Não há suporte a alias encadeados (ex: um alias apontando para outro alias).
🎯 Exemplo prático
final class ShopModule extends Module {
@override
List<IRoute> routes() => [
// rota canônica
child(
path: '/product/:id',
child: (_, state) => ProductPage(id: state.pathParameters['id']!),
),
// rota alternativa (alias)
alias(
from: '/item/:id',
to: '/product/:id',
),
];
}
➡️ O usuário acessa /item/42, mas internamente o Modugo entrega o mesmo ProductPage de /product/42.
💡 Vantagens sobre RedirectRoute
- Evita loops infinitos comuns em redirecionamentos.
- Mantém o histórico de navegação intacto (não "teleporta" o usuário para outra URL, apenas resolve a rota).
🔒 Resumo: Use alias para apelidos de child. Se precisar de comportamento mais avançado (como autenticação ou lógica condicional), continue usando guards (IGuard) ou child com cuidado.
🔒 Guards — Protegendo suas rotas com IGuard #
Os Guards no Modugo permitem controlar o acesso a rotas com base em condições lógicas, como autenticação, papéis de usuário ou verificações de sessão. Eles são executados antes do carregamento da rota e podem redirecionar o usuário conforme necessário.
🧩 Exemplo básico de Guard #
final class CustomGuard implements IGuard {
final IRepository _repository;
CustomGuard({required IRepository repository}) : _repository = repository;
@override
FutureOr<String?> call(BuildContext context, GoRouterState state) async {
final data = await _repository.call();
return data == null ? '/welcome' : null;
}
}
Neste exemplo:
- O guard implementa a interface
IGuard. - O método
callé executado antes de entrar na rota. - Retornar uma string redireciona o usuário para outro caminho.
- Retornar null permite o acesso normalmente.
🚀 Aplicando Guards em rotas #
Você pode aplicar guards diretamente nas rotas usando o parâmetro guards:
child(
'/',
child: (_, _) => const HomePage(),
guards: [CustomGuard(repository: i.get<Repository>())],
);
➡️ Este parâmetro está disponível apenas para child.
🌀 propagateGuards — Propagando Guards para submódulos #
Quando você deseja aplicar um guard de forma recursiva para todos os filhos de um módulo, use propagateGuards:
List<IRoute> routes() => propagateGuards(
guards: [CustomGuard(repository: i.get<Repository>())],
routes: [
module(path: '/', module: HomeModule()),
],
);
✅ Assim, todas as rotas internas de HomeModule herdam automaticamente o guard.
🔐 Comportamento interno #
Os guards no Modugo seguem esta ordem de execução:
- Guards da rota atual são executados primeiro.
- Se todos retornarem
null, a navegação prossegue. - Se algum retornar uma
String, ocorre um redirect para esse caminho. - Caso a rota contenha um
redirectpróprio, ele será avaliado após os guards.
⚙️ Vantagens dos Guards #
- Evitam navegação não autorizada.
- Permitem lógica condicional antes da renderização da página.
- Suportam dependências injetadas pelo Modugo (
i.get()oucontext.read()).
💡 Dica #
Guards são executados de forma assíncrona, permitindo validações complexas como chamadas a APIs, verificações em cache ou consultas ao banco local.
🛠️ Injeção de Dependência #
final class HomeModule extends Module {
@override
void binds() {
i
..registerSingleton<ServiceRepository>(ServiceRepository())
..registerLazySingleton<ApiClient>(ApiClient.new);
}
}
Acesse com:
final repository = i.get<ServiceRepository>();
Ou via contexto:
final repository = context.read<ServiceRepository>();
Ou via Modugo:
final repository = Modugo.i.get<ServiceRepository>();
⏳ AfterLayoutMixin #
Mixin para executar código após o primeiro layout do widget.
class MyScreen extends StatefulWidget {
const MyScreen({super.key});
@override
State<MyScreen> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> with AfterLayoutMixin {
@override
Widget build(BuildContext context) {
return const Scaffold(body: Center(child: Text('Hello World')));
}
@override
Future<void> afterFirstLayout(BuildContext context) async {
debugPrint('Tela pronta!');
}
}
💡 Útil para:
- Carregar dados iniciais.
- Disparar animações.
- Abrir dialogs/snackbars com
BuildContextválido.
🔎 Regex e Matching #
Use CompilerRoute para validar e extrair parâmetros:
final route = CompilerRoute('/user/:id');
route.match('/user/42'); // true
route.extract('/user/42'); // { id: "42" }
📡 Sistema de Eventos #
Permite comunicação desacoplada entre módulos.
final class MyEvent {
final String message;
MyEvent(this.message);
}
Event.on<MyEvent>((event) {
print(event.message);
});
Event.emit(MyEvent('Olá Modugo!'));
📝 Logging e Diagnóstico #
Modugo.configure(
module: AppModule(),
debugLogDiagnostics: true,
);
Exibe logs de injeção, navegação e erros.
📚 Documentação MkDocs [Em desenvolvimento] #
Toda a documentação também está disponível em MkDocs para navegação mais amigável: 👉 Modugo Docs
Estrutura baseada em múltiplos tópicos (Rotas, Injeção, Guards, Eventos), permitindo leitura incremental.
🤝 Contribuições #
Pull requests e sugestões são bem-vindos! 💜
📜 Licença #
MIT ©