diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index bc64a6f1c0ed..3d3731979866 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.9.5 + +* Adds dispose methods for HostApi and FlutterApi of JavaObject. + ## 2.9.4 * Fixes avoid_redundant_argument_values lint warnings and minor typos. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 2e163311d6d4..34fd8b79b315 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.3), do not edit directly. +// Autogenerated from Pigeon (v3.2.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -271,6 +271,87 @@ public interface Result { void error(Throwable error); } + private static class JavaObjectHostApiCodec extends StandardMessageCodec { + public static final JavaObjectHostApiCodec INSTANCE = new JavaObjectHostApiCodec(); + + private JavaObjectHostApiCodec() {} + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface JavaObjectHostApi { + void dispose(@NonNull Long identifier); + + /** The codec used by JavaObjectHostApi. */ + static MessageCodec getCodec() { + return JavaObjectHostApiCodec.INSTANCE; + } + + /** + * Sets up an instance of `JavaObjectHostApi` to handle messages through the `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.JavaObjectHostApi.dispose", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + api.dispose((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + + private static class JavaObjectFlutterApiCodec extends StandardMessageCodec { + public static final JavaObjectFlutterApiCodec INSTANCE = new JavaObjectFlutterApiCodec(); + + private JavaObjectFlutterApiCodec() {} + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class JavaObjectFlutterApi { + private final BinaryMessenger binaryMessenger; + + public JavaObjectFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + + static MessageCodec getCodec() { + return JavaObjectFlutterApiCodec.INSTANCE; + } + + public void dispose(@NonNull Long identifierArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.JavaObjectFlutterApi.dispose", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg)), + channelReply -> { + callback.reply(null); + }); + } + } + private static class CookieManagerHostApiCodec extends StandardMessageCodec { public static final CookieManagerHostApiCodec INSTANCE = new CookieManagerHostApiCodec(); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java new file mode 100644 index 000000000000..978e5232657d --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java @@ -0,0 +1,32 @@ +// 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. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; + +/** + * A pigeon Host API implementation that handles creating {@link Object}s and invoking its static + * and instance methods. + * + *

{@link Object} instances created by {@link JavaObjectHostApiImpl} are used to intercommunicate + * with a paired Dart object. + */ +public class JavaObjectHostApiImpl implements GeneratedAndroidWebView.JavaObjectHostApi { + private final InstanceManager instanceManager; + + /** + * Constructs a {@link JavaObjectHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public JavaObjectHostApiImpl(InstanceManager instanceManager) { + this.instanceManager = instanceManager; + } + + @Override + public void dispose(@NonNull Long identifier) { + instanceManager.remove(identifier); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java new file mode 100644 index 000000000000..8ac349e76418 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java @@ -0,0 +1,32 @@ +// 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. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class JavaObjectHostApiTest { + @Test + public void dispose() { + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + + final JavaObjectHostApiImpl hostApi = new JavaObjectHostApiImpl(instanceManager); + + Object object = new Object(); + instanceManager.addDartCreatedInstance(object, 0); + + // To free object for garbage collection. + //noinspection UnusedAssignment + object = null; + + hostApi.dispose(0L); + Runtime.getRuntime().gc(); + + assertNull(instanceManager.getInstance(0)); + + instanceManager.close(); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index f2e305fe85c5..30c59ea43d2c 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -13,6 +13,7 @@ import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart' show BinaryMessenger; import 'package:flutter/widgets.dart' show AndroidViewSurface; import 'android_webview.pigeon.dart'; @@ -22,17 +23,36 @@ import 'instance_manager.dart'; /// Root of the Java class hierarchy. /// /// See https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html. -abstract class JavaObject with Copyable { +class JavaObject with Copyable { /// Constructs a [JavaObject] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. - JavaObject.detached(); + JavaObject.detached({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + }) : _api = JavaObjectHostApiImpl( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ); /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); + + /// Pigeon Host Api implementation for [JavaObject]. + final JavaObjectHostApiImpl _api; + + /// Release the reference to a native Java instance. + static void dispose(JavaObject instance) { + instance._api.instanceManager.removeWeakReference(instance); + } + + @override + JavaObject copy() { + return JavaObject.detached(); + } } /// An Android View that displays web pages. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 4491e162ce9c..f7c83b79d25a 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.3), do not edit directly. +// Autogenerated from Pigeon (v3.2.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name // @dart = 2.12 @@ -78,6 +78,78 @@ class WebResourceErrorData { } } +class _JavaObjectHostApiCodec extends StandardMessageCodec { + const _JavaObjectHostApiCodec(); +} + +class JavaObjectHostApi { + /// Constructor for [JavaObjectHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + JavaObjectHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _JavaObjectHostApiCodec(); + + Future dispose(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } +} + +class _JavaObjectFlutterApiCodec extends StandardMessageCodec { + const _JavaObjectFlutterApiCodec(); +} + +abstract class JavaObjectFlutterApi { + static const MessageCodec codec = _JavaObjectFlutterApiCodec(); + + void dispose(int identifier); + static void setup(JavaObjectFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.JavaObjectFlutterApi.dispose', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.JavaObjectFlutterApi.dispose was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.JavaObjectFlutterApi.dispose was null, expected non-null int.'); + api.dispose(arg_identifier!); + return; + }); + } + } + } +} + class _CookieManagerHostApiCodec extends StandardMessageCodec { const _CookieManagerHostApiCodec(); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index c3e2096bbe0f..00ededabd977 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -36,11 +36,14 @@ WebResourceError _toWebResourceError(WebResourceErrorData data) { class AndroidWebViewFlutterApis { /// Creates a [AndroidWebViewFlutterApis]. AndroidWebViewFlutterApis({ + JavaObjectFlutterApiImpl? javaObjectFlutterApi, DownloadListenerFlutterApiImpl? downloadListenerFlutterApi, WebViewClientFlutterApiImpl? webViewClientFlutterApi, WebChromeClientFlutterApiImpl? webChromeClientFlutterApi, JavaScriptChannelFlutterApiImpl? javaScriptChannelFlutterApi, }) { + this.javaObjectFlutterApi = + javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); this.downloadListenerFlutterApi = downloadListenerFlutterApi ?? DownloadListenerFlutterApiImpl(); this.webViewClientFlutterApi = @@ -58,6 +61,9 @@ class AndroidWebViewFlutterApis { /// This should only be changed for testing purposes. static AndroidWebViewFlutterApis instance = AndroidWebViewFlutterApis(); + /// Handles callbacks methods for the native Java Object class. + late final JavaObjectFlutterApi javaObjectFlutterApi; + /// Flutter Api for [DownloadListener]. late final DownloadListenerFlutterApiImpl downloadListenerFlutterApi; @@ -73,6 +79,7 @@ class AndroidWebViewFlutterApis { /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { + JavaObjectFlutterApi.setup(javaObjectFlutterApi); DownloadListenerFlutterApi.setup(downloadListenerFlutterApi); WebViewClientFlutterApi.setup(webViewClientFlutterApi); WebChromeClientFlutterApi.setup(webChromeClientFlutterApi); @@ -82,6 +89,40 @@ class AndroidWebViewFlutterApis { } } +/// Handles methods calls to the native Java Object class. +class JavaObjectHostApiImpl extends JavaObjectHostApi { + /// Constructs a [JavaObjectHostApiImpl]. + JavaObjectHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; +} + +/// Handles callbacks methods for the native Java Object class. +class JavaObjectFlutterApiImpl implements JavaObjectFlutterApi { + /// Constructs a [JavaObjectFlutterApiImpl]. + JavaObjectFlutterApiImpl({InstanceManager? instanceManager}) + : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void dispose(int identifier) { + instanceManager.remove(identifier); + } +} + /// Host api implementation for [WebView]. class WebViewHostApiImpl extends WebViewHostApi { /// Constructs a [WebViewHostApiImpl]. diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 70ecd99d3638..eb979870a1d3 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -51,6 +51,24 @@ class WebResourceErrorData { String description; } +/// Handles methods calls to the native Java Object class. +/// +/// Also handles calls to remove the reference to an instance with `dispose`. +/// +/// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. +@HostApi(dartHostTestHandler: 'TestJavaObjectHostApi') +abstract class JavaObjectHostApi { + void dispose(int identifier); +} + +/// Handles callbacks methods for the native Java Object class. +/// +/// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. +@FlutterApi() +abstract class JavaObjectFlutterApi { + void dispose(int identifier); +} + @HostApi() abstract class CookieManagerHostApi { @async diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 70ca71de50db..bbff4fe7dce5 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.9.4 +version: 2.9.5 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index fdb98fc43dbe..d57eadc1d5cc 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -18,6 +18,7 @@ import 'test_android_webview.pigeon.dart'; DownloadListener, JavaScriptChannel, TestDownloadListenerHostApi, + TestJavaObjectHostApi, TestJavaScriptChannelHostApi, TestWebChromeClientHostApi, TestWebSettingsHostApi, @@ -33,6 +34,58 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Android WebView', () { + group('JavaObject', () { + late MockTestJavaObjectHostApi mockPlatformHostApi; + + setUp(() { + mockPlatformHostApi = MockTestJavaObjectHostApi(); + TestJavaObjectHostApi.setup(mockPlatformHostApi); + }); + + tearDown(() { + TestJavaObjectHostApi.setup(null); + }); + + test('JavaObject.dispose', () async { + int? callbackIdentifier; + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (int identifier) { + callbackIdentifier = identifier; + }, + ); + + final JavaObject object = JavaObject.detached( + instanceManager: instanceManager, + ); + instanceManager.addHostCreatedInstance(object, 0); + + JavaObject.dispose(object); + + expect(callbackIdentifier, 0); + }); + + test('JavaObjectFlutterApi.dispose', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final JavaObject object = JavaObject.detached( + instanceManager: instanceManager, + ); + instanceManager.addHostCreatedInstance(object, 0); + instanceManager.removeWeakReference(object); + + expect(instanceManager.containsIdentifier(0), isTrue); + + final JavaObjectFlutterApiImpl flutterApi = JavaObjectFlutterApiImpl( + instanceManager: instanceManager, + ); + flutterApi.dispose(0); + + expect(instanceManager.containsIdentifier(0), isFalse); + }); + }); + group('WebView', () { late MockTestWebViewHostApi mockPlatformHostApi; diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index db877e5870b6..3a4043c22864 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -114,6 +114,21 @@ class MockTestDownloadListenerHostApi extends _i1.Mock returnValueForMissingStub: null); } +/// A class which mocks [TestJavaObjectHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestJavaObjectHostApi extends _i1.Mock + implements _i5.TestJavaObjectHostApi { + MockTestJavaObjectHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void dispose(int? identifier) => + super.noSuchMethod(Invocation.method(#dispose, [identifier]), + returnValueForMissingStub: null); +} + /// A class which mocks [TestJavaScriptChannelHostApi]. /// /// See the documentation for Mockito's code generation for more information. diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart index e3c5909f3207..88429fdb337e 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.3), do not edit directly. +// Autogenerated from Pigeon (v3.2.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis // ignore_for_file: avoid_relative_lib_imports @@ -14,6 +14,38 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; +class _TestJavaObjectHostApiCodec extends StandardMessageCodec { + const _TestJavaObjectHostApiCodec(); +} + +abstract class TestJavaObjectHostApi { + static const MessageCodec codec = _TestJavaObjectHostApiCodec(); + + void dispose(int identifier); + static void setup(TestJavaObjectHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose was null, expected non-null int.'); + api.dispose(arg_identifier!); + return {}; + }); + } + } + } +} + class _TestWebViewHostApiCodec extends StandardMessageCodec { const _TestWebViewHostApiCodec(); }