DEV Community

Cover image for Navigate without animation in Flutter
flutter-clutter
flutter-clutter

Posted on • Originally published at flutterclutter.dev

Navigate without animation in Flutter

By default, Flutter configures your navigation in a way that the transition from one screen to another is presented with a slide or fade animation. While it can be a decent way to navigate for most of the scenarios, there are situations in which you might not want this to happen.
Let's see how we can change this predefined behavior.

Anonymous routes {#anonymous-routes}

The most basic and a very common way to navigate is by using an anonymous route. This means, we use Navigator.push() and thus create the Route during this call. The route is called anonymous because compared to Navigator.push(), this route does not have a name - similar to an anonymous function.

But if we create the Route in the moment of calling Navigator.push(), how can we configure the transition?

If we use a MaterialPageRoute like this:

Navigator.push(
  context,
  MaterialPageRoute(builder: (BuildContext context) => OtherScreen()),
);
Enter fullscreen mode Exit fullscreen mode

we end up with a slide animation on iOS and macOS and a fade animation on Android.
This is because the animation inside the MaterialPageRoute is determined by the PageTransitionsTheme which is used in the respective buildTransitions() function.:

class PageTransitionsTheme with Diagnosticable {
  /// Constructs an object that selects a transition based on the platform.
  ///
  /// By default the list of builders is: [ZoomPageTransitionsBuilder]
  /// for [TargetPlatform.android], and [CupertinoPageTransitionsBuilder] for
  /// [TargetPlatform.iOS] and [TargetPlatform.macOS].
  const PageTransitionsTheme({ Map<TargetPlatform, PageTransitionsBuilder> builders = _defaultBuilders }) : _builders = builders;

  static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
    TargetPlatform.android: ZoomPageTransitionsBuilder(),
    TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
    TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
  };
Enter fullscreen mode Exit fullscreen mode

Luckily, we have another option. Let's have a look at the inheritance of MaterialPageRoute:

ObjectRouteOverlayRouteTransitionRouteModalRoutePageRouteMaterialPageRoute

Since the second parameter of the push() method is of type Route, we just have to find another class that extends Route but is more configurable.

While we can not directly configure the Route regarding its transition, we can certainly do that using a descendant of this class: the PageRouteBuilder, which also extends Route as this inheritance order shows:

ObjectRouteOverlayRouteTransitionRouteModalRoutePageRoutePageRouteBuilder

In order to prevent the animation, we use the named arguments transitionDuration and reverseTransitionDuration, which we both set to Duration.zero:

Navigator.push(
  context, 
  PageRouteBuilder(
    pageBuilder: (BuildContext context, Animation<double> animation1, Animation<double> animation2) {
      return OtherScreen();
    },
    transitionDuration: Duration.zero,
    reverseTransitionDuration: Duration.zero,
  ),
);
Enter fullscreen mode Exit fullscreen mode

By doing this, we ensure that neither navigating to nor returning from the screen results in an animation.

Generated routes {#generated-routes}

A better way to organize routes instead of directly using the push() method is to use generated routes. The idea is to have a single place where all routes are defined. By giving every route a name, you can refer to that route from everywhere inside your code (by using pushNamed()) instead of redefining this navigation logic multiple times.

For this approach, we will generate a separate class that extends MaterialPageRoute. Instead of creating an animation inside the buildTransitions() function, we just instantly return the child:

class UnanimatedPageRoute<T> extends MaterialPageRoute<T> {
  UnanimatedPageRoute({
    required Widget Function(BuildContext) builder,
    RouteSettings? settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  }) : super(
          builder: builder,
          settings: settings,
          maintainState: maintainState,
          fullscreenDialog: fullscreenDialog,
        );

  @override
  Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return child;
  }
}
Enter fullscreen mode Exit fullscreen mode

In the MaterialApp widget inside the onGenerateRoute function, we can now use this new class for navigation:

MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == OtherScreen.routeName) {
      return UnanimatedPageRoute(
        builder: (context) => OtherScreen(),
      );
    }
  },
)
Enter fullscreen mode Exit fullscreen mode

We could even decide per route if it's animated or not by creating a function that expects a bool parameter and uses the one or the other Route class depending on this parameter:

  Route<T?> buildPageRoute<T>(
    Widget child,
    bool animated,
  ) {
    if (animated) {
      return CupertinoPageRoute<T?>(
        builder: (BuildContext context) => child,
      );
    }

    return UnanimatedPageRoute<T?>(
      builder: (BuildContext context) => child,
    );
  }
Enter fullscreen mode Exit fullscreen mode
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == OtherScreen.routeName) {
      return buildPageRoute(
        builder: (context) => OtherScreen(),
        false // or true if we want it to be slided in
      );
    }
  },
)
Enter fullscreen mode Exit fullscreen mode

We can utilize this solution for anonymous routing using push() as well:

Navigator.push(
  context,
  UnanimatedPageRoute(builder: (BuildContext context) => OtherScreen()),
);
Enter fullscreen mode Exit fullscreen mode

go_router

With the go_router package we get a high level API for declarative routing in Flutter. The package wraps around the Navigator 2.0 API. Instead of routes, we deal with pages there.\
So in order to configure go_router in a similar fashion like we did above, we create a function called buildPageWithoutAnimation() that ignores the given animation arguments and returns the child immediately.

CustomTransitionPage<T> buildPageWithoutAnimation({
  required BuildContext context, 
  required GoRouterState state, 
  required Widget child,
}) {
  return CustomTransitionPage<T>(
    key: state.pageKey,
    child: child,
    transitionsBuilder: (context, animation, secondaryAnimation, child) => child
  );
}



GoRoute(
  path: '/other-screen',
  builder: (BuildContext context, GoRouterState state) => const OtherScreen(),
  pageBuilder: (BuildContext context, GoRouterState state) => buildPageWithoutAnimation<void>(
    context: context, 
    state: state, 
    child: OtherScreen(),
  ),
),
Enter fullscreen mode Exit fullscreen mode

Conclusion

The concept of how to prevent an animation during the transition from one screen to another is pretty simple: instead of returning an animation in the respective builder function, we return the new screen (widget) directly.

When dealing with push(), we just set the transition duration to zero which leaves no time for the animation.

Top comments (0)