๐ vex_router
The ultimate Flutter Navigation 2.0 router.
No code generation. No GetMaterialApp. No compromise.
๐ Full Package Comparison
Every popular Flutter routing package in one table.
โ = supported โ = not supported โ ๏ธ = partial / workaround needed
| Feature | vex_router | go_router | auto_route | GetX nav | beamer | routemaster |
|---|---|---|---|---|---|---|
| Navigation 2.0 (Router API) | โ | โ | โ | โ ๏ธ partial | โ | โ |
| No code generation needed | โ | โ | โ build_runner | โ | โ | โ |
| No GetMaterialApp / custom App | โ | โ | โ | โ required | โ | โ |
| Type-safe routes | โ | โ ๏ธ opt-in builder | โ generated | โ string-based | โ | โ ๏ธ |
| Named route push | โ | โ | โ | โ | โ ๏ธ | โ |
| Path parameters | โ | โ | โ | โ ๏ธ | โ | โ |
| Query parameters | โ | โ | โ | โ ๏ธ | โ | โ |
| Typed extra data | โ | โ | โ | โ ๏ธ | โ ๏ธ | โ ๏ธ |
| Typed pop result (VexResult) | โ | โ | โ | โ | โ | โ |
| Nested / child routes | โ | โ | โ | โ ๏ธ | โ | โ |
| Shell / tab navigation | โ | โ StatefulShell | โ | โ ๏ธ | โ | โ |
| Tab state preserved (IndexedStack) | โ | โ | โ | โ ๏ธ | โ ๏ธ | โ ๏ธ |
| Route guards (imperative) | โ | โ | โ | โ | โ | โ |
| Declarative redirects | โ | โ | โ ๏ธ | โ | โ | โ ๏ธ |
| Multiple guards per route | โ | โ | โ | โ | โ | โ |
| Global guards | โ | โ ๏ธ single redirect | โ | โ | โ | โ |
| GetX controller binding | โ native | โ | โ | โ | โ | โ |
| Any controller binding (DI-agnostic) | โ | โ | โ AutoRouteWrapper | โ GetX only | โ | โ |
| Inline binding (no class) | โ | โ | โ | โ | โ | โ |
| Multi-binding per route | โ | โ | โ | โ | โ | โ |
| Auto controller dispose on pop | โ | โ manual | โ | โ | โ | โ |
| Navigation observer / analytics | โ | โ | โ | โ ๏ธ | โ | โ ๏ธ |
| 9+ built-in transitions | โ | โ ๏ธ custom only | โ ๏ธ | โ | โ | โ |
| Per-route transition | โ | โ | โ | โ | โ | โ |
| Custom transition builder | โ | โ | โ | โ | โ | โ |
| Global default transition | โ | โ | โ | โ | โ | โ |
| Deep linking | โ | โ | โ | โ ๏ธ | โ | โ |
| Web URL sync | โ | โ | โ | โ ๏ธ limited | โ | โ |
| Platform back button support | โ | โ | โ | โ ๏ธ | โ | โ |
| Error screen builder | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| 404 not-found screen | โ | โ | โ | โ | โ | โ ๏ธ |
| Context-free navigation | โ VexNavigator.root | โ | โ | โ Get.to | โ | โ |
| BuildContext extensions | โ | โ | โ | โ | โ | โ |
| String route extensions | โ | โ | โ | โ | โ | โ |
| Navigate by raw URL path | โ | โ | โ | โ ๏ธ | โ | โ |
| Navigate by route name | โ | โ | โ | โ | โ ๏ธ | โ |
| pushAndClearStack | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| pushAndRemoveUntil | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| popUntil(name) | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| popToRoot | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| maybePop() | โ | โ | โ | โ | โ | โ |
| showDialog as route | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| showBottomSheet as route | โ | โ | โ | โ | โ ๏ธ | โ ๏ธ |
| Full-screen dialog flag | โ | โ | โ | โ ๏ธ | โ | โ ๏ธ |
| State restoration | โ | โ | โ ๏ธ | โ | โ | โ ๏ธ |
| Opaque route control | โ | โ | โ | โ ๏ธ | โ | โ |
| maintainState control | โ | โ | โ | โ ๏ธ | โ | โ |
| Page title (web) | โ | โ | โ | โ | โ | โ ๏ธ |
| Actively maintained (2025) | โ | โ ๏ธ feature-complete | โ | โ ๏ธ slow | โ ๏ธ | โ |
| No GetX dependency | โ | โ | โ | โ required | โ | โ |
| Unit testable (no static context) | โ | โ | โ | โ | โ | โ |
| Works with any state manager | โ | โ | โ | โ GetX only | โ | โ |
| Zero runtime dependencies | โ | โ collection | โ many | โ many | โ | โ |
โก Why vex_router wins
Problems with go_router โ
- Feature-frozen ("feature-complete") since early 2025 โ no new APIs
- Tabs /
StatefulShellRouteboilerplate is extremely verbose - No route guards โ only a single global
redirectcallback - No controller binding โ you manage DI manually
- No typed pop results
- No context-free navigation without workarounds
- Bloc scoping across nested shells requires painful nested ShellRoutes
Problems with auto_route โ
- Requires
build_runnerand code generation โ every route change โflutter pub run build_runner build - Generated code is hard to debug and adds CI overhead
- Steep onboarding curve for new team members
- No inline bindings โ must extend
AutoRouteWrapper - No global guards (only per-route
AutoRouteGuard) - No typed pop results
Problems with GetX navigation โ
- Forces
GetMaterialAppโ replaces Flutter's own MaterialApp router - Bypasses Flutter's
BuildContextentirely (anti-pattern) - Navigation is not unit-testable (static
Get.to(),Get.offNamed()) - GetX navigation conflicts with Navigation 2.0 deep-link handling on web
- "GetX does too much" โ one package trying to be state manager + DI + router + HTTP
- Maintained by a single developer; slow issue resolution
- Only 35% of API documented
Problems with beamer โ
- Largely unmaintained โ last major release 2022
- Complex
BeamLocationsetup - No route guards
- No transition control
Problems with routemaster โ
- Unmaintained โ archived/inactive since 2022
- Limited ecosystem support
๐ Quick Start (2 minutes)
1. Install
dependencies:
vex_router: ^1.0.0
2. Define routes
final router = VexRouter(
initialRoute: 'home',
routes: [
VexRoute(name: 'home', path: '/home', builder: (_) => HomeScreen()),
VexRoute(name: 'profile', path: '/profile/:id', builder: (i) => ProfileScreen(id: i.requireParam('id'))),
VexRoute(name: 'login', path: '/login', builder: (_) => LoginScreen()),
],
);
3. Plug into MaterialApp
MaterialApp.router(
routerConfig: router.config, // โ that's all
)
No GetMaterialApp. No AutoRouter(). No build_runner. Done. ๐
๐๏ธ All APIs
Navigation โ from context
// Push
context.vexPush('profile', params: {'id': '42'});
context.vexPush('posts', query: {'tab': 'recent'});
context.vexPush('checkout', extra: cartItems);
// Replace
context.vexReplace('home');
// Push + clear stack
context.vexPushAndClear('login');
// Navigate by raw URL
context.vexPushPath('/posts/101?highlight=dart');
// Pop
context.vexPop();
context.vexPop('returned_value');
// Current route info
final info = context.currentRoute;
Navigation โ context-free (from controllers / services)
// Works from anywhere โ GetX controllers, Riverpod providers, Bloc listenersโฆ
VexNavigator.root.push('home');
VexNavigator.root.pushAndClearStack('login');
VexNavigator.root.pop();
Typed pop results
final result = await context.vexPush<UserProfile>('editProfile', extra: profile);
result.when(
onOk: (profile) => save(profile),
onErr: (error) => showError(error),
onCancelled: () => debugPrint('user cancelled'),
);
// Or simple guards:
if (result.isOk) doSomethingWith(result.value);
Type-safe parameter access
VexRoute(
name: 'order',
path: '/orders/:orderId',
builder: (info) {
final id = info.requireParam('orderId'); // String, throws if missing
final intId = info.requireIntParam('orderId'); // int, throws if non-numeric
final tab = info.query('tab') ?? 'summary'; // String?, null if absent
final flag = info.queryBool('active'); // bool
final order = info.requireExtra<Order>(); // typed extra
return OrderScreen(id: id, tab: tab);
},
),
String / extension method navigation
// On String โ reads like English
'profile'.vexPush(context, params: {'id': '42'});
'login'.vexPushAndClear(context);
'editProfile'.vexReplace(context, extra: myProfile);
๐ Guards
Auth guard (built-in)
final authGuard = VexAuthGuard(
isAuthenticated: () => AuthService.isLoggedIn,
loginRoute: 'login',
excludedRoutes: ['splash', 'register', 'onboarding'],
);
VexRouter(
guards: [authGuard], // โ applied globally
routes: [...],
)
Custom guard
class PremiumGuard extends VexGuard {
@override
Future<VexGuardResult> canNavigate(VexRouteInfo to) async {
if (User.isPremium) return const VexGuardAllow();
return const VexGuardRedirect('upgrade');
}
}
Guard results:
const VexGuardAllow() // proceed
const VexGuardRedirect('routeName') // redirect by name
const VexGuardRedirectPath('/some/path') // redirect by path
const VexGuardBlock(reason: 'no access') // cancel silently
Multiple guards per route
VexRoute(
name: 'admin',
path: '/admin',
builder: (_) => AdminScreen(),
guards: [authGuard, AdminGuard(), AuditGuard()], // evaluated in order
),
๐ฎ GetX Controller Binding
vex_router is the only router that supports GetX controller bindings without using GetMaterialApp.
// Define a binding
class HomeBinding extends VexBinding {
@override
void onInit() => Get.put(HomeController()); // GetX put
@override
void onDispose() => Get.delete<HomeController>();
}
// Attach to a route
VexRoute(
name: 'home',
path: '/home',
builder: (_) => HomeScreen(),
binding: HomeBinding(),
)
Inline binding โ no class needed:
VexRoute(
name: 'profile',
path: '/profile/:id',
builder: (i) => ProfileScreen(id: i.requireParam('id')),
binding: VexInlineBinding(
init: () => Get.put(ProfileController()),
dispose: () => Get.delete<ProfileController>(),
),
)
Multi-binding โ initialise several controllers:
VexRoute(
name: 'dashboard',
path: '/dashboard',
builder: (_) => DashboardScreen(),
binding: VexMultiBinding([
UserBinding(),
FeedBinding(),
NotificationBinding(),
]),
)
Works equally well with Riverpod, Bloc, Provider, MobX, setState โ any DI pattern.
๐๏ธ Transitions (9 built-in)
VexRoute(
name: 'profile',
path: '/profile',
builder: (_) => ProfileScreen(),
transition: VexTransitionType.slideRight,
)
| Name | Description |
|---|---|
material |
Platform-adaptive (Material / Cupertino) |
cupertino |
iOS cubic slide from right |
fade |
Fade in/out |
scale |
Scale from center with ease-out-back |
slideUp |
Slide from bottom (modal style) |
slideLeft |
Slide from left |
slideRight |
Slide from right |
rotation |
Rotate + fade |
size |
Size + fade |
none |
Instant, no animation |
custom |
Your own transitionBuilder |
Custom builder:
VexRoute(
name: 'modal',
path: '/modal',
transition: VexTransitionType.custom,
customTransitionBuilder: (ctx, anim, secAnim, child) {
return SlideTransition(
position: Tween(begin: const Offset(0, 1), end: Offset.zero)
.animate(CurvedAnimation(parent: anim, curve: Curves.elasticOut)),
child: child,
);
},
builder: (_) => ModalScreen(),
)
๐ Shell (Tab) Navigation
class MainShell extends StatelessWidget {
final int tabIndex;
final Widget body;
final void Function(int) onTabChanged;
@override
Widget build(BuildContext context) => Scaffold(
body: body,
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabIndex,
onTap: onTabChanged,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
// Configure the shell
VexShell(
tabs: [
VexShellTab(rootRouteName: 'feed'),
VexShellTab(rootRouteName: 'search'),
VexShellTab(rootRouteName: 'profile'),
],
navigators: [...], // provided by VexRouter
keepAlive: true, // tab state preserved when switching
builder: (ctx, tabIndex, body, onTabChanged) =>
MainShell(tabIndex: tabIndex, body: body, onTabChanged: onTabChanged),
)
๐ญ Observers (Analytics / Logging)
class AnalyticsObserver extends VexObserver {
@override
void onNavigate(VexRouteInfo? from, VexRouteInfo to) =>
Analytics.screen(to.name);
@override
void onPop(VexRouteInfo popped, VexRouteInfo? revealed) =>
Analytics.event('back', {'from': popped.name});
@override
void onDeepLink(Uri uri) => Analytics.deepLink(uri.toString());
@override
void onGuardBlocked(VexRouteInfo attempted, String reason) =>
Analytics.event('guard_blocked', {'route': attempted.name});
}
VexRouter(
observers: [AnalyticsObserver(), CrashReportingObserver()],
routes: [...],
)
๐บ๏ธ Nested & Child Routes
VexRoute(
name: 'settings',
path: '/settings',
builder: (_) => SettingsScreen(),
children: [
VexRoute(
name: 'settings.account',
path: 'account', // resolves to /settings/account
builder: (_) => AccountSettingsScreen(),
),
VexRoute(
name: 'settings.privacy',
path: 'privacy', // resolves to /settings/privacy
builder: (_) => PrivacySettingsScreen(),
),
],
)
๐ช Dialogs & Bottom Sheets as Routes
// Bottom sheet
final result = await VexNavigator.of(context).showBottomSheet<String>(
'editName',
extra: currentName,
);
// Dialog
final confirmed = await VexNavigator.of(context).showDialog<bool>(
'confirmDelete',
barrierDismissible: false,
);
๐งฉ Works with Every State Manager
| State manager | How to use |
|---|---|
| GetX | binding: VexInlineBinding(init: () => Get.put(Ctrl())) |
| Riverpod | binding: VexInlineBinding(init: () => container.read(provider)) |
| Bloc | binding: VexInlineBinding(init: () => BlocProvider.of<B>(ctx)..add(Init())) |
| Provider | No binding needed โ Provider is widget-tree scoped |
| MobX | binding: VexInlineBinding(init: () => store.init()) |
| setState | No binding needed |
๐บ๏ธ Roadmap
Tab-level deep linking (restore exact tab + child stack)Route history persistence (SharedPreferences)Animated tab switchingvex_router_builderoptional code-gen layer for compile-time route namesFirst-class Flutter Web hash / history mode toggleMiddleware pipeline (pre/post navigation hooks)
๐ License
MIT ยฉ 2025 vex_router contributors
Libraries
- vex_router
- vex_router โ The ultimate Flutter Navigation 2.0 router.