When does route.didPop(result) is equal to false in Flutter Navigator 2.0

Issue

One of the main mecanism of Flutter Navigator 2.0 it the function onPopPage inside RouterDelegate > build > Navigator. However, I do not understand when route.didPop(result) returns false.

We can use the John Ryan’s famous example to show my question. His demo code.

onPopPage: (route, result) {
  if (!route.didPop(result)) {
    return false;
  }

  // Update the list of pages by setting _selectedBook to null
  _selectedBook = null;
  show404 = false;
  notifyListeners();

  return true;
},

On all of my tests, using AppBar autogenerated back button, route.didPop(result) returns true.

The doc stays :

bool didPop(dynamic result)
package:flutter/src/widgets/navigator.dart

A request was made to pop this route. If the route can handle it internally (e.g. because it has its own stack of internal state) then return false, otherwise return true (by returning the value of calling super.didPop). Returning false will prevent the default behavior of [NavigatorState.pop].

When this function returns true, the navigator removes this route from the history but does not yet call [dispose]. Instead, it is the route's responsibility to call [NavigatorState.finalizeRoute], which will in turn call [dispose] on the route. This sequence lets the route perform an exit animation (or some other visual effect) after being popped but prior to being disposed.

This method should call [didComplete] to resolve the [popped] future (and this is all that the default implementation does); routes should not wait for their exit animation to complete before doing so.

See [popped], [didComplete], and [currentResult] for a discussion of the result argument.

But was does "If the route can handle it internally (e.g. because it has its own stack of internal state) then return false" mean ? The route has its own stack of internal state ? How to produce this result ?

Thank you, stay safe

Solution

After some research to fully understand the Navigator 2.0, I think this might be the answer to the question:
route.didPop(result) will return false, when the Route, which are asked to pop, keeps local history entries and they have to be removed before popping the complete Route.

So what are local history entries (the stack of internal states)?

Local history entries are a way to implement local navigation within a page. You can do so using the method addLocalHistoryEntry. To understand this better, take a look at the official Flutter Docs sample:

The following example is an app with 2 pages: HomePage and SecondPage.
The HomePage can navigate to the SecondPage. The SecondPage uses a
LocalHistoryEntry to implement local navigation within that page.
Pressing ‘show rectangle’ displays a red rectangle and adds a local
history entry. At that point, pressing the ‘< back’ button pops the
latest route, which is the local history entry, and the red rectangle
disappears. Pressing the ‘< back’ button a second time once again pops
the latest route, which is the SecondPage, itself. Therefore, the
second press navigates back to the HomePage.

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (BuildContext context) => HomePage(),
        '/second_page': (BuildContext context) => SecondPage(),
      },
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage();

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text('HomePage'),
            // Press this button to open the SecondPage.
            ElevatedButton(
              child: Text('Second Page >'),
              onPressed: () {
                Navigator.pushNamed(context, '/second_page');
              },
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  @override
  _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {

  bool _showRectangle = false;

  void _navigateLocallyToShowRectangle() async {
    // This local history entry essentially represents the display of the red
    // rectangle. When this local history entry is removed, we hide the red
    // rectangle.
    setState(() => _showRectangle = true);
    ModalRoute.of(context).addLocalHistoryEntry(
        LocalHistoryEntry(
            onRemove: () {
              // Hide the red rectangle.
              setState(() => _showRectangle = false);
            }
        )
    );
  }

  @override
  Widget build(BuildContext context) {
    final localNavContent = _showRectangle
      ? Container(
          width: 100.0,
          height: 100.0,
          color: Colors.red,
        )
      : ElevatedButton(
          child: Text('Show Rectangle'),
          onPressed: _navigateLocallyToShowRectangle,
        );

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            localNavContent,
            ElevatedButton(
              child: Text('< Back'),
              onPressed: () {
                // Pop a route. If this is pressed while the red rectangle is
                // visible then it will will pop our local history entry, which
                // will hide the red rectangle. Otherwise, the SecondPage will
                // navigate back to the HomePage.
                Navigator.of(context).pop();
              },
            ),
          ],
        ),
      ),
    );
  }
}

To see the sample in the docs, click here.

I hope I answered the question in an understandable way.

Answered By – Nico

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *