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

Commit d2fba38

Browse files
[webview_flutter_android][webview_flutter_wkwebview] Adds support to retrieve native WebView (#7071)
* implementation of the webViewIdentifier field * change to external classes * formatting * update readmes * iOS * improve * hmmmm * add note about not using other apis * project changes * add external api tests to project * ordering * fix docs and use id
1 parent 66d5724 commit d2fba38

19 files changed

+311
-14
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.3.0
2+
3+
* Adds support to access native `WebView`.
4+
15
## 3.2.4
26

37
* Renames Pigeon output files.

packages/webview_flutter/webview_flutter_android/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ This can be configured for versions >=23 with
3232
`AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features
3333
for more details on setting platform-specific features in the main plugin.
3434

35+
### External Native API
36+
37+
The plugin also provides a native API accessible by the native code of Android applications or
38+
packages. This API follows the convention of breaking changes of the Dart API, which means that any
39+
changes to the class that are not backwards compatible will only be made with a major version change
40+
of the plugin. Native code other than this external API does not follow breaking change conventions,
41+
so app or plugin clients should not use any other native APIs.
42+
43+
The API can be accessed by importing the native class `WebViewFlutterAndroidExternalApi`:
44+
45+
Java:
46+
47+
```java
48+
import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi;
49+
```
50+
3551
## Contributing
3652

3753
This package uses [pigeon][3] to generate the communication layer between Flutter and the host
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.webkit.WebView;
8+
import androidx.annotation.Nullable;
9+
import io.flutter.embedding.engine.FlutterEngine;
10+
11+
/**
12+
* App and package facing native API provided by the `webview_flutter_android` plugin.
13+
*
14+
* <p>This class follows the convention of breaking changes of the Dart API, which means that any
15+
* changes to the class that are not backwards compatible will only be made with a major version
16+
* change of the plugin.
17+
*
18+
* <p>Native code other than this external API does not follow breaking change conventions, so app
19+
* or plugin clients should not use any other native APIs.
20+
*/
21+
@SuppressWarnings("unused")
22+
public interface WebViewFlutterAndroidExternalApi {
23+
/**
24+
* Retrieves the {@link WebView} that is associated with `identifier`.
25+
*
26+
* <p>See the Dart method `AndroidWebViewController.webViewIdentifier` to get the identifier of an
27+
* underlying `WebView`.
28+
*
29+
* @param engine the execution environment the {@link WebViewFlutterPlugin} should belong to. If
30+
* the engine doesn't contain an attached instance of {@link WebViewFlutterPlugin}, this
31+
* method returns null.
32+
* @param identifier the associated identifier of the `WebView`.
33+
* @return the `WebView` associated with `identifier` or null if a `WebView` instance associated
34+
* with `identifier` could not be found.
35+
*/
36+
@Nullable
37+
static WebView getWebView(FlutterEngine engine, long identifier) {
38+
final WebViewFlutterPlugin webViewPlugin =
39+
(WebViewFlutterPlugin) engine.getPlugins().get(WebViewFlutterPlugin.class);
40+
41+
if (webViewPlugin != null && webViewPlugin.getInstanceManager() != null) {
42+
final Object instance = webViewPlugin.getInstanceManager().getInstance(identifier);
43+
if (instance instanceof WebView) {
44+
return (WebView) instance;
45+
}
46+
}
47+
48+
return null;
49+
}
50+
}

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* <p>Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead.
3434
*/
3535
public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
36-
private InstanceManager instanceManager;
36+
@Nullable private InstanceManager instanceManager;
3737

3838
private FlutterPluginBinding pluginBinding;
3939
private WebViewHostApiImpl webViewHostApi;
@@ -148,7 +148,10 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
148148

149149
@Override
150150
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
151-
instanceManager.close();
151+
if (instanceManager != null) {
152+
instanceManager.close();
153+
instanceManager = null;
154+
}
152155
}
153156

154157
@Override
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
package io.flutter.plugins.webviewflutter;
66

7+
import static org.junit.Assert.assertEquals;
78
import static org.junit.Assert.assertNotNull;
9+
import static org.mockito.Mockito.mock;
810
import static org.mockito.Mockito.when;
911

1012
import android.content.Context;
13+
import android.webkit.WebView;
14+
import io.flutter.embedding.engine.FlutterEngine;
1115
import io.flutter.embedding.engine.plugins.FlutterPlugin;
16+
import io.flutter.embedding.engine.plugins.PluginRegistry;
1217
import io.flutter.plugin.common.BinaryMessenger;
1318
import io.flutter.plugin.platform.PlatformViewRegistry;
1419
import org.junit.Rule;
@@ -17,7 +22,7 @@
1722
import org.mockito.junit.MockitoJUnit;
1823
import org.mockito.junit.MockitoRule;
1924

20-
public class WebViewFlutterPluginTest {
25+
public class WebViewFlutterAndroidExternalApiTest {
2126
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
2227

2328
@Mock Context mockContext;
@@ -29,7 +34,7 @@ public class WebViewFlutterPluginTest {
2934
@Mock FlutterPlugin.FlutterPluginBinding mockPluginBinding;
3035

3136
@Test
32-
public void getInstanceManagerAfterOnAttachedToEngine() {
37+
public void getWebView() {
3338
final WebViewFlutterPlugin webViewFlutterPlugin = new WebViewFlutterPlugin();
3439

3540
when(mockPluginBinding.getApplicationContext()).thenReturn(mockContext);
@@ -38,7 +43,19 @@ public void getInstanceManagerAfterOnAttachedToEngine() {
3843

3944
webViewFlutterPlugin.onAttachedToEngine(mockPluginBinding);
4045

41-
assertNotNull(webViewFlutterPlugin.getInstanceManager());
46+
final InstanceManager instanceManager = webViewFlutterPlugin.getInstanceManager();
47+
assertNotNull(instanceManager);
48+
49+
final WebView mockWebView = mock(WebView.class);
50+
instanceManager.addDartCreatedInstance(mockWebView, 0);
51+
52+
final PluginRegistry mockPluginRegistry = mock(PluginRegistry.class);
53+
when(mockPluginRegistry.get(WebViewFlutterPlugin.class)).thenReturn(webViewFlutterPlugin);
54+
55+
final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class);
56+
when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry);
57+
58+
assertEquals(WebViewFlutterAndroidExternalApi.getWebView(mockFlutterEngine, 0), mockWebView);
4259

4360
webViewFlutterPlugin.onDetachedFromEngine(mockPluginBinding);
4461
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ class AndroidWebViewController extends PlatformWebViewController {
144144
return webViewProxy.setWebContentsDebuggingEnabled(enabled);
145145
}
146146

147+
/// Identifier used to retrieve the underlying native `WKWebView`.
148+
///
149+
/// This is typically used by other plugins to retrieve the native `WebView`
150+
/// from an `InstanceManager`.
151+
///
152+
/// See Java method `WebViewFlutterPlugin.getWebView`.
153+
int get webViewIdentifier =>
154+
// ignore: invalid_use_of_visible_for_testing_member
155+
android_webview.WebView.api.instanceManager.getIdentifier(_webView)!;
156+
147157
@override
148158
Future<void> loadFile(
149159
String absoluteFilePath,

packages/webview_flutter/webview_flutter_android/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter_android
22
description: A Flutter plugin that provides a WebView widget on Android.
33
repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 3.2.4
5+
version: 3.3.0
66

77
environment:
88
sdk: ">=2.17.0 <3.0.0"

packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart

+24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:mockito/mockito.dart';
1414
import 'package:webview_flutter_android/src/android_proxy.dart';
1515
import 'package:webview_flutter_android/src/android_webview.dart'
1616
as android_webview;
17+
import 'package:webview_flutter_android/src/android_webview_api_impls.dart';
1718
import 'package:webview_flutter_android/src/instance_manager.dart';
1819
import 'package:webview_flutter_android/src/platform_views_service_proxy.dart';
1920
import 'package:webview_flutter_android/webview_flutter_android.dart';
@@ -884,6 +885,29 @@ void main() {
884885
verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1);
885886
});
886887

888+
test('webViewIdentifier', () {
889+
final MockWebView mockWebView = MockWebView();
890+
final InstanceManager instanceManager = InstanceManager(
891+
onWeakReferenceRemoved: (_) {},
892+
);
893+
instanceManager.addHostCreatedInstance(mockWebView, 0);
894+
895+
android_webview.WebView.api = WebViewHostApiImpl(
896+
instanceManager: instanceManager,
897+
);
898+
899+
final AndroidWebViewController controller = createControllerWithMocks(
900+
mockWebView: mockWebView,
901+
);
902+
903+
expect(
904+
controller.webViewIdentifier,
905+
0,
906+
);
907+
908+
android_webview.WebView.api = WebViewHostApiImpl();
909+
});
910+
887911
group('AndroidWebViewWidget', () {
888912
testWidgets('Builds Android view using supplied parameters',
889913
(WidgetTester tester) async {

packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.1.0
2+
3+
* Adds support to access native `WKWebView`.
4+
15
## 3.0.5
26

37
* Renames Pigeon output files.

packages/webview_flutter/webview_flutter_wkwebview/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ The Apple WKWebView implementation of [`webview_flutter`][1].
77
This package is [endorsed][2], which means you can simply use `webview_flutter`
88
normally. This package will be automatically included in your app when you do.
99

10+
### External Native API
11+
12+
The plugin also provides a native API accessible by the native code of iOS applications or packages.
13+
This API follows the convention of breaking changes of the Dart API, which means that any changes to
14+
the class that are not backwards compatible will only be made with a major version change of the
15+
plugin. Native code other than this external API does not follow breaking change conventions, so
16+
app or plugin clients should not use any other native APIs.
17+
18+
The API can be accessed by importing the native plugin `webview_flutter_wkwebview`:
19+
20+
Objective-C:
21+
22+
```objectivec
23+
@import webview_flutter_wkwebview;
24+
```
25+
26+
Then you will have access to the native class `FWFWebViewFlutterWKWebViewExternalAPI`.
27+
1028
## Contributing
1129

1230
This package uses [pigeon][3] to generate the communication layer between Flutter and the host

packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj

+36-7
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 50;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXBuildFile section */
1010
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1111
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12+
8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; };
1213
8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; };
1314
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; };
1415
8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */; };
@@ -76,6 +77,7 @@
7677
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7778
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7879
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
80+
8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = "<group>"; };
7981
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = "<group>"; };
8082
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = "<group>"; };
8183
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = "<group>"; };
@@ -145,6 +147,7 @@
145147
isa = PBXGroup;
146148
children = (
147149
68BDCAED23C3F7CB00D9C032 /* Info.plist */,
150+
8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */,
148151
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */,
149152
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */,
150153
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */,
@@ -379,10 +382,12 @@
379382
/* Begin PBXShellScriptBuildPhase section */
380383
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
381384
isa = PBXShellScriptBuildPhase;
385+
alwaysOutOfDate = 1;
382386
buildActionMask = 2147483647;
383387
files = (
384388
);
385389
inputPaths = (
390+
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
386391
);
387392
name = "Thin Binary";
388393
outputPaths = (
@@ -415,6 +420,7 @@
415420
};
416421
9740EEB61CF901F6004384FC /* Run Script */ = {
417422
isa = PBXShellScriptBuildPhase;
423+
alwaysOutOfDate = 1;
418424
buildActionMask = 2147483647;
419425
files = (
420426
);
@@ -463,6 +469,7 @@
463469
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */,
464470
8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */,
465471
8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */,
472+
8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */,
466473
8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */,
467474
8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */,
468475
8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */,
@@ -533,7 +540,11 @@
533540
BUNDLE_LOADER = "$(TEST_HOST)";
534541
CODE_SIGN_STYLE = Automatic;
535542
INFOPLIST_FILE = RunnerTests/Info.plist;
536-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
543+
LD_RUNPATH_SEARCH_PATHS = (
544+
"$(inherited)",
545+
"@executable_path/Frameworks",
546+
"@loader_path/Frameworks",
547+
);
537548
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
538549
PRODUCT_NAME = "$(TARGET_NAME)";
539550
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
@@ -547,7 +558,11 @@
547558
BUNDLE_LOADER = "$(TEST_HOST)";
548559
CODE_SIGN_STYLE = Automatic;
549560
INFOPLIST_FILE = RunnerTests/Info.plist;
550-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
561+
LD_RUNPATH_SEARCH_PATHS = (
562+
"$(inherited)",
563+
"@executable_path/Frameworks",
564+
"@loader_path/Frameworks",
565+
);
551566
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
552567
PRODUCT_NAME = "$(TARGET_NAME)";
553568
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
@@ -672,7 +687,10 @@
672687
"$(PROJECT_DIR)/Flutter",
673688
);
674689
INFOPLIST_FILE = Runner/Info.plist;
675-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
690+
LD_RUNPATH_SEARCH_PATHS = (
691+
"$(inherited)",
692+
"@executable_path/Frameworks",
693+
);
676694
LIBRARY_SEARCH_PATHS = (
677695
"$(inherited)",
678696
"$(PROJECT_DIR)/Flutter",
@@ -695,7 +713,10 @@
695713
"$(PROJECT_DIR)/Flutter",
696714
);
697715
INFOPLIST_FILE = Runner/Info.plist;
698-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
716+
LD_RUNPATH_SEARCH_PATHS = (
717+
"$(inherited)",
718+
"@executable_path/Frameworks",
719+
);
699720
LIBRARY_SEARCH_PATHS = (
700721
"$(inherited)",
701722
"$(PROJECT_DIR)/Flutter",
@@ -711,7 +732,11 @@
711732
buildSettings = {
712733
CODE_SIGN_STYLE = Automatic;
713734
INFOPLIST_FILE = RunnerUITests/Info.plist;
714-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
735+
LD_RUNPATH_SEARCH_PATHS = (
736+
"$(inherited)",
737+
"@executable_path/Frameworks",
738+
"@loader_path/Frameworks",
739+
);
715740
MTL_FAST_MATH = YES;
716741
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
717742
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -724,7 +749,11 @@
724749
buildSettings = {
725750
CODE_SIGN_STYLE = Automatic;
726751
INFOPLIST_FILE = RunnerUITests/Info.plist;
727-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
752+
LD_RUNPATH_SEARCH_PATHS = (
753+
"$(inherited)",
754+
"@executable_path/Frameworks",
755+
"@loader_path/Frameworks",
756+
);
728757
MTL_FAST_MATH = YES;
729758
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
730759
PRODUCT_NAME = "$(TARGET_NAME)";

0 commit comments

Comments
 (0)