[webview_flutter] Update webview packages for Android and iOS to implement `runJavascript` and `runJavascriptReturningResult`. (#4402)

* [webview_flutter_platform_interface] Update webview platform interface with new methods for running JavaScript

* Fix tests

* [webview_flutter] Implemented `runJavaScript` and `runJavaScriptForResult` in iOS and Android packages.

* Remove accidental development team inclusion from project.pbxproj

* Implemented PR feedback

* Updated runJavaScriptForResult behaviour

* Implemented PR feedback partially

* Implement PR feedback

* Update changelog

* Implement PR feedback from interface PR

* Update changelog

* Revert inclusion of development team

* Implement PR feedback

* Implemented platform interface PR feedback

* Update pubspec dependency

* Fixed capitalisation

* Fixed capitalisation

* Fix warning

* Partially implement PR feedback

* Partially implement PR feedback

* Format

* Update podfile

* Update podfile

* Update podfiles

* Update iOS project files

* Update podspec

* Remove unnecessary podfile configuration

* Implemented PR feedback

* Format

* Revert podfile changes

* Implemented PR feedback

* Fix formatting

* Fix formatting

* Fixed test.

* Re-add integration tests for deprecated evaluateJavascript method.

* Fix merge conflicts
diff --git a/packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.pbxproj
index 62428d0..0759b31 100644
--- a/packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.pbxproj
@@ -11,13 +11,13 @@
 		334734012669319100DCC49E /* FLTWebViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */; };
 		334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		63D2F2FB307F1F037702C198 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */; };
-		D9A9D48F1A75E5C682944DDD /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CC950C9005575711528C12 /* libPods-RunnerTests.a */; };
+		E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0ABA59F25635F077C9EA161 /* libPods-Runner.a */; };
 		F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; };
 /* End PBXBuildFile section */
 
@@ -52,11 +52,13 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		11DF059E983DF25F078B44CC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
-		27CC950C9005575711528C12 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		4D2B3F45D8E6CA81EA52591E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = "<group>"; };
 		68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -64,7 +66,6 @@
 		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>"; };
-		8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,9 +74,8 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
-		E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
-		F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		C0ABA59F25635F077C9EA161 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = "<group>"; };
 		F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -86,7 +86,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				D9A9D48F1A75E5C682944DDD /* libPods-RunnerTests.a in Frameworks */,
+				63D2F2FB307F1F037702C198 /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -94,7 +94,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */,
+				E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -108,6 +108,15 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		00D2395F7DDFEE571DF3C0B1 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				C0ABA59F25635F077C9EA161 /* libPods-Runner.a */,
+				BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 		68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
@@ -137,8 +146,8 @@
 				68BDCAEA23C3F7CB00D9C032 /* RunnerTests */,
 				F7151F75266057800028CB91 /* RunnerUITests */,
 				97C146EF1CF9000F007C117D /* Products */,
-				C6FFB52F5C2B8A41A7E39DE2 /* Pods */,
-				B6736FC417BDCCDA377E779D /* Frameworks */,
+				EA36D6F90B795550E32A139A /* Pods */,
+				00D2395F7DDFEE571DF3C0B1 /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -176,24 +185,16 @@
 			name = "Supporting Files";
 			sourceTree = "<group>";
 		};
-		B6736FC417BDCCDA377E779D /* Frameworks */ = {
+		EA36D6F90B795550E32A139A /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */,
-				27CC950C9005575711528C12 /* libPods-RunnerTests.a */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		C6FFB52F5C2B8A41A7E39DE2 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */,
-				C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */,
-				F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */,
-				E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */,
+				4D2B3F45D8E6CA81EA52591E /* Pods-Runner.debug.xcconfig */,
+				11DF059E983DF25F078B44CC /* Pods-Runner.release.xcconfig */,
+				3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */,
+				5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */,
 			);
 			name = Pods;
+			path = Pods;
 			sourceTree = "<group>";
 		};
 		F7151F75266057800028CB91 /* RunnerUITests */ = {
@@ -212,7 +213,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
 			buildPhases = (
-				53FD4CBDD9756D74B5A3B4C1 /* [CP] Check Pods Manifest.lock */,
+				EA0C9BB56C9A98B4F095051B /* [CP] Check Pods Manifest.lock */,
 				68BDCAE523C3F7CB00D9C032 /* Sources */,
 				68BDCAE623C3F7CB00D9C032 /* Frameworks */,
 				68BDCAE723C3F7CB00D9C032 /* Resources */,
@@ -231,7 +232,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
-				B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */,
+				1B3EA6BF26F6D525A8503093 /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
@@ -338,21 +339,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n";
-		};
-		53FD4CBDD9756D74B5A3B4C1 /* [CP] Check Pods Manifest.lock */ = {
+		1B3EA6BF26F6D525A8503093 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -367,13 +354,27 @@
 			outputFileListPaths = (
 			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n";
+		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -388,18 +389,22 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
 		};
-		B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */ = {
+		EA0C9BB56C9A98B4F095051B /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
+			inputFileListPaths = (
+			);
 			inputPaths = (
 				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
 				"${PODS_ROOT}/Manifest.lock",
 			);
 			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -473,7 +478,7 @@
 /* Begin XCBuildConfiguration section */
 		68BDCAF023C3F7CB00D9C032 /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */;
+			baseConfigurationReference = 3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
@@ -487,7 +492,7 @@
 		};
 		68BDCAF123C3F7CB00D9C032 /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */;
+			baseConfigurationReference = 5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index c083726..176028f 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Implemented new `runJavascript` and `runJavascriptReturningResult` methods in platform interface.
+
 ## 2.1.0
 
 * Add `zoomEnabled` functionality.
diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle
index ef1485a..bb9fe97 100644
--- a/packages/webview_flutter/webview_flutter_android/android/build.gradle
+++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle
@@ -58,4 +58,8 @@
             }
         }
     }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index b2a453a..ed14107 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -245,7 +245,11 @@
         currentUrl(result);
         break;
       case "evaluateJavascript":
-        evaluateJavaScript(methodCall, result);
+      case "runJavascriptReturningResult":
+        evaluateJavaScript(methodCall, result, true);
+        break;
+      case "runJavascript":
+        evaluateJavaScript(methodCall, result, false);
         break;
       case "addJavascriptChannels":
         addJavaScriptChannels(methodCall, result);
