-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Allow changing the webview's platform specific implementation #1618
Conversation
| /// | ||
| /// An instance implementing this interface is passed to the `onWebViewCreated` callback that is | ||
| /// passed to [WebViewPlatformInterface#onWebViewCreated]. | ||
| abstract class WebViewPlatformControllerInterface { |
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.
Actually I think we better make this class non-abstract and have a default implementation(that throws?) for all methods, this way a new implementor doesn't have to implement the full API surface all at once (and when we're adding new methods here it's going to take time for implementors to catch-up).
I'll update the PR to make this non abstract tomorrow.
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 think it can stay abstract but you can just add default implementation which others will inherit when extending it
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.
what @nkoroste says
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.
ok, added a default implementation that throws.
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.
If the default implementation throws, it's probably best if the default is abstract (so that the analyzer tells you to implement it). Otherwise you risk crashes at runtime and there's no lint to tell you what you need to implement. If it makes sense to have an actual default that doesn't throw, I'd go with that. That way you can get up and going quickly on a new platform.
FWIW, GitHub has a "create PR as WIP" mode when you create the PR that will make the PR appear grey instead of green until you say it's ready. |
nkoroste
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.
Overall LGTM
| /// | ||
| /// An instance implementing this interface is passed to the `onWebViewCreated` callback that is | ||
| /// passed to [WebViewPlatformInterface#onWebViewCreated]. | ||
| abstract class WebViewPlatformControllerInterface { |
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.
nit: maybe "WebViewControllerPlatformInterface
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.
how about WebViewPlatform and FuchsiaWebViewPlatform/AndroidWebViewPlatform/etc?
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.
Ok I think II'm going with:
WebViewPlatform/AndroidWebViewPlatform and then WebViewBuilder/AndroidWebViewBuilder(instead of WebViewPlatformInterface)
This will mean to set it up for Fuchsia we will do:
WebView.platformBuilder = FuchsiaWebViewBuilder();
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.
Do WebViewBuilder and WebViewPlatform need to be distinct?
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 have a single WebViewPlatform per a platform webview instance, while a WebViewBuilder can create zero or more WebViewPlatform instances.
I don't see a nice way to merge them(other than adding a webviewId parameter to all the methods and then using a single instance of that merges WebViewPlatform to serve all of the webview instance).
| /// [WebView#implementation] controls the platform interface that is used by [WebView]. | ||
| /// [WebViewAndroidImplementation] and [WebViewIosImplementation] are the default implementations | ||
| /// for Android and iOS respectively. | ||
| abstract class WebViewPlatformInterface { |
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.
nit: maybe move each interface into it's own file to emphasize their isolation
| /// | ||
| /// An instance implementing this interface is passed to the `onWebViewCreated` callback that is | ||
| /// passed to [WebViewPlatformInterface#onWebViewCreated]. | ||
| abstract class WebViewPlatformControllerInterface { |
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 think it can stay abstract but you can just add default implementation which others will inherit when extending it
| Future<void> loadUrl( | ||
| String url, | ||
| Map<String, String> headers, | ||
| ); |
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.
trivial nit: indenting
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
| import '../platform_interface.dart'; | ||
| import 'webview_method_channel.dart'; | ||
|
|
||
| class WebViewIosImplementation implements WebViewPlatformInterface { |
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.
Avoid using Ios in classes, the correct capitalization is iOS. Use Cupertino if necessary.
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
| class WebViewIosImplementation implements WebViewPlatformInterface { | ||
| @override | ||
| Widget build({BuildContext context, Map<String, dynamic> creationParams, WebViewCreatedCallback onWebViewCreated, Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers}) { | ||
| return UiKitView( |
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.
assert onWebViewCreated isn't 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.
done
| @@ -0,0 +1,33 @@ | |||
| import 'dart:async'; | |||
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.
missing copyright statement
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
| return implementation; | ||
| } | ||
|
|
||
| if (defaultTargetPlatform == TargetPlatform.android) { |
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.
prefer using a switch when comparing on an enum. see style guide.
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
| } | ||
|
|
||
| throw UnsupportedError("Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default 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.
did you intend to cache the returned value?
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
| int get id; | ||
| } | ||
|
|
||
| typedef WebViewCreatedCallback = void Function(WebViewPlatformControllerInterface); |
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.
nit: we like to name the arguments in typedefs. In some places, without a name, the type becomes the name (e.g. void bar(Foo) means void bar(dynamic Foo)) so it's better to be consistent throughout.
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
|
LGTM for the overall approach. I like how you are abstracting the "communicate with the platform side" part of the implementation with I think the problem is that Flutter assumes that communication with the platform-side must be done by encoding the data into bytes, but with the new platform implementations (Fuchsia, web) we know that is not necessarily the case. |
e0b0ee5 to
07d892b
Compare
|
Looks like we're generally ok with this approach. I'm going to start landing it, to make review easier I'd rather land this in pieces, this first piece sets up the plumbing, and moves a single method I plan to followup with 2 PRs:
As each of these PRs will be a "breaking change" from the current PR, I'm not going to publish the plugin until everything landed. |
| /// | ||
| /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] | ||
| /// callback for a [WebView] widget. | ||
| class WebViewController { |
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 the long term plan to get rid of this class in favor of WebViewPlatform?
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 is the public API we expose to our users(vs the public WebViewPlatform which is the API we expose to platform implementors), while the public API for these 2 currently matches I'm not sure we should require it to match.
Also note that renaming WebViewController right now will be a breaking change, I guess we could eventually make WebViewController extend WebViewPlatform if we want to save one indirection layer, and we extract all the utility code(like the logic in _updateJavaScriptChannels) out of it.
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 agree that this should not be part of this PR, but in general I think this controller and WebViewPlatform going to look fairly similar so it would be nice if we can unify them
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.
Let's re-discuss it after we move all the methodchannel stuff out of WebViewController?
I'm actually not sure which option I prefer, I can imagine having a value in separating the interface that users are using to control the webview from the interface that platforms need to provide for the plugin to control the webview (e.g I can see us adding some logic in WebViewController that don't belong in the platform implementation).
But I do see your point about the code duplication.
We should decide at the end of this PR series before publishing to avoid a breaking change.
cyanglaz
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 some minor nits.
| /// | ||
| /// `url` must not be null. | ||
| /// | ||
| /// Throws an ArgumentError if `url` is not a valid URL string. |
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.
How do we enforce the specific platform implementation to honor the parameter constraints mentioned here?
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.
Not sure if we can enforce it. The closet thing we have is the e2e test which we should hope implementors of new platforms will run.
| /// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations | ||
| /// for Android and iOS respectively. | ||
| abstract class WebViewBuilder { | ||
| /// Builds a new WebView. |
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.
Nit: Should have an extra space above the dart doc?
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.
Should we? I wasn't aware that we enforce something like that, I can't find anything about it in the style guide, are we enforcing this convention?
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 asked because it seems other places in this file is doing that. If it is not enforced then I think we are ok. However, I do think it is better to keep the style consistent throughout a file or even a package.
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.
Not sure I understand, how can weI enforce that "onWebViewPlatformCreated will be invoked after the platform specific [WebViewPlatform] implementation is created with the [WebViewPlatform] instance as a parameter."
nkoroste
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.
Overall LGTM
| /// [WebView#platformBuilder] controls the builder that is used by [WebView]. | ||
| /// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations | ||
| /// for Android and iOS respectively. | ||
| abstract class WebViewBuilder { |
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.
nit: still think you should move this to it's own file
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'd prefer to extract it if this file ever becomes too big, I kind of like the idea of having a single file that we can point implementors of new platforms to where everything they need to implement is specified...
If you feel strongly about it I'll split it now.
| /// | ||
| /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] | ||
| /// callback for a [WebView] widget. | ||
| class WebViewController { |
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 agree that this should not be part of this PR, but in general I think this controller and WebViewPlatform going to look fairly similar so it would be nice if we can unify them
…ce (#1645) This is a followup for #1618 and #1624, and moves all of the plugins.flutter.io/webview method channel calls behind the platform channel, which allows a third party package to provide a new platform implementation for all of these methods. See the description for #1618 for more details. The last remaining part is the plugins.flutter.io/cookie_manager channel which I'm leaving for a followup PR.
This is a followup for #1618, #1624, and #1645, and moves the plugins.flutter.io/cookie_manager method channel behind the platform interface which allows a third party package to provide a new platform implementation the cooke manager. See the description for #1618 for more details. Following this PR all platform specific code can be replaced by an external package.
The goal of this PR is to allow separate packages to extend the plugin to work on more platforms.
This is done by abstracting all the platform specific logic behind the
WebViewPlatformandWebViewBuilderinterfaces(inplatform_interface.dart), and adding a staticWebView.platformBuilderfield that can be set to change the the platform specific code.For example, in order to extend the webview to support Fuchsia, a
fuchsia_webview_flutterplugin can implementWebViewBuilderandWebViewPlatform(reference implementations available inwebview_android.dartandwebview_cupertino.dart).When running on Fuchsia the plugin(or the app developer if the plugin doesn't have the right hooks) can then set itself as the implementation for the webview by running: