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

Commit 15ef622

Browse files
authored
Initial (Android only) implementation of a WebView widget. (#752)
The focus of this CL is the overall widget/controller structure, and test plumbing. The API surface is just the bare minimum at this point (only loadUrl).
1 parent 2dc4852 commit 15ef622

File tree

7 files changed

+307
-63
lines changed

7 files changed

+307
-63
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="io.flutter.plugins.webviewflutter">
3+
<uses-sdk android:minSdkVersion="20" android:targetSdkVersion="26"/>
34
</manifest>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.flutter.plugins.webviewflutter;
2+
3+
import static io.flutter.plugin.common.MethodChannel.MethodCallHandler;
4+
import static io.flutter.plugin.common.MethodChannel.Result;
5+
6+
import android.content.Context;
7+
import android.view.View;
8+
import android.webkit.WebView;
9+
import io.flutter.plugin.common.BinaryMessenger;
10+
import io.flutter.plugin.common.MethodCall;
11+
import io.flutter.plugin.common.MethodChannel;
12+
import io.flutter.plugin.platform.PlatformView;
13+
14+
public class FlutterWebView implements PlatformView, MethodCallHandler {
15+
private final WebView webView;
16+
private final MethodChannel methodChannel;
17+
18+
FlutterWebView(Context context, BinaryMessenger messenger, int id) {
19+
webView = new WebView(context);
20+
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
21+
methodChannel.setMethodCallHandler(this);
22+
}
23+
24+
@Override
25+
public View getView() {
26+
return webView;
27+
}
28+
29+
@Override
30+
public void onMethodCall(MethodCall methodCall, Result result) {
31+
switch (methodCall.method) {
32+
case "loadUrl":
33+
loadUrl(methodCall, result);
34+
break;
35+
default:
36+
result.notImplemented();
37+
}
38+
}
39+
40+
private void loadUrl(MethodCall methodCall, Result result) {
41+
String url = (String) methodCall.arguments;
42+
webView.loadUrl(url);
43+
result.success(null);
44+
}
45+
46+
@Override
47+
public void dispose() {}
48+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.flutter.plugins.webviewflutter;
2+
3+
import android.content.Context;
4+
import io.flutter.plugin.common.BinaryMessenger;
5+
import io.flutter.plugin.common.StandardMessageCodec;
6+
import io.flutter.plugin.platform.PlatformView;
7+
import io.flutter.plugin.platform.PlatformViewFactory;
8+
9+
public class WebViewFactory extends PlatformViewFactory {
10+
private final BinaryMessenger messenger;
11+
12+
public WebViewFactory(BinaryMessenger messenger) {
13+
super(StandardMessageCodec.INSTANCE);
14+
this.messenger = messenger;
15+
}
16+
17+
@Override
18+
public PlatformView create(Context context, int id, Object args) {
19+
return new FlutterWebView(context, messenger, id);
20+
}
21+
}
Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
package io.flutter.plugins.webviewflutter;
22

3-
import io.flutter.plugin.common.MethodCall;
4-
import io.flutter.plugin.common.MethodChannel;
5-
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
6-
import io.flutter.plugin.common.MethodChannel.Result;
73
import io.flutter.plugin.common.PluginRegistry.Registrar;
84

95
/** WebviewFlutterPlugin */
10-
public class WebviewFlutterPlugin implements MethodCallHandler {
6+
public class WebviewFlutterPlugin {
117
/** Plugin registration. */
128
public static void registerWith(Registrar registrar) {
13-
final MethodChannel channel = new MethodChannel(registrar.messenger(), "webview_flutter");
14-
channel.setMethodCallHandler(new WebviewFlutterPlugin());
15-
}
16-
17-
@Override
18-
public void onMethodCall(MethodCall call, Result result) {
19-
if (call.method.equals("getPlatformVersion")) {
20-
result.success("Android " + android.os.Build.VERSION.RELEASE);
21-
} else {
22-
result.notImplemented();
23-
}
9+
registrar
10+
.platformViewRegistry()
11+
.registerViewFactory(
12+
"plugins.flutter.io/webview", new WebViewFactory(registrar.messenger()));
2413
}
2514
}

packages/webview_flutter/example/lib/main.dart

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,51 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:async';
6-
75
import 'package:flutter/material.dart';
8-
import 'package:flutter/services.dart';
96
import 'package:webview_flutter/webview_flutter.dart';
107

11-
void main() => runApp(new MyApp());
8+
void main() => runApp(MaterialApp(home: new WebViewExample()));
129

13-
class MyApp extends StatefulWidget {
10+
class WebViewExample extends StatelessWidget {
1411
@override
15-
_MyAppState createState() => new _MyAppState();
16-
}
17-
18-
class _MyAppState extends State<MyApp> {
19-
String _platformVersion = 'Unknown';
20-
21-
@override
22-
void initState() {
23-
super.initState();
24-
initPlatformState();
12+
Widget build(BuildContext context) {
13+
return Scaffold(
14+
appBar: AppBar(
15+
title: const Text('Flutter WebView example'),
16+
// This drop down menu demonstrates that Flutter widgets can be shown over the web view.
17+
actions: <Widget>[const SampleMenu()],
18+
),
19+
body: WebView(
20+
onWebViewCreated: _onWebViewCreated,
21+
),
22+
);
2523
}
2624

27-
// Platform messages are asynchronous, so we initialize in an async method.
28-
Future<void> initPlatformState() async {
29-
String platformVersion;
30-
// Platform messages may fail, so we use a try/catch PlatformException.
31-
try {
32-
platformVersion = await WebviewFlutter.platformVersion;
33-
} on PlatformException {
34-
platformVersion = 'Failed to get platform version.';
35-
}
36-
37-
// If the widget was removed from the tree while the asynchronous platform
38-
// message was in flight, we want to discard the reply rather than calling
39-
// setState to update our non-existent appearance.
40-
if (!mounted) return;
41-
42-
setState(() {
43-
_platformVersion = platformVersion;
44-
});
25+
void _onWebViewCreated(WebViewController controller) {
26+
controller.loadUrl('https://flutter.io');
4527
}
28+
}
29+
30+
class SampleMenu extends StatelessWidget {
31+
const SampleMenu();
4632

4733
@override
4834
Widget build(BuildContext context) {
49-
return new MaterialApp(
50-
home: new Scaffold(
51-
appBar: new AppBar(
52-
title: const Text('Plugin example app'),
53-
),
54-
body: new Center(
55-
child: new Text('Running on: $_platformVersion\n'),
56-
),
57-
),
35+
return PopupMenuButton<String>(
36+
onSelected: (String value) {
37+
Scaffold.of(context).showSnackBar(
38+
new SnackBar(content: new Text('You selected: $value')));
39+
},
40+
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
41+
const PopupMenuItem<String>(
42+
value: 'Item 1',
43+
child: Text('Item 1'),
44+
),
45+
const PopupMenuItem<String>(
46+
value: 'Item 2',
47+
child: Text('Item 2'),
48+
),
49+
],
5850
);
5951
}
6052
}

packages/webview_flutter/lib/webview_flutter.dart

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,110 @@
44

55
import 'dart:async';
66

7+
import 'package:flutter/foundation.dart';
8+
import 'package:flutter/gestures.dart';
79
import 'package:flutter/services.dart';
10+
import 'package:flutter/widgets.dart';
811

9-
class WebviewFlutter {
10-
static const MethodChannel _channel = MethodChannel('webview_flutter');
12+
typedef void WebViewCreatedCallback(WebViewController controller);
1113

12-
static Future<String> get platformVersion async {
13-
final String version = await _channel.invokeMethod('getPlatformVersion');
14-
return version;
14+
/// A web view widget for showing html content.
15+
class WebView extends StatefulWidget {
16+
/// Creates a new web view.
17+
///
18+
/// The web view can be controlled using a `WebViewController` that is passed to the
19+
/// `onWebViewCreated` callback once the web view is created.
20+
///
21+
/// The `gestureRecognizers` parameter must not be null;
22+
const WebView({
23+
Key key,
24+
this.onWebViewCreated,
25+
this.gestureRecognizers = const <OneSequenceGestureRecognizer>[],
26+
}) : assert(gestureRecognizers != null),
27+
super(key: key);
28+
29+
/// If not null invoked once the web view is created.
30+
final WebViewCreatedCallback onWebViewCreated;
31+
32+
/// Which gestures should be consumed by the web view.
33+
///
34+
/// It is possible for other gesture recognizers to be competing with the web view on pointer
35+
/// events, e.g if the webview is inside a [ListView] the [ListView] will want to handle
36+
/// vertical drags. The web view will claim gestures that are recognized by any of the
37+
/// recognizers on this list.
38+
///
39+
/// When this list is empty, the web view will only handle pointer events for gestures that
40+
/// were not claimed by any other gesture recognizer.
41+
final List<OneSequenceGestureRecognizer> gestureRecognizers;
42+
43+
@override
44+
State<StatefulWidget> createState() => _WebViewState();
45+
}
46+
47+
class _WebViewState extends State<WebView> {
48+
@override
49+
Widget build(BuildContext context) {
50+
if (defaultTargetPlatform == TargetPlatform.android) {
51+
return new GestureDetector(
52+
// We prevent text selection by intercepting long press event.
53+
// This is a temporary workaround to prevent a native crash on a second
54+
// text selection.
55+
// TODO(amirh): remove this when the selection handles crash is resolved.
56+
// https://github.com/flutter/flutter/issues/21239
57+
onLongPress: () {},
58+
child: AndroidView(
59+
viewType: 'plugins.flutter.io/webview',
60+
onPlatformViewCreated: _onPlatformViewCreated,
61+
gestureRecognizers: widget.gestureRecognizers,
62+
// WebView content is not affected by the Android view's layout direction,
63+
// we explicitly set it here so that the widget doesn't require an ambient
64+
// directionality.
65+
layoutDirection: TextDirection.rtl,
66+
),
67+
);
68+
}
69+
return Text(
70+
'$defaultTargetPlatform is not yet supported by the webview_flutter plugin');
71+
}
72+
73+
void _onPlatformViewCreated(int id) {
74+
if (widget.onWebViewCreated == null) {
75+
return;
76+
}
77+
widget.onWebViewCreated(new WebViewController._(id));
78+
}
79+
}
80+
81+
/// Controls a [WebView].
82+
///
83+
/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated]
84+
/// callback for a [WebView] widget.
85+
class WebViewController {
86+
WebViewController._(int id)
87+
: _channel = new MethodChannel('plugins.flutter.io/webview_$id');
88+
89+
final MethodChannel _channel;
90+
91+
/// Loads the specified URL.
92+
///
93+
/// `url` must not be null.
94+
///
95+
/// Throws an ArgumentError if `url` is not a valid URL string.
96+
Future<void> loadUrl(String url) async {
97+
assert(url != null);
98+
_validateUrlString(url);
99+
return _channel.invokeMethod('loadUrl', url);
100+
}
101+
}
102+
103+
// Throws an ArgumentError if url is not a valid url string.
104+
void _validateUrlString(String url) {
105+
try {
106+
final Uri uri = Uri.parse(url);
107+
if (uri.scheme.isEmpty) {
108+
throw new ArgumentError('Missing scheme in URL string: "$url"');
109+
}
110+
} on FormatException catch (e) {
111+
throw new ArgumentError(e);
15112
}
16113
}

0 commit comments

Comments
 (0)