@@ -326,7 +330,8 @@
   }
 
   @TargetApi(Build.VERSION_CODES.KITKAT)
-  private void evaluateJavaScript(MethodCall methodCall, final Result result) {
+  private void evaluateJavaScript(
+      MethodCall methodCall, final Result result, final boolean returnValue) {
     String jsString = (String) methodCall.arguments;
     if (jsString == null) {
       throw new UnsupportedOperationException("JavaScript string cannot be null");
@@ -336,7 +341,11 @@
         new android.webkit.ValueCallback<String>() {
           @Override
           public void onReceiveValue(String value) {
-            result.success(value);
+            if (returnValue) {
+              result.success(value);
+            } else {
+              result.success(null);
+            }
           }
         });
   }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
index fd79bcc..f26a0ea 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
@@ -6,31 +6,46 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.webkit.DownloadListener;
 import android.webkit.WebChromeClient;
 import android.webkit.WebView;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
 import java.util.HashMap;
 import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.MockedStatic;
 
 public class FlutterWebViewTest {
   private WebChromeClient mockWebChromeClient;
   private DownloadListener mockDownloadListener;
   private WebViewBuilder mockWebViewBuilder;
   private WebView mockWebView;
+  private MethodChannel.Result mockResult;
+  private Context mockContext;
+  private MethodChannel mockMethodChannel;
 
   @Before
   public void before() {
+
     mockWebChromeClient = mock(WebChromeClient.class);
     mockWebViewBuilder = mock(WebViewBuilder.class);
     mockWebView = mock(WebView.class);
     mockDownloadListener = mock(DownloadListener.class);
+    mockResult = mock(MethodChannel.Result.class);
+    mockContext = mock(Context.class);
+    mockMethodChannel = mock(MethodChannel.class);
 
     when(mockWebViewBuilder.setDomStorageEnabled(anyBoolean())).thenReturn(mockWebViewBuilder);
     when(mockWebViewBuilder.setJavaScriptCanOpenWindowsAutomatically(anyBoolean()))
@@ -42,12 +57,11 @@
         .thenReturn(mockWebViewBuilder);
     when(mockWebViewBuilder.setDownloadListener(any(DownloadListener.class)))
         .thenReturn(mockWebViewBuilder);
-
     when(mockWebViewBuilder.build()).thenReturn(mockWebView);
   }
 
   @Test
-  public void createWebView_should_create_webview_with_default_configuration() {
+  public void createWebView_shouldCreateWebViewWithDefaultConfiguration() {
     FlutterWebView.createWebView(
         mockWebViewBuilder, createParameterMap(false), mockWebChromeClient, mockDownloadListener);
 
@@ -59,6 +73,97 @@
     verify(mockWebViewBuilder, times(1)).setZoomControlsEnabled(true);
   }
 
+  @Test(expected = UnsupportedOperationException.class)
+  public void evaluateJavaScript_shouldThrowForNullString() {
+    try (MockedStatic<FlutterWebView> mockedFlutterWebView = mockStatic(FlutterWebView.class)) {
+      // Setup
+      mockedFlutterWebView
+          .when(
+              new MockedStatic.Verification() {
+                @Override
+                public void apply() throws Throwable {
+                  FlutterWebView.createWebView(
+                      (WebViewBuilder) any(),
+                      (Map<String, Object>) any(),
+                      (WebChromeClient) any(),
+                      (DownloadListener) any());
+                }
+              })
+          .thenReturn(mockWebView);
+      FlutterWebView flutterWebView =
+          new FlutterWebView(mockContext, mockMethodChannel, new HashMap<String, Object>(), null);
+
+      // Run
+      flutterWebView.onMethodCall(new MethodCall("runJavascript", null), mockResult);
+    }
+  }
+
+  @Test
+  public void evaluateJavaScript_shouldReturnValueOnSuccessForReturnValue() {
+    try (MockedStatic<FlutterWebView> mockedFlutterWebView = mockStatic(FlutterWebView.class)) {
+      // Setup
+      mockedFlutterWebView
+          .when(
+              () ->
+                  FlutterWebView.createWebView(
+                      (WebViewBuilder) any(),
+                      (Map<String, Object>) any(),
+                      (WebChromeClient) any(),
+                      (DownloadListener) any()))
+          .thenReturn(mockWebView);
+      doAnswer(
+              invocation -> {
+                android.webkit.ValueCallback<String> callback = invocation.getArgument(1);
+                callback.onReceiveValue("Test JavaScript Result");
+                return null;
+              })
+          .when(mockWebView)
+          .evaluateJavascript(eq("Test JavaScript String"), any());
+      FlutterWebView flutterWebView =
+          new FlutterWebView(mockContext, mockMethodChannel, new HashMap<String, Object>(), null);
+
+      // Run
+      flutterWebView.onMethodCall(
+          new MethodCall("runJavascriptReturningResult", "Test JavaScript String"), mockResult);
+
+      // Verify
+      verify(mockResult, times(1)).success("Test JavaScript Result");
+    }
+  }
+
+  @Test
+  public void evaluateJavaScript_shouldReturnNilOnSuccessForNoReturnValue() {
+    try (MockedStatic<FlutterWebView> mockedFlutterWebView = mockStatic(FlutterWebView.class)) {
+      // Setup
+      mockedFlutterWebView
+          .when(
+              () ->
+                  FlutterWebView.createWebView(
+                      (WebViewBuilder) any(),
+                      (Map<String, Object>) any(),
+                      (WebChromeClient) any(),
+                      (DownloadListener) any()))
+          .thenReturn(mockWebView);
+      doAnswer(
+              invocation -> {
+                android.webkit.ValueCallback<String> callback = invocation.getArgument(1);
+                callback.onReceiveValue("Test JavaScript Result");
+                return null;
+              })
+          .when(mockWebView)
+          .evaluateJavascript(eq("Test JavaScript String"), any());
+      FlutterWebView flutterWebView =
+          new FlutterWebView(mockContext, mockMethodChannel, new HashMap<String, Object>(), null);
+
+      // Run
+      flutterWebView.onMethodCall(
+          new MethodCall("runJavascript", "Test JavaScript String"), mockResult);
+
+      // Verify
+      verify(mockResult, times(1)).success(isNull());
+    }
+  }
+
   private Map<String, Object> createParameterMap(boolean usesHybridComposition) {
     Map<String, Object> params = new HashMap<>();
     params.put("usesHybridComposition", usesHybridComposition);
diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
index c57d2bd..8e3dcb4 100644
--- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
@@ -76,6 +76,27 @@
     expect(currentUrl, secondaryUrl);
   }, skip: _skipDueToIssue86757);
 
+  testWidgets('evaluateJavascript', (WidgetTester tester) async {
+    final Completer<WebViewController> controllerCompleter =
+        Completer<WebViewController>();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebView(
+          key: GlobalKey(),
+          initialUrl: primaryUrl,
+          onWebViewCreated: (WebViewController controller) {
+            controllerCompleter.complete(controller);
+          },
+          javascriptMode: JavascriptMode.unrestricted,
+        ),
+      ),
+    );
+    final WebViewController controller = await controllerCompleter.future;
+    final String result = await controller.evaluateJavascript('1 + 1');
+    expect(result, equals('2'));
+  });
+
   // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
   testWidgets('loadUrl with headers', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
@@ -114,12 +135,12 @@
     await pageLoads.stream.firstWhere((String url) => url == currentUrl);
 
     final String content = await controller
-        .evaluateJavascript('document.documentElement.innerText');
+        .runJavascriptReturningResult('document.documentElement.innerText');
     expect(content.contains('flutter_test_header'), isTrue);
   }, skip: _skipDueToIssue86757);
 
   // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
