diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 136d71485a0f..ed6c546ed147 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.3.0
+
+* Adds support to access native `WebView`.
+
## 3.2.4
* Renames Pigeon output files.
diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md
index 1a54808379fb..d2f4d94bfed4 100644
--- a/packages/webview_flutter/webview_flutter_android/README.md
+++ b/packages/webview_flutter/webview_flutter_android/README.md
@@ -32,6 +32,22 @@ This can be configured for versions >=23 with
`AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features
for more details on setting platform-specific features in the main plugin.
+### External Native API
+
+The plugin also provides a native API accessible by the native code of Android applications or
+packages. This API follows the convention of breaking changes of the Dart API, which means that any
+changes to the class that are not backwards compatible will only be made with a major version change
+of the plugin. Native code other than this external API does not follow breaking change conventions,
+so app or plugin clients should not use any other native APIs.
+
+The API can be accessed by importing the native class `WebViewFlutterAndroidExternalApi`:
+
+Java:
+
+```java
+import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi;
+```
+
## Contributing
This package uses [pigeon][3] to generate the communication layer between Flutter and the host
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java
new file mode 100644
index 000000000000..3819d7b26f62
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java
@@ -0,0 +1,50 @@
+// 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 android.webkit.WebView;
+import androidx.annotation.Nullable;
+import io.flutter.embedding.engine.FlutterEngine;
+
+/**
+ * App and package facing native API provided by the `webview_flutter_android` plugin.
+ *
+ *
This class follows the convention of breaking changes of the Dart API, which means that any
+ * changes to the class that are not backwards compatible will only be made with a major version
+ * change of the plugin.
+ *
+ *
Native code other than this external API does not follow breaking change conventions, so app
+ * or plugin clients should not use any other native APIs.
+ */
+@SuppressWarnings("unused")
+public interface WebViewFlutterAndroidExternalApi {
+ /**
+ * Retrieves the {@link WebView} that is associated with `identifier`.
+ *
+ *
See the Dart method `AndroidWebViewController.webViewIdentifier` to get the identifier of an
+ * underlying `WebView`.
+ *
+ * @param engine the execution environment the {@link WebViewFlutterPlugin} should belong to. If
+ * the engine doesn't contain an attached instance of {@link WebViewFlutterPlugin}, this
+ * method returns null.
+ * @param identifier the associated identifier of the `WebView`.
+ * @return the `WebView` associated with `identifier` or null if a `WebView` instance associated
+ * with `identifier` could not be found.
+ */
+ @Nullable
+ static WebView getWebView(FlutterEngine engine, long identifier) {
+ final WebViewFlutterPlugin webViewPlugin =
+ (WebViewFlutterPlugin) engine.getPlugins().get(WebViewFlutterPlugin.class);
+
+ if (webViewPlugin != null && webViewPlugin.getInstanceManager() != null) {
+ final Object instance = webViewPlugin.getInstanceManager().getInstance(identifier);
+ if (instance instanceof WebView) {
+ return (WebView) instance;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
index 1c5a55057ca6..04a9735e0281 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -33,7 +33,7 @@
*
Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead.
*/
public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
- private InstanceManager instanceManager;
+ @Nullable private InstanceManager instanceManager;
private FlutterPluginBinding pluginBinding;
private WebViewHostApiImpl webViewHostApi;
@@ -148,7 +148,10 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
- instanceManager.close();
+ if (instanceManager != null) {
+ instanceManager.close();
+ instanceManager = null;
+ }
}
@Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java
similarity index 58%
rename from packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java
rename to packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java
index 16dc6cf5de2b..0877dcaf2b06 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java
@@ -4,11 +4,16 @@
package io.flutter.plugins.webviewflutter;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.webkit.WebView;
+import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.embedding.engine.plugins.PluginRegistry;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformViewRegistry;
import org.junit.Rule;
@@ -17,7 +22,7 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-public class WebViewFlutterPluginTest {
+public class WebViewFlutterAndroidExternalApiTest {
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock Context mockContext;
@@ -29,7 +34,7 @@ public class WebViewFlutterPluginTest {
@Mock FlutterPlugin.FlutterPluginBinding mockPluginBinding;
@Test
- public void getInstanceManagerAfterOnAttachedToEngine() {
+ public void getWebView() {
final WebViewFlutterPlugin webViewFlutterPlugin = new WebViewFlutterPlugin();
when(mockPluginBinding.getApplicationContext()).thenReturn(mockContext);
@@ -38,7 +43,19 @@ public void getInstanceManagerAfterOnAttachedToEngine() {
webViewFlutterPlugin.onAttachedToEngine(mockPluginBinding);
- assertNotNull(webViewFlutterPlugin.getInstanceManager());
+ final InstanceManager instanceManager = webViewFlutterPlugin.getInstanceManager();
+ assertNotNull(instanceManager);
+
+ final WebView mockWebView = mock(WebView.class);
+ instanceManager.addDartCreatedInstance(mockWebView, 0);
+
+ final PluginRegistry mockPluginRegistry = mock(PluginRegistry.class);
+ when(mockPluginRegistry.get(WebViewFlutterPlugin.class)).thenReturn(webViewFlutterPlugin);
+
+ final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class);
+ when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry);
+
+ assertEquals(WebViewFlutterAndroidExternalApi.getWebView(mockFlutterEngine, 0), mockWebView);
webViewFlutterPlugin.onDetachedFromEngine(mockPluginBinding);
}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
index fd287a515c65..6bd3dc03746c 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
@@ -144,6 +144,16 @@ class AndroidWebViewController extends PlatformWebViewController {
return webViewProxy.setWebContentsDebuggingEnabled(enabled);
}
+ /// Identifier used to retrieve the underlying native `WKWebView`.
+ ///
+ /// This is typically used by other plugins to retrieve the native `WebView`
+ /// from an `InstanceManager`.
+ ///
+ /// See Java method `WebViewFlutterPlugin.getWebView`.
+ int get webViewIdentifier =>
+ // ignore: invalid_use_of_visible_for_testing_member
+ android_webview.WebView.api.instanceManager.getIdentifier(_webView)!;
+
@override
Future loadFile(
String absoluteFilePath,
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index d90844d9ce08..ac8971006ba2 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: 3.2.4
+version: 3.3.0
environment:
sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
index 03e71ec5d987..43bab384e0cc 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
@@ -14,6 +14,7 @@ import 'package:mockito/mockito.dart';
import 'package:webview_flutter_android/src/android_proxy.dart';
import 'package:webview_flutter_android/src/android_webview.dart'
as android_webview;
+import 'package:webview_flutter_android/src/android_webview_api_impls.dart';
import 'package:webview_flutter_android/src/instance_manager.dart';
import 'package:webview_flutter_android/src/platform_views_service_proxy.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
@@ -884,6 +885,29 @@ void main() {
verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1);
});
+ test('webViewIdentifier', () {
+ final MockWebView mockWebView = MockWebView();
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ instanceManager.addHostCreatedInstance(mockWebView, 0);
+
+ android_webview.WebView.api = WebViewHostApiImpl(
+ instanceManager: instanceManager,
+ );
+
+ final AndroidWebViewController controller = createControllerWithMocks(
+ mockWebView: mockWebView,
+ );
+
+ expect(
+ controller.webViewIdentifier,
+ 0,
+ );
+
+ android_webview.WebView.api = WebViewHostApiImpl();
+ });
+
group('AndroidWebViewWidget', () {
testWidgets('Builds Android view using supplied parameters',
(WidgetTester tester) async {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index d8442c2c1f0e..d0c5a726b5f7 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.1.0
+
+* Adds support to access native `WKWebView`.
+
## 3.0.5
* Renames Pigeon output files.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/README.md b/packages/webview_flutter/webview_flutter_wkwebview/README.md
index 79359636e742..a393a71d2248 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/README.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/README.md
@@ -7,6 +7,24 @@ The Apple WKWebView implementation of [`webview_flutter`][1].
This package is [endorsed][2], which means you can simply use `webview_flutter`
normally. This package will be automatically included in your app when you do.
+### External Native API
+
+The plugin also provides a native API accessible by the native code of iOS applications or packages.
+This API follows the convention of breaking changes of the Dart API, which means that any changes to
+the class that are not backwards compatible will only be made with a major version change of the
+plugin. Native code other than this external API does not follow breaking change conventions, so
+app or plugin clients should not use any other native APIs.
+
+The API can be accessed by importing the native plugin `webview_flutter_wkwebview`:
+
+Objective-C:
+
+```objectivec
+@import webview_flutter_wkwebview;
+```
+
+Then you will have access to the native class `FWFWebViewFlutterWKWebViewExternalAPI`.
+
## Contributing
This package uses [pigeon][3] to generate the communication layer between Flutter and the host
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
index 1efee8f844ef..9e1038d08279 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,12 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; };
8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; };
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; };
8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */; };
@@ -76,6 +77,7 @@
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = ""; };
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = ""; };
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = ""; };
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = ""; };
@@ -145,6 +147,7 @@
isa = PBXGroup;
children = (
68BDCAED23C3F7CB00D9C032 /* Info.plist */,
+ 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */,
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */,
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */,
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */,
@@ -379,10 +382,12 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -415,6 +420,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -463,6 +469,7 @@
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */,
8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */,
8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */,
+ 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */,
8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */,
8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */,
8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */,
@@ -533,7 +540,11 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
@@ -547,7 +558,11 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
@@ -672,7 +687,10 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@@ -695,7 +713,10 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@@ -711,7 +732,11 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerUITests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -724,7 +749,11 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerUITests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m
new file mode 100644
index 000000000000..1452edeaa647
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m
@@ -0,0 +1,28 @@
+// 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
+#import
+
+@import webview_flutter_wkwebview;
+
+@interface FWFWebViewFlutterWKWebViewExternalAPITests : XCTestCase
+@end
+
+@implementation FWFWebViewFlutterWKWebViewExternalAPITests
+- (void)testWebViewForIdentifier {
+ WKWebView *webView = [[WKWebView alloc] init];
+ FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
+ [instanceManager addDartCreatedInstance:webView withIdentifier:0];
+
+ id mockPluginRegistry = OCMProtocolMock(@protocol(FlutterPluginRegistry));
+ OCMStub([mockPluginRegistry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"])
+ .andReturn(instanceManager);
+
+ XCTAssertEqualObjects(
+ [FWFWebViewFlutterWKWebViewExternalAPI webViewForIdentifier:0
+ withPluginRegistry:mockPluginRegistry],
+ webView);
+}
+@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h
index 2a80c7d886f2..a1c035e40185 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h
@@ -3,6 +3,11 @@
// found in the LICENSE file.
#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
@interface FLTWebViewFlutterPlugin : NSObject
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h
new file mode 100644
index 000000000000..297f8c37ec3e
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h
@@ -0,0 +1,37 @@
+// 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
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * App and package facing native API provided by the `webview_flutter_wkwebview` plugin.
+ *
+ * This class follows the convention of breaking changes of the Dart API, which means that any
+ * changes to the class that are not backwards compatible will only be made with a major version
+ * change of the plugin. Native code other than this external API does not follow breaking change
+ * conventions, so app or plugin clients should not use any other native APIs.
+ */
+@interface FWFWebViewFlutterWKWebViewExternalAPI : NSObject
+/**
+ * Retrieves the `WKWebView` that is associated with `identifier`.
+ *
+ * See the Dart method `WebKitWebViewController.webViewIdentifier` to get the identifier of an
+ * underlying `WKWebView`.
+ *
+ * @param identifier The associated identifier of the `WebView`.
+ * @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. If
+ * the registry doesn't contain an attached instance of `FLTWebViewFlutterPlugin`,
+ * this method returns nil.
+ * @return The `WKWebView` associated with `identifier` or nil if a `WKWebView` instance associated
+ * with `identifier` could not be found.
+ */
++ (nullable WKWebView *)webViewForIdentifier:(long)identifier
+ withPluginRegistry:(id)registry;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m
new file mode 100644
index 000000000000..4e5d6efeb129
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m
@@ -0,0 +1,21 @@
+// 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 "FWFWebViewFlutterWKWebViewExternalAPI.h"
+#import "FWFInstanceManager.h"
+
+@implementation FWFWebViewFlutterWKWebViewExternalAPI
++ (nullable WKWebView *)webViewForIdentifier:(long)identifier
+ withPluginRegistry:(id)registry {
+ FWFInstanceManager *instanceManager =
+ (FWFInstanceManager *)[registry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"];
+
+ id instance = [instanceManager instanceForIdentifier:identifier];
+ if ([instance isKindOfClass:[WKWebView class]]) {
+ return instance;
+ }
+
+ return nil;
+}
+@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h
index dbcd876d15c9..b9ba942b4ed5 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h
@@ -17,5 +17,6 @@
#import
#import
#import
+#import
#import
#import
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
index 02b5b73b5971..8abd0c1afe8a 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
@@ -164,6 +164,16 @@ class WebKitWebViewController extends PlatformWebViewController {
WebKitWebViewControllerCreationParams get _webKitParams =>
params as WebKitWebViewControllerCreationParams;
+ /// Identifier used to retrieve the underlying native `WKWebView`.
+ ///
+ /// This is typically used by other plugins to retrieve the native `WKWebView`
+ /// from an `FWFInstanceManager`.
+ ///
+ /// See Objective-C method
+ /// `FLTWebViewFlutterPlugin:webViewForIdentifier:withPluginRegistry`.
+ int get webViewIdentifier =>
+ _webKitParams._instanceManager.getIdentifier(_webView)!;
+
@override
Future loadFile(String absoluteFilePath) {
return _webView.loadFileUrl(
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index 5c4df9922840..d1aaa7cf9203 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 3.0.5
+version: 3.1.0
environment:
sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
index 0360c13b052a..b7b729a97926 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
@@ -81,6 +81,7 @@ void main() {
return nonNullMockWebView;
},
),
+ instanceManager: instanceManager,
);
final WebKitWebViewController controller = WebKitWebViewController(
@@ -935,6 +936,25 @@ void main() {
expect(callbackProgress, 0);
});
+
+ test('webViewIdentifier', () {
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ final MockWKWebView mockWebView = MockWKWebView();
+ when(mockWebView.copy()).thenReturn(MockWKWebView());
+ instanceManager.addHostCreatedInstance(mockWebView, 0);
+
+ final WebKitWebViewController controller = createControllerWithMocks(
+ createMockWebView: (_, {dynamic observeValue}) => mockWebView,
+ instanceManager: instanceManager,
+ );
+
+ expect(
+ controller.webViewIdentifier,
+ instanceManager.getIdentifier(mockWebView),
+ );
+ });
});
group('WebKitJavaScriptChannelParams', () {