[webview_flutter_android] Adds a WebViewFlutterApi (#3324)

[webview_flutter_android] Adds a WebViewFlutterApi
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 6bd5861..5c20834 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 3.4.1
+
+* Fixes a potential bug where a `WebView` that was not added to the `InstanceManager` could be
+  returned by a `WebViewClient` or `WebChromeClient`.
+
 ## 3.4.0
 
 * Adds support to set text zoom of a page. See `AndroidWebViewController.setTextZoom`.
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java
index 6797859..cacf364 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java
@@ -57,18 +57,16 @@
   /**
    * Stores the FileChooserParams instance and notifies Dart to create a new FileChooserParams
    * instance that is attached to this one.
-   *
-   * @return the instanceId of the stored instance
    */
-  public long create(WebChromeClient.FileChooserParams instance, Reply<Void> callback) {
-    final long instanceId = instanceManager.addHostCreatedInstance(instance);
-    create(
-        instanceId,
-        instance.isCaptureEnabled(),
-        Arrays.asList(instance.getAcceptTypes()),
-        toFileChooserEnumData(instance.getMode()),
-        instance.getFilenameHint(),
-        callback);
-    return instanceId;
+  public void create(WebChromeClient.FileChooserParams instance, Reply<Void> callback) {
+    if (!instanceManager.containsInstance(instance)) {
+      create(
+          instanceManager.addHostCreatedInstance(instance),
+          instance.isCaptureEnabled(),
+          Arrays.asList(instance.getAcceptTypes()),
+          toFileChooserEnumData(instance.getMode()),
+          instance.getFilenameHint(),
+          callback);
+    }
   }
 }
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 6fa20fe..189b85c 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
@@ -1574,6 +1574,42 @@
       }
     }
   }
+  /**
+   * Flutter API for `WebView`.
+   *
+   * <p>This class may handle instantiating and adding Dart instances that are attached to a native
+   * instance or receiving callback methods from an overridden native class.
+   *
+   * <p>See https://developer.android.com/reference/android/webkit/WebView.
+   *
+   * <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
+   */
+  public static class WebViewFlutterApi {
+    private final BinaryMessenger binaryMessenger;
+
+    public WebViewFlutterApi(BinaryMessenger argBinaryMessenger) {
+      this.binaryMessenger = argBinaryMessenger;
+    }
+
+    public interface Reply<T> {
+      void reply(T reply);
+    }
+    /** The codec used by WebViewFlutterApi. */
+    static MessageCodec<Object> getCodec() {
+      return new StandardMessageCodec();
+    }
+    /** Create a new Dart instance and add it to the `InstanceManager`. */
+    public void create(@NonNull Long identifierArg, Reply<Void> callback) {
+      BasicMessageChannel<Object> channel =
+          new BasicMessageChannel<>(
+              binaryMessenger, "dev.flutter.pigeon.WebViewFlutterApi.create", getCodec());
+      channel.send(
+          new ArrayList<Object>(Collections.singletonList(identifierArg)),
+          channelReply -> {
+            callback.reply(null);
+          });
+    }
+  }
   /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
   public interface WebSettingsHostApi {
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
index 92f0e41..ece8e62 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
@@ -21,6 +21,7 @@
 public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi {
   private final BinaryMessenger binaryMessenger;
   private final InstanceManager instanceManager;
+  private final WebViewFlutterApiImpl webViewFlutterApi;
 
   /**
    * Creates a Flutter api that sends messages to Dart.
@@ -33,15 +34,16 @@
     super(binaryMessenger);
     this.binaryMessenger = binaryMessenger;
     this.instanceManager = instanceManager;
+    webViewFlutterApi = new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
   }
 
   /** Passes arguments from {@link WebChromeClient#onProgressChanged} to Dart. */
   public void onProgressChanged(
       WebChromeClient webChromeClient, WebView webView, Long progress, Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     super.onProgressChanged(
         getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback);
   }
@@ -53,17 +55,15 @@
       WebView webView,
       WebChromeClient.FileChooserParams fileChooserParams,
       Reply<List<String>> callback) {
-    Long paramsInstanceId = instanceManager.getIdentifierForStrongReference(fileChooserParams);
-    if (paramsInstanceId == null) {
-      final FileChooserParamsFlutterApiImpl flutterApi =
-          new FileChooserParamsFlutterApiImpl(binaryMessenger, instanceManager);
-      paramsInstanceId = flutterApi.create(fileChooserParams, reply -> {});
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    new FileChooserParamsFlutterApiImpl(binaryMessenger, instanceManager)
+        .create(fileChooserParams, reply -> {});
 
     onShowFileChooser(
         Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webChromeClient)),
         Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView)),
