vex_router 1.0.0 copy "vex_router: ^1.0.0" to clipboard
vex_router: ^1.0.0 copied to clipboard

The ultimate Flutter router. Navigation 2.0 done right — no code generation, no GetMaterialApp lock-in, full GetX controller binding support, type-safe routes, deep linking, guards, nested navigation, [...]

🚀 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

2
likes
150
points
0
downloads

Documentation

Documentation
API reference

Publisher

verified publishermysteriouscoder.com

Weekly Downloads

The ultimate Flutter router. Navigation 2.0 done right — no code generation, no GetMaterialApp lock-in, full GetX controller binding support, type-safe routes, deep linking, guards, nested navigation, and a simple API. Zero boilerplate. Zero compromises.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on vex_router