Route — what it is, types and creating routes in Flutter

Author: IT Sectr Published: 2026-07-02 Reading time: 8 min

Route is an abstract class in Flutter that represents a separate screen or page in the Navigator's navigation history. Concrete implementations of Route — MaterialPageRoute, CupertinoPageRoute and PlatformRoute — determine how the screen is displayed and what animation is used during the transition. Unlike a regular widget, Route has its own lifecycle with methods didPush, didPop, didReplace and didChangeNext. According to the Flutter API Reference (2026), each Route manages ModalBarrier, accounts for platform-specific features (swipe-back on iOS) and ensures state isolation between screens.

Key Takeaways

  • Route — an abstract screen class in Flutter with its own lifecycle (didPush, didPop, didReplace, didChangeNext)
  • MaterialPageRoute — the standard Route implementation with Material animation (slide from bottom) for Android and desktop
  • CupertinoPageRoute — Route implementation with iOS animation (slide from right + gesture swipe-back) for iPhone and iPad
  • Lifecycle — Route goes through stages: Transition, Active, Inactive and Disposed
  • Data transfer — Route accepts arguments via constructor and returns a result via Future when finished

What is Route in Flutter

Route is the foundation of the Flutter navigation system. Every screen that a user sees in an application is represented by a Route object located in the Navigator stack. Route abstracts the screen from the controlling code: Navigator adds and removes Route, while inside the Route is the interface that the user sees. This architecture separates responsibility between navigation and display.

Unlike simple widget replacement, Route provides services unavailable to regular widgets: ModalBarrier (background dimming when a dialog is open), enter and exit animation control, hardware back button handling on Android, and integration with Hero animation for smooth transitions between screens.

According to Flutter Cookbook (2026), Route is a key element for Hero animation: a Hero widget on one Route automatically animates the transition to a Hero widget on the next Route, creating a "flying" element effect. This is possible precisely because Route keeps both screens in the Overlay during animation.

Route as a Building Block of Navigation

Route is the foundation for both simple mobile applications (via Navigator 1.0) and complex scenarios with deep links (Navigator 2.0). In Navigator 2.0, Route represents a Page that RouterDelegate converts from the route configuration. Thus, understanding Route is essential for working with any Flutter navigation system — regardless of the chosen approach or package.

Route Types: MaterialPageRoute and CupertinoPageRoute

Flutter provides several built-in Route implementations, each adapting behavior to a specific platform. Choosing the right Route type affects user experience: Material animation on Android and Cupertino animation on iOS create the feel of a "native" application.

Route TypeAnimationPlatformFeatures
MaterialPageRouteSlide from bottom to topAndroid, desktopShadow on transition, automatic SafeArea handling
CupertinoPageRouteSlide from right to leftiOS, iPadOSGesture swipe-back, transparent background during transition
PlatformRouteAutomatic selectionAll platformsSelects type based on TargetPlatform
PageRouteBuilderCustomAll platformsFull control over animation via AnimationController

MaterialPageRoute: Android Standard

MaterialPageRoute is the most commonly used Route implementation. It animates the new screen entering from bottom to top with gradual appearance. When exiting, the screen animates from top to bottom, returning to its original position. The toolbar (AppBar) and the screen body animate separately, creating a hierarchy effect.

CupertinoPageRoute: iOS Navigation Style

CupertinoPageRoute mimics UINavigationController from iOS. The new screen enters from the right, covering the previous one. The key feature is support for interactive swipe-back gesture, implemented via CupertinoBackGestureDetector. This gesture is handled even mid-animation, providing natural behavior familiar to iPhone users.

Route Lifecycle: from Creation to Disposal

Route has its own lifecycle, which differs from the lifecycle of a regular StatefulWidget. Understanding this cycle is necessary for proper data initialization, stream subscriptions, and resource release when closing a screen.

Route Lifecycle Stages

The Route lifecycle consists of four main stages. Transition — Route is created and animated on entry (didPush is called). Active — Route is fully displayed and interacting with the user. Inactive — another Route covers the current one (dialog, bottom sheet), but the Route remains in the stack. Disposed — Route is removed from the stack and destroyed, didPop and dispose are called.

Route lifecycle methods can be overridden in a custom implementation. For example, didPop is called when Route is removed from the stack — here you can save draft data. didChangeNext is called when the next Route in the stack has changed — useful for updating UI when navigation history changes.

According to Flutter API Route.didPop (2026), it is important not to confuse the Route lifecycle with the lifecycle of State inside Route. StatefulWidget inside Route has its own initState and dispose, which are called during the Transition and Disposed stages respectively. Route lives longer than its internal State — Route remains in the Overlay even when its widgets are temporarily hidden by another Route.

Passing Data via Route Between Screens

Route provides mechanisms for passing data both on input (when created) and on output (when finished). Proper data passing via Route eliminates the need for global variables and InheritedWidget, making navigation type-safe and predictable.

To pass data to a new screen, use the constructor of the receiving widget or the arguments parameter in Navigator.pushNamed. Inside Route, data is accessible via RouteSettings.arguments, which is stored in the Route object. This approach works for all Route types — MaterialPageRoute, CupertinoPageRoute and custom implementations.

To return data, use the second argument of Navigator.pop(context, result). Navigator.push returns Future, which completes with the value passed to pop. If pop is called without an argument, Future completes with null. This mechanism is analogous to startActivityForResult in Android and completion handler in iOS, but implemented via Dart Futures.

Passing Data via Route Constructor

When calling Navigator.push directly with MaterialPageRoute, data is passed through the constructor of the target screen. MethodChannel is not used — this is pure Dart interaction. This approach is preferred for type-safe passing of complex objects.

Custom Route Creation Example

Let's look at an example of creating a custom Route with its own animation and data passing. PageRouteBuilder allows you to define enter and exit animations with full control over the animation curve and duration.

dart
// Custom Route with slide animation
Navigator.push(context, PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation) {
    return DetailPage(productId: '42');
  },
  transitionsBuilder: (context, animation, secondaryAnimation, child) {
    const begin = Offset(0.0, 0.3);
    const end = Offset.zero;
    final tween = Tween(begin: begin, end: end);
    final offsetAnimation = animation.drive(tween);
    return SlideTransition(position: offsetAnimation, child: child);
  },
  transitionDuration: const Duration(milliseconds: 400),
));

// Return data from screen
ElevatedButton(
  onPressed: () => Navigator.of(context).pop({'selected': true, 'id': '42'}),
  child: const Text('Select'),
);

// Get result on calling screen
final result = await Navigator.push(context, MaterialPageRoute(
  builder: (context) => const SelectionPage(),
));
if (result != null) {
  print('Selected: ${result['selected']}');
}

In the example, PageRouteBuilder defines a custom slide-from-bottom animation with transparency. transitionDuration sets the animation speed. The code also demonstrates result passing: the detail screen returns a Map with the user's selection, and the calling screen receives this data via Future from push. Route ensures complete isolation: drafts on the detail screen do not affect the list state.

Frequently Asked Questions

What is the difference between Route and Widget in Flutter?

Route is an object that manages a screen at the navigation level: it stores animation, ModalBarrier and lifecycle. Widget is a description of a part of the interface. Route contains Widget inside itself, but also provides services (Overlay layer, Hero animation) unavailable to regular widgets. One Route can contain a complex hierarchy of widgets of any depth.

How to create a Route with custom animation?

Use PageRouteBuilder with pageBuilder (screen construction) and transitionsBuilder (animation definition) parameters. In transitionsBuilder, animation (0.0–1.0) and secondaryAnimation are available for parallel animations. For full control, create a Route subclass and override buildPage, createAnimationController and buildTransitions, giving access to low-level AnimationController.

How to pass a complex object between Route?

To pass complex objects, use the constructor of the target screen when calling Navigator.push directly or the arguments parameter with pushNamed. Make sure the object is serializable (Map, JSON or custom class). For type-safe passing in Flutter, use freezed or json_serializable models, which ensure correct deserialization when passing via RouteSettings.

Why might Route not call dispose?

Route.dispose is not called if Route remains in the Navigator stack. For example, when opening a new Route, the old Route goes into inactive state but is not destroyed — it remains in the stack for quick return. Dispose is called only when Route is removed from the stack via pop, pushReplacement or pushAndRemoveUntil. To release resources, use State's dispose inside Route, not Route's own dispose.

How to check which Route is currently active?

Use ModalRoute.of(context) to get the current Route from BuildContext. The ModalRoute.isActive property shows whether the Route is the currently visible screen. ModalRoute.isCurrent — true if the Route is the top of the stack. To observe stack changes, subscribe to Navigator observers via RouteAware and RouteObserver, which notify about active Route changes.

Summary

  • Route — an abstract screen class in Flutter with a lifecycle (didPush, didPop, didReplace, didChangeNext) and support for ModalBarrier and Hero animation
  • MaterialPageRoute — Android implementation with slide-from-bottom animation, shadow and automatic SafeArea
  • CupertinoPageRoute — iOS implementation with slide-from-right animation and interactive swipe-back gesture
  • PageRouteBuilder — utility for creating Route with custom animation via transitionsBuilder and AnimationController
  • Data passing — via Route constructor (push) or arguments (pushNamed) on input; via Navigator.pop(context, result) on output with Future
  • Lifecycle — four stages: Transition (didPush), Active, Inactive (covered by another Route), Disposed (didPop + dispose)
  • Screen isolation — each Route has its own BuildContext and state, preventing leaks and simplifying data management

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also