Skip to main content

๐Ÿ“– Migration Guide

Migrate your existing GoRouter Modular app from version 2.x to 4.x with this comprehensive guide.

โš ๏ธ Breaking Changesโ€‹

Important Changes in 4.x:โ€‹

  • Root routes now required
  • binds() method replaces getter
  • New lifecycle methods
  • ModularApp.router replaces MaterialApp.router

๐Ÿ”„ Migration Stepsโ€‹

1. Update Routesโ€‹

Before (2.x):

class HomeModule extends Module {

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

After (4.x):

class HomeModule extends Module {

List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => UserPage()), // Root required
ChildRoute('/profile', child: (context, state) => ProfilePage()),
];
}

2. Convert Bindsโ€‹

Before (2.x):

class HomeModule extends Module {

List<Bind<Object>> get binds => [
Bind.singleton<UserService>((i) => UserService()),
Bind.factory<UserRepository>((i) => UserRepository()),
];
}

After (4.x):

class HomeModule extends Module {

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

3. Update App Widgetโ€‹

Before (2.x):

class AppWidget extends StatelessWidget {

Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Modular.routerConfig,
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}

After (4.x):

class AppWidget extends StatelessWidget {

Widget build(BuildContext context) {
return ModularApp.router(
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}

4. Update Route Parametersโ€‹

Before (2.x):

ChildRoute('/user/:id', builder: (context, state) => 
UserPage(id: state.params['id'])),

After (4.x):

ChildRoute('/user/:id', child: (context, state) => 
UserPage(id: state.pathParameters['id']!)),

5. Update Navigationโ€‹

Before (2.x):

Modular.to.navigate('/user/123');
Modular.to.pushNamed('/modal');

After (4.x):

context.go('/user/123');
context.push('/modal');

๐ŸŽฏ Detailed Migration Examplesโ€‹

Complete Module Migrationโ€‹

Before (2.x):

class AuthModule extends Module {

List<Bind<Object>> get binds => [
Bind.singleton<AuthService>((i) => AuthService()),
Bind.singleton<AuthController>((i) => AuthController()),
];


List<ModularRoute> get routes => [
ChildRoute('/login', builder: (context, state) => LoginPage()),
ChildRoute('/register', builder: (context, state) => RegisterPage()),
ChildRoute('/user/:id', builder: (context, state) =>
UserPage(id: state.params['id'])),
];
}

After (4.x):

class AuthModule extends Module {

FutureOr<List<Bind<Object>>> binds() => [
Bind.singleton<AuthService>((i) => AuthService()),
Bind.singleton<AuthController>((i) => AuthController()),
];


List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => LoginPage()), // Root route
ChildRoute('/register', child: (context, state) => RegisterPage()),
ChildRoute('/user/:id', child: (context, state) =>
UserPage(id: state.pathParameters['id']!)),
];
}

App Module Migrationโ€‹

Before (2.x):

class AppModule extends Module {

List<Bind<Object>> get binds => [
Bind.singleton<AppController>((i) => AppController()),
];


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

After (4.x):

class AppModule extends Module {

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


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

Main Function Migrationโ€‹

Before (2.x):

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Modular.configure(Modular.configure(appModule: AppModule(), initialRoute: "/");
runApp(AppWidget());
}

class AppWidget extends StatelessWidget {

Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Modular.routerConfig,
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}

After (4.x):

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Modular.configure(Modular.configure(appModule: AppModule(), initialRoute: "/");
runApp(AppWidget());
}

class AppWidget extends StatelessWidget {

Widget build(BuildContext context) {
return ModularApp.router(
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}

๐Ÿ”ง Advanced Migrationโ€‹

Event Module Migrationโ€‹

Before (2.x):

class NotificationModule extends Module {

void initState() {
modularEvent.on<ShowNotificationEvent>().listen((event) {
// Handle event
});
}
}

After (4.x):

class NotificationModule extends EventModule {

void listen() {
on<ShowNotificationEvent>((event, context) {
// Handle event with context
});
}
}

Dependency Access Migrationโ€‹

Before (2.x):

// In widgets
final controller = Modular.get<UserController>();

// In services
final service = Modular.get<UserService>();

After (4.x):

// In widgets (recommended)
final controller = context.read<UserController>();

// In services (still works)
final service = Modular.get<UserService>();

๐Ÿšจ Common Issuesโ€‹

1. Missing Root Routeโ€‹

// โŒ Error - Missing root route
List<ModularRoute> get routes => [
ChildRoute('/profile', child: (context, state) => ProfilePage()),
];

// โœ… Fix - Add root route
List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => HomePage()),
ChildRoute('/profile', child: (context, state) => ProfilePage()),
];

2. Wrong Parameter Accessโ€‹

// โŒ Error - Old parameter access
final id = state.params['id'];

// โœ… Fix - New parameter access
final id = state.pathParameters['id']!;

3. Wrong Navigation Methodโ€‹

// โŒ Error - Old navigation
Modular.to.navigate('/user/123');

// โœ… Fix - New navigation
context.go('/user/123');

๐Ÿงช Testing Migrationโ€‹

Update Test Filesโ€‹

// Before (2.x)
void main() {
setUp(() {
Modular.configure(appModule: TestModule());
});
}

// After (4.x)
void main() {
setUp(() {
Modular.configure(appModule: TestModule(), initialRoute: "/");
});
}