[webview_flutter] Implement loadRequest in Android package. (#4563)

Implements the `loadRequest` method added in #4450 for the Android package.

Related issue:
- flutter/flutter#27730
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index e8d9e63..ce73ab2 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.7.0
+
+* Adds support for the `loadRequest` method from the platform interface.
+
 ## 2.6.0
 
 * Adds implementation of the `loadFlutterAsset` method from the platform interface.
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 0123790..0e6776e 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
@@ -186,6 +186,8 @@
 
     void loadUrl(Long instanceId, String url, Map<String, String> headers);
 
+    void postUrl(Long instanceId, String url, byte[] data);
+
     String getUrl(Long instanceId);
 
     Boolean canGoBack(Long instanceId);
@@ -413,6 +415,39 @@
       {
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.postUrl", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  Number instanceIdArg = (Number) args.get(0);
+                  if (instanceIdArg == null) {
+                    throw new NullPointerException("instanceIdArg unexpectedly null.");
+                  }
+                  String urlArg = (String) args.get(1);
+                  if (urlArg == null) {
+                    throw new NullPointerException("urlArg unexpectedly null.");
+                  }
+                  byte[] dataArg = (byte[]) args.get(2);
+                  if (dataArg == null) {
+                    throw new NullPointerException("dataArg unexpectedly null.");
+                  }
+                  api.postUrl(instanceIdArg.longValue(), urlArg, dataArg);
+                  wrapped.put("result", null);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
                 binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.getUrl", getCodec());
         if (api != null) {
           channel.setMessageHandler(
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
index 78b06aa..0f31613 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
@@ -383,6 +383,12 @@
   }
 
   @Override
+  public void postUrl(Long instanceId, String url, byte[] data) {
+    final WebView webView = (WebView) instanceManager.getInstance(instanceId);
+    webView.postUrl(url, data);
+  }
+
+  @Override
   public String getUrl(Long instanceId) {
     final WebView webView = (WebView) instanceManager.getInstance(instanceId);
     final String result = webView.getUrl();
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 dc58b9b..2312b76 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
@@ -209,6 +209,12 @@
   }
 
   @Test
+  public void postUrl() {
+    testHostApiImpl.postUrl(0L, "https://www.google.com", new byte[] {0x01, 0x02});
+    verify(mockWebView).postUrl("https://www.google.com", new byte[] {0x01, 0x02});
+  }
+
+  @Test
   public void getUrl() {
     when(mockWebView.getUrl()).thenReturn("https://www.google.com");
     assertEquals(testHostApiImpl.getUrl(0L), "https://www.google.com");
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
index 3bd283c..eb3162a 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:typed_data';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_driver/driver_extension.dart';
@@ -189,6 +190,7 @@
   loadLocalFile,
   loadHtmlString,
   transparentBackground,
+  doPostRequest,
 }
 
 class _SampleMenu extends StatelessWidget {
@@ -239,6 +241,9 @@
               case _MenuOptions.transparentBackground:
                 _onTransparentBackground(controller.data!, context);
                 break;
+              case _MenuOptions.doPostRequest:
+                _onDoPostRequest(controller.data!, context);
+                break;
             }
           },
           itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -288,6 +293,10 @@
               value: _MenuOptions.transparentBackground,
               child: Text('Transparent background example'),
             ),
+            const PopupMenuItem<_MenuOptions>(
+              value: _MenuOptions.doPostRequest,
+              child: Text('Post Request'),
+            ),
           ],
         );
       },
@@ -382,6 +391,16 @@
     await controller.loadHtmlString(kExamplePage);
   }
 
+  Future<void> _onDoPostRequest(
+      WebViewController controller, BuildContext context) async {
+    final WebViewRequest request = WebViewRequest(
+      uri: Uri.parse('https://httpbin.org/post'),
+      method: WebViewRequestMethod.post,
+      body: Uint8List.fromList('Test Body'.codeUnits),
+    );
+    await controller.loadRequest(request);
+  }
+
   Widget _getCookieList(String cookies) {
     if (cookies == null || cookies == '""') {
       return Container();
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
index b32deab..adaf7fb 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
@@ -418,6 +418,11 @@
     return _webViewPlatformController.loadUrl(url, headers);
   }
 
+  /// Loads a page by making the specified request.
+  Future<void> loadRequest(WebViewRequest request) async {
+    return _webViewPlatformController.loadRequest(request);
+  }
+
   /// Accessor to the current URL that the WebView is displaying.
   ///
   /// If [WebView.initialUrl] was never specified, returns `null`.
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index a7561ce..d027016 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:typed_data';
 import 'dart:ui';
 
 import 'package:flutter/foundation.dart';
@@ -169,6 +170,13 @@
     return api.loadUrlFromInstance(this, url, headers);
   }
 
+  /// Loads the URL with postData using "POST" method into this WebView.
+  ///
+  /// If url is not a network URL, it will be loaded with [loadUrl] instead, ignoring the postData param.
+  Future<void> postUrl(String url, Uint8List data) {
+    return api.postUrlFromInstance(this, url, data);
+  }
+
   /// Gets the URL for the current page.
   ///
   /// This is not always the same as the URL passed to
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
index f936856..e0a1db3 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -220,6 +220,33 @@
     }
   }
 
+  Future<void> postUrl(
+      int arg_instanceId, String arg_url, Uint8List arg_data) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.WebViewHostApi.postUrl', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(<Object>[arg_instanceId, arg_url, arg_data])
+            as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return;
+    }
+  }
+
   Future<String> getUrl(int arg_instanceId) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.WebViewHostApi.getUrl', codec,
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 51efdaa..1db5ed4 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
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:typed_data';
+
 import 'package:flutter/services.dart';
 
 import 'android_webview.dart';
@@ -153,6 +155,15 @@
   }
 
   /// Helper method to convert instances ids to objects.
+  Future<void> postUrlFromInstance(
+    WebView instance,
+    String url,
+    Uint8List data,
+  ) {
+    return postUrl(instanceManager.getInstanceId(instance)!, url, data);
+  }
+
+  /// Helper method to convert instances ids to objects.
   Future<String> getUrlFromInstance(WebView instance) {
     return getUrl(instanceManager.getInstanceId(instance)!);
   }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index 25f9874..fc295be 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:typed_data';
 
 import 'package:flutter/widgets.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
@@ -210,6 +211,28 @@
     return webView.loadUrl(url, headers ?? <String, String>{});
   }
 
+  /// When making a POST request, headers are ignored. As a workaround, make
+  /// the request manually and load the response data using [loadHTMLString].
+  @override
+  Future<void> loadRequest(
+    WebViewRequest request,
+  ) async {
+    if (!request.uri.hasScheme) {
+      throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
+    }
+    switch (request.method) {
+      case WebViewRequestMethod.get:
+        return webView.loadUrl(request.uri.toString(), request.headers);
+      case WebViewRequestMethod.post:
+        return webView.postUrl(
+            request.uri.toString(), request.body ?? Uint8List(0));
+      default:
+        throw UnimplementedError(
+          'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.',
+        );
+    }
+  }
+
   @override
   Future<String?> currentUrl() => webView.getUrl();
 
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 78672ea..4907dad 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -46,6 +46,12 @@
     Map<String, String> headers,
   );
 
+  void postUrl(
+    int instanceId,
+    String url,
+    Uint8List data,
+  );
+
   String getUrl(int instanceId);
 
   bool canGoBack(int instanceId);
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index bbe9ee1..08bcbb5 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/plugins/tree/master/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.6.0
+version: 2.7.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
index 90c1474..1e47c79 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
@@ -27,6 +27,7 @@
   void loadDataWithBaseUrl(int instanceId, String baseUrl, String data,
       String mimeType, String encoding, String historyUrl);
   void loadUrl(int instanceId, String url, Map<String?, String?> headers);
+  void postUrl(int instanceId, String url, Uint8List data);
   String getUrl(int instanceId);
   bool canGoBack(int instanceId);
   bool canGoForward(int instanceId);
