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

[webview_flutter] Added Android implementation of PlatformWebViewController #6674

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ class JavaObject with Copyable {
/// When a [WebView] is no longer needed [release] must be called.
class WebView extends JavaObject {
/// Constructs a new WebView.
///
/// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have
/// any effect and should not be exposed publicly. More info here:
/// https://github.com/flutter/flutter/issues/108106
WebView({this.useHybridComposition = false}) : super.detached() {
api.createFromInstance(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,22 @@ import '../../android_webview.dart' as android_webview;
class AndroidWebViewProxy {
/// Constructs a [AndroidWebViewProxy].
const AndroidWebViewProxy({
this.createAndroidWebView = android_webview.WebView.new,
this.createAndroidWebChromeClient = android_webview.WebChromeClient.new,
this.createAndroidWebViewClient = android_webview.WebViewClient.new,
this.createFlutterAssetManager = android_webview.FlutterAssetManager.new,
this.createJavaScriptChannel = android_webview.JavaScriptChannel.new,
});

/// Constructs a [android_webview.WebView].
///
/// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have
/// any effect and should not be exposed publicly. More info here:
/// https://github.com/flutter/flutter/issues/108106
final android_webview.WebView Function({
required bool useHybridComposition,
}) createAndroidWebView;

/// Constructs a [android_webview.WebChromeClient].
final android_webview.WebChromeClient Function({
void Function(android_webview.WebView webView, int progress)?
Expand Down Expand Up @@ -51,4 +63,26 @@ class AndroidWebViewProxy {
requestLoading,
void Function(android_webview.WebView webView, String url)? urlLoading,
}) createAndroidWebViewClient;

/// Constructs a [android_webview.FlutterAssetManager].
final android_webview.FlutterAssetManager Function()
createFlutterAssetManager;

/// Constructs a [android_webview.JavaScriptChannel].
final android_webview.JavaScriptChannel Function(
String channelName, {
required void Function(String) postMessage,
}) createJavaScriptChannel;

/// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application.
///
/// This flag can be enabled in order to facilitate debugging of web layouts
/// and JavaScript code running inside WebViews. Please refer to
/// [android_webview.WebView] documentation for the debugging guide. The
/// default is false.
///
/// See [android_webview.WebView].setWebContentsDebuggingEnabled.
Future<void> setWebContentsDebuggingEnabled(bool enabled) {
return android_webview.WebView.setWebContentsDebuggingEnabled(enabled);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math';

// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
// ignore: unnecessary_import
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';

import '../../android_webview.dart' as android_webview;
import '../../android_webview.dart';
import '../../instance_manager.dart';
import '../../weak_reference_utils.dart';
import 'android_navigation_delegate.dart';
import 'android_proxy.dart';

/// Object specifying creation parameters for creating a [AndroidWebViewController].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for
/// more information.
@immutable
class AndroidWebViewControllerCreationParams
extends PlatformWebViewControllerCreationParams {
/// Creates a new [AndroidWebViewControllerCreationParams] instance.
AndroidWebViewControllerCreationParams({
@visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(),
@visibleForTesting android_webview.WebStorage? androidWebStorage,
}) : androidWebStorage =
androidWebStorage ?? android_webview.WebStorage.instance,
super();

/// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams].
factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams(
// Recommended placeholder to prevent being broken by platform interface.
// ignore: avoid_unused_constructor_parameters
PlatformWebViewControllerCreationParams params, {
@visibleForTesting
AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(),
@visibleForTesting android_webview.WebStorage? androidWebStorage,
}) {
return AndroidWebViewControllerCreationParams(
androidWebViewProxy: androidWebViewProxy,
androidWebStorage:
androidWebStorage ?? android_webview.WebStorage.instance,
);
}

/// Handles constructing objects and calling static methods for the Android WebView
/// native library.
@visibleForTesting
final AndroidWebViewProxy androidWebViewProxy;

/// Manages the JavaScript storage APIs provided by the [android_webview.WebView].
@visibleForTesting
final android_webview.WebStorage androidWebStorage;
}

/// Implementation of the [PlatformWebViewController] with the Android WebView API.
class AndroidWebViewController extends PlatformWebViewController {
/// Creates a new [AndroidWebViewCookieManager].
AndroidWebViewController(PlatformWebViewControllerCreationParams params)
: super.implementation(params is AndroidWebViewControllerCreationParams
? params
: AndroidWebViewControllerCreationParams
.fromPlatformWebViewControllerCreationParams(params));

AndroidWebViewControllerCreationParams get _androidWebViewParams =>
params as AndroidWebViewControllerCreationParams;

/// The native [android_webview.WebView] being controlled.
late final android_webview.WebView _webView =
_androidWebViewParams.androidWebViewProxy.createAndroidWebView(
// Due to changes in Flutter 3.0 the `useHybridComposition` doesn't have
// any effect and is purposefully not exposed publicly by the
// [AndroidWebViewController]. More info here:
// https://github.com/flutter/flutter/issues/108106
useHybridComposition: true,
);

/// The native [android_webview.FlutterAssetManager] allows managing assets.
late final android_webview.FlutterAssetManager _flutterAssetManager =
_androidWebViewParams.androidWebViewProxy.createFlutterAssetManager();

final Map<String, AndroidJavaScriptChannelParams> _javaScriptChannelParams =
<String, AndroidJavaScriptChannelParams>{};

@override
Future<void> loadFile(
String absoluteFilePath,
) {
final String url = absoluteFilePath.startsWith('file://')
? absoluteFilePath
: Uri.file(absoluteFilePath).toString();

_webView.settings.setAllowFileAccess(true);
return _webView.loadUrl(url, <String, String>{});
}

@override
Future<void> loadFlutterAsset(
String key,
) async {
final String assetFilePath =
await _flutterAssetManager.getAssetFilePathByName(key);
final List<String> pathElements = assetFilePath.split('/');
final String fileName = pathElements.removeLast();
final List<String?> paths =
await _flutterAssetManager.list(pathElements.join('/'));

if (!paths.contains(fileName)) {
throw ArgumentError(
'Asset for key "$key" not found.',
'key',
);
}

return _webView.loadUrl(
Uri.file('/android_asset/$assetFilePath').toString(),
<String, String>{},
);
}

@override
Future<void> loadHtmlString(
String html, {
String? baseUrl,
}) {
return _webView.loadDataWithBaseUrl(
baseUrl: baseUrl,
data: html,
mimeType: 'text/html',
);
}

@override
Future<void> loadRequest(
LoadRequestParams params,
) {
if (!params.uri.hasScheme) {
throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
}
switch (params.method) {
case LoadRequestMethod.get:
return _webView.loadUrl(params.uri.toString(), params.headers);
case LoadRequestMethod.post:
return _webView.postUrl(
params.uri.toString(), params.body ?? Uint8List(0));
default:
throw UnimplementedError(
'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.',
);
}
}

@override
Future<String?> currentUrl() => _webView.getUrl();

@override
Future<bool> canGoBack() => _webView.canGoBack();

@override
Future<bool> canGoForward() => _webView.canGoForward();

@override
Future<void> goBack() => _webView.goBack();

@override
Future<void> goForward() => _webView.goForward();

@override
Future<void> reload() => _webView.reload();

@override
Future<void> clearCache() => _webView.clearCache(true);

@override
Future<void> clearLocalStorage() =>
_androidWebViewParams.androidWebStorage.deleteAllData();

@override
Future<void> setPlatformNavigationDelegate(
covariant AndroidNavigationDelegate handler) async {
_webView.setWebViewClient(handler.androidWebViewClient);
_webView.setWebChromeClient(handler.androidWebChromeClient);
}

@override
Future<void> runJavaScript(String javaScript) {
return _webView.evaluateJavascript(javaScript);
}

@override
Future<String> runJavaScriptReturningResult(String javaScript) async {
return await _webView.evaluateJavascript(javaScript) ?? '';
}

@override
Future<void> addJavaScriptChannel(
JavaScriptChannelParams javaScriptChannelParams,
) {
final AndroidJavaScriptChannelParams androidJavaScriptParams =
javaScriptChannelParams is AndroidJavaScriptChannelParams
? javaScriptChannelParams
: AndroidJavaScriptChannelParams.fromJavaScriptChannelParams(
javaScriptChannelParams);

// When JavaScript channel with the same name exists make sure to remove it
// before registering the new channel.
if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) {
_webView
.removeJavaScriptChannel(androidJavaScriptParams._javaScriptChannel);
}

_javaScriptChannelParams[androidJavaScriptParams.name] =
androidJavaScriptParams;

return _webView
.addJavaScriptChannel(androidJavaScriptParams._javaScriptChannel);
}

@override
Future<void> removeJavaScriptChannel(String javaScriptChannelName) async {
final AndroidJavaScriptChannelParams? javaScriptChannelParams =
_javaScriptChannelParams[javaScriptChannelName];
if (javaScriptChannelParams == null) {
return;
}

_javaScriptChannelParams.remove(javaScriptChannelName);
return _webView
.removeJavaScriptChannel(javaScriptChannelParams._javaScriptChannel);
}

@override
Future<String?> getTitle() => _webView.getTitle();

@override
Future<void> scrollTo(int x, int y) => _webView.scrollTo(x, y);

@override
Future<void> scrollBy(int x, int y) => _webView.scrollBy(x, y);

@override
Future<Point<int>> getScrollPosition() async {
final Offset position = await _webView.getScrollPosition();
return Point<int>(position.dx.round(), position.dy.round());
}

@override
Future<void> enableDebugging(bool enabled) =>
_androidWebViewParams.androidWebViewProxy
.setWebContentsDebuggingEnabled(enabled);

@override
Future<void> enableZoom(bool enabled) =>
_webView.settings.setSupportZoom(enabled);

@override
Future<void> setBackgroundColor(Color color) =>
_webView.setBackgroundColor(color);

@override
Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) =>
_webView.settings
.setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted);

@override
Future<void> setUserAgent(String? userAgent) =>
_webView.settings.setUserAgentString(userAgent);
}

/// An implementation of [JavaScriptChannelParams] with the Android WebView API.
///
/// See [AndroidWebViewController.addJavaScriptChannel].
@immutable
class AndroidJavaScriptChannelParams extends JavaScriptChannelParams {
/// Constructs a [AndroidJavaScriptChannelParams].
AndroidJavaScriptChannelParams({
required super.name,
required super.onMessageReceived,
@visibleForTesting
AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(),
}) : assert(name.isNotEmpty),
_javaScriptChannel = webViewProxy.createJavaScriptChannel(
name,
postMessage: withWeakRefenceTo(
onMessageReceived,
(WeakReference<void Function(JavaScriptMessage)> weakReference) {
return (
String message,
) {
if (weakReference.target != null) {
weakReference.target!(
JavaScriptMessage(message: message),
);
}
};
},
),
);

/// Constructs a [AndroidJavaScriptChannelParams] using a
/// [JavaScriptChannelParams].
AndroidJavaScriptChannelParams.fromJavaScriptChannelParams(
JavaScriptChannelParams params, {
@visibleForTesting
AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(),
}) : this(
name: params.name,
onMessageReceived: params.onMessageReceived,
webViewProxy: webViewProxy,
);

final android_webview.JavaScriptChannel _javaScriptChannel;
}
Loading