Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[connectivity_for_web] Fix JS Interop in release mode. #2869

Merged
merged 8 commits into from
Jul 13, 2020

Conversation

ditman
Copy link
Member

@ditman ditman commented Jul 10, 2020

Description

The old version of the plugin used a generated JS-interop file for a class that is available natively from dart:html.

This is not allowed.

Since the native dart:html class is available, this PR changes the plugin to use that (I can't believe I missed that on my previous version!)

This PR also cleans up all the paraphernalia used to generate the old JS-interop file.

Related Issues

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.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (flutter analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • No, this is not a breaking change.

@ditman ditman requested review from yjbanov and cyanglaz July 10, 2020 21:01
Comment on lines -5 to -10
// We can't be more specific on the signature of this method because the API is odd,
// data can come from a static value in the DOM, or as the 'target' of a DOM Event.
//
// If we type info as `NetworkInformation`, Dart will complain with:
// "Uncaught Error: Expected a value of type 'NetworkInformation',
// but got one of type 'NetworkInformation'"
Copy link
Member Author

Choose a reason for hiding this comment

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

This was probably Dart telling me that there already existed a native NetworkInformation class, but I misinterpreted as "coming from different parts"

Copy link
Contributor

@mdebbar mdebbar left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -38,7 +37,7 @@ class NetworkInformationApiConnectivityPlugin extends ConnectivityPlugin {
Stream<ConnectivityResult> get onConnectivityChanged {
if (_connectivityResult == null) {
_connectivityResult = StreamController<ConnectivityResult>();
_networkInformation.onchange = allowInterop((_) {
_networkInformation.onChange.listen((_) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is unrelated to your PR, but I noticed it and wanted to point it out. The listener being attached here is never cleaned up. This will potentially cause problems with hot restart since the old listener will remain intact while a new listener is created.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, before this PR there used to be only one listener that you keep overriding. Now you're using a stream that could have multiple listeners which makes it possible to leak listeners.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting! I don't know of any way to detect the app is hot-restarting to clean-up the plugin!

Copy link
Contributor

Choose a reason for hiding this comment

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

This is how we do it in the web engine. But that's only one example that requires clean up, there may be others.

One thing you can do is make the listening lazy. So you only attach a listener when someone subscribes to _connectivityResult.stream. And as soon as there are no more subscribers to _connectivityResult.stream you can release your listener. Doing it this way ensures that your code won't leak anything as long as the user code isn't leaking the stream subscription.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see what you mean, I'm going to create an issue, because this is not exclusive of this plugin!

Copy link
Member Author

Choose a reason for hiding this comment

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

There are indeed listeners leaking. Two problems:

  • The hot restart never calls "dispose" on the main app, so the client never detaches from the stream (this can be worked around on the app side)
  • Even if I manually cancel the subscription to the outermost Stream, the innermost stream is never canceled (this is the bug that you were mentioning)

I'm going to fix the current asymmetry: subscribe to the inner Stream only when there's a new listener, and unsubscribe from it when there are none left.

Thanks for bringing this up!

Copy link
Member Author

Choose a reason for hiding this comment

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

I've created a Dart issue regarding the first issue: dart-lang/sdk#42679

Without knowing when to call "dispose", we can't clear up the Stream.

I've reverted that part of the code to write directly to the "onchange" DOM property of the object, until we get a clear way of cleaning up after ourselves (from the plugin)

@ditman ditman merged commit d6435f0 into flutter:master Jul 13, 2020
@ditman ditman deleted the fix-connectivity-js-interop branch July 13, 2020 22:46
jorgefspereira pushed a commit to jorgefspereira/plugins_flutter that referenced this pull request Oct 10, 2020
* Switch to dart:html window.navigator.connection instead of package:js JS-interop.
* Overwrite connection.onchange instead of listening to connection.onChange Stream (prevents multiple subscriptions after hot-reload).
* Cleaned up old code related to generating the JS facade.
FlutterSu pushed a commit to FlutterSu/flutter-plugins that referenced this pull request Nov 20, 2020
* Switch to dart:html window.navigator.connection instead of package:js JS-interop.
* Overwrite connection.onchange instead of listening to connection.onChange Stream (prevents multiple subscriptions after hot-reload).
* Cleaned up old code related to generating the JS facade.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[connectivity_for_web] Crash when run or build in release mode
3 participants