Skip to content

Commit

Permalink
[webview_flutter_android][webview_flutter_wkwebview] Adds support to …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
bparrishMines committed Feb 14, 2023
1 parent 9dc38ac commit 6995c7c
Show file tree
Hide file tree
Showing 19 changed files with 311 additions and 14 deletions.
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.3.0

* Adds support to access native `WebView`.

## 3.2.4

* Renames Pigeon output files.
Expand Down
16 changes: 16 additions & 0 deletions packages/webview_flutter/webview_flutter_android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>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`.
*
* <p>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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* <p>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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> loadFile(
String absoluteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.1.0

* Adds support to access native `WKWebView`.

## 3.0.5

* Renames Pigeon output files.
Expand Down
18 changes: 18 additions & 0 deletions packages/webview_flutter/webview_flutter_wkwebview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -76,6 +77,7 @@
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = "<group>"; };
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = "<group>"; };
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = "<group>"; };
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -145,6 +147,7 @@
isa = PBXGroup;
children = (
68BDCAED23C3F7CB00D9C032 /* Info.plist */,
8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */,
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */,
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */,
8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */,
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -415,6 +420,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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)";
Expand All @@ -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)";
Expand Down
Loading

0 comments on commit 6995c7c

Please sign in to comment.