go_router_modular 4.0.0 copy "go_router_modular: ^4.0.0" to clipboard
go_router_modular: ^4.0.0 copied to clipboard

go_router wrapper with a modular twist dependency injections and automatic dispose

🧩 GoRouter Modular πŸ’‰ #

Simplifying modules dependency injections #

GoRouter Modular simplifies Flutter development by implementing a modular architecture.

It utilizes GoRouter for route management 🧩 and supports per-module dependency injection with auto-dispose πŸ’‰.

With GoRouter Modular, you can easily organize your application into independent modules, streamlining code development and maintenance while promoting component reuse and project scalability.

Simplify your Flutter app development and accelerate your workflow with GoRouter Modular.

πŸš€ Getting Started #

  1. Create an app_module.dart
  2. Create an app_widget.dart
  3. Configure your main.dart

Below you will find examples of the structure and how each file should look.

Project Structure Example #

πŸ“ src
   πŸ“ modules
      πŸ“ home
         πŸ“„ home_controller.dart
         πŸ“„ home_page.dart
         πŸ“„ home_module.dart
   πŸ“„ app_module.dart
   πŸ“„ app_widget.dart
πŸ“„ main.dart

Main Example

import 'package:example/src/app_module.dart';
import 'package:example/src/app_widget.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router_modular/go_router_modular.dart';
import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  if (kIsWeb) usePathUrlStrategy();

  Modular.configure(appModule: AppModule(), initialRoute: "/"); // Configure Modular

  runApp(AppWidget()); // Define AppWidget
}

AppWidget Example

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

class AppWidget extends StatelessWidget {
  const AppWidget({super.key});

  @overrideβ—Š
  Widget build(BuildContext context) {
    return ModularApp.router( // Use ModularApp.router instead of MaterialApp.router
      title: 'Modular GoRoute Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

AppModule Example

import 'package:go_router_modular/go_router_modular.dart';
import 'modules/home/home_module.dart';
import 'modules/shared/shared_module.dart';

class AppModule extends Module {
  @override
  FutureOr<List<Module>> imports() => [SharedModule()];

  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<AuthController>((i) => AuthController()), // Define global binds in app_module
  ];

  @override
  List<ModularRoute> get routes => [
    ModuleRoute("/", module: HomeModule()),
  ];
}

🎯 LOADER SYSTEM #

GoRouter Modular includes a built-in loader system that automatically shows during module registration and can be controlled manually.

Automatic Loader #

The loader automatically appears when navigating between modules during the dependency injection process.

Manual Loader Control #

You can manually control the loader in your code:

// Show the loader
ModularLoader.show();

// Hide the loader
ModularLoader.hide();

Custom Loader #

You can customize the loader appearance by creating a CustomModularLoader:

class MyLoader extends CustomModularLoader {
  @override
  Color get backgroundColor => Colors.black87;
  
  @override
  Widget get child => const Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      CircularProgressIndicator(
        valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
      ),
      SizedBox(height: 16),
      Text(
        'Carregando...',
        style: TextStyle(color: Colors.white),
      ),
    ],
  );
}

Then use it in your ModularApp.router:

return ModularApp.router(
  customModularLoader: MyLoader(),
  title: 'My App',
);

πŸ’‰ DEPENDENCY INJECTION #

⚠️ Attention

Every dependency when placed in a BIND must be TYPED for correct operation.

Example:

βœ…  Bind.singleton<HomeController>((i) => HomeController())
βœ…  Bind.singleton((i) => HomeController())

Injecting Dependencies #

Create a class for your module and extend it from Module. Add your dependencies in the binds() method.

As soon as there are no active routes for your module in the widget tree, the module will automatically dispose of the binds.

Example:

import 'package:go_router_modular/go_router_modular.dart';
import 'pages/home_page.dart';

class HomeModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<HomeController>((i) => HomeController()), // DEFINE BINDS FOR MODULE
    Bind.factory<IUserRepository>((i) => UserRepository()),
  ];

  @override
  List<ModularRoute> get routes => [
    ChildRoute('/', child: (context, state) => const HomePage()),
  ];
}

Module Imports #

You can import other modules to share their dependencies:

class UserModule extends Module {
  @override
  FutureOr<List<Module>> imports() => [SharedModule()];

  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<UserService>((i) => UserService()),
  ];
}

Module Lifecycle #

Modules have lifecycle methods for initialization and cleanup:

class AuthModule extends Module {
  bool _isInitialized = false;
  Timer? _authTimer;

  @override
  void initState(Injector i) {
    if (_isInitialized) return;

    // Initialize module resources
    _setupAuthListeners();
    _loadAuthConfig();
    _isInitialized = true;
  }

  @override
  void dispose() {
    // Clean up resources
    _authTimer?.cancel();
    _authTimer = null;
    _isInitialized = false;
  }
}

Global Dependencies #

Simply place your binds in your AppModule.

Your AppModule will never be disposed of.

class AppModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<AuthController>((i) => AuthController()), // DEFINE GLOBAL BINDS IN APP_MODULE
  ];
}

Retrieving Dependencies #

To retrieve a dependency, you have three options:

