[webview_flutter] Add loadRequest functionality to app facing package. (#4573)

* Add android implementations for loadRequest.

* Update changelog and pubspec.

* Fix comment.

* Fix comment.

* Add tests

* Add back removed license headers

* Fix analysis error

* Add support for loadRequest to app facing package

* Add test

* Comment pending dependency in pubspec

* Remove workaround for supporting custom headers when making post requests on Android.

* Document android workaround in dart doc

* Enforce uri scheme

* Update loadRequest dartdoc

* Document android workaround in readme

* Processed PR feedback.

* Updated dependency, version and changelog.
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index 1f8a33d..a9e133d 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.6.0
+
+* Adds support for the `loadRequest` method.
+
 ## 2.5.0
 
 * Adds an option to set the background color of the webview.
diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md
index de475ad..c3982ea 100644
--- a/packages/webview_flutter/webview_flutter/README.md
+++ b/packages/webview_flutter/webview_flutter/README.md
@@ -92,3 +92,8 @@
 
 To use Material Components when the user interacts with input elements in the WebView,
 follow the steps described in the [Enabling Material Components instructions](https://flutter.dev/docs/deployment/android#enabling-material-components).
+
+### Setting custom headers on POST requests
+
+Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android.
+If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHTMLString` instead. 
\ No newline at end of file
diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart
index c870ae9..02e6ef8 100644
--- a/packages/webview_flutter/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:typed_data';
 
 import 'package:flutter/material.dart';
 import 'package:path_provider/path_provider.dart';
@@ -176,6 +177,7 @@
   listCache,
   clearCache,
   navigationDelegate,
+  doPostRequest,
   loadLocalFile,
   loadHtmlString,
   transparentBackground,
@@ -218,6 +220,9 @@
               case MenuOptions.navigationDelegate:
                 _onNavigationDelegateExample(controller.data!, context);
                 break;
+              case MenuOptions.doPostRequest:
+                _onDoPostRequest(controller.data!, context);
+                break;
               case MenuOptions.loadLocalFile:
                 _onLoadLocalFileExample(controller.data!, context);
                 break;
@@ -260,6 +265,10 @@
               child: Text('Navigation Delegate example'),
             ),
             const PopupMenuItem<MenuOptions>(
+              value: MenuOptions.doPostRequest,
+              child: Text('Post Request'),
+            ),
+            const PopupMenuItem<MenuOptions>(
               value: MenuOptions.loadHtmlString,
               child: Text('Load HTML string'),
             ),
@@ -348,6 +357,17 @@
     await controller.loadUrl('data:text/html;base64,$contentBase64');
   }
 
+  Future<void> _onDoPostRequest(
+      WebViewController controller, BuildContext context) async {
+    final WebViewRequest request = WebViewRequest(
+      uri: Uri.parse('https://httpbin.org/post'),
+      method: WebViewRequestMethod.post,
+      headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
+      body: Uint8List.fromList('Test Body'.codeUnits),
+    );
+    await controller.loadRequest(request);
+  }
+
   Future<void> _onLoadLocalFileExample(
       WebViewController controller, BuildContext context) async {
     final String pathToIndex = await _prepareLocalFile();
diff --git a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
index aa7b3a0..ab1cbb1 100644
--- a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
+++ b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
@@ -21,4 +21,6 @@
         WebSetting,
         WebSettings,
         WebResourceError,
-        WebResourceErrorType;
+        WebResourceErrorType,
+        WebViewRequest,
+        WebViewRequestMethod;
diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
index b2cc690..d76f7b3 100644
--- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart
+++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
@@ -550,6 +550,26 @@
     return _webViewPlatformController.loadUrl(url, headers);
   }
 
+  /// Makes a specific HTTP request and loads the response in the webview.
+  ///
+  /// [WebViewRequest.method] must be one of the supported HTTP methods
+  /// in [WebViewRequestMethod].
+  ///
+  /// If [WebViewRequest.headers] is not empty, its key-value pairs will be
+  /// added as the headers for the request.
+  ///
+  /// If [WebViewRequest.body] is not null, it will be added as the body
+  /// for the request.
+  ///
+  /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme.
+  ///
+  /// Android only:
+  /// When making a POST request, headers are ignored. As a workaround, make
+  /// the request manually and load the response data using [loadHTMLString].
+  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/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml
index 6c00f65..1bc6bab 100644
--- a/packages/webview_flutter/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android and iOS.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.5.0
+version: 2.6.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -19,8 +19,8 @@
 dependencies:
   flutter:
     sdk: flutter
-  webview_flutter_android: ^2.5.0
-  webview_flutter_platform_interface: ^1.7.0
+  webview_flutter_android: ^2.7.0
+  webview_flutter_platform_interface: ^1.8.0
   webview_flutter_wkwebview: ^2.5.0
 
 dev_dependencies:
diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart
index da860f0..d422402 100644
--- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.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/src/foundation/basic_types.dart';
 import 'package:flutter/src/gestures/recognizer.dart';
 import 'package:flutter/widgets.dart';
@@ -246,6 +248,29 @@
     ));
   });
 
+  testWidgets('loadRequest', (WidgetTester tester) async {
+    WebViewController? controller;
+    await tester.pumpWidget(
+      WebView(
+        onWebViewCreated: (WebViewController webViewController) {
+          controller = webViewController;
+        },
+      ),
+    );
+    expect(controller, isNotNull);
+
+    final WebViewRequest req = WebViewRequest(
+      uri: Uri.parse('https://flutter.dev'),
+      method: WebViewRequestMethod.post,
+      headers: <String, String>{'foo': 'bar'},
+      body: Uint8List.fromList('Test Body'.codeUnits),
+    );
+
+    await controller!.loadRequest(req);
+
+    verify(mockWebViewPlatformController.loadRequest(req));
+  });
+
   testWidgets('Clear Cache', (WidgetTester tester) async {
     WebViewController? controller;
     await tester.pumpWidget(
diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart
index 8857f60..fe30601 100644
--- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart
@@ -1,22 +1,26 @@
+// 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/test/webview_flutter_test.dart.
 // Do not manually edit this file.
 
 import 'dart:async' as _i9;
 
-import 'package:flutter/foundation.dart' as _i7;
+import 'package:flutter/foundation.dart' as _i3;
 import 'package:flutter/gestures.dart' as _i8;
 import 'package:flutter/widgets.dart' as _i2;
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart'
-    as _i6;
+    as _i7;
 import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform.dart'
-    as _i3;
+    as _i4;
 import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_callbacks_handler.dart'
-    as _i5;
+    as _i6;
 import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_controller.dart'
     as _i10;
-import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i4;
+import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i5;
 
 // ignore_for_file: avoid_redundant_argument_values
 // ignore_for_file: avoid_setters_without_getters
@@ -29,14 +33,14 @@
 
 class _FakeWidget_0 extends _i1.Fake implements _i2.Widget {
   @override
-  String toString({_i2.DiagnosticLevel? minLevel = _i2.DiagnosticLevel.info}) =>
+  String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) =>
       super.toString();
 }
 
 /// A class which mocks [WebViewPlatform].
 ///
 /// See the documentation for Mockito's code generation for more information.
-class MockWebViewPlatform extends _i1.Mock implements _i3.WebViewPlatform {
+class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform {
   MockWebViewPlatform() {
     _i1.throwOnMissingStub(this);
   }
@@ -44,11 +48,11 @@
   @override
   _i2.Widget build(
           {_i2.BuildContext? context,
-          _i4.CreationParams? creationParams,
-          _i5.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler,
-          _i6.JavascriptChannelRegistry? javascriptChannelRegistry,
-          _i3.WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
-          Set<_i7.Factory<_i8.OneSequenceGestureRecognizer>>?
+          _i5.CreationParams? creationParams,
+          _i6.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler,
+          _i7.JavascriptChannelRegistry? javascriptChannelRegistry,
+          _i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
+          Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>?
               gestureRecognizers}) =>
       (super.noSuchMethod(
           Invocation.method(#build, [], {
@@ -83,6 +87,11 @@
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
   @override
+  _i9.Future<void> loadFlutterAsset(String? key) =>
+      (super.noSuchMethod(Invocation.method(#loadFlutterAsset, [key]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
+  @override
   _i9.Future<void> loadHtmlString(String? html, {String? baseUrl}) =>
       (super.noSuchMethod(
           Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}),
@@ -94,12 +103,12 @@
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
   @override
-  _i9.Future<void> loadRequest(_i4.WebViewRequest? request) =>
+  _i9.Future<void> loadRequest(_i5.WebViewRequest? request) =>
       (super.noSuchMethod(Invocation.method(#loadRequest, [request]),
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
   @override
-  _i9.Future<void> updateSettings(_i4.WebSettings? setting) =>
+  _i9.Future<void> updateSettings(_i5.WebSettings? setting) =>
       (super.noSuchMethod(Invocation.method(#updateSettings, [setting]),
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);