-        paramsInstanceId,
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(fileChooserParams)),
         callback);
   }
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
index 0dc0bbb..aad569d 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
@@ -15,6 +15,7 @@
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi;
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
  * Flutter Api implementation for {@link WebViewClient}.
@@ -22,7 +23,9 @@
  * <p>Passes arguments of callbacks methods from a {@link WebViewClient} to Dart.
  */
 public class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi {
+  private final BinaryMessenger binaryMessenger;
   private final InstanceManager instanceManager;
+  private final WebViewFlutterApiImpl webViewFlutterApi;
 
   @RequiresApi(api = Build.VERSION_CODES.M)
   static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData(
@@ -71,26 +74,28 @@
   public WebViewClientFlutterApiImpl(
       BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
     super(binaryMessenger);
+    this.binaryMessenger = binaryMessenger;
     this.instanceManager = instanceManager;
+    webViewFlutterApi = new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
   }
 
   /** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */
   public void onPageStarted(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
   /** Passes arguments from {@link WebViewClient#onPageFinished} to Dart. */
   public void onPageFinished(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
@@ -105,10 +110,10 @@
       WebResourceRequest request,
       WebResourceError error,
       Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     onReceivedRequestError(
         getIdentifierForClient(webViewClient),
         webViewIdentifier,
@@ -128,10 +133,10 @@
       WebResourceRequest request,
       WebResourceErrorCompat error,
       Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     onReceivedRequestError(
         getIdentifierForClient(webViewClient),
         webViewIdentifier,
@@ -151,10 +156,10 @@
       String descriptionArg,
       String failingUrlArg,
       Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     onReceivedError(
         getIdentifierForClient(webViewClient),
         webViewIdentifier,
@@ -174,10 +179,10 @@
       WebView webView,
       WebResourceRequest request,
       Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     requestLoading(
         getIdentifierForClient(webViewClient),
         webViewIdentifier,
@@ -190,10 +195,10 @@
    */
   public void urlLoading(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
-    if (webViewIdentifier == null) {
-      throw new IllegalStateException("Could not find identifier for WebView.");
-    }
+    webViewFlutterApi.create(webView, reply -> {});
+
+    final Long webViewIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
     urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterApiImpl.java
new file mode 100644
index 0000000..d468cf8
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterApiImpl.java
@@ -0,0 +1,59 @@
+// 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.NonNull;
+import androidx.annotation.VisibleForTesting;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;
+
+/**
+ * Flutter API implementation for `WebView`.
+ *
+ * <p>This class may handle adding native instances that are attached to a Dart instance or passing
+ * arguments of callbacks methods to a Dart instance.
+ */
+public class WebViewFlutterApiImpl {
+  // To ease adding additional methods, this value is added prematurely.
+  @SuppressWarnings({"unused", "FieldCanBeLocal"})
+  private final BinaryMessenger binaryMessenger;
+
+  private final InstanceManager instanceManager;
+  private WebViewFlutterApi api;
+
+  /**
+   * Constructs a {@link WebViewFlutterApiImpl}.
+   *
+   * @param binaryMessenger used to communicate with Dart over asynchronous messages
+   * @param instanceManager maintains instances stored to communicate with attached Dart objects
+   */
+  public WebViewFlutterApiImpl(
+      @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
+    this.binaryMessenger = binaryMessenger;
+    this.instanceManager = instanceManager;
+    api = new WebViewFlutterApi(binaryMessenger);
+  }
+
+  /**
+   * Stores the `WebView` instance and notifies Dart to create and store a new `WebView` instance
+   * that is attached to this one. If `instance` has already been added, this method does nothing.
+   */
+  public void create(@NonNull WebView instance, @NonNull WebViewFlutterApi.Reply<Void> callback) {
+    if (!instanceManager.containsInstance(instance)) {
+      api.create(instanceManager.addHostCreatedInstance(instance), callback);
+    }
+  }
+
+  /**
+   * Sets the Flutter API used to send messages to Dart.
+   *
+   * <p>This is only visible for testing.
+   */
+  @VisibleForTesting
+  void setApi(@NonNull WebViewFlutterApi api) {
+    this.api = api;
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
index e821537..be301fa 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
@@ -47,8 +47,6 @@
   public void setUp() {
     instanceManager = InstanceManager.open(identifier -> {});
 
-    instanceManager.addDartCreatedInstance(mockWebView, 0L);
-
     final WebChromeClientCreator webChromeClientCreator =
         new WebChromeClientCreator() {
           @Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java
index 3267291..b1cf110 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java
@@ -43,8 +43,6 @@
   public void setUp() {
     instanceManager = InstanceManager.open(identifier -> {});
 
-    instanceManager.addDartCreatedInstance(mockWebView, 0L);
-
     final WebViewClientCreator webViewClientCreator =
         new WebViewClientCreator() {
           @Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java
index 1721ccd..4a36184 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -18,6 +19,7 @@
 import android.webkit.WebChromeClient;
 import android.webkit.WebViewClient;
 import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;
 import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView;
 import java.util.HashMap;
 import java.util.Objects;
@@ -314,4 +316,23 @@
 
     assertTrue(destroyCalled[0]);
   }
+
+  @Test
+  public void flutterApiCreate() {
+    final InstanceManager instanceManager = InstanceManager.open(identifier -> {});
+
+    final WebViewFlutterApiImpl flutterApiImpl =
+        new WebViewFlutterApiImpl(mockBinaryMessenger, instanceManager);
+
+    final WebViewFlutterApi mockFlutterApi = mock(WebViewFlutterApi.class);
+    flutterApiImpl.setApi(mockFlutterApi);
+
+    flutterApiImpl.create(mockWebView, reply -> {});
+
+    final long instanceIdentifier =
+        Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockWebView));
+    verify(mockFlutterApi).create(eq(instanceIdentifier), any());
+
+    instanceManager.close();
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
index 6c9ba8e..ebfb0cc 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
@@ -944,6 +944,43 @@
   }
 }
 
+/// Flutter API for `WebView`.
+///
+/// This class may handle instantiating and adding Dart instances that are
+/// attached to a native instance or receiving callback methods from an
+/// overridden native class.
+///
+/// See https://developer.android.com/reference/android/webkit/WebView.
+abstract class WebViewFlutterApi {
+  static const MessageCodec<Object?> codec = StandardMessageCodec();
+
+  /// Create a new Dart instance and add it to the `InstanceManager`.
+  void create(int identifier);
+
+  static void setup(WebViewFlutterApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebViewFlutterApi.create', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebViewFlutterApi.create was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_identifier = (args[0] as int?);
+          assert(arg_identifier != null,
+              'Argument for dev.flutter.pigeon.WebViewFlutterApi.create was null, expected non-null int.');
+          api.create(arg_identifier!);
+          return;
+        });
+      }
+    }
+  }
+}
+
 class WebSettingsHostApi {
   /// Constructor for [WebSettingsHostApi].  The [binaryMessenger] named argument is
   /// available for dependency injection.  If it is left null, the default
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 0db0f04..ce34673 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
@@ -346,6 +346,33 @@
   }
 }
 
+/// Flutter API implementation for [WebView].
+///
+/// This class may handle instantiating and adding Dart instances that are
+/// attached to a native instance or receiving callback methods from an
+/// overridden native class.
+class WebViewFlutterApiImpl implements WebViewFlutterApi {
+  /// Constructs a [WebViewFlutterApiImpl].
+  WebViewFlutterApiImpl({
+    this.binaryMessenger,
+    InstanceManager? instanceManager,
+  }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
+
+  /// 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;
+
+  @override
+  void create(int identifier) {
+    instanceManager.addHostCreatedInstance(WebView.detached(), identifier);
+  }
+}
+
 /// Host api implementation for [WebSettings].
 class WebSettingsHostApiImpl extends WebSettingsHostApi {
   /// Constructs a [WebSettingsHostApiImpl].
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 b8c22e5..20bc915 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -191,6 +191,19 @@
   void setBackgroundColor(int instanceId, int color);
 }
 
+/// Flutter API for `WebView`.
+///
+/// This class may handle instantiating and adding Dart instances that are
+/// attached to a native instance or receiving callback methods from an
+/// overridden native class.
+///
+/// See https://developer.android.com/reference/android/webkit/WebView.
+@FlutterApi()
+abstract class WebViewFlutterApi {
+  /// Create a new Dart instance and add it to the `InstanceManager`.
+  void create(int identifier);
+}
+
 @HostApi(dartHostTestHandler: 'TestWebSettingsHostApi')
 abstract class WebSettingsHostApi {
   void create(int instanceId, int webViewInstanceId);
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 3217263..d38b866 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/packages/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.4.0
+version: 3.4.1
 
 environment:
   sdk: ">=2.17.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 79b5b3f..a618472 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
@@ -364,6 +364,24 @@
         ));
       });
 
+      test('FlutterAPI create', () {
+        final InstanceManager instanceManager = InstanceManager(
+          onWeakReferenceRemoved: (_) {},
+        );
+
+        final WebViewFlutterApiImpl api = WebViewFlutterApiImpl(
+          instanceManager: instanceManager,
+        );
+
+        const int instanceIdentifier = 0;
+        api.create(instanceIdentifier);
+
+        expect(
+          instanceManager.getInstanceWithWeakReference(instanceIdentifier),
+          isA<WebView>(),
+        );
+      });
+
       test('copy', () {
         expect(webView.copy(), isA<WebView>());
       });