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

Conversation

@bparrishMines
Copy link
Contributor

@bparrishMines bparrishMines commented Nov 11, 2021

Replaces MethodChannelPlatformController with WebViewAndroidPlatformController and WebViewAndroidWidget.

Android portion of flutter/flutter#93732

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/plugins repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • I updated CHANGELOG.md to add a description of the change.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@google-cla google-cla bot added the cla: yes label Nov 11, 2021
@github-actions github-actions bot added p: webview_flutter Edits files for a webview_flutter plugin platform-android labels Nov 11, 2021
@bparrishMines bparrishMines changed the title Webview widget [webview_flutter_android] Implementation of Android WebView widget using pigeon Nov 12, 2021

@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
setUp(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't onAttachedToActivity be called multiple times? What are the implications of moving platform view registration from the engine attachment to the activity attachment?

And similarly, are the pigeon setup APIs safe to call repeatedly?

Copy link
Contributor Author

@bparrishMines bparrishMines Nov 12, 2021

Choose a reason for hiding this comment

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

We had a similar problem with doing this with google_mobile_ads. It mostly only affect loading views with cached engines. The solution was to set everything up in onAttachedToEngine with binding.applicationContext and then switch the context when the plugin was attached to an Activity. I went ahead and changed this plugin to do the same. I'm not aware of any other problems this implementation causes.

}

@Override
public void onDetachedFromActivityForConfigChanges() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

The activity was passed to WebViewHostApi; doesn't that need to be disconnected somehow at this point?

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 switch the context for WebViewHostApi to FlutterPluginBinding.applicationContext() for the explanation I gave above for onAttachedToActivity.

_trySetJavaScriptMode(settings.javascriptMode),
_trySetDebuggingEnabled(settings.debuggingEnabled),
_trySetUserAgent(settings.userAgent),
_trySetZoomEnabled(settings.zoomEnabled),
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

break;
default:
webView.settings.setMediaPlaybackRequiresUserGesture(true);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just:
webView.settings.setMediaPlaybackRequiresUserGesture(autoMediaPlaybackPolicy != AutoMediaPlaybackPolicy.always_allow)?

Copy link
Contributor Author

@bparrishMines bparrishMines Nov 12, 2021

Choose a reason for hiding this comment

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

I prefer to use switch statements for enums because the Dart code will throw a lint error when one of the values wasn't listed. We had a bug once when we didn't support all the enum values.

Copy link
Contributor

Choose a reason for hiding this comment

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

default covers all cases, so you'll never get a linter warning here no matter what is added to the enum.

We had a bug once when we didn't support all the enum values.

If someone is adding new functionality, they should be adding test coverage, which is how we should catch this. Relying on the lint (by removing default to enable it) would make adding new enum values repository-breaking. We've already had a sever issue with this; see flutter/flutter#89866

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 don't think flutter/flutter#89866 actually provides a recommendation on the implementation. Are you suggesting that we keep the default value? If the platform interface adds a new enum value, wouldn't only an integration test in the main plugin (e.g. webview_flutter) catch a change like that? The unit tests in the platform interface and main plugin should be platform agnostic.

I think I prefer option 3 from flutter/flutter#89866. That way there is a process to locate everywhere enum used in the implementations. And we can add TODOs to update at a later point or in a followup PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think flutter/flutter#89866 actually provides a recommendation on the implementation. Are you suggesting that we keep the default value?

I think in cases where we can have a reasonable default, we should do that, because it reduces the chances of cross-package breakage. (We don't yet have a way to detect that in CI.)

If the platform interface adds a new enum value, wouldn't only an integration test in the main plugin (e.g. webview_flutter) catch a change like that? The unit tests in the platform interface and main plugin should be platform agnostic.

I'm not sure I follow. If someone is adding a new enum value, it's presumably to add new functionality based on that new value. That would generally require a change to each platform implementation package to actually use the new enum value, and those changes should have tests. So it should be something that would be caught by tests both at the platform implementation level and potentially at the app-facing level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, ok. I assumed someone would be able to make a change to a platform interface enum without needing to update the implementations. If that's the case, then yea we should be able to catch all the uses if we require this.

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 changed it to your suggestion

addJavascriptChannels(creationParams.javascriptChannelNames);
}

Future<void> _trySetHasProgressTracking(bool? hasProgressTracking) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we call all of these something more like setFooOrIgnoreNull? I wasn't sure what "try" meant at the call sites.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternately, you could make these not take nullables and use conditionals at the call site:

