modugo 4.2.0 copy "modugo: ^4.2.0" to clipboard
modugo: ^4.2.0 copied to clipboard

Modular Routing and Dependency Injection for Flutter with GetIt and GoRouter.

Modugo Logo

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 #

  • 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: /cart e /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

  1. O AliasRoute só funciona para child.

    • Ele não pode apontar para module ou shell.
    • Essa limitação é intencional, pois módulos inteiros ou shells representam estruturas de navegação maiores e complexas.
  2. O alias precisa apontar para uma child existente 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.
      
  3. 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:

  1. Guards da rota atual são executados primeiro.
  2. Se todos retornarem null, a navegação prossegue.
  3. Se algum retornar uma String, ocorre um redirect para esse caminho.
  4. Caso a rota contenha um redirect pró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() ou context.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 BuildContext vá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 ©

12
likes
0
points
151
downloads

Publisher

unverified uploader

Weekly Downloads

Modular Routing and Dependency Injection for Flutter with GetIt and GoRouter.

Repository (GitHub)
View/report issues

Topics

#modugo #routing #injection #navigation #deep-linking

License

unknown (license)

Dependencies

flutter, get_it, go_router

More

Packages that depend on modugo