smart_deeplink_router 0.1.1
smart_deeplink_router: ^0.1.1 copied to clipboard
A simple, elegant Flutter package for deep link routing with guards and redirect memory. Solves deep link + auth + redirect flow with minimal API.
smart_deeplink_router #
A simple, elegant Flutter package for deep link routing with guards and redirect memory.
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
transitionBuilderonLinkRoute. - Navigation history + back helper:
SmartLinkRouter.historyandSmartLinkRouter.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- TheRouterConfigto pass toMaterialApp.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:
- Run the example app on your device or emulator.
- Capture screenshots (Android:
adb exec-out screencap -p > screenshot.png, iOS: use Simulator's Save Screenshot). Or use Flutter's screenshot tooling. - 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, andauth-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/:idand you callopenNamed('product')withoutparams: {'id': '...'}, anArgumentErroris thrown explaining which parameter(s) are missing. - Any entries in
paramsthat are not used to fill:paramplaceholders are converted into query parameters. - The explicit
querymap passed toopenNamedtakes precedence on key collisions. Example:params: {'ref':'notif'}andquery: {'ref':'email'}will result inref=emailin 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');
- ✅ Named routes support (v0.1.0)
- ✅ Transition animations (per-route transitions, v0.1.0)
- ❌ Nested navigation
- ✅ Route history management (v0.1.0)
- ✅ 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!