return Future.wait(<Future<void>>[
  if (settings.hasProgressTracking != null)
    _setHasProgressTracking(settings.hasProgressTracking),
  if (settings.hasNavigationDelegate)
    _trySetHasNavigationDelegate(settings.hasNavigationDelegate),
  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I decided to go with this solution.

}

Future<void> _trySetZoomEnabled(bool? zoomEnabled) async {
if (zoomEnabled == null) return Future<void>.sync(() => null);
Copy link
Contributor

Choose a reason for hiding this comment

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

This inline condition+statement reminds me: since you're rewriting ~all of the Dart, how hard would it be to flip the analysis options of this package to the new options? It would substantially cut down on later churn.

(To do just this one, you'd move packages/webview_flutter/analysis_option.yaml into all of the subpackages except for this one.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I updated the analysis options for this plugin

@stuartmorgan-g
Copy link
Contributor

Adding @mvanbeusekom for a second review, since this is a substantial rewrite, so would benefit from more eyes.

@stuartmorgan-g
Copy link
Contributor

Is this ready for re-review?

Copy link
Contributor

@mvanbeusekom mvanbeusekom left a comment

Choose a reason for hiding this comment

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

I am wondering if it would be possible to replace the create method channel call with the default structure that is offered by the AndroidView widget. See also my comment for the FlutterWebViewFactory.java file.

Comment on lines 20 to 27
@Override
public PlatformView create(Context context, int id, Object args) {
Map<String, Object> params = (Map<String, Object>) args;
MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
return new FlutterWebView(context, methodChannel, params, containerView);
final PlatformView view = (PlatformView) instanceManager.getInstance((Integer) args);
if (view == null) {
throw new IllegalStateException("Unable to find WebView instance: " + args);
}
return view;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there is special reason why the Android WebView control is created through the separate create method channel command and not by this PlatformViewFactory?

I understand how it works at the moment but I can't really see the benefit. And since this is a factory class I would expect this class to create the instance and register it with the InstanceManager.

The current implementation also means that one should always make sure the create method is called before the AndroidView widget is added to the widget tree. In general the current Dart implementation will take care of that however it is something that deviates from what I would expect and thus requires extra attention.

To me it seems possible to create the Dart WebView instance and register it with the Dart version of the InstanceManager and only create it on the Java side through this factory. This would remove the need for the create method channel call. In my opinion this would simplify the API a little bit and also makes use of the PlatformViewFactory in a way I would expect it to work (namely, responsible for creating the WebView control).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The main reason I didn't want create to the WebView in the PlatformViewFactory was that I wanted to eventually have an update for flutter/flutter#51875. Where a user would be able to load a URL for a WebView and make calls to scrolling without needing to add the Widget to the widget tree. We probably should actually have the PlatformView API provide support to create the View without adding it to the tree, but I think this is the only way around it at the moment.

Also, the Pigeon API is built to mimic the android.webkit API. My goal was to avoid significant deviation from this because it can make it harder to handle changes to the API later or run into conflicts when adding features we weren't aware of before.

In the long term, it should become a standard to always call create in the constructor of a class and dispose would be handled automatically by garbage collection. This is probably at least 2 quarters away, but the changes to webview_flutter are essentially the stepping stones to get there.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for elaborating on this @bparrishMines. I indeed see the clear benefit doing it this way if you take flutter/flutter#51875 into account.

@bparrishMines
Copy link
Contributor Author

@stuartmorgan This is ready for another review!

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

LGTM with nits

(With the caveat that with a full rewrite like this, it's of course very hard to have confidence that nothing regressed.)

@@ -1,3 +1,7 @@
## 2.3.0

* Replace platform implementation with API built with pigeon.
Copy link
Contributor

Choose a reason for hiding this comment

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

Replaces

(Per the new style guide.)

@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
final Activity activity = activityPluginBinding.getActivity();
webViewHostApi.setContext(activityPluginBinding.getActivity());
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be activity?

public void onDetachedFromActivity() {
final Context context = pluginBinding.getApplicationContext();
webViewHostApi.setContext(context);
javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(context.getMainLooper()));
Copy link
Contributor

Choose a reason for hiding this comment

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

This pattern is repeated four times; it seems like changing this (e.g., adding another thing that needs to be adjusted) would be very error-prone. How about extracting a helper that takes the Context, and then each of these can be a one-liner to call that helper with the right context for the scenario.

Copy link
Contributor

@mvanbeusekom mvanbeusekom left a comment

Choose a reason for hiding this comment

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

LGTM

@bparrishMines bparrishMines merged commit 8f55fb6 into flutter:master Nov 17, 2021
@bparrishMines bparrishMines deleted the webview_widget branch November 17, 2021 18:37
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Nov 17, 2021
amantoux pushed a commit to amantoux/plugins that referenced this pull request Dec 11, 2021
KyleFin pushed a commit to KyleFin/plugins that referenced this pull request Dec 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cla: yes p: webview_flutter Edits files for a webview_flutter plugin platform-android

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants