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

Commit 341fe29

Browse files
authored
[webview_flutter] Added Android implementation of PlatformWebViewController (#6674)
* Added Android implementation of PlatformWebViewController * Apply feedback from PR * Apply feedback from PR * Remove AndroidWebViewWidget which was added to early
1 parent 895b36e commit 341fe29

File tree

5 files changed

+2179
-0
lines changed

5 files changed

+2179
-0
lines changed

packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ class JavaObject with Copyable {
7878
/// When a [WebView] is no longer needed [release] must be called.
7979
class WebView extends JavaObject {
8080
/// Constructs a new WebView.
81+
///
82+
/// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have
83+
/// any effect and should not be exposed publicly. More info here:
84+
/// https://github.com/flutter/flutter/issues/108106
8185
WebView({this.useHybridComposition = false}) : super.detached() {
8286
api.createFromInstance(this);
8387
}

packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart

+34
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,22 @@ import '../../android_webview.dart' as android_webview;
1616
class AndroidWebViewProxy {
1717
/// Constructs a [AndroidWebViewProxy].
1818
const AndroidWebViewProxy({
19+
this.createAndroidWebView = android_webview.WebView.new,
1920
this.createAndroidWebChromeClient = android_webview.WebChromeClient.new,
2021
this.createAndroidWebViewClient = android_webview.WebViewClient.new,
22+
this.createFlutterAssetManager = android_webview.FlutterAssetManager.new,
23+
this.createJavaScriptChannel = android_webview.JavaScriptChannel.new,
2124
});
2225

26+
/// Constructs a [android_webview.WebView].
27+
///
28+
/// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have
29+
/// any effect and should not be exposed publicly. More info here:
30+
/// https://github.com/flutter/flutter/issues/108106
31+
final android_webview.WebView Function({
32+
required bool useHybridComposition,
33+
}) createAndroidWebView;
34+
2335
/// Constructs a [android_webview.WebChromeClient].
2436
final android_webview.WebChromeClient Function({
2537
void Function(android_webview.WebView webView, int progress)?
@@ -51,4 +63,26 @@ class AndroidWebViewProxy {
5163
requestLoading,
5264
void Function(android_webview.WebView webView, String url)? urlLoading,
5365
}) createAndroidWebViewClient;
66+
67+
/// Constructs a [android_webview.FlutterAssetManager].
68+
final android_webview.FlutterAssetManager Function()
69+
createFlutterAssetManager;
70+
71+
/// Constructs a [android_webview.JavaScriptChannel].
72+
final android_webview.JavaScriptChannel Function(
73+
String channelName, {
74+
required void Function(String) postMessage,
75+
}) createJavaScriptChannel;
76+
77+
/// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application.
78+
///
79+
/// This flag can be enabled in order to facilitate debugging of web layouts
80+
/// and JavaScript code running inside WebViews. Please refer to
81+
/// [android_webview.WebView] documentation for the debugging guide. The
82+
/// default is false.
83+
///
84+
/// See [android_webview.WebView].setWebContentsDebuggingEnabled.
85+
Future<void> setWebContentsDebuggingEnabled(bool enabled) {
86+
return android_webview.WebView.setWebContentsDebuggingEnabled(enabled);
87+
}
5488
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:math';
6+
7+
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
8+
// ignore: unnecessary_import
9+
import 'dart:typed_data';
10+
11+
import 'package:flutter/foundation.dart';
12+
import 'package:flutter/material.dart';
13+
import 'package:flutter/services.dart';
14+
import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';
15+
16+
import '../../android_webview.dart' as android_webview;
17+
import '../../android_webview.dart';
18+
import '../../instance_manager.dart';
19+
import '../../weak_reference_utils.dart';
20+
import 'android_navigation_delegate.dart';
21+
import 'android_proxy.dart';
22+
23+
/// Object specifying creation parameters for creating a [AndroidWebViewController].
24+
///
25+
/// When adding additional fields make sure they can be null or have a default
26+
/// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for
27+
/// more information.
28+
@immutable
29+
class AndroidWebViewControllerCreationParams
30+
extends PlatformWebViewControllerCreationParams {
31+
/// Creates a new [AndroidWebViewControllerCreationParams] instance.
32+
AndroidWebViewControllerCreationParams({
33+
@visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(),
34+
@visibleForTesting android_webview.WebStorage? androidWebStorage,
35+
}) : androidWebStorage =
36+
androidWebStorage ?? android_webview.WebStorage.instance,
37+
super();
38+
39+
/// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams].
40+
factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams(
41+
// Recommended placeholder to prevent being broken by platform interface.
42+
// ignore: avoid_unused_constructor_parameters
43+
PlatformWebViewControllerCreationParams params, {
44+
@visibleForTesting
45+
AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(),
46+
@visibleForTesting android_webview.WebStorage? androidWebStorage,
47+
}) {
48+
return AndroidWebViewControllerCreationParams(
49+
androidWebViewProxy: androidWebViewProxy,
50+
androidWebStorage:
51+
androidWebStorage ?? android_webview.WebStorage.instance,
52+
);
53+
}
54+
55+
/// Handles constructing objects and calling static methods for the Android WebView
56+
/// native library.
57+
@visibleForTesting
58+
final AndroidWebViewProxy androidWebViewProxy;
59+
60+
/// Manages the JavaScript storage APIs provided by the [android_webview.WebView].
61+
@visibleForTesting
62+
final android_webview.WebStorage androidWebStorage;
63+
}
64+
65+
/// Implementation of the [PlatformWebViewController] with the Android WebView API.
66+
class AndroidWebViewController extends PlatformWebViewController {
67+
/// Creates a new [AndroidWebViewCookieManager].
68+
AndroidWebViewController(PlatformWebViewControllerCreationParams params)
69+
: super.implementation(params is AndroidWebViewControllerCreationParams
70+
? params
71+
: AndroidWebViewControllerCreationParams
72+
.fromPlatformWebViewControllerCreationParams(params));
73+
74+
AndroidWebViewControllerCreationParams get _androidWebViewParams =>
75+
params as AndroidWebViewControllerCreationParams;
76+
77+
/// The native [android_webview.WebView] being controlled.
78+
late final android_webview.WebView _webView =
79+
_androidWebViewParams.androidWebViewProxy.createAndroidWebView(
80+
// Due to changes in Flutter 3.0 the `useHybridComposition` doesn't have
81+
// any effect and is purposefully not exposed publicly by the
82+
// [AndroidWebViewController]. More info here:
83+
// https://github.com/flutter/flutter/issues/108106
84+
useHybridComposition: true,
85+
);
86+
87+
/// The native [android_webview.FlutterAssetManager] allows managing assets.
88+
late final android_webview.FlutterAssetManager _flutterAssetManager =
89+
_androidWebViewParams.androidWebViewProxy.createFlutterAssetManager();
90+
91+
final Map<String, AndroidJavaScriptChannelParams> _javaScriptChannelParams =
92+
<String, AndroidJavaScriptChannelParams>{};
93+
94+
@override
95+
Future<void> loadFile(
96+
String absoluteFilePath,
97+
) {
98+
final String url = absoluteFilePath.startsWith('file://')
99+
? absoluteFilePath
100+
: Uri.file(absoluteFilePath).toString();
101+
102+
_webView.settings.setAllowFileAccess(true);
103+
return _webView.loadUrl(url, <String, String>{});
104+
}
105+
106+
@override
107+
Future<void> loadFlutterAsset(
108+
String key,
109+
) async {
110+
final String assetFilePath =
111+
await _flutterAssetManager.getAssetFilePathByName(key);
112+
final List<String> pathElements = assetFilePath.split('/');
113+
final String fileName = pathElements.removeLast();
114+
final List<String?> paths =
115+
await _flutterAssetManager.list(pathElements.join('/'));
116+
117+
if (!paths.contains(fileName)) {
118+
throw ArgumentError(
119+
'Asset for key "$key" not found.',
120+
'key',
121+
);
122+
}
123+
124+
return _webView.loadUrl(
125+
Uri.file('/android_asset/$assetFilePath').toString(),
126+
<String, String>{},
127+
);
128+
}
129+
130+
@override
131+
Future<void> loadHtmlString(
132+
String html, {
133+
String? baseUrl,
134+
}) {
135+
return _webView.loadDataWithBaseUrl(
136+
baseUrl: baseUrl,
137+
data: html,
138+
mimeType: 'text/html',
139+
);
140+
}
141+
142+
@override
143+
Future<void> loadRequest(
144+
LoadRequestParams params,
145+
) {
146+
if (!params.uri.hasScheme) {
147+
throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
148+
}
149+
switch (params.method) {
150+
case LoadRequestMethod.get:
151+
return _webView.loadUrl(params.uri.toString(), params.headers);
152+
case LoadRequestMethod.post:
153+
return _webView.postUrl(
154+
params.uri.toString(), params.body ?? Uint8List(0));
155+
default:
156+
throw UnimplementedError(
157+
'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.',
158+
);
159+
}
160+
}
161+
162+
@override
163+
Future<String?> currentUrl() => _webView.getUrl();
164+
165+
@override
166+
Future<bool> canGoBack() => _webView.canGoBack();
167+
168+
@override
169+
Future<bool> canGoForward() => _webView.canGoForward();
170+
171+
@override
172+
Future<void> goBack() => _webView.goBack();
173+
174+
@override
175+
Future<void> goForward() => _webView.goForward();
176+
177+
@override
178+
Future<void> reload() => _webView.reload();
179+
180+
@override
181+
Future<void> clearCache() => _webView.clearCache(true);
182+
183+
@override
184+
Future<void> clearLocalStorage() =>
185+
_androidWebViewParams.androidWebStorage.deleteAllData();
186+
187+
@override
188+
Future<void> setPlatformNavigationDelegate(
189+
covariant AndroidNavigationDelegate handler) async {
190+
_webView.setWebViewClient(handler.androidWebViewClient);
191+
_webView.setWebChromeClient(handler.androidWebChromeClient);
192+
}
193+
194+
@override
195+
Future<void> runJavaScript(String javaScript) {
196+
return _webView.evaluateJavascript(javaScript);
197+
}
198+
199+
@override
200+
Future<String> runJavaScriptReturningResult(String javaScript) async {
201+
return await _webView.evaluateJavascript(javaScript) ?? '';
202+
}
203+
204+
@override
205+
Future<void> addJavaScriptChannel(
206+
JavaScriptChannelParams javaScriptChannelParams,
207+
) {
208+
final AndroidJavaScriptChannelParams androidJavaScriptParams =
209+
javaScriptChannelParams is AndroidJavaScriptChannelParams
210+
? javaScriptChannelParams
211+
: AndroidJavaScriptChannelParams.fromJavaScriptChannelParams(
212+
javaScriptChannelParams);
213+
214+
// When JavaScript channel with the same name exists make sure to remove it
215+
// before registering the new channel.
216+
if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) {
217+
_webView
218+
.removeJavaScriptChannel(androidJavaScriptParams._javaScriptChannel);
219+
}
220+
221+
_javaScriptChannelParams[androidJavaScriptParams.name] =
222+
androidJavaScriptParams;
223+
224+
return _webView
225+
.addJavaScriptChannel(androidJavaScriptParams._javaScriptChannel);
226+
}
227+
228+
@override
229+
Future<void> removeJavaScriptChannel(String javaScriptChannelName) async {
230+
final AndroidJavaScriptChannelParams? javaScriptChannelParams =
231+
_javaScriptChannelParams[javaScriptChannelName];
232+
if (javaScriptChannelParams == null) {
233+
return;
234+
}
235+
236+
_javaScriptChannelParams.remove(javaScriptChannelName);
237+
return _webView
238+
.removeJavaScriptChannel(javaScriptChannelParams._javaScriptChannel);
239+
}
240+
241+
@override
242+
Future<String?> getTitle() => _webView.getTitle();
243+
244+
@override
245+
Future<void> scrollTo(int x, int y) => _webView.scrollTo(x, y);
246+
247+
@override
248+
Future<void> scrollBy(int x, int y) => _webView.scrollBy(x, y);
249+
250+
@override
251+
Future<Point<int>> getScrollPosition() async {
252+
final Offset position = await _webView.getScrollPosition();
253+
return Point<int>(position.dx.round(), position.dy.round());
254+
}
255+
256+
@override
257+
Future<void> enableDebugging(bool enabled) =>
258+
_androidWebViewParams.androidWebViewProxy
259+
.setWebContentsDebuggingEnabled(enabled);
260+
261+
@override
262+
Future<void> enableZoom(bool enabled) =>
263+
_webView.settings.setSupportZoom(enabled);
264+
265+
@override
266+
Future<void> setBackgroundColor(Color color) =>
267+
_webView.setBackgroundColor(color);
268+
269+
@override
270+
Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) =>
271+
_webView.settings
272+
.setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted);
273+
274+
@override
275+
Future<void> setUserAgent(String? userAgent) =>
276+
_webView.settings.setUserAgentString(userAgent);
277+
}
278+
279+
/// An implementation of [JavaScriptChannelParams] with the Android WebView API.
280+
///
281+
/// See [AndroidWebViewController.addJavaScriptChannel].
282+
@immutable
283+
class AndroidJavaScriptChannelParams extends JavaScriptChannelParams {
284+
/// Constructs a [AndroidJavaScriptChannelParams].
285+
AndroidJavaScriptChannelParams({
286+
required super.name,
287+
required super.onMessageReceived,
288+
@visibleForTesting
289+
AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(),
290+
}) : assert(name.isNotEmpty),
291+
_javaScriptChannel = webViewProxy.createJavaScriptChannel(
292+
name,
293+
postMessage: withWeakRefenceTo(
294+
onMessageReceived,
295+
(WeakReference<void Function(JavaScriptMessage)> weakReference) {
296+
return (
297+
String message,
298+
) {
299+
if (weakReference.target != null) {
300+
weakReference.target!(
301+
JavaScriptMessage(message: message),
302+
);
303+
}
304+
};
305+
},
306+
),
307+
);
308+
309+
/// Constructs a [AndroidJavaScriptChannelParams] using a
310+
/// [JavaScriptChannelParams].
311+
AndroidJavaScriptChannelParams.fromJavaScriptChannelParams(
312+
JavaScriptChannelParams params, {
313+
@visibleForTesting
314+
AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(),
315+
}) : this(
316+
name: params.name,
317+
onMessageReceived: params.onMessageReceived,
318+
webViewProxy: webViewProxy,
319+
);
320+
321+
final android_webview.JavaScriptChannel _javaScriptChannel;
322+
}

0 commit comments

Comments
 (0)