Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the preferred way to dispatch events#64846
Conversation
| expect(find.text('second'), findsOneWidget); | ||
| }); | ||
|
|
||
| testWidgets('Popping routes should cancel down events', (WidgetTester tester) async { |
There was a problem hiding this comment.
how does this assert that the down event is cancelled?
There was a problem hiding this comment.
I've added the following comment, is it clearer?
// The test target, _TestPostRouteCancel, shows on the home route how many
// [PointerCancelEvent] it has received. Initially it will show "Home 0".
// After the route is popped, it should show "Home 1", meaning it received
// one [PointerCancelEvent].| TestBindingEventSource pointerEventSource = TestBindingEventSource.device; | ||
|
|
||
| @override | ||
| void dispatchEvent( |
There was a problem hiding this comment.
Presumably, we would want to keep this override to ensure that only TestBindingEventSource.test reach this method?
There was a problem hiding this comment.
It's something I've thought for a long time, and I don't think this design is either sufficient or necessary to achieve what it seems to.
Redefining TestWidgetsFlutterBinding.dispatchEvent provides a way to diverge the behavior of dispatchEvent based on event source, although the new behavior is only implemented by its subclass LiveTestWidgetsFlutterBinding. How it behaves based on the source is shown as follows:
// TestWidgetsFlutterBinding binding;
binding.dispatchEvent(event, result) // AssertionError
binding.dispatchEvent(event, result, source: TestBindingEventSource.test) // Test behavior
(binding as GestureBinding).dispatchEvent(event, result) // Original behavior
But this is why the assertion is unnecessary: If some class calls dispatchEvent without adding source, the class must be unaware of TestWidgetsFlutterBinding, which means it will always call the method as GestureBinding.dispatchEvent. This assertion protects no one.
And why shouldn't any class aware of TestWidgetsFlutterBinding be allowed call the device version? The LiveTestWidgetsFlutterBinding.dispatchEvent even defines its own way of handling device-sourced events. The only dispatchEvent that does not accept device-sourced events is TestWidgetsFlutterBinding's, which itself does not even redefine test-sourced events. If we really want to diverge by the source, we better do it in LiveTestWidgetsFlutterBinding (which does not need it either, since it allows device-sourced events).
What's worse, it forces a test in gesture_binding_resample_event_on_widget_test.dart to shadow the argument.
Since the assertion is not needed, and the divergence is already implemented by handlePointerEvent, and it brings trouble, I can really think of no reason to keep this assertion.
|
Can you also check that this API change doesn't break any customers (e.g. google3)? |
|
@goderbauer I've fixed per your comments, and FRoB shows that this PR does not break g3. |
| await tester.pumpWidget(_TestPostRouteCancel()); | ||
|
|
||
| final TestGesture gesture = await tester.createGesture(); | ||
| await gesture.down(tester.getCenter(find.text('Home 0'))); |
There was a problem hiding this comment.
I think the intentions of the test would be a little clearer if the app would more clearly say what the "0" means. You could for example have an additional text widget that says "PointerCancelEvents: 0". The test would then be self-explanatory and you wouldn't even need the comment above.
| // The [pointerEventSource] is set as the `source` parameter of | ||
| // [handlePointerEvent] and can be used in the immediate enclosing | ||
| // [dispatchEvent]. | ||
| TestBindingEventSource _pointerEventSource = TestBindingEventSource.device; |
There was a problem hiding this comment.
Looks like this property (and the logic for setting/retrieving it) can be moved into the LiveTestWidgetsFlutterBinding class since it is only needed there?
There was a problem hiding this comment.
Sadly not. WidgetTester calls the handlePointerEvent with source but WidgetTester.binding is of type TestWidgetFlutterBinding, despite its concrete type being LiveTestWidgetsFlutterBinding.
|
Thanks. |
|
@dkwingsmt : can you please check if all these big jumps are expected? At least some of them seem to be unexpected according to #66608 |
|
Also CC @kf6gpe as triaging this alert might be difficult. Maybe some of them are legit improvements while some are unexpected regressions. Let's wait for @dkwingsmt 's check, and then figure out if we need to ask help from Skia perf to see how to handle this difficult case. |
Description
This PR exposes
GestureBinding.handlePointerEvent(previously a private method), which is used to replace all occurrences ofGestureBinding.dispatchEvent. This fixes an issue where post-route cancel events can not be received in unit tests, and reduces a number of boilerplates and duplicate code.Difference between the two methods
The
handlePointerEventtakes aPointerEvent. It performs a hit test or reuse existing ones based on event types, then callsdispatchEventwith the event and the result.The
dispatchEventtakes aPointerEventand aHitTestResult?. It sends the event to every targets of the result, as well as all registered routes.Improvements
Remove boilerplates: Most of the cases (actually all cases found in this repo), one who calls
dispatchEventonly wants to dispatch an event to emulate event dispatching, but sincehandlePointerEventis not exposed, it has to performhitTestand even remember the result itself. Some classes even have to duplicate the entirehandlePointerEvent. This PR removes all of these boilerplates.Expose and fix incorrect tests: A few tests are currently passing incorrectly due to this duplicate logic. These are tests that compares focus highlight of a widget after removing a mouse pointer, but focus highlights should only be displayed when at least one mouse device is connected, so the tests aren't supposed to work. This PR exposes this issue and corrects the tests.
Fix unit test framework: The current structure has an issue where the "post-route cancel events" can't be detected in unit tests, despite working in real apps. Post-route cancel events are synthesized events that are dispatched when there are "down" pointers at the moment a route is popped. These events can't be detected in unit tests because pointer events are dispatched in unit tests using
TestGesture, which performs its own hit tests and remembers them (for reasons stated above). In this wayGestureBinding._hitTestResultsare not aware of these events, andcancelPointerwill not cancel anything. This PR fixes this problem by unifying these states.API Changes
GestureBinding:handlePointerEventresetGestureTestWidgetsFlutterBindingpointerEventSource(used in the same file)handlePointerEventis overridden with an additional parametersourcedispatchEventis no longer overridden with an additional parametersourceTestGestureHitTester hitTesterEventDispatcher dispatcherno longer takesHitTestResult resultWidgetTestersendEventToBindingchanges signature: no longer takesHitTestResultRelated Issues
Tests
I added the following tests:
Checklist
Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.