nav_bridge 3.0.2 copy "nav_bridge: ^3.0.2" to clipboard
nav_bridge: ^3.0.2 copied to clipboard

A router-agnostic navigation layer for Flutter with testable guards and progressive GoRouter migration.

Nav Bridge #

pub package pub points popularity likes CI codecov License: MIT

A progressive, router-agnostic navigation layer for Flutter that allows you to wrap existing GoRouter apps and migrate to a clean, testable, decoupled architecture — without rewriting your routes.

Architecture

Why Nav Bridge? #

Flutter apps are usually tightly coupled to a routing library:

// ❌ Your business logic depends on GoRouter
context.go('/users/42');
context.push('/settings');

This creates several problems:

  • Vendor lock-in: Business logic depends on GoRouter/AutoRoute
  • Untestable: Navigation requires Flutter widgets
  • Expensive migrations: Changing routers means rewriting navigation
  • Tight coupling: UI and routing are inseparable

Nav Bridge solves this with a thin abstraction layer:

// ✅ Your app talks to AppRouter, not GoRouter
appRouter.goToUserProfile('42');

Key Features #

Feature Description
Wrap Mode Use your existing GoRouter without any changes
Progressive Migration Migrate one feature at a time, old & new coexist
Full DI Support Riverpod Ref available in all guards
Guard Bridges Keep existing guards working immediately
InMemoryAdapter Unit test navigation without Flutter
Shell Navigation Full StatefulShellRoute support

Installation #

dependencies:
  nav_bridge: ^2.0.0
  go_router: ^15.0.0

  # Optional - for Riverpod guards
  flutter_riverpod: ^2.4.0
flutter pub get

Quick Start #

Step 1: Wrap Your Existing Router #

Zero changes to your existing code:

import 'package:nav_bridge/nav_bridge.dart';

// Your existing GoRouter (unchanged)
final goRouter = GoRouter(
  routes: [...],
  redirect: myRedirectLogic,
);

// Wrap it with Nav Bridge
final adapter = GoRouterAdapter.wrap(goRouter);

// Everything still works!
context.go('/profile/42');  // ✅ Still works

Step 2: Add Guards with DI Support #

final adapter = GoRouterAdapter.wrap(
  goRouter,
  additionalGuards: [AuthGuard(), RoleGuard()],
);

// Inject dependencies (Riverpod Ref, etc.)
adapter.contextBuilder = (state) => {
  'ref': ref,
  'goRouterState': state,
  'context': navigatorKey.currentContext,
};

Step 3: Create Type-Safe Navigation (Optional) #

abstract class AppRouter {
  Future<void> goToHome();
  Future<void> goToUserProfile(String userId);
}

class MyAppRouter implements AppRouter {
  final GoRouterAdapter _adapter;

  MyAppRouter(this._adapter);

  @override
  Future<void> goToUserProfile(String userId) =>
      _adapter.go('/profile/$userId');
}

Guards #

Modern Riverpod Guards #

class AuthGuard extends RiverpodRouteGuard {
  @override
  int get priority => 100;  // Higher = runs first

  @override
  List<String>? get excludes => ['/login', '/register'];

  @override
  Future<GuardResult> canActivateWithRef(
    GuardContext context,
    Ref ref,
  ) async {
    final isAuthenticated = ref.read(authProvider).isAuthenticated;

    if (!isAuthenticated) {
      return GuardResult.redirect('/login');
    }

    return GuardResult.allow();
  }
}

Bridge Existing Guards (Zero Rewrite) #

Already have guards? Bridge them without any changes:

// Your existing guard function
FutureOr<GuardResult> myExistingGuard(
  BuildContext context,
  GoRouterState state,
  Ref ref,
) async {
  // Your existing logic...
}

// Bridge it - zero changes needed!
final bridgedGuard = GoRouterGuardBridge(myExistingGuard);

Guard Result Types #

sealed class GuardResult {
  // Allow navigation
  static GuardAllow allow();

  // Redirect to another path
  static GuardRedirect redirect(String path, {Map<String, dynamic>? extra});

  // Block navigation
  static GuardReject reject({String? reason});
}

Unit Testing #

Test navigation without Flutter widgets:

