-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[webview_flutter_android] Implementation of Android WebView widget using pigeon #4503
Conversation
|
|
||
| @Override | ||
| public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { | ||
| setUp( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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() {} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
Show resolved
Hide resolved
| _trySetJavaScriptMode(settings.javascriptMode), | ||
| _trySetDebuggingEnabled(settings.debuggingEnabled), | ||
| _trySetUserAgent(settings.userAgent), | ||
| _trySetZoomEnabled(settings.zoomEnabled), |
There was a problem hiding this comment.
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); | ||
| } |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
defaultvalue?
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
enumvalue, 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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),
...
There was a problem hiding this comment.
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.
packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| Future<void> _trySetZoomEnabled(bool? zoomEnabled) async { | ||
| if (zoomEnabled == null) return Future<void>.sync(() => null); |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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
packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
Show resolved
Hide resolved
|
Adding @mvanbeusekom for a second review, since this is a substantial rewrite, so would benefit from more eyes. |
|
Is this ready for re-review? |
mvanbeusekom
left a comment
There was a problem hiding this 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.
| @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; | ||
| } |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
@stuartmorgan This is ready for another review! |
stuartmorgan-g
left a comment
There was a problem hiding this 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. | |||
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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.
mvanbeusekom
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…idget using pigeon (flutter/plugins#4503)
Replaces
MethodChannelPlatformControllerwithWebViewAndroidPlatformControllerandWebViewAndroidWidget.Android portion of flutter/flutter#93732
Pre-launch Checklist
dart format.)[shared_preferences]pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.mdto add a description of the change.///).If you need help, consider asking for advice on the #hackers-new channel on Discord.