@@ -182,6 +183,31 @@
     }
     {
       final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebViewHostApi.postUrl', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_instanceId = (args[0] as int?);
+          assert(arg_instanceId != null,
+              'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null int.');
+          final String? arg_url = (args[1] as String?);
+          assert(arg_url != null,
+              'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null String.');
+          final Uint8List? arg_data = (args[2] as Uint8List?);
+          assert(arg_data != null,
+              'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null Uint8List.');
+          api.postUrl(arg_instanceId!, arg_url!, arg_data!);
+          return <Object?, Object?>{};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
           'dev.flutter.pigeon.WebViewHostApi.getUrl', codec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index a08019e..8bb5694 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -1,9 +1,14 @@
+// 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.
+
 // Mocks generated by Mockito 5.0.16 from annotations
 // in webview_flutter_android/test/android_webview_test.dart.
 // Do not manually edit this file.
 
-import 'dart:async' as _i4;
-import 'dart:ui' as _i5;
+import 'dart:async' as _i5;
+import 'dart:typed_data' as _i4;
+import 'dart:ui' as _i6;
 
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:webview_flutter_android/src/android_webview.dart' as _i2;
@@ -241,6 +246,10 @@
           Invocation.method(#loadUrl, [instanceId, url, headers]),
           returnValueForMissingStub: null);
   @override
+  void postUrl(int? instanceId, String? url, _i4.Uint8List? data) =>
+      super.noSuchMethod(Invocation.method(#postUrl, [instanceId, url, data]),
+          returnValueForMissingStub: null);
+  @override
   String getUrl(int? instanceId) =>
       (super.noSuchMethod(Invocation.method(#getUrl, [instanceId]),
           returnValue: '') as String);
@@ -270,12 +279,12 @@
           Invocation.method(#clearCache, [instanceId, includeDiskFiles]),
           returnValueForMissingStub: null);
   @override
-  _i4.Future<String> evaluateJavascript(
+  _i5.Future<String> evaluateJavascript(
           int? instanceId, String? javascriptString) =>
       (super.noSuchMethod(
           Invocation.method(
               #evaluateJavascript, [instanceId, javascriptString]),
-          returnValue: Future<String>.value('')) as _i4.Future<String>);
+          returnValue: Future<String>.value('')) as _i5.Future<String>);
   @override
   String getTitle(int? instanceId) =>
       (super.noSuchMethod(Invocation.method(#getTitle, [instanceId]),
@@ -394,15 +403,15 @@
       (super.noSuchMethod(Invocation.getter(#settings),
           returnValue: _FakeWebSettings_0()) as _i2.WebSettings);
   @override
-  _i4.Future<void> loadData(
+  _i5.Future<void> loadData(
           {String? data, String? mimeType, String? encoding}) =>
       (super.noSuchMethod(
           Invocation.method(#loadData, [],
               {#data: data, #mimeType: mimeType, #encoding: encoding}),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> loadDataWithBaseUrl(
+  _i5.Future<void> loadDataWithBaseUrl(
           {String? baseUrl,
           String? data,
           String? mimeType,
@@ -417,109 +426,114 @@
             #historyUrl: historyUrl
           }),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> loadUrl(String? url, Map<String, String>? headers) =>
+  _i5.Future<void> loadUrl(String? url, Map<String, String>? headers) =>
       (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<String?> getUrl() =>
+  _i5.Future<void> postUrl(String? url, _i4.Uint8List? data) =>
+      (super.noSuchMethod(Invocation.method(#postUrl, [url, data]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+  @override
+  _i5.Future<String?> getUrl() =>
       (super.noSuchMethod(Invocation.method(#getUrl, []),
-          returnValue: Future<String?>.value()) as _i4.Future<String?>);
+          returnValue: Future<String?>.value()) as _i5.Future<String?>);
   @override
-  _i4.Future<bool> canGoBack() =>
+  _i5.Future<bool> canGoBack() =>
       (super.noSuchMethod(Invocation.method(#canGoBack, []),
-          returnValue: Future<bool>.value(false)) as _i4.Future<bool>);
+          returnValue: Future<bool>.value(false)) as _i5.Future<bool>);
   @override
-  _i4.Future<bool> canGoForward() =>
+  _i5.Future<bool> canGoForward() =>
       (super.noSuchMethod(Invocation.method(#canGoForward, []),
-          returnValue: Future<bool>.value(false)) as _i4.Future<bool>);
+          returnValue: Future<bool>.value(false)) as _i5.Future<bool>);
   @override
-  _i4.Future<void> goBack() =>
+  _i5.Future<void> goBack() =>
       (super.noSuchMethod(Invocation.method(#goBack, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> goForward() =>
+  _i5.Future<void> goForward() =>
       (super.noSuchMethod(Invocation.method(#goForward, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> reload() =>
+  _i5.Future<void> reload() =>
       (super.noSuchMethod(Invocation.method(#reload, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> clearCache(bool? includeDiskFiles) =>
+  _i5.Future<void> clearCache(bool? includeDiskFiles) =>
       (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<String?> evaluateJavascript(String? javascriptString) => (super
+  _i5.Future<String?> evaluateJavascript(String? javascriptString) => (super
       .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]),
-          returnValue: Future<String?>.value()) as _i4.Future<String?>);
+          returnValue: Future<String?>.value()) as _i5.Future<String?>);
   @override
-  _i4.Future<String?> getTitle() =>
+  _i5.Future<String?> getTitle() =>
       (super.noSuchMethod(Invocation.method(#getTitle, []),
-          returnValue: Future<String?>.value()) as _i4.Future<String?>);
+          returnValue: Future<String?>.value()) as _i5.Future<String?>);
   @override
-  _i4.Future<void> scrollTo(int? x, int? y) =>
+  _i5.Future<void> scrollTo(int? x, int? y) =>
       (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> scrollBy(int? x, int? y) =>
+  _i5.Future<void> scrollBy(int? x, int? y) =>
       (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<int> getScrollX() =>
+  _i5.Future<int> getScrollX() =>
       (super.noSuchMethod(Invocation.method(#getScrollX, []),
-          returnValue: Future<int>.value(0)) as _i4.Future<int>);
+          returnValue: Future<int>.value(0)) as _i5.Future<int>);
   @override
-  _i4.Future<int> getScrollY() =>
+  _i5.Future<int> getScrollY() =>
       (super.noSuchMethod(Invocation.method(#getScrollY, []),
-          returnValue: Future<int>.value(0)) as _i4.Future<int>);
+          returnValue: Future<int>.value(0)) as _i5.Future<int>);
   @override
-  _i4.Future<void> setWebViewClient(_i2.WebViewClient? webViewClient) =>
+  _i5.Future<void> setWebViewClient(_i2.WebViewClient? webViewClient) =>
       (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> addJavaScriptChannel(
+  _i5.Future<void> addJavaScriptChannel(
           _i2.JavaScriptChannel? javaScriptChannel) =>
       (super.noSuchMethod(
           Invocation.method(#addJavaScriptChannel, [javaScriptChannel]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> removeJavaScriptChannel(
+  _i5.Future<void> removeJavaScriptChannel(
           _i2.JavaScriptChannel? javaScriptChannel) =>
       (super.noSuchMethod(
           Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> setDownloadListener(_i2.DownloadListener? listener) =>
+  _i5.Future<void> setDownloadListener(_i2.DownloadListener? listener) =>
       (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> setWebChromeClient(_i2.WebChromeClient? client) =>
+  _i5.Future<void> setWebChromeClient(_i2.WebChromeClient? client) =>
       (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> setBackgroundColor(_i5.Color? color) =>
+  _i5.Future<void> setBackgroundColor(_i6.Color? color) =>
       (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
-  _i4.Future<void> release() =>
+  _i5.Future<void> release() =>
       (super.noSuchMethod(Invocation.method(#release, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
   @override
   String toString() => super.toString();
 }
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index f3867b3..c203ef0 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:typed_data';
 
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -407,6 +408,81 @@
         ));
       });
 
+      group('loadRequest', () {
+        testWidgets('Throws ArgumentError for empty scheme',
+            (WidgetTester tester) async {
+          await buildWidget(tester);
+
+          expect(
+              () async => await testController.loadRequest(
+                    WebViewRequest(
+                      uri: Uri.parse('www.google.com'),
+                      method: WebViewRequestMethod.get,
+                    ),
+                  ),
+              throwsA(const TypeMatcher<ArgumentError>()));
+        });
+
+        testWidgets('GET without headers', (WidgetTester tester) async {
+          await buildWidget(tester);
+
+          await testController.loadRequest(WebViewRequest(
+            uri: Uri.parse('https://www.google.com'),
+            method: WebViewRequestMethod.get,
+          ));
+
+          verify(mockWebView.loadUrl(
+            'https://www.google.com',
+            <String, String>{},
+          ));
+        });
+
+        testWidgets('GET with headers', (WidgetTester tester) async {
+          await buildWidget(tester);
+
+          await testController.loadRequest(WebViewRequest(
+            uri: Uri.parse('https://www.google.com'),
+            method: WebViewRequestMethod.get,
+            headers: <String, String>{'a': 'header'},
+          ));
+
+          verify(mockWebView.loadUrl(
+            'https://www.google.com',
+            <String, String>{'a': 'header'},
+          ));
+        });
+
+        testWidgets('POST without body', (WidgetTester tester) async {
+          await buildWidget(tester);
+
+          await testController.loadRequest(WebViewRequest(
+            uri: Uri.parse('https://www.google.com'),
+            method: WebViewRequestMethod.post,
+          ));
+
+          verify(mockWebView.postUrl(
+            'https://www.google.com',
+            Uint8List(0),
+          ));
+        });
+
+        testWidgets('POST with body', (WidgetTester tester) async {
+          await buildWidget(tester);
+
+          final Uint8List body = Uint8List.fromList('Test Body'.codeUnits);
+
+          await testController.loadRequest(WebViewRequest(
+              uri: Uri.parse('https://www.google.com'),
+              method: WebViewRequestMethod.post,
+              body: body));
+
+          verify(mockWebView.postUrl(
+            'https://www.google.com',
+            body,
+          ));
+        });
+      });
+
       testWidgets('currentUrl', (WidgetTester tester) async {
         await buildWidget(tester);
 
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index f3b06ea..ece17ad 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -1,13 +1,18 @@
+// 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.
+
 // Mocks generated by Mockito 5.0.16 from annotations
 // in webview_flutter_android/test/webview_android_widget_test.dart.
 // Do not manually edit this file.
 
 import 'dart:async' as _i4;
-import 'dart:ui' as _i5;
+import 'dart:typed_data' as _i5;
+import 'dart:ui' as _i6;
 
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:webview_flutter_android/src/android_webview.dart' as _i2;
-import 'package:webview_flutter_android/webview_android_widget.dart' as _i6;
+import 'package:webview_flutter_android/webview_android_widget.dart' as _i7;
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'
     as _i3;
 
@@ -165,6 +170,11 @@
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
+  _i4.Future<void> postUrl(String? url, _i5.Uint8List? data) =>
+      (super.noSuchMethod(Invocation.method(#postUrl, [url, data]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+  @override
   _i4.Future<String?> getUrl() =>
       (super.noSuchMethod(Invocation.method(#getUrl, []),
           returnValue: Future<String?>.value()) as _i4.Future<String?>);
@@ -252,7 +262,7 @@
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i4.Future<void> setBackgroundColor(_i5.Color? color) =>
+  _i4.Future<void> setBackgroundColor(_i6.Color? color) =>
       (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]),
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
@@ -269,7 +279,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockWebViewAndroidDownloadListener extends _i1.Mock
-    implements _i6.WebViewAndroidDownloadListener {
+    implements _i7.WebViewAndroidDownloadListener {
   MockWebViewAndroidDownloadListener() {
     _i1.throwOnMissingStub(this);
   }
@@ -295,7 +305,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockWebViewAndroidJavaScriptChannel extends _i1.Mock
-    implements _i6.WebViewAndroidJavaScriptChannel {
+    implements _i7.WebViewAndroidJavaScriptChannel {
   MockWebViewAndroidJavaScriptChannel() {
     _i1.throwOnMissingStub(this);
   }
@@ -321,7 +331,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockWebViewAndroidWebChromeClient extends _i1.Mock
-    implements _i6.WebViewAndroidWebChromeClient {
+    implements _i7.WebViewAndroidWebChromeClient {
   MockWebViewAndroidWebChromeClient() {
     _i1.throwOnMissingStub(this);
   }
@@ -338,7 +348,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockWebViewAndroidWebViewClient extends _i1.Mock
-    implements _i6.WebViewAndroidWebViewClient {
+    implements _i7.WebViewAndroidWebViewClient {
   MockWebViewAndroidWebViewClient() {
     _i1.throwOnMissingStub(this);
   }
@@ -470,7 +480,7 @@
 /// A class which mocks [WebViewProxy].
 ///
 /// See the documentation for Mockito's code generation for more information.
-class MockWebViewProxy extends _i1.Mock implements _i6.WebViewProxy {
+class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy {
   MockWebViewProxy() {
     _i1.throwOnMissingStub(this);
   }