void main() {
  group('Navigation', () {
    test('authenticated user can access profile', () async {
      final router = InMemoryAdapter(
        guards: [MockAuthGuard(isAuthenticated: true)],
      );

      await router.go('/profile/42');

      expect(router.currentLocation, '/profile/42');
    });

    test('unauthenticated user is redirected to login', () async {
      final router = InMemoryAdapter(
        guards: [MockAuthGuard(isAuthenticated: false)],
      );

      await router.go('/profile/42');

      expect(router.currentLocation, '/login');
    });

    test('tracks navigation history', () async {
      final router = InMemoryAdapter();

      await router.go('/');
      await router.push('/profile/42');
      await router.push('/settings');

      expect(router.navigationHistory, ['/', '/profile/42', '/settings']);

      router.pop();
      expect(router.currentLocation, '/profile/42');
    });
  });
}

Progressive Migration Guide #

Phase 1: Wrap (Day 1) #

// Just wrap, nothing else changes
final adapter = GoRouterAdapter.wrap(existingRouter);

Phase 2: Bridge Guards (Week 1) #

// Bridge existing guards
final bridged = GoRouterGuardBridge(existingGuard);

Phase 3: Add AppRouter (Week 2) #

// Create type-safe navigation
abstract class AppRouter {
  Future<void> goToProfile(String id);
}

Phase 4: Migrate Features (Ongoing) #

// Old and new coexist
context.go('/old');           // Old code ✅
appRouter.goToProfile('42');  // New code ✅

Architecture #

┌─────────────────────────────────────────────────────────┐
│                    Feature Code                          │
│              (Uses AppRouter interface)                  │
└─────────────────────────┬───────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                     AppRouter                            │
│            (Your type-safe abstraction)                  │
└─────────────────────────┬───────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                     Nav Bridge                           │
│         (Guards, DI, Navigation abstraction)             │
└─────────────────────────┬───────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│              GoRouterAdapter.wrap()                      │
│            (Wraps your existing router)                  │
└─────────────────────────┬───────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│              Your Existing GoRouter                      │
│          (Routes, Shell, everything intact)              │
└─────────────────────────────────────────────────────────┘

API Reference #

Adapters #

Adapter Use Case
GoRouterAdapter.wrap() Existing GoRouter apps (recommended)
GoRouterAdapter.create() New applications
GoRouterAdapter.withGuards() New with integrated guard system
InMemoryAdapter Unit testing

Guards #

Class Description
RouteGuard Base class for all guards
RiverpodRouteGuard Guard with Riverpod Ref access
GoRouterGuardBridge Bridge for existing guards with Ref
SimpleGoRouterGuardBridge Bridge for guards without Ref
GoRouterRedirectBridge Bridge for redirect functions
CompositeGuard Combine guards with AND logic
AnyGuard Combine guards with OR logic

Core Classes #

Class Description
GuardContext Context passed to guards with DI support
GuardResult Sealed class: Allow, Redirect, Reject
RouteDefinition Router-agnostic route definition
ShellRouteDefinition Shell/tab navigation support

Roadmap #

  • ✅ GoRouter wrap mode
  • ✅ Riverpod guard support
  • ✅ Guard bridge adapters
  • ✅ InMemoryAdapter for testing
  • ✅ Shell navigation support
  • ❌ AutoRoute adapter
  • ❌ Beamer adapter
  • ❌ Typed route code generation
  • ❌ Analytics observers
  • ❌ Transition abstraction

When Should You Use This? #

Scenario Recommendation
Existing GoRouter app Perfect fit
Large team / enterprise Highly recommended
Need navigation unit tests Essential
Planning router migration Future-proof
Small personal app Optional

Community #

Contributing #

Contributions are welcome! Please read our Contributing Guide first.

# Clone the repo
git clone https://github.com/chekarhamza88-stack/nav_bridge.git

# Install dependencies
flutter pub get

# Run tests
flutter test

# Check analysis
flutter analyze

License #

MIT License - see LICENSE for details.


Nav Bridge doesn't replace GoRouter.
It makes GoRouter testable, replaceable, decoupled, and enterprise-ready.

Made with love by chekarhamza88-stack

1
likes
160
points
75
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

A router-agnostic navigation layer for Flutter with testable guards and progressive GoRouter migration.

Repository (GitHub)
View/report issues
Contributing

Topics

#navigation #routing #go-router #architecture #testing

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, go_router

More

Packages that depend on nav_bridge