๐Ÿš€ vex_router

The ultimate Flutter Navigation 2.0 router.
No code generation. No GetMaterialApp. No compromise.

pub.dev License: MIT Flutter Dart 3 Platforms


๐Ÿ“Š 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 / StatefulShellRoute boilerplate is extremely verbose
  • No route guards โ€” only a single global redirect callback
  • 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_runner and 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 BuildContext entirely (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 BeamLocation setup
  • 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

// 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;
// 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 switching
  • vex_router_builder optional code-gen layer for compile-time route names
  • First-class Flutter Web hash / history mode toggle
  • Middleware pipeline (pre/post navigation hooks)

๐Ÿ“„ License

MIT ยฉ 2025 vex_router contributors

Libraries

vex_router
vex_router โ€” The ultimate Flutter Navigation 2.0 router.