final homeController = context.read<HomeController>();
// or
final homeController = Modular.get<HomeController>();
// or
final homeController = Bind.get<HomeController>();

πŸ›£οΈ ROUTES #

Route control is handled by GoRouter. The main difference is that we use ChildRoute for module route configurations, which has the same structure as GoRoute.

You can follow the GoRouter documentation for navigation: GoRouter Documentation

ChildRoute() is equivalent to GoRoute(). You'll notice they have the same parameters.

Note that every module must have a root route ("/") that serves as the parent route for the module.

ChildRoutes #

class HomeModule extends Module {
  @override
  List<ModularRoute> get routes => [
    ChildRoute('/', child: (context, state) => const HomePage()), // Required root route
    ChildRoute('/config', child: (context, state) => const ConfigPage()),
    ChildRoute('/info_product/:id', child: (context, state) => 
      InfoProductPage(id: state.pathParameters['id']!)),
  ];
}

Module Routes #

Your module can also have submodules:

Note that when a route calls a module, it will fall into the module's "/" route.

class AppModule extends Module {
  @override
  List<ModularRoute> get routes => [
    ModuleRoute("/", module: HomeModule()),
    ModuleRoute("/user", module: UserModule()),
    ChildRoute('/splash', child: (context, state) => const SplashPage()),
  ];
}

Shell Routes #

ShellModularRoute is the equivalent of Flutter Modular's RouteOutlet. It allows you to have a navigation window within a page, commonly used for menu construction where options change but only the screen content updates.

For more details: GoRouter ShellRoute Documentation

class HomeShellModule extends Module {
  @override
  List<ModularRoute> get routes => [
    ShellModularRoute(
      builder: (context, state, child) => ShellPageExample(shellChild: child),
      routes: [
        ChildRoute("/config", child: (context, state) => const ConfigPage()),
        ChildRoute("/user", child: (context, state) => const UserPage()),
        ChildRoute("/orders", child: (context, state) => const OrdersPage()),
      ],
    ),
  ];
}

ShellPageExample

class ShellPageExample extends StatefulWidget {
  final Widget shellChild; // Request a child WIDGET to be rendered in the shell
  const ShellPageExample({super.key, required this.shellChild});

  @override
  State<ShellPageExample> createState() => _ShellPageExampleState();
}

class _ShellPageExampleState extends State<ShellPageExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(children: [
        Expanded(child: widget.shellChild), // Your routes will be re-rendered here
        Row(
          children: [
            IconButton(
              onPressed: () => context.go("/home"),
              icon: const Icon(Icons.home),
            ),
            IconButton(
              onPressed: () => context.go("/config"),
              icon: const Icon(Icons.settings),
            ),
          ],
        ),
      ]),
    );
  }
}

Navigating to a destination in GoRouter will replace the current stack of screens:

build(BuildContext context) {
  return TextButton(
    onPressed: () => context.go('/users/123'),
  );
}

This is shorthand for calling GoRouter.of(context).go('/users/123').

To build a URI with query parameters, you can use the Uri class:

context.go(Uri(path: '/users/123', queryParameters: {'filter': 'abc'}).toString());

For more complete examples, visit the GoRouter Documentation

πŸ”„ MIGRATION GUIDE #

Migrating from Version 2.x to 4.x #

Breaking Changes Migration Steps #

  1. Update Root Routes: Ensure every module has a root ChildRoute with path "/"
  2. Convert Binds Getter: Change get binds => to binds() =>
  3. Add Lifecycle Methods: Implement initState() and dispose() if needed
  4. Consider Module Imports: Use imports() to share dependencies between modules
  5. Use ModularApp.router: Replace MaterialApp.router with ModularApp.router

Example Complete Migration #

Before (2.x):

class UserModule extends Module {
  @override
  List<Bind<Object>> get binds => [
    Bind.singleton<UserService>((i) => UserService()),
  ];

  @override
  List<ModularRoute> get routes => [
    ChildRoute('/user', builder: (context, state) => const UserPage()),
    ChildRoute('/profile', builder: (context, state) => const ProfilePage()),
  ];
}

// AppWidget
class AppWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: Modular.routerConfig,
      title: 'My App',
    );
  }
}

After (4.x):

class UserModule extends Module {
  @override
  FutureOr<List<Module>> imports() => [SharedModule()];

  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<UserService>((i) => UserService()),
  ];

  @override
  List<ModularRoute> get routes => [
    ChildRoute('/', child: (context, state) => const UserPage()), // βœ… Root route required
    ChildRoute('/profile', child: (context, state) => const ProfilePage()),
  ];

  @override
  void initState(Injector i) {
    // Initialize user-related services
  }

  @override
  void dispose() {
    // Clean up user resources
  }
}

// AppWidget
class AppWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ModularApp.router( // βœ… Use ModularApp.router

      title: 'My App',
      loaderDecorator: CustomLoaderDecorator(), // βœ… Optional custom loader
    );
  }
}

Contributions #

Made with contrib.rocks.

31
likes
0
points
372
downloads

Publisher

unverified uploader

Weekly Downloads

go_router wrapper with a modular twist dependency injections and automatic dispose

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

event_bus, flutter, go_router

More

Packages that depend on go_router_modular