smart_deeplink_router

A simple, elegant Flutter package for deep link routing with guards and redirect memory.

pub package GitHub tag

Why this package?

Deep linking in Flutter often requires solving this common problem:

User clicks deep link → needs authentication → redirect to login → after login, return to original destination

Most routing solutions make this complex. smart_deeplink_router solves it with a clean, minimal API.

What's new (v0.1.0)

  • Named-route helpers: SmartLinkRouter.openNamed(name, params: {...}, query: {...}) for convenient programmatic navigation.
  • Per-route transition support via an optional transitionBuilder on LinkRoute.
  • Navigation history + back helper: SmartLinkRouter.history and SmartLinkRouter.back().
  • Optional persistent redirect memory: call await RedirectMemory.instance.initialize(persistent: true) at app startup to persist redirect targets across restarts.

These features keep the public API minimal while adding practical, production-focused capabilities.

Features

Simple API - Just routes and guards, nothing more
Auth Guards - Protect routes with async authentication checks
Redirect Memory - Automatically return to the original destination after login
Persistent Redirect Memory - Optional SharedPreferences-backed persistence (call initialize) ✅ Path Parameters - Support for :id style parameters
Query Parameters - Automatically parsed and passed to builders
Deep Links - Works out of the box with Flutter's deep linking
Production Ready - Null-safe, well-tested, documented

Quick Start

Installation

dependencies:
  smart_deeplink_router: ^0.1.0

Basic Usage

import 'package:flutter/material.dart';
import 'package:smart_deeplink_router/smart_deeplink_router.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final router = SmartLinkRouter(
      routes: [
        LinkRoute(
          path: '/',
          builder: (context, params) => const HomePage(),
        ),
        LinkRoute(
          path: '/product/:id',
          builder: (context, params) => ProductPage(
            id: params['id']!,
          ),
        ),
        LinkRoute(
          path: '/login',
          builder: (context, params) => const LoginPage(),
        ),
      ],
      guards: [
        RequireAuthGuard(
          isAuthenticated: () async => authService.isLoggedIn,
          redirectTo: '/login',
        ),
      ],
    );

    return MaterialApp.router(
      routerConfig: router.config,
    );
  }
}

Creating a Guard

class RequireAuthGuard extends LinkGuard {
  final Future<bool> Function() isAuthenticated;
  final String redirectTo;

  RequireAuthGuard({
    required this.isAuthenticated,
    required this.redirectTo,
  });

  @override
  Future<bool> canActivate(Uri uri) => isAuthenticated();

  @override
  Uri? onRedirect(Uri uri) => Uri.parse(redirectTo);
}

Handling Redirects After Login

// In your login success handler:
authService.login();

final redirectTarget = RedirectMemory.instance.consume();
if (redirectTarget != null) {
  // Navigate back to the original deep link destination
  router.open(redirectTarget);
} else {
  // No redirect, go to home
  router.open(Uri.parse('/'));
}

Complete Example

See the example directory for a complete working app demonstrating:

  • Home page with navigation
  • Protected product page requiring authentication
  • Login page with redirect memory
  • Deep link handling with path parameters

API Reference

SmartLinkRouter

Main router class that handles navigation and guards.

SmartLinkRouter({
  required List<LinkRoute> routes,
  List<LinkGuard> guards = const [],
  LinkRoute? notFoundRoute,
});

Properties:

  • config - The RouterConfig to pass to MaterialApp.router

Methods:

  • open(Uri uri) - Manually navigate to a URI

LinkRoute

Defines a single route in your application.

LinkRoute({
  required String path,
  required Widget Function(BuildContext, Map<String, String>) builder,
  String? name,
});

Path Syntax:

  • Static: /home, /about
  • With parameters: /product/:id, /user/:userId/post/:postId

LinkGuard

Abstract class for creating route guards.

abstract class LinkGuard {
  Future<bool> canActivate(Uri uri);
  Uri? onRedirect(Uri uri);
}

RedirectMemory

