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

Conversation

@amirh
Copy link
Contributor

@amirh amirh commented May 20, 2019

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 WebViewPlatform and WebViewBuilder interfaces(in platform_interface.dart), and adding a static WebView.platformBuilder field 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_flutter plugin can implement WebViewBuilder and WebViewPlatform(reference implementations available in webview_android.dart and webview_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:

WebView.platformBuilder = FuchsiaWebViewBuilder();

///
/// An instance implementing this interface is passed to the `onWebViewCreated` callback that is
/// passed to [WebViewPlatformInterface#onWebViewCreated].
abstract class WebViewPlatformControllerInterface {
Copy link
Contributor Author

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.

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

Copy link
Contributor

Choose a reason for hiding this comment

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

what @nkoroste says

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@Hixie
Copy link
Contributor

Hixie commented May 21, 2019

Note: This PR is not ready to be merged

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.

Copy link

@nkoroste nkoroste left a 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 {

Choose a reason for hiding this comment

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

nit: maybe "WebViewControllerPlatformInterface

Copy link
Contributor

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?

Copy link
Contributor Author

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();

Copy link
Contributor

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?

Copy link
Contributor Author

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 {

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 {

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,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

trivial nit: indenting

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

import '../platform_interface.dart';
import 'webview_method_channel.dart';

class WebViewIosImplementation implements WebViewPlatformInterface {
Copy link
Contributor

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.

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

class WebViewIosImplementation implements WebViewPlatformInterface {
@override
Widget build({BuildContext context, Map<String, dynamic> creationParams, WebViewCreatedCallback onWebViewCreated, Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers}) {
return UiKitView(
Copy link
Contributor

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

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

@@ -0,0 +1,33 @@
import 'dart:async';
Copy link
Contributor

Choose a reason for hiding this comment

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

missing copyright statement

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

return implementation;
}

if (defaultTargetPlatform == TargetPlatform.android) {
Copy link
Contributor

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.

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

}

throw UnsupportedError("Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one");
}
Copy link
Contributor

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?

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

int get id;
}

typedef WebViewCreatedCallback = void Function(WebViewPlatformControllerInterface);
Copy link
Contributor

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.

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

@harryterkelsen
Copy link
Contributor

LGTM for the overall approach.

I like how you are abstracting the "communicate with the platform side" part of the implementation with WebViewPlatformControllerInterface, but if something similar will be required for all plugins with implementations that wish to avoid platform channel overhead, perhaps this idea should be pushed into the framework itself.

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.

@amirh amirh force-pushed the multi_platform_webview branch from e0b0ee5 to 07d892b Compare May 21, 2019 22:12
@amirh amirh changed the title [WIP] Allow changing the webview's platform specific implementation Allow changing the webview's platform specific implementation May 21, 2019
@amirh
Copy link
Contributor Author

amirh commented May 21, 2019

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 loadUrl to the Dart interface.

I plan to followup with 2 PRs:

  1. Move the rest of the method channel methods behind the Dart interface.
  2. Replace the creationParams map with Dart objects.

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.

@amirh amirh requested a review from cyanglaz May 21, 2019 22:14
@amirh
Copy link
Contributor Author

amirh commented May 21, 2019

I think this is ready for review now @cyanglaz @nkoroste

///
/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated]
/// callback for a [WebView] widget.
class WebViewController {

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?

Copy link
Contributor Author

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.

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

Copy link
Contributor Author

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.

Copy link
Contributor

@cyanglaz cyanglaz 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 some minor nits.

///
/// `url` must not be null.
///
/// Throws an ArgumentError if `url` is not a valid URL string.
Copy link
Contributor

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?

Copy link
Contributor Author

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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."

Copy link

@nkoroste nkoroste left a 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 {

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

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'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 {

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

@amirh amirh merged commit 0e5933c into flutter:master May 22, 2019
@amirh amirh deleted the multi_platform_webview branch May 22, 2019 19:21
amirh added a commit that referenced this pull request May 30, 2019
…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.
amirh added a commit that referenced this pull request May 31, 2019
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.
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.

6 participants