Support for vetoing an attempt to pop the current route#7488
Support for vetoing an attempt to pop the current route#7488HansMuller merged 4 commits intoflutter:masterfrom
Conversation
There was a problem hiding this comment.
Maybe change these to pumpUntilNoTransientCallbacks ?
There was a problem hiding this comment.
That's WAY better. I didn't realize that pumpUntil... existed.
There was a problem hiding this comment.
Don't we need to check mounted here too?
There was a problem hiding this comment.
Here's the way I understand didPopRoute's role in all of this.
If didPopRoute() returns true it just means that the pop request has been dealt with. In this case willPop() defeats the request, i.e. it's been dealt with, even if the route was disposed.
There was a problem hiding this comment.
What if there are two forms in a route? Also, don't we need to unregister the callback on dispose?
There was a problem hiding this comment.
You're right (Ian had brought this up as well, but I thought I could avoid it). A Route can have an arbitrary number of Form descendants. So I'll need to manage a list of callbacks, rather than just one.
|
What's the expected interaction on iOS? Does this kind of interaction only happen for full-screen dialogs on ios? Presumably this doesn't occur when someone uses the backgesture on ios? :D |
There was a problem hiding this comment.
Also need to check mounted here
There was a problem hiding this comment.
shouldn't the route check the isCurrent thing?
I think this can all be simplified to just return route.willPop(), if the route verifies isCurrent.
There was a problem hiding this comment.
you don't need to check mounted here, that's the caller's problem.
|
edit: see later comment |
|
@eseidelGoogle Yes. That's what the change in page.dart is for. |
There was a problem hiding this comment.
you're returning a Future<Future> here. You should just return true, and async will wrap it for you.
There was a problem hiding this comment.
we probably need to take a local copy of _observers here or otherwise handle what happens while this now long-running API gets fiddled with while in flight. We used to have the rule that didPopRoute couldn't change the observers but that no longer works since it's entirely async and minutes could pass.
There was a problem hiding this comment.
dynamic, not Null. You don't know what the route's return type will be here.
There was a problem hiding this comment.
Future<bool> willPop async => true;
There was a problem hiding this comment.
the route callback mechanism should be a list of callbacks, not a single callback
There was a problem hiding this comment.
I wasn't sure if the _ModalScope would always exist, even when the route wasn't current. Maybe it always does; I'll remove the isCurrent checks if that's so.
There was a problem hiding this comment.
The _ModalScope is just another widget. If you're in the tree in a Navigator in a ModalRoute, then you must have a _ModalScope because the ModalRoute builds it.
|
In reading the patch I found that the way the patch does the back gesture thing is it just disallows the back gesture if you have any callbacks registered. That seems fine too. |
There was a problem hiding this comment.
won't this return a Future<Null>?
There was a problem hiding this comment.
This should be showDialog/*<bool>*/(...
There was a problem hiding this comment.
Yes, it returns a Future whose value is the bool passed to the Navgator.pop() that dismisses the Alert dialog.
There was a problem hiding this comment.
Right. So it should be showDialog/*<bool>*/(... (rather than just showDialog( like here)
There was a problem hiding this comment.
we should be careful about using pumpUntilNoTransientCallbacks. It can hide bugs. In particular here I think it would be better to just have the two pumps, because then we're clear about how many frames we're expecting. Why would we be getting more?
There was a problem hiding this comment.
Scaffold's back button callback now awaits the willPop() method before actually calling Navigator.pop(). That means that tests that back out of a route by tapping the back button now need to pump three times to be consistent with the implementation. I'll add the explicit pumps (back) but binding the test to the implementation in this way doesn't seem that useful to me.
There was a problem hiding this comment.
Same here, do we fully understand why we're getting more than two?
There was a problem hiding this comment.
This should probably be mentioned in the docs, too.
There was a problem hiding this comment.
I don't really understand this comment. There's no test anywhere that it's the same modal route...
There was a problem hiding this comment.
This happens in the gallery. Each time the "This form has errors" warning dialog is pushed or popped the underlying Form's dependenciesChanged() method runs. We don't want to keep adding the same callback to the route.
I still need to write a test that simulates this scenario.
There was a problem hiding this comment.
why does it matter if it's current?
There was a problem hiding this comment.
It doesn't matter. I'd meant to remove that, thanks for repointing it out.
There was a problem hiding this comment.
don't you need to check if oldConfig.onWillPop was null?
There was a problem hiding this comment.
Maybe this would be better starting off as null, since it's a rare case.
There was a problem hiding this comment.
I had it that way originally. Doing so makes all of the other references to willPopCallbacks messier; I don't think there's much of a win here.
There was a problem hiding this comment.
Oh, I see. I didn't realise it was used from outside this class.
There was a problem hiding this comment.
assert that it's not that const that you set in the dispose method.
There was a problem hiding this comment.
I've added an assert(mounted)
There was a problem hiding this comment.
should probably warn that the callback might be called long after the route was unmounted.
There was a problem hiding this comment.
never mind, i see this is the private api
There was a problem hiding this comment.
should probably warn that this callback might get called long after the route and the widget tree is disposed.
There was a problem hiding this comment.
I've updated the doc.
There was a problem hiding this comment.
if it's null you return true?
There was a problem hiding this comment.
if this is intentional, please add a comment as to why...
There was a problem hiding this comment.
It was intentional and consistent and unnecessary. I've removed all of them.
|
LGTM with the one remaining fix (mentioning the effect of registering a willPop callback on the iOS back gesture). |
It's often useful for a Form widget to warn or even block the user if they attempt to back out of an invalid or unsaved modal form.
The Form
willPopcallback is called before a Navigator.pop() triggered by the Scaffold's back button or Android's system back button. The value of the callback is aFuture<bool>. The enclosing modal route will only be popped if willPop returns true. A simple application of willPop would be to show an alert that asks the user to confirm:In this example
showDialogreturns true or false depending on the Navigator.pop() value it was dismissed with.The Route class now has a willPop method with just returns
Future<bool>.value(true)by default.The ModalRoute class overrides willPop and delegates to its _ModalScope. This makes it possible to define willPop from a stateful descendant of a ModalRoute. Typically apps don't define ModalRoute subclasses directly, they just use higher level primitives that create ModalRoutes, like showDialog() or MaterialPageRoute. The ModalRoute
scopedWillPopCallbackmakes it possible for a ModalRoute's stateful descendant, like showDialog's child, to define the value ofwillPop.Navigator.willPop() now delegates to the current route's willPop method. The Scaffold's back button and the binding for the Android system back button now check Navigator.willPop() before actually popping the current route.
Fixes #6331