unrouter 0.11.0 copy "unrouter: ^0.11.0" to clipboard
unrouter: ^0.11.0 copied to clipboard

Declarative, guard-driven router for Flutter with nested routes and typed helpers.

unrouter

Declarative, composable router for Flutter.

Test dart flutter pub license


Features #

  • 🧩 Nested Routes — Define route trees with Inlet and render child views via Outlet
  • 🏷️ Named Routes — Navigate by route name with params, query, and state
  • 🛡️ Guards — Navigation-time guards for allow/block/redirect decisions
  • 📦 Route Meta — Attach arbitrary metadata to each route, inherited by children
  • 🔗 Dynamic Params & Wildcards:id params and * catch-all segments
  • 🔍 Query Params — First-class URLSearchParams support
  • 📍 History APIpush, replace, pop, back, forward, go(delta)
  • Reactive HooksuseRouter, useLocation, useRouteParams, useQuery, useRouteMeta, useRouteState, useFromLocation

Quick Start #

Install #

dependencies:
  unrouter: <latest>
flutter pub add unrouter

Define Routes #

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

final authGuard = defineGuard((context) async {
  final token = context.query.get('token');
  if (token == 'valid') {
    return const GuardResult.allow();
  }
  return GuardResult.redirect('login');
});

final router = createRouter(
  guards: [authGuard],
  maxRedirectDepth: 8,
  routes: [
    Inlet(name: 'landing', path: '/', view: LandingView.new),
    Inlet(name: 'login', path: '/login', view: LoginView.new),
    Inlet(
      path: '/workspace',
      view: WorkspaceLayoutView.new,
      children: [
        Inlet(name: 'workspaceHome', path: '', view: DashboardView.new),
        Inlet(name: 'profile', path: 'users/:id', view: ProfileView.new),
        Inlet(name: 'search', path: 'search', view: SearchView.new),
      ],
    ),
    Inlet(name: 'docs', path: '/docs/*', view: DocsView.new),
  ],
);

routes supports multiple top-level Inlets. Use a single parent Inlet with Outlet only when views share the same layout. For concise route definitions, prefer constructor tear-offs such as MyView.new.

Bootstrap the App #

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: createRouterConfig(router),
    );
  }
}

Render Nested Views with Outlet #

class LayoutView extends StatelessWidget {
  const LayoutView();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('My App')),
      body: const Outlet(),
    );
  }
}

Runnable Example #

Run the full example app:

cd example
flutter pub get
flutter run -d chrome

Source entry points:

Core Concepts #

Inlet — Route Definition #

Inlet is the route-tree building block. Each Inlet describes a path segment, a view builder, optional children, guards, meta, and an optional route name.

Inlet(
  name: 'profile',
  path: 'users/:id',
  view: ProfileView.new,
  meta: const {'title': 'Profile', 'requiresAuth': true},
  guards: [authGuard],
  children: [/* nested Inlets */],
)
Property Type Description
path String URI path segment pattern. Defaults to '/'
view ViewBuilder () => Widget factory, typically MyView.new
name String? Route name alias for navigation APIs
meta Map<String, Object?>? Route metadata, merged with parent meta
guards Iterable<Guard> Route-level guard chain
children Iterable<Inlet> Nested child routes

Guard #

A guard runs before navigation is committed and returns one of three outcomes:

  • GuardResult.allow()
  • GuardResult.block()
  • GuardResult.redirect(pathOrName, {params, query, state})
final adminGuard = defineGuard((context) async {
  final isAdmin = context.query.get('role') == 'admin';
  if (isAdmin) {
    return const GuardResult.allow();
  }
  return GuardResult.redirect(
    'login',
    query: URLSearchParams({'from': 'admin'}),
  );
});

Guard order is: global → parent → child.

  • Redirects are re-validated by guards.
  • Redirect commits use replace.
  • Redirect depth is capped by maxRedirectDepth (default 8) to prevent infinite loops.

GuardContext #

GuardContext provides navigation details:

  • from / to (HistoryLocation)
  • action (HistoryAction.push, .replace, .pop)
  • params (RouteParams)
  • query (URLSearchParams)
  • meta (Map<String, Object?>)
  • state (Object?)

Named Navigation #

push/replace(pathOrName) resolves in this order:

  1. Try route name first
  2. If missing, fallback to absolute path
final router = useRouter(context);

await router.push('profile', params: {'id': '42'});
await router.push('/users/42?tab=posts');
await router.replace('landing');

Query Merging #

If both the input string and query argument contain query params, they are merged and explicit query entries override same-name keys.

await router.push(
  '/search?q=old&page=1',
  query: URLSearchParams({'q': 'flutter'}),
);
// => /search?q=flutter&page=1

Link is a lightweight widget that triggers navigation.

Link(
  to: 'profile',
  params: const {'id': '42'},
  child: const Text('Open Profile'),
)

Supported props:

  • to
  • params
  • query
  • state
  • replace
  • enabled
  • onTap
  • child

Outlet — Nested View Rendering #

Outlet renders the matched child view inside its parent. Every level of nesting requires an Outlet in the parent widget tree.

Route Meta #

Meta is merged from parent to child routes.

Inlet(
  view: Layout.new,
  meta: const {'layout': 'dashboard'},
  children: [
    Inlet(
      path: 'admin',
      view: AdminView.new,
      meta: const {'title': 'Admin', 'requiresAuth': true},
    ),
  ],
)

Read meta in a widget:

final meta = useRouteMeta(context);

History Control #

final router = useRouter(context);

await router.pop();
router.back();
router.forward();
router.go(-2);
router.go(1);

Reactive Hooks #

Hook Returns Description
useRouter(context) Unrouter Router instance
useLocation(context) HistoryLocation Current location (uri + state)
useRouteParams(context) RouteParams Matched :param values
useQuery(context) URLSearchParams Parsed query string
useRouteMeta(context) Map<String, Object?> Merged route metadata
useRouteState<T>(context) T? Typed navigation state
useRouteURI(context) Uri Current route URI
useFromLocation(context) HistoryLocation? Previous location

API Reference #

createRouter #

Unrouter createRouter({
  required Iterable<Inlet> routes,
  Iterable<Guard>? guards,
  String base = '/',
  int maxRedirectDepth = 8,
  History? history,
  HistoryStrategy strategy = HistoryStrategy.browser,
})

createRouterConfig #

RouterConfig<HistoryLocation> createRouterConfig(Unrouter router)

defineGuard #

Guard defineGuard(Guard guard)

defineDataLoader #

DataLoader<T> defineDataLoader<T>(
  DataFetcher<T> fetcher, {
  ValueGetter<T?>? defaults,
})

License #

MIT © Seven Du

6
likes
160
points
458
downloads

Publisher

verified publishermedz.dev

Weekly Downloads

Declarative, guard-driven router for Flutter with nested routes and typed helpers.

Repository (GitHub)
View/report issues

Topics

#flutter #router #navigation #guard #history

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, ht, oref, roux, unstory

More

Packages that depend on unrouter