-  testWidgets('JavaScriptChannel', (WidgetTester tester) async {
+  testWidgets('JavascriptChannel', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
     final Completer<void> pageStarted = Completer<void>();
@@ -159,11 +180,7 @@
     await pageLoaded.future;
 
     expect(messagesReceived, isEmpty);
-    // Append a return value "1" in the end will prevent an iOS platform exception.
-    // See: https://github.com/flutter/flutter/issues/66318#issuecomment-701105380
-    // TODO(cyanglaz): remove the workaround "1" in the end when the below issue is fixed.
-    // https://github.com/flutter/flutter/issues/66318
-    await controller.evaluateJavascript('Echo.postMessage("hello");1;');
+    await controller.runJavascript('Echo.postMessage("hello");');
     expect(messagesReceived, equals(<String>['hello']));
   }, skip: _skipDueToIssue86757);
 
@@ -410,7 +427,8 @@
       WebViewController controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       controllerCompleter = Completer<WebViewController>();
@@ -439,7 +457,7 @@
       controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(true));
     });
 
@@ -470,7 +488,8 @@
       final WebViewController controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       pageLoaded = Completer<void>();
@@ -498,7 +517,7 @@
 
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
     });
 
@@ -548,7 +567,7 @@
       await videoPlaying.future;
 
       String fullScreen =
-          await controller.evaluateJavascript('isFullScreen();');
+          await controller.runJavascriptReturningResult('isFullScreen();');
       expect(fullScreen, _webviewBool(false));
     });
   });
@@ -614,7 +633,8 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       controllerCompleter = Completer<WebViewController>();
@@ -648,7 +668,7 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(true));
     });
 
@@ -684,7 +704,8 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       pageStarted = Completer<void>();
@@ -717,7 +738,7 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
     });
   });
@@ -982,15 +1003,16 @@
 
       final WebViewController controller = await controllerCompleter.future;
       await pageLoaded.future;
