Tutorial · 12 min · beginner
Navigation & routes
Pushes, named routes and slide-up full-screen routes through the Flutter Navigator API — with the platform's own transitions and back gestures doing the work.
What you'll build — running on device.
Navigation is where native rendering pays a quiet dividend: the navigation
stack IS the platform’s (a real UINavigationController on iOS), so
transitions, the back button, and the edge-swipe gesture aren’t
re-implemented — they’re simply there. You’ll build a three-screen mini-app
that exercises the whole surface.
What you need
- A project from Your first DartNative app
Step 1 — Push a screen
Navigator.push always takes a Route — wrap the destination in
PageRoute:
Navigator.push(
context,
PageRoute(builder: (_) => const DetailScreen()),
);
That’s the standard slide-from-right. On the pushed screen you write no
back-button code: AppBar’s automaticallyImplyLeading (default true)
shows the native back button because the screen can pop — and the iOS
edge-swipe / Android back gesture work because the stack is native.
Coming from Flutter, the diff is small: MaterialPageRoute →
PageRoute, and there’s no Navigator.of(context) dance — Navigator.push
is a static.
Step 2 — Named routes
Register once in main() — there’s no MaterialApp(routes:):
void main() {
DartNativePluginRegistrant.registerAll();
registerRoutes({
'/settings': (_) => const SettingsScreen(),
});
runApp(const HomeScreen());
}
Then from anywhere:
Navigator.pushNamed(context, '/settings');
Named routes also give screens stable identities for deep links and state restoration — worth using for anything reachable from more than one place.
Step 3 — Slide-up routes
A transition is a PageRoute parameter, not a custom builder:
Navigator.push(
context,
PageRoute(
builder: (_) => const ComposeScreen(),
transition: RouteTransition.slideFromBottom, // bottom-up, full screen
duration: const Duration(milliseconds: 380),
),
);
Available transitions: slideFromRight (default), slideFromBottom,
slideFromLeft, slideFromTop, fade. There’s no
PageRouteBuilder/transitionsBuilder — custom curves don’t port, and in
exchange every transition is the platform’s own animation.
The pushed screen is a normal full-screen route wearing the modal grammar — so it should close, not “back”:
appBar: AppBar(
automaticallyImplyLeading: false, // close, don't "back"
actions: [
BarButtonItem(
title: 'Close',
onPressed: () => Navigator.pop(context),
),
],
),
Navigator.pop(context) dismisses any pushed screen — and can return a
value to the pusher’s awaited Future, exactly as in Flutter.
For a real system modal — a detachable sheet over the current screen — use
showModalBottomSheet (both platforms) or showCupertinoSheet (iOS detent
sheet); this tutorial stays on the Navigator surface.
Why this is native
Flutter and React Native re-implement navigation transitions and gesture handling in their own rendering layers — always slightly off from the OS of the day. Here the stack is a real
UINavigationController: the push curve, the interactive edge-swipe with its cancel physics, the back button (Liquid Glass capsule on iOS 26) are the system’s, current by definition. You wrote none of it.
The finished code
One file, four screens, in the public repo — dn create ., dn run.