Skip to content

Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the preferred way to dispatch events#64846

Merged
fluttergithubbot merged 18 commits intoflutter:masterfrom
dkwingsmt:post-route-cancel-in-tests
Sep 16, 2020
Merged

Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the preferred way to dispatch events#64846
fluttergithubbot merged 18 commits intoflutter:masterfrom
dkwingsmt:post-route-cancel-in-tests

Conversation

@dkwingsmt
Copy link
Contributor

@dkwingsmt dkwingsmt commented Aug 29, 2020

Description

This PR exposes GestureBinding.handlePointerEvent (previously a private method), which is used to replace all occurrences of GestureBinding.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 handlePointerEvent takes a PointerEvent. It performs a hit test or reuse existing ones based on event types, then calls dispatchEvent with the event and the result.

The dispatchEvent takes a PointerEvent and a HitTestResult?. 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 dispatchEvent only wants to dispatch an event to emulate event dispatching, but since handlePointerEvent is not exposed, it has to perform hitTest and even remember the result itself. Some classes even have to duplicate the entire handlePointerEvent. 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 way GestureBinding._hitTestResults are not aware of these events, and cancelPointer will not cancel anything. This PR fixes this problem by unifying these states.

API Changes

GestureBinding:

  • New: handlePointerEvent
  • New protected: resetGesture

TestWidgetsFlutterBinding

  • New private: pointerEventSource (used in the same file)
  • handlePointerEvent is overridden with an additional parameter source
  • dispatchEvent is no longer overridden with an additional parameter source

TestGesture

  • Constructor
    • No longer takes HitTester hitTester
    • EventDispatcher dispatcher no longer takes HitTestResult result

WidgetTester

  • sendEventToBinding changes signature: no longer takes HitTestResult

Related Issues

Tests

I added the following tests:

  • Popping routes should cancel down events
    • This currently works in real apps, but not in unit 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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

  • Yes, but this is not a breaking change. Some existing tests failed because they are written in a way that does not reflect actual behavior (see the section Expose and fix incorrect tests above), and were not supposed to pass even before this PR. They have been fixed in this PR.

@flutter-dashboard flutter-dashboard bot added a: tests "flutter test", flutter_test, or one of our tests f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels. c: contributor-productivity Team-specific productivity, code health, technical debt. labels Aug 29, 2020
@flutter-dashboard flutter-dashboard bot added the f: material design flutter/packages/flutter/material repository. label Aug 31, 2020
expect(find.text('second'), findsOneWidget);
});

testWidgets('Popping routes should cancel down events', (WidgetTester tester) async {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this assert that the down event is cancelled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably, we would want to keep this override to ensure that only TestBindingEventSource.test reach this method?

Copy link
Contributor Author

@dkwingsmt dkwingsmt Sep 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@goderbauer
Copy link
Member

Can you also check that this API change doesn't break any customers (e.g. google3)?

@dkwingsmt
Copy link
Contributor Author

@goderbauer I've fixed per your comments, and FRoB shows that this PR does not break g3.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

await tester.pumpWidget(_TestPostRouteCancel());

final TestGesture gesture = await tester.createGesture();
await gesture.down(tester.getCenter(find.text('Home 0')));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this property (and the logic for setting/retrieving it) can be moved into the LiveTestWidgetsFlutterBinding class since it is only needed there?

Copy link
Contributor Author

@dkwingsmt dkwingsmt Sep 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly not. WidgetTester calls the handlePointerEvent with source but WidgetTester.binding is of type TestWidgetFlutterBinding, despite its concrete type being LiveTestWidgetsFlutterBinding.

@CareF
Copy link
Contributor

CareF commented Sep 23, 2020

Thanks.
This may open up possible improvement of the smoothness test to use PointerEvent rather than PointerData. @liyuqian

@dkwingsmt dkwingsmt deleted the post-route-cancel-in-tests branch September 23, 2020 22:14
@liyuqian
Copy link
Contributor

@dkwingsmt : can you please check if all these big jumps are expected? At least some of them seem to be unexpected according to #66608

@liyuqian
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: tests "flutter test", flutter_test, or one of our tests c: contributor-productivity Team-specific productivity, code health, technical debt. f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants