back_stack 0.1.0 copy "back_stack: ^0.1.0" to clipboard
back_stack: ^0.1.0 copied to clipboard

You own the back stack. Navigation is a list you push and pop — type-safe, observable, no route graph.

back_stack #

You own the back stack. Navigation is a List you push and pop — type-safe, observable, no route graph.

pub package license tests

[back_stack demo]

Push adds, pop removes, the UI follows. No go vs push. No route graph. No RouterDelegate ceremony. Destinations are plain Dart types, checked by the compiler.

// 1. Destinations are typed objects. Arguments are real, checked fields.
sealed class AppKey extends NavKey {}
class Home extends AppKey { const Home(); }
class Product extends AppKey { const Product(this.id); final int id; }

// 2. The back stack is a list you own.
final stack = NavStack.of(const Home());

// 3. Render it. NavDisplay watches the list and follows.
NavDisplay(
  stack: stack,
  builder: (context, key) => switch (key as AppKey) {
    Home()             => const HomeScreen(),
    Product(:final id) => ProductScreen(id: id),
  },
);

// Navigate by changing the list:
stack.push(const Product(42));    // add
stack.pop();                      // remove
stack.replaceAll([const Home()]); // reset the flow (e.g. after login)
stack.popUntil((k) => k is Home); // unwind

System back, the Android predictive-back gesture, and the hardware back button all flow into the list automatically — you never wire that up.

Install #

dependencies:
  back_stack: ^0.1.0

Reach the stack from anywhere #

NavDisplay provides the stack to every screen below it — no passing it down by hand:

onTap: () => BackStack.of(context).push(const Product(42)),

It doesn't subscribe by default (right for event handlers); pass listen: true to rebuild on change.

Why #

Common pain (go_router / Navigator 2.0) back_stack
go vs push confusion; differs web vs mobile One operation: mutate the list.
extra is Object? — not type-safe Typed destinations, compiler-checked args.
Back stack is a black box stack.keys is plain, observable data.
Redirect loops (/login → /login → …) redirect is a pure function, applied once. Loop-proof.
50-line route tables away from screens One switch next to your screens.

Features #

  • Own the stackpush / pop / replaceAll / popUntil / edit. It's just a List.
  • Resultsawait stack.pushForResult<Color>(picker); complete it with pop(value). Never hangs.
  • Web & deep links — one NavStackCodec (Uri ⇄ List) gives URL sync, browser back/forward, and you decide what a link materializes.
  • Auth gatingredirect (pure transform) and guard (veto), applied once per change. Loop-proof.
  • Adaptive layoutNavListDetail turns one stack into list-detail / panes on wide screens, a stack on phones.
  • Per-tab historyMultiNavStack gives each bottom-nav tab its own persistent back stack.
  • Shared elementsHero transitions just work, including inside nested displays.
  • Custom transitionsTransitionPage (fade / slideUp / scale), DialogPage, SheetPage.
  • RestorationRestorableBackStack survives process death without a URL.
  • Leak-safe — leaving the stack disposes the route; the whole suite runs under leak_tracker.

One small class translates the stack ⇄ a Uri; the stack stays the source of truth.

class ShopCodec extends NavStackCodec {
  @override
  Uri encode(List<NavKey> stack) => switch (stack.last) {
    Home()             => Uri(path: '/'),
    Product(:final id) => Uri(path: '/products/$id'),
    _                  => Uri(path: '/'),
  };

  @override
  List<NavKey> decode(Uri uri) {
    final s = uri.pathSegments;
    if (s.length == 2 && s[0] == 'products') {
      return [const Home(), Product(int.parse(s[1]))]; // layer on Home, not replace
    }
    return [const Home()];
  }
}

MaterialApp.router(
  routerDelegate: NavStackRouterDelegate(
    stack: NavStack.of(const Home()),
    codec: ShopCodec(),
    builder: (context, key) => /* your screen */,
  ),
  routeInformationParser: const NavStackRouteInformationParser(),
  restorationScopeId: 'app', // survive process death
);

Auth gating, without loops #

stack.redirect = (proposed) {
  final guarded = proposed.any((k) => k is Account);
  return (guarded && !isLoggedIn) ? [const Login()] : proposed;
};

A pure function applied once per change — it can't ping-pong like a URL-redirect engine.

Example #

cd example && flutter run               # the shop demo
cd example && flutter run -t lib/pokedex.dart   # the Pokédex above

See doc/PHILOSOPHY.md for how each Flutter navigation leak and caveat is handled.

License #

MIT

5
likes
0
points
1.02k
downloads

Publisher

verified publisherandroidpoet.dev

Weekly Downloads

You own the back stack. Navigation is a list you push and pop — type-safe, observable, no route graph.

Repository (GitHub)
View/report issues

Topics

#navigation #router #navigator #routing

License

unknown (license)

Dependencies

flutter

More

Packages that depend on back_stack