modular_router 2.0.0
modular_router: ^2.0.0 copied to clipboard
A flutter package to manage routes
Modular Router 🚀 #
A powerful, modular, and type-safe routing solution for Flutter applications. Manage complex navigation structures, dependency injection, and authorization with ease.
✨ Features #
- 🏆 Modular Architecture: Organize your app into logical features (modules).
- 🌲 Nested Routing: Create hierarchical path structures effortlessly.
- 💉 Built-in Dependency Injection: Powered by
auto_injectoranddynamic_constructor. - 🛡️ Integrated Authorization: Control access at both module and route levels.
- 📍 Type-Safe Navigation: Navigate using types instead of hardcoded strings.
- 📱 Platform-Native Transitions: Automatic Material/Cupertino transitions.
- 🎨 Custom Transitions: Full control over page transitions when needed.
- 🔧 Stateful Integration: Seamlessly binds
Controllers toStatefulWidgetBinderviews.
🚀 Getting Started #
1. Installation #
Add modular_router to your pubspec.yaml:
dependencies:
modular_router: ^2.0.0
2. Define your Modules and Routes #
Create a module by extending Module and define its routes. Views should extend StatefulWidgetBinder and Controllers should implement DisposableController.
class UserModule extends Module {
UserModule() : super(path: '/user');
@override
List<ModuleRoute> get routes => [
ModuleRoute<ProfilePage>(
path: '/profile',
viewBuilder: ProfilePage.new,
controllerBuilder: ProfileController.new,
),
ModuleRoute<SettingsPage>(
path: '/settings',
viewBuilder: SettingsPage.new,
controllerBuilder: SettingsController.new,
),
];
}
3. Initialize the Router #
Create your router and pass its onGenerateRoute to your MaterialApp.
final router = ModularRouter(
modules: [
UserModule(),
// ... other modules
],
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: router.onGenerateRoute,
initialRoute: '/user/profile',
);
}
}
💉 Dependency Injection #
ModularRouter uses auto_injector to handle dependencies. You can register factories, lazy singletons, or singletons during initialization:
final router = ModularRouter(
modules: [ ... ],
factories: [
Injector<AuthService>(AuthService.new),
],
singletons: [
Injector<ApiClient>(ApiClient.new),
],
);
Or by extending ModularRouter:
class MyRouter extends ModularRouter {
MyRouter({required super.modules}) : super(
factories: [ ... ],
);
}
Dependencies are automatically injected into your viewBuilder and controllerBuilder based on their constructor parameters!
Required Base Classes
For DI and binding to work, follow these base class requirements:
- Views: Must extend
StatefulWidgetBinder. - Controllers: Must implement
DisposableController(or useListenableControllerfor reactivity).
class MyController extends ListenableController {
@override
void dispose() {
super.dispose();
}
}
class MyPage extends StatefulWidgetBinder {
const MyPage({required super.controller, super.key});
@override
State<MyPage> createState() => _MyPageState();
}
📍 Type-Safe Navigation #
Forget about hardcoded path strings. Use the provided extensions on NavigatorState:
// Push to a specific view type
Navigator.of(context).pushTo<ProfilePage>();
// Push and replace
Navigator.of(context).pushToReplacement<SettingsPage>();
// Push and remove until
Navigator.of(context).pushToAndRemoveUntil<DashboardPage, LoginPage>();
// Pop all and push
Navigator.of(context).popAllAndPushTo<HomePage>();
📦 Passing Arguments #
ModularRouter makes it incredibly easy to pass data directly into your controller or view constructors. The system handles DI automatically, but you can also pass custom arguments via the Navigator:
1. Positional Arguments #
Just pass your arguments as a List:
// Controller constructor: SettingsController(this.username, this.userId)
Navigator.of(context).pushTo<SettingsPage>(
arguments: ['tercyo', 123],
);
2. Named Arguments #
Pass your arguments as a Map:
// Controller constructor: ProfileController({required String bio})
Navigator.of(context).pushTo<ProfilePage>(
arguments: {'bio': 'Always building 🚀'},
);
3. Mixed Arguments #
Pass a List with a Map as the last element:
// Controller constructor: UserDetailController(this.userId, {required String source})
Navigator.of(context).pushTo<UserDetailPage>(
arguments: [123, {'source': 'profile_view'}],
);
🛡️ Authorization #
You can easily protect routes or entire modules:
class AdminModule extends Module {
AdminModule() : super(
path: '/admin',
allowAnonymous: false, // Requires authorization
);
@override
List<ModuleRoute> get routes => [
ModuleRoute<Dashboard>(
path: '/',
viewBuilder: AdminDashboard.new,
),
ModuleRoute<PublicInfo>(
path: '/info',
viewBuilder: PublicInfoPage.new,
allowAnonymous: true, // Override module setting
),
];
}
Initialize your router with the current auth state:
final router = MainRouter(
modules: [...],
enableAuthorize: true,
authorized: userIsLoggedIn,
unauthorizedRedirectRoute: '/login',
);
🛠️ Advanced Usage #
Custom Page Transitions #
ModuleRoute<PremiumPage>(
path: '/premium',
viewBuilder: PremiumPage.new,
customPageTransition: <T>({settings, required view}) {
return PageRouteBuilder<T>(
settings: settings,
pageBuilder: (context, animation, secondaryAnimation) => view,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
);
},
);
🎨 Example #
Check out the example project for a complete demonstration, including:
- Module and Route definitions
- Dependency Injection with a global
Configobject - Counter state management using
ListenableController - Type-safe Navigation using
NavigatorStateExtension
📄 License #
This project is licensed under the MIT License - see the LICENSE file for details.