Singleton for managing redirect targets.

RedirectMemory.instance.save(uri);    // Save target
RedirectMemory.instance.consume();    // Get and clear
RedirectMemory.instance.peek();       // Get without clearing
RedirectMemory.instance.clear();      // Clear without getting

Screenshots

Brief placeholders below — replace with real screenshots or GIFs from the example app.

Example screenshot 1

Example screenshot 2

How to replace:

  1. Run the example app on your device or emulator.
  2. Capture screenshots (Android: adb exec-out screencap -p > screenshot.png, iOS: use Simulator's Save Screenshot). Or use Flutter's screenshot tooling.
  3. Place final images under assets/screenshots/ and commit. Prefer 1280x720 or similar landscape sizes for best display.

Automating screenshots / GIF (local instructions)

I included a helper script scripts/capture_instructions.sh with suggested commands to capture screenshots and build a GIF using common tools (adb, ffmpeg, ImageMagick). The script is a guide — run it locally on your machine or CI runner that has access to a device/emulator.

Notes on adding the final images:

  • Put PNG/JPEG/GIF files into assets/screenshots/ and commit.
  • Prefer descriptive filenames like auth-redirect-01.png, auth-redirect-02.png, and auth-redirect.gif.
  • Update this README line if you replace placeholders so the repository README shows real images.

Compared to Other Solutions

vs go_router

  • Simpler API: No complex nested routes or shell routes
  • Focused: Solves deep link + auth problem specifically
  • Lighter: Minimal dependencies and smaller package size

vs auto_route

  • No code generation: Pure Dart, no build_runner needed
  • More flexible guards: Async guards with redirect memory built-in
  • Easier to learn: Fewer concepts to understand

Roadmap

openNamed — parameter validation & query merging

SmartLinkRouter.openNamed(name, params: {...}, query: {...}) is a convenience builder that constructs the final Uri for you and opens it using router.open(Uri).

Rules:

  • Required path parameters are validated. If a route has a path like /product/:id and you call openNamed('product') without params: {'id': '...'} , an ArgumentError is thrown explaining which parameter(s) are missing.
  • Any entries in params that are not used to fill :param placeholders are converted into query parameters.
  • The explicit query map passed to openNamed takes precedence on key collisions. Example: params: {'ref':'notif'} and query: {'ref':'email'} will result in ref=email in the final URI.

Example:

// Route: /product/:id
await router.openNamed(
  'product',
  params: {'id': '42', 'ref': 'notif'},
  query: {'ref': 'email', 'utm': 'spring'},
);
// final URI -> /product/42?ref=email&utm=spring

This behavior reduces duplication of query-building code and makes programmatic navigation less error-prone.

Nested routes (quick note)

LinkRoute supports an optional children list. Children are matched against the remainder of the path after the parent route's pattern is consumed. Use child routes when building nested flows (for example, an /account shell with /account/profile and /account/settings).

Example nested route declaration and usage:

final router = SmartLinkRouter(
  routes: [
    LinkRoute(
      path: '/account',
      builder: (c, p) => AccountShell(),
      children: [
        LinkRoute(path: 'profile', name: 'account.profile', builder: (c, p) => ProfilePage()),
        LinkRoute(path: 'settings', name: 'account.settings', builder: (c, p) => SettingsPage()),
      ],
    ),
  ],
);

// Programmatic navigation to a nested child using openNamed
await router.openNamed('account.profile');
  • x Named routes support (v0.1.0)
  • x Transition animations (per-route transitions, v0.1.0)
  • Nested navigation
  • x Route history management (v0.1.0)
  • x Persistent redirect memory (SharedPreferences) (optional, v0.1.0)

Contributing

Contributions are welcome! Please read our contributing guidelines first.

License

MIT License - see LICENSE file for details.

Author

Created with ❤️ for the Flutter community


⭐ If you find this package helpful, please give it a star on GitHub!

Libraries

A simple, elegant Flutter package for deep link routing with guards and redirect memory.