-      final String viewportRectJSON = await _evaluateJavascript(
+      final String viewportRectJSON = await _runJavaScriptReturningResult(
           controller, 'JSON.stringify(viewport.getBoundingClientRect())');
       final Map<String, dynamic> viewportRectRelativeToViewport =
           jsonDecode(viewportRectJSON);
 
       // Check that the input is originally outside of the viewport.
 
-      final String initialInputClientRectJSON = await _evaluateJavascript(
-          controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
+      final String initialInputClientRectJSON =
+          await _runJavaScriptReturningResult(
+              controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
       final Map<String, dynamic> initialInputClientRectRelativeToViewport =
           jsonDecode(initialInputClientRectJSON);
 
@@ -999,12 +1021,13 @@
               viewportRectRelativeToViewport['bottom'],
           isFalse);
 
-      await controller.evaluateJavascript('inputEl.focus()');
+      await controller.runJavascript('inputEl.focus()');
 
       // Check that focusing the input brought it into view.
 
-      final String lastInputClientRectJSON = await _evaluateJavascript(
-          controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
+      final String lastInputClientRectJSON =
+          await _runJavaScriptReturningResult(
+              controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
       final Map<String, dynamic> lastInputClientRectRelativeToViewport =
           jsonDecode(lastInputClientRectJSON);
 
@@ -1060,7 +1083,7 @@
 
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
-      await controller.evaluateJavascript('location.href = "$secondaryUrl"');
+      await controller.runJavascript('location.href = "$secondaryUrl"');
 
       await pageLoads.stream.first; // Wait for the next page load.
       final String? currentUrl = await controller.currentUrl();
@@ -1186,7 +1209,7 @@
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
       await controller
-          .evaluateJavascript('location.href = "https://www.youtube.com/"');
+          .runJavascript('location.href = "https://www.youtube.com/"');
 
       // There should never be any second page load, since our new URL is
       // blocked. Still wait for a potential page change for some time in order
@@ -1226,7 +1249,7 @@
 
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
-      await controller.evaluateJavascript('location.href = "$secondaryUrl"');
+      await controller.runJavascript('location.href = "$secondaryUrl"');
 
       await pageLoads.stream.first; // Wait for second page to load.
       final String? currentUrl = await controller.currentUrl();
@@ -1281,7 +1304,7 @@
       ),
     );
     final WebViewController controller = await controllerCompleter.future;
-    await controller.evaluateJavascript('window.open("$primaryUrl", "_blank")');
+    await controller.runJavascript('window.open("$primaryUrl", "_blank")');
     await pageLoaded.future;
     final String? currentUrl = await controller.currentUrl();
     expect(currentUrl, primaryUrl);
@@ -1317,7 +1340,7 @@
       await pageLoaded.future;
       pageLoaded = Completer<void>();
 
-      await controller.evaluateJavascript('window.open("$secondaryUrl")');
+      await controller.runJavascript('window.open("$secondaryUrl")');
       await pageLoaded.future;
       pageLoaded = Completer<void>();
       expect(controller.currentUrl(), completion(secondaryUrl));
@@ -1331,7 +1354,7 @@
   );
 
   testWidgets(
-    'javascript does not run in parent window',
+    'JavaScript does not run in parent window',
     (WidgetTester tester) async {
       final String iframe = '''
         <!DOCTYPE html>
@@ -1388,9 +1411,10 @@
       final WebViewController controller = await controllerCompleter.future;
       await pageLoadCompleter.future;
 
-      expect(controller.evaluateJavascript('iframeLoaded'), completion('true'));
+      expect(controller.runJavascriptReturningResult('iframeLoaded'),
+          completion('true'));
       expect(
-        controller.evaluateJavascript(
+        controller.runJavascriptReturningResult(
             'document.querySelector("p") && document.querySelector("p").textContent'),
         completion('null'),
       );
@@ -1409,10 +1433,10 @@
 
 /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
 Future<String> _getUserAgent(WebViewController controller) async {
-  return _evaluateJavascript(controller, 'navigator.userAgent;');
+  return _runJavaScriptReturningResult(controller, 'navigator.userAgent;');
 }
 
-Future<String> _evaluateJavascript(
+Future<String> _runJavaScriptReturningResult(
     WebViewController controller, String js) async {
-  return jsonDecode(await controller.evaluateJavascript(js));
+  return jsonDecode(await controller.runJavascriptReturningResult(js));
 }
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 65f4971..a22d165 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -195,14 +195,14 @@
       WebViewController controller, BuildContext context) async {
     // Send a message with the user agent string to the Snackbar JavaScript channel we registered
     // with the WebView.
-    await controller.evaluateJavascript(
+    await controller.runJavascript(
         'Snackbar.postMessage("User Agent: " + navigator.userAgent);');
   }
 
   void _onListCookies(
       WebViewController controller, BuildContext context) async {
     final String cookies =
-        await controller.evaluateJavascript('document.cookie');
+        await controller.runJavascriptReturningResult('document.cookie');
     // ignore: deprecated_member_use
     Scaffold.of(context).showSnackBar(SnackBar(
       content: Column(
@@ -217,7 +217,7 @@
   }
 
   void _onAddToCache(WebViewController controller, BuildContext context) async {
-    await controller.evaluateJavascript(
+    await controller.runJavascript(
         'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
     // ignore: deprecated_member_use
     Scaffold.of(context).showSnackBar(const SnackBar(
@@ -226,7 +226,7 @@
   }
 
   void _onListCache(WebViewController controller, BuildContext context) async {
-    await controller.evaluateJavascript('caches.keys()'
+    await controller.runJavascript('caches.keys()'
         '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
         '.then((caches) => Snackbar.postMessage(caches))');
   }
@@ -340,5 +340,5 @@
   }
 }
 
-/// Callback type for handling messages sent from Javascript running in a web view.
+/// Callback type for handling messages sent from JavaScript running in a web view.
 typedef void JavascriptMessageHandler(JavascriptMessage message);
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 5e8a279..b435074 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
@@ -116,7 +116,7 @@
   /// The initial URL to load.
   final String? initialUrl;
 
-  /// Whether Javascript execution is enabled.
+  /// Whether JavaScript execution is enabled.
   final JavascriptMode javascriptMode;
 
   /// The set of [JavascriptChannel]s available to JavaScript code running in the web view.
@@ -126,7 +126,7 @@
   /// The JavaScript code can then call `postMessage` on that object to send a message that will be
   /// passed to [JavascriptChannel.onMessageReceived].
   ///
-  /// For example for the following JavascriptChannel:
+  /// For example for the following [JavascriptChannel]:
   ///
   /// ```dart
   /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); });
@@ -189,7 +189,7 @@
   /// When [onPageFinished] is invoked on Android, the page being rendered may
   /// not be updated yet.
   ///
-  /// When invoked on iOS or Android, any Javascript code that is embedded
+  /// When invoked on iOS or Android, any JavaScript code that is embedded
   /// directly in the HTML has been loaded and code injected with
   /// [WebViewController.evaluateJavascript] can assume this.
   final PageFinishedCallback? onPageFinished;
@@ -487,33 +487,48 @@
     _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels);
   }
 
-  /// Evaluates a JavaScript expression in the context of the current page.
-  ///
-  /// On Android returns the evaluation result as a JSON formatted string.
-  ///
-  /// On iOS depending on the value type the return value would be one of:
-  ///
-  ///  - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').
-  ///  - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').
-  ///  - Other non-primitive types are not supported on iOS and will complete the Future with an error.
-  ///
-  /// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the
-  /// evaluated expression is not supported as described above.
-  ///
-  /// When evaluating Javascript in a [WebView], it is best practice to wait for
-  /// the [WebView.onPageFinished] callback. This guarantees all the Javascript
-  /// embedded in the main frame HTML has been loaded.
+  @visibleForTesting
+  // ignore: public_member_api_docs
   Future<String> evaluateJavascript(String javascriptString) {
     if (_settings.javascriptMode == JavascriptMode.disabled) {
       return Future<String>.error(FlutterError(
           'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.'));
     }
-    // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
-    // https://github.com/flutter/flutter/issues/26431
-    // ignore: strong_mode_implicit_dynamic_method
     return _webViewPlatformController.evaluateJavascript(javascriptString);
   }
 
+  /// Runs the given JavaScript in the context of the current page.
+  /// If you are looking for the result, use [runJavascriptReturningResult] instead.
+  /// The Future completes with an error if a JavaScript error occurred.
+  ///
+  /// When running JavaScript in a [WebView], it is best practice to wait for
+  //  the [WebView.onPageFinished] callback. This guarantees all the JavaScript
+  //  embedded in the main frame HTML has been loaded.
+  Future<void> runJavascript(String javaScriptString) {
+    if (_settings.javascriptMode == JavascriptMode.disabled) {
+      return Future<void>.error(FlutterError(
+          'Javascript mode must be enabled/unrestricted when calling runJavascript.'));
+    }
+    return _webViewPlatformController.runJavascript(javaScriptString);
+  }
+
+  /// Runs the given JavaScript in the context of the current page, and returns the result.
+  ///
+  /// Returns the evaluation result as a JSON formatted string.
+  /// The Future completes with an error if a JavaScript error occurred.
+  ///
+  /// When evaluating JavaScript in a [WebView], it is best practice to wait for
+  /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript
+  /// embedded in the main frame HTML has been loaded.
+  Future<String> runJavascriptReturningResult(String javaScriptString) {
+    if (_settings.javascriptMode == JavascriptMode.disabled) {
+      return Future<String>.error(FlutterError(
+          'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.'));
+    }
+    return _webViewPlatformController
+        .runJavascriptReturningResult(javaScriptString);
+  }
+
   /// Returns the title of the currently loaded page.
   Future<String?> getTitle() {
     return _webViewPlatformController.getTitle();
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 577d373..ac208a0 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.1.0
+version: 2.2.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index b75519a..4db6dbf 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Implemented new `runJavascript` and `runJavascriptReturningResult` methods in platform interface.
+
 ## 2.1.0
 
 * Add `zoomEnabled` functionality.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
index 8ba17a2..17e896c 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
@@ -73,6 +73,27 @@
     expect(currentUrl, secondaryUrl);
   }, skip: _skipDueToIssue86757);
 
+  testWidgets('evaluateJavascript', (WidgetTester tester) async {
+    final Completer<WebViewController> controllerCompleter =
+        Completer<WebViewController>();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebView(
+          key: GlobalKey(),
+          initialUrl: primaryUrl,
+          onWebViewCreated: (WebViewController controller) {
+            controllerCompleter.complete(controller);
+          },
+          javascriptMode: JavascriptMode.unrestricted,
+        ),
+      ),
+    );
+    final WebViewController controller = await controllerCompleter.future;
+    final String result = await controller.evaluateJavascript('1 + 1');
+    expect(result, equals('2'));
+  });
+
   testWidgets('loadUrl with headers', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
@@ -110,11 +131,11 @@
     await pageLoads.stream.firstWhere((String url) => url == currentUrl);
 
     final String content = await controller
-        .evaluateJavascript('document.documentElement.innerText');
+        .runJavascriptReturningResult('document.documentElement.innerText');
     expect(content.contains('flutter_test_header'), isTrue);
   });
 
-  testWidgets('JavaScriptChannel', (WidgetTester tester) async {
+  testWidgets('JavascriptChannel', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
     final Completer<void> pageStarted = Completer<void>();
@@ -154,11 +175,7 @@
     await pageLoaded.future;
 
     expect(messagesReceived, isEmpty);
-    // Append a return value "1" in the end will prevent an iOS platform exception.
-    // See: https://github.com/flutter/flutter/issues/66318#issuecomment-701105380
-    // TODO(cyanglaz): remove the workaround "1" in the end when the below issue is fixed.
-    // https://github.com/flutter/flutter/issues/66318
-    await controller.evaluateJavascript('Echo.postMessage("hello");1;');
+    await controller.runJavascript('Echo.postMessage("hello");');
     expect(messagesReceived, equals(<String>['hello']));
   });
 
@@ -404,7 +421,8 @@
       WebViewController controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       controllerCompleter = Completer<WebViewController>();
@@ -433,7 +451,7 @@
       controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(true));
     });
 
@@ -464,7 +482,8 @@
       final WebViewController controller = await controllerCompleter.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       pageLoaded = Completer<void>();
@@ -492,7 +511,7 @@
 
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
     });
 
@@ -542,7 +561,7 @@
       await videoPlaying.future;
 
       String fullScreen =
-          await controller.evaluateJavascript('isFullScreen();');
+          await controller.runJavascriptReturningResult('isFullScreen();');
       expect(fullScreen, _webviewBool(false));
     });
 
@@ -593,7 +612,7 @@
       await videoPlaying.future;
 
       String fullScreen =
-          await controller.evaluateJavascript('isFullScreen();');
+          await controller.runJavascriptReturningResult('isFullScreen();');
       expect(fullScreen, _webviewBool(true));
     });
   });
@@ -659,7 +678,8 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       controllerCompleter = Completer<WebViewController>();
@@ -693,7 +713,7 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(true));
     });
 
@@ -729,7 +749,8 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      String isPaused = await controller.evaluateJavascript('isPaused();');
+      String isPaused =
+          await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
 
       pageStarted = Completer<void>();
@@ -762,7 +783,7 @@
       await pageStarted.future;
       await pageLoaded.future;
 
-      isPaused = await controller.evaluateJavascript('isPaused();');
+      isPaused = await controller.runJavascriptReturningResult('isPaused();');
       expect(isPaused, _webviewBool(false));
     });
   });
@@ -919,7 +940,7 @@
 
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
-      await controller.evaluateJavascript('location.href = "$secondaryUrl"');
+      await controller.runJavascript('location.href = "$secondaryUrl"');
 
       await pageLoads.stream.first; // Wait for the next page load.
       final String? currentUrl = await controller.currentUrl();
@@ -1050,7 +1071,7 @@
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
       await controller
-          .evaluateJavascript('location.href = "https://www.youtube.com/"');
+          .runJavascript('location.href = "https://www.youtube.com/"');
 
       // There should never be any second page load, since our new URL is
       // blocked. Still wait for a potential page change for some time in order
@@ -1090,7 +1111,7 @@
 
       await pageLoads.stream.first; // Wait for initial page load.
       final WebViewController controller = await controllerCompleter.future;
-      await controller.evaluateJavascript('location.href = "$secondaryUrl"');
+      await controller.runJavascript('location.href = "$secondaryUrl"');
 
       await pageLoads.stream.first; // Wait for second page to load.
       final String? currentUrl = await controller.currentUrl();
@@ -1145,7 +1166,7 @@
       ),
     );
     final WebViewController controller = await controllerCompleter.future;
-    await controller.evaluateJavascript('window.open("$primaryUrl", "_blank")');
+    await controller.runJavascript('window.open("$primaryUrl", "_blank")');
     await pageLoaded.future;
     final String? currentUrl = await controller.currentUrl();
     expect(currentUrl, primaryUrl);
@@ -1179,7 +1200,7 @@
       await pageLoaded.future;
       pageLoaded = Completer<void>();
 
-      await controller.evaluateJavascript('window.open("$secondaryUrl")');
+      await controller.runJavascript('window.open("$secondaryUrl")');
       await pageLoaded.future;
       pageLoaded = Completer<void>();
       expect(controller.currentUrl(), completion(secondaryUrl));
@@ -1204,13 +1225,13 @@
 
 /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
 Future<String> _getUserAgent(WebViewController controller) async {
-  return _evaluateJavascript(controller, 'navigator.userAgent;');
+  return _runJavascriptReturningResult(controller, 'navigator.userAgent;');
 }
 
-Future<String> _evaluateJavascript(
+Future<String> _runJavascriptReturningResult(
     WebViewController controller, String js) async {
   if (defaultTargetPlatform == TargetPlatform.iOS) {
-    return await controller.evaluateJavascript(js);
+    return await controller.runJavascriptReturningResult(js);
   }
-  return jsonDecode(await controller.evaluateJavascript(js));
+  return jsonDecode(await controller.runJavascriptReturningResult(js));
 }
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
index 62428d0..ba0deb4 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
@@ -16,8 +16,8 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */; };
-		D9A9D48F1A75E5C682944DDD /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CC950C9005575711528C12 /* libPods-RunnerTests.a */; };
+		AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */; };
+		DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; };
 		F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; };
 /* End PBXBuildFile section */
 
@@ -52,11 +52,12 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
-		27CC950C9005575711528C12 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = "<group>"; };
 		68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -64,7 +65,6 @@
 		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>"; };
-		8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,12 +73,12 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
-		E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
-		F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = "<group>"; };
 		F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -86,7 +86,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				D9A9D48F1A75E5C682944DDD /* libPods-RunnerTests.a in Frameworks */,
+				AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -94,7 +94,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */,
+				DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -108,6 +108,15 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		52FBC2B567345431F81A0A0F /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				572FFC2B2BA326B420B22679 /* libPods-Runner.a */,
+				528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 		68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
@@ -137,8 +146,8 @@
 				68BDCAEA23C3F7CB00D9C032 /* RunnerTests */,
 				F7151F75266057800028CB91 /* RunnerUITests */,
 				97C146EF1CF9000F007C117D /* Products */,
-				C6FFB52F5C2B8A41A7E39DE2 /* Pods */,
-				B6736FC417BDCCDA377E779D /* Frameworks */,
+				B8AEEA11D6ECBD09750349AE /* Pods */,
+				52FBC2B567345431F81A0A0F /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -176,24 +185,16 @@
 			name = "Supporting Files";
 			sourceTree = "<group>";
 		};
-		B6736FC417BDCCDA377E779D /* Frameworks */ = {
+		B8AEEA11D6ECBD09750349AE /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */,
-				27CC950C9005575711528C12 /* libPods-RunnerTests.a */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		C6FFB52F5C2B8A41A7E39DE2 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */,
-				C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */,
-				F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */,
-				E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */,
+				F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */,
+				B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */,
+				C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */,
+				5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */,
 			);
 			name = Pods;
+			path = Pods;
 			sourceTree = "<group>";
 		};
 		F7151F75266057800028CB91 /* RunnerUITests */ = {
@@ -212,7 +213,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
 			buildPhases = (
-				53FD4CBDD9756D74B5A3B4C1 /* [CP] Check Pods Manifest.lock */,
+				0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */,
 				68BDCAE523C3F7CB00D9C032 /* Sources */,
 				68BDCAE623C3F7CB00D9C032 /* Frameworks */,
 				68BDCAE723C3F7CB00D9C032 /* Resources */,
@@ -231,7 +232,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
-				B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */,
+				6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
@@ -338,21 +339,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n";
-		};
-		53FD4CBDD9756D74B5A3B4C1 /* [CP] Check Pods Manifest.lock */ = {
+		0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -374,6 +361,42 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n";
+		};
+		6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -388,24 +411,6 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
 		};
-		B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -473,7 +478,7 @@
 /* Begin XCBuildConfiguration section */
 		68BDCAF023C3F7CB00D9C032 /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = F674B2A05DAC369B4FF27850 /* Pods-RunnerTests.debug.xcconfig */;
+			baseConfigurationReference = C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
@@ -487,7 +492,7 @@
 		};
 		68BDCAF123C3F7CB00D9C032 /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = E14113434CCE6D3186B5CBC3 /* Pods-RunnerTests.release.xcconfig */;
+			baseConfigurationReference = 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
index 631c4a1..9d127c2 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
@@ -88,4 +88,217 @@
   }
 }
 
+- (void)testRunJavascriptFailsForNullString {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return error result over the method channel."];
+
+  // Run
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascript"
+                                                             arguments:nil]
+                    result:^(id _Nullable result) {
+                      XCTAssertTrue([result class] == [FlutterError class]);
+                      [resultExpectation fulfill];
+                    }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptRunsStringWithSuccessResult {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return successful result over the method channel."];
+  FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+  [OCMStub([mockView evaluateJavaScript:[OCMArg any]
+                      completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
+    // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668
+    __unsafe_unretained void (^evalResultHandler)(id, NSError *);
+    [invocation getArgument:&evalResultHandler atIndex:3];
+    evalResultHandler(@"RESULT", nil);
+  }];
+  controller.webView = mockView;
+
+  // Run
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascript"
+                                                             arguments:@"Test JavaScript String"]
+                    result:^(id _Nullable result) {
+                      XCTAssertNil(result);
+                      [resultExpectation fulfill];
+                    }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptReturnsErrorResultForWKError {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return error result over the method channel."];
+  NSError *testError =
+      [NSError errorWithDomain:@""
+                          // Any error code but WKErrorJavascriptResultTypeIsUnsupported
+                          code:WKErrorJavaScriptResultTypeIsUnsupported + 1
+                      userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}];
+  FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+  [OCMStub([mockView evaluateJavaScript:[OCMArg any]
+                      completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
+    // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668
+    __unsafe_unretained void (^evalResultHandler)(id, NSError *);
+    [invocation getArgument:&evalResultHandler atIndex:3];
+    evalResultHandler(nil, testError);
+  }];
+  controller.webView = mockView;
+
+  // Run
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascript"
+                                                             arguments:@"Test JavaScript String"]
+                    result:^(id _Nullable result) {
+                      XCTAssertTrue([result class] == [FlutterError class]);
+                      [resultExpectation fulfill];
+                    }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptReturnsSuccessForWKErrorJavascriptResultTypeIsUnsupported {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return nil result over the method channel."];
+  NSError *testError = [NSError errorWithDomain:@""
+                                           code:WKErrorJavaScriptResultTypeIsUnsupported
+                                       userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}];
+  FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+  [OCMStub([mockView evaluateJavaScript:[OCMArg any]
+                      completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
+    // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668
+    __unsafe_unretained void (^evalResultHandler)(id, NSError *);
+    [invocation getArgument:&evalResultHandler atIndex:3];
+    evalResultHandler(nil, testError);
+  }];
+  controller.webView = mockView;
+
+  // Run
+  [controller onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascript"
+                                                             arguments:@"Test JavaScript String"]
+                    result:^(id _Nullable result) {
+                      XCTAssertNil(result);
+                      [resultExpectation fulfill];
+                    }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptReturningResultFailsForNullString {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return error result over the method channel."];
+
+  // Run
+  [controller
+      onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascriptReturningResult"
+                                                     arguments:nil]
+            result:^(id _Nullable result) {
+              XCTAssertTrue([result class] == [FlutterError class]);
+              [resultExpectation fulfill];
+            }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptReturningResultRunsStringWithSuccessResult {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return successful result over the method channel."];
+  FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+  [OCMStub([mockView evaluateJavaScript:[OCMArg any]
+                      completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
+    // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668
+    __unsafe_unretained void (^evalResultHandler)(id, NSError *);
+    [invocation getArgument:&evalResultHandler atIndex:3];
+    evalResultHandler(@"RESULT", nil);
+  }];
+  controller.webView = mockView;
+
+  // Run
+  [controller
+      onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascriptReturningResult"
+                                                     arguments:@"Test JavaScript String"]
+            result:^(id _Nullable result) {
+              XCTAssertTrue([@"RESULT" isEqualToString:result]);
+              [resultExpectation fulfill];
+            }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
+- (void)testRunJavascriptReturningResultReturnsErrorResultForWKError {
+  // Setup
+  FLTWebViewController *controller =
+      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
+                                   viewIdentifier:1
+                                        arguments:nil
+                                  binaryMessenger:self.mockBinaryMessenger];
+  XCTestExpectation *resultExpectation =
+      [self expectationWithDescription:@"Should return error result over the method channel."];
+  NSError *testError = [NSError errorWithDomain:@""
+                                           code:5
+                                       userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}];
+  FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class);
+  [OCMStub([mockView evaluateJavaScript:[OCMArg any]
+                      completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
+    // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668
+    __unsafe_unretained void (^evalResultHandler)(id, NSError *);
+    [invocation getArgument:&evalResultHandler atIndex:3];
+    evalResultHandler(nil, testError);
+  }];
+  controller.webView = mockView;
+
+  // Run
+  [controller
+      onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascriptReturningResult"
+                                                     arguments:@"Test JavaScript String"]
+            result:^(id _Nullable result) {
+              XCTAssertTrue([result class] == [FlutterError class]);
+              [resultExpectation fulfill];
+            }];
+
+  // Verify
+  [self waitForExpectationsWithTimeout:30.0 handler:nil];
+}
+
 @end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
index 15b4cfc..21240f6 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -199,14 +199,14 @@
       WebViewController controller, BuildContext context) async {
     // Send a message with the user agent string to the Snackbar JavaScript channel we registered
     // with the WebView.
-    await controller.evaluateJavascript(
+    await controller.runJavascript(
         'Snackbar.postMessage("User Agent: " + navigator.userAgent);');
   }
 
   void _onListCookies(
       WebViewController controller, BuildContext context) async {
     final String cookies =
-        await controller.evaluateJavascript('document.cookie');
+        await controller.runJavascriptReturningResult('document.cookie');
     ScaffoldMessenger.of(context).showSnackBar(SnackBar(
       content: Column(
         mainAxisAlignment: MainAxisAlignment.end,
@@ -220,7 +220,7 @@
   }
 
   void _onAddToCache(WebViewController controller, BuildContext context) async {
-    await controller.evaluateJavascript(
+    await controller.runJavascript(
         'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
     ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
       content: Text('Added a test entry to cache.'),
@@ -228,7 +228,7 @@
   }
 
   void _onListCache(WebViewController controller, BuildContext context) async {
-    await controller.evaluateJavascript('caches.keys()'
+    await controller.runJavascript('caches.keys()'
         '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
         '.then((caches) => Snackbar.postMessage(caches))');
   }
@@ -340,5 +340,5 @@
   }
 }
 
-/// Callback type for handling messages sent from Javascript running in a web view.
+/// Callback type for handling messages sent from JavaScript running in a web view.
 typedef void JavascriptMessageHandler(JavascriptMessage message);
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
index d57499d..403db1f 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
@@ -94,7 +94,7 @@
   /// The initial URL to load.
   final String? initialUrl;
 
-  /// Whether Javascript execution is enabled.
+  /// Whether JavaScript execution is enabled.
   final JavascriptMode javascriptMode;
 
   /// The set of [JavascriptChannel]s available to JavaScript code running in the web view.
@@ -167,7 +167,7 @@
   /// When [onPageFinished] is invoked on Android, the page being rendered may
   /// not be updated yet.
   ///
-  /// When invoked on iOS or Android, any Javascript code that is embedded
+  /// When invoked on iOS or Android, any JavaScript code that is embedded
   /// directly in the HTML has been loaded and code injected with
   /// [WebViewController.evaluateJavascript] can assume this.
   final PageFinishedCallback? onPageFinished;
@@ -416,33 +416,53 @@
     _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels);
   }
 
-  /// Evaluates a JavaScript expression in the context of the current page.
-  ///
-  /// On Android returns the evaluation result as a JSON formatted string.
-  ///
-  /// On iOS depending on the value type the return value would be one of:
-  ///
-  ///  - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').
-  ///  - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').
-  ///  - Other non-primitive types are not supported on iOS and will complete the Future with an error.
-  ///
-  /// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the
-  /// evaluated expression is not supported as described above.
-  ///
-  /// When evaluating Javascript in a [WebView], it is best practice to wait for
-  /// the [WebView.onPageFinished] callback. This guarantees all the Javascript
-  /// embedded in the main frame HTML has been loaded.
+  @visibleForTesting
+  // ignore: public_member_api_docs
   Future<String> evaluateJavascript(String javascriptString) {
     if (_settings.javascriptMode == JavascriptMode.disabled) {
       return Future<String>.error(FlutterError(
           'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.'));
     }
-    // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
-    // https://github.com/flutter/flutter/issues/26431
-    // ignore: strong_mode_implicit_dynamic_method
     return _webViewPlatformController.evaluateJavascript(javascriptString);
   }
 
+  /// Runs the given JavaScript in the context of the current page.
+  /// If you are looking for the result, use [runJavascriptReturningResult] instead.
+  /// The Future completes with an error if a JavaScript error occurred.
+  ///
+  /// When running JavaScript in a [WebView], it is best practice to wait for
+  ///  the [WebView.onPageFinished] callback. This guarantees all the JavaScript
+  ///  embedded in the main frame HTML has been loaded.
+  Future<void> runJavascript(String javaScriptString) {
+    if (_settings.javascriptMode == JavascriptMode.disabled) {
+      return Future<void>.error(FlutterError(
+          'Javascript mode must be enabled/unrestricted when calling runJavascript.'));
+    }
+    return _webViewPlatformController.runJavascript(javaScriptString);
+  }
+
+  /// Runs the given JavaScript in the context of the current page, and returns the result.
+  ///
+  /// Depending on the value type the return value would be one of:
+  ///  - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').
+  ///  - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').
+  ///
+  /// The Future completes with an error if a JavaScript error occurred, or if the
+  /// type the given expression evaluates to is unsupported. Unsupported values include
+  /// certain non primitive types, as well as `undefined` or `null` on iOS 14+.
+  ///
+  /// When evaluating JavaScript in a [WebView], it is best practice to wait for
+  /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript
+  /// embedded in the main frame HTML has been loaded.
+  Future<String> runJavascriptReturningResult(String javaScriptString) {
+    if (_settings.javascriptMode == JavascriptMode.disabled) {
+      return Future<String>.error(FlutterError(
+          'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.'));
+    }
+    return _webViewPlatformController
+        .runJavascriptReturningResult(javaScriptString);
+  }
+
   /// Returns the title of the currently loaded page.
   Future<String?> getTitle() {
     return _webViewPlatformController.getTitle();
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
index 6e795f7..db52124 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
@@ -7,26 +7,30 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+/**
+ * The WkWebView used for the plugin.
+ *
+ * This class overrides some methods in `WKWebView` to serve the needs for the plugin.
+ */
+@interface FLTWKWebView : WKWebView
+@end
+
 @interface FLTWebViewController : NSObject <FlutterPlatformView, WKUIDelegate>
 
+@property(nonatomic) FLTWKWebView* webView;
+
 - (instancetype)initWithFrame:(CGRect)frame
                viewIdentifier:(int64_t)viewId
                     arguments:(id _Nullable)args
               binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
 
 - (UIView*)view;
+
+- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
 @end
 
 @interface FLTWebViewFactory : NSObject <FlutterPlatformViewFactory>
 - (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
 @end
 
-/**
- * The WkWebView used for the plugin.
- *
- * This class overrides some methods in `WKWebView` to serve the needs for the plugin.
- */
-@interface FLTWKWebView : WKWebView
-@end
-
 NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
index 7fd78ee..5e12f8a 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
@@ -151,6 +151,10 @@
     [self onCurrentUrl:call result:result];
   } else if ([[call method] isEqualToString:@"evaluateJavascript"]) {
     [self onEvaluateJavaScript:call result:result];
+  } else if ([[call method] isEqualToString:@"runJavascript"]) {
+    [self onRunJavaScript:call result:result sendReturnValue:NO];
+  } else if ([[call method] isEqualToString:@"runJavascriptReturningResult"]) {
+    [self onRunJavaScript:call result:result sendReturnValue:YES];
   } else if ([[call method] isEqualToString:@"addJavascriptChannels"]) {
     [self onAddJavaScriptChannels:call result:result];
   } else if ([[call method] isEqualToString:@"removeJavascriptChannels"]) {
@@ -244,6 +248,41 @@
              }];
 }
 
+- (void)onRunJavaScript:(FlutterMethodCall*)call
+                 result:(FlutterResult)result
+        sendReturnValue:(BOOL)sendReturnValue {
+  NSString* jsString = [call arguments];
+  if (!jsString) {
+    result([FlutterError errorWithCode:@"runJavascript_failed"
+                               message:@"JavaScript String cannot be null"
+                               details:nil]);
+    return;
+  }
+  [_webView
+      evaluateJavaScript:jsString
+       completionHandler:^(_Nullable id evaluateResult, NSError* _Nullable error) {
+         if (error) {
+           // WebKit will throw an error (WKErrorJavaScriptResultTypeIsUnsupported) when the
+           // type of the evaluated value is unsupported. This also goes for
+           // `null` and `undefined` on iOS 14+, for example when running a void function.
+           // For ease of use this specific error is ignored when no return value is expected.
+           BOOL sendError =
+               sendReturnValue || error.code != WKErrorJavaScriptResultTypeIsUnsupported;
+           result(sendError
+                      ? [FlutterError
+                            errorWithCode:(sendReturnValue ? @"runJavascriptReturningResult_failed"
+                                                           : @"runJavascript_failed")
+                                  message:@"Failed running JavaScript"
+                                  details:[NSString
+                                              stringWithFormat:@"JavaScript string was: '%@'\n%@",
+                                                               jsString, error]]
+                      : nil);
+           return;
+         }
+         result(sendReturnValue ? [NSString stringWithFormat:@"%@", evaluateResult] : nil);
+       }];
+}
+
 - (void)onAddJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result {
   NSArray* channelNames = [call arguments];
   NSSet* channelNamesSet = [[NSSet alloc] initWithArray:channelNames];
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec b/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
index 37905f1..2dfb336 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec
@@ -18,6 +18,6 @@
   s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
 
-  s.platform = :ios, '8.0'
+  s.platform = :ios, '9.0'
   s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
 end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index bfa4b80..5176adb 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.1.0
+version: 2.2.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"