Skip to main content

Command Palette

Search for a command to run...

Navigator onPopPage versus onDidRemovePage

Updated
4 min read

During the development of my RubigoRouter package I ran into some limitations of the Navigator object that I explain here below.

The Flutter Navigator has a mechanism to inform the app about a back navigation event.

Historically the onPopPage callback was used for this, but with the new predictive-back gesture or the iOS-style back swipe, a new onDidRemovePage callback is introduced. Below, we can compare the two signatures.

/// Signature for the [Navigator.onPopPage] callback.
///
/// This callback must call [Route.didPop] on the specified route and must
/// properly update the pages list the next time it is passed into
/// [Navigator.pages] so that it no longer includes the corresponding [Page].
/// (Otherwise, the page will be interpreted as a new page to show when the
/// [Navigator.pages] list is next updated.)
typedef PopPageCallback = bool Function(Route<dynamic> route, dynamic result);

/// Signature for the [Navigator.onDidRemovePage] callback.
///
/// This must properly update the pages list the next time it is passed into
/// [Navigator.pages] so that it no longer includes the input `page`.
/// (Otherwise, the page will be interpreted as a new page to show when the
/// [Navigator.pages] list is next updated.)
typedef DidRemovePageCallback = void Function(Page<Object?> page);

The differences between the two are:

  • onPopPage

    • This callback is called before the pop is executed. With the boolean return value we can instruct the Navigator whether or not to actually perform the pop. (In my old code, I always returned false and then handled the pop in my own code, informing Flutter if the stack did change.)

    • This callback is called only for the topmost route.

  • onDidRemovePage

    • This callback is called after the pop transition has started. There is no way to prevent the pop.

    • This callback is called individually for each and every page that leaves the stack. We can have the same behaviour as onPopPage if we check if the provided page parameter corresponds to the current topmost page. Although I’m a bit concerned about race conditions..

I have seen some problems in the past with onPopPage and using back gestures, where the animation does not finish and the two pages get stuck. But this is something I haven’t investigated further. Also because onPopPage is deprecated after v3.16.0-17.0.pre.

Disable back gestures and back button

If you know in advance that the page can not be popped, you can use the PopScope widget.

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      child: Scaffold(),
    );
  }
}

Downside: This will disable the back-gestures, but a standard AppBar still shows a clickable BackButton that does nothing, and that is confusing.

Disable back-gestures but respond to back button

If you want to disable the back gestures, but still want to respond to BackButton events, you can also use the PopScope widget, but with the onPopInvokedWithResult property.

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult:(didPop, result) => controller.didPop(),
      child: Scaffold(),
    );
  }
}

Downside: we have to do this trickery on each page. It is also a shame that we lose support for back-gestures.

What if we are stubborn and want it all?

What if I want the following:

  • Have back-gestures enabled.

  • A working BackButton.

  • No widgets like `PopScope` to do some magic.

  • Use the new onDidRemovePage callback.

  • Alter the page stack to my liking (like undo the pop) after onDidRemovePage has been called.

With the back-gesture it works surprisingly well.

Here I made a video, where I show a few times a successful back-gesture, and then a few times a cancelled back-gesture. I swiped the page to 2/3 to the right and then let go.

The navigator animates nicely and naturally back to the original screen.

With the BackButton it works not that well.

Here I made a video, where I show a few times a successful back button event, and a few times a cancelled back button event.

The navigator quickly animates back and forth between the pages. This looks really terrible.

It seems we need both onPopPage and onDidRemovePage, but one callback for the back button and the other for back-gestures.

  • Back button
    callback: onPopPage
    We return false to inform the Navigator we handle the pop ourselves. The transition animation is therefore not started yet. If we want to honour the pop we can inform the Navigator about the new screen stack by calling notifyListeners, otherwise we do nothing.

  • Back-gesture

    callback: onDidRemovePage
    We can cancel the event by just notifying the Navigator about the ‘new’ screen stack, by calling notifyListeners.

You can find the package that I am working on here, with a working sample.
https://github.com/jsroest/rubigo_router

Let me know what you think!

Sander

»As of the time of this writing, the stable version of Flutter is V3.27.3«

RubigoRouter

Part 3 of 3

In this series, I will talk about the development and use of the RubigoRouter package.

Start from the beginning

Consumer apps versus workforce apps

Both are used by humans, but they are not the same.