[webview_flutter] Fixes bug when onNavigationRequestCallback returns false (#5981)

diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 66393c8..67c633f 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.8.13
+
+* Fixes a bug which causes an exception when the `onNavigationRequestCallback` return `false`.
+
 ## 2.8.12
 
 * Bumps mockito-inline from 3.11.1 to 4.6.1.
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index f1b130c..ff6265d 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -652,8 +652,8 @@
 
     if (returnValue is bool && returnValue) {
       loadUrl!(url, <String, String>{});
-    } else {
-      (returnValue as Future<bool>).then((bool shouldLoadUrl) {
+    } else if (returnValue is Future<bool>) {
+      returnValue.then((bool shouldLoadUrl) {
         if (shouldLoadUrl) {
           loadUrl!(url, <String, String>{});
         }
@@ -677,8 +677,8 @@
 
     if (returnValue is bool && returnValue) {
       loadUrl!(request.url, <String, String>{});
-    } else {
-      (returnValue as Future<bool>).then((bool shouldLoadUrl) {
+    } else if (returnValue is Future<bool>) {
+      returnValue.then((bool shouldLoadUrl) {
         if (shouldLoadUrl) {
           loadUrl!(request.url, <String, String>{});
         }
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 53f25c7..759e9d7 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/main/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.12
+version: 2.8.13
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index a987f1c..2432b35 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:typed_data';
 
 import 'package:flutter/widgets.dart';
@@ -24,6 +25,7 @@
   android_webview.WebSettings,
   android_webview.WebStorage,
   android_webview.WebView,
+  android_webview.WebResourceRequest,
   WebViewAndroidDownloadListener,
   WebViewAndroidJavaScriptChannel,
   WebViewAndroidWebChromeClient,
@@ -843,4 +845,192 @@
       verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false));
     });
   });
+
+  group('WebViewAndroidWebViewClient', () {
+    test(
+        'urlLoading should call loadUrl when onNavigationRequestCallback returns true',
+        () {
+      final Completer<void> completer = Completer<void>();
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  true,
+              loadUrl: (String url, Map<String, String>? headers) async {
+                completer.complete();
+              });
+
+      webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+      expect(completer.isCompleted, isTrue);
+    });
+
+    test(
+        'urlLoading should call loadUrl when onNavigationRequestCallback returns a Future true',
+        () async {
+      final Completer<void> completer = Completer<void>();
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  Future<bool>.value(true),
+              loadUrl: (String url, Map<String, String>? headers) async {
+                completer.complete();
+              });
+
+      webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+      expect(completer.future, completes);
+    });
+
+    test(
+        'urlLoading should not call laodUrl when onNavigationRequestCallback returns false',
+        () async {
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  false,
+              loadUrl: (String url, Map<String, String>? headers) async {
+                fail(
+                    'loadUrl should not be called if onNavigationRequestCallback returns false.');
+              });
+
+      webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+    });
+
+    test(
+        'urlLoading should not call loadUrl when onNavigationRequestCallback returns a Future false',
+        () {
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  Future<bool>.value(false),
+              loadUrl: (String url, Map<String, String>? headers) async {
+                fail(
+                    'loadUrl should not be called if onNavigationRequestCallback returns false.');
+              });
+
+      webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+    });
+
+    test(
+        'requestLoading should call loadUrl when onNavigationRequestCallback returns true',
+        () {
+      final Completer<void> completer = Completer<void>();
+      final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+      when(mockRequest.isForMainFrame).thenReturn(true);
+      when(mockRequest.url).thenReturn('https://flutter.dev');
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  true,
+              loadUrl: (String url, Map<String, String>? headers) async {
+                expect(url, 'https://flutter.dev');
+                completer.complete();
+              });
+
+      webViewClient.requestLoading(MockWebView(), mockRequest);
+      expect(completer.isCompleted, isTrue);
+    });
+
+    test(
+        'requestLoading should call loadUrl when onNavigationRequestCallback returns a Future true',
+        () async {
+      final Completer<void> completer = Completer<void>();
+      final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+      when(mockRequest.isForMainFrame).thenReturn(true);
+      when(mockRequest.url).thenReturn('https://flutter.dev');
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  Future<bool>.value(true),
+              loadUrl: (String url, Map<String, String>? headers) async {
+                expect(url, 'https://flutter.dev');
+                completer.complete();
+              });
+
+      webViewClient.requestLoading(MockWebView(), mockRequest);
+      expect(completer.future, completes);
+    });
+
+    test(
+        'requestLoading should not call loadUrl when onNavigationRequestCallback returns false',
+        () {
+      final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+      when(mockRequest.isForMainFrame).thenReturn(true);
+      when(mockRequest.url).thenReturn('https://flutter.dev');
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  false,
+              loadUrl: (String url, Map<String, String>? headers) {
+                fail(
+                    'loadUrl should not be called if onNavigationRequestCallback returns false.');
+              });
+
+      webViewClient.requestLoading(MockWebView(), mockRequest);
+    });
+
+    test(
+        'requestLoading should not call loadUrl when onNavigationRequestCallback returns a Future false',
+        () {
+      final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+      when(mockRequest.isForMainFrame).thenReturn(true);
+      when(mockRequest.url).thenReturn('https://flutter.dev');
+      final WebViewAndroidWebViewClient webViewClient =
+          WebViewAndroidWebViewClient.handlesNavigation(
+              onPageStartedCallback: (_) {},
+              onPageFinishedCallback: (_) {},
+              onWebResourceErrorCallback: (_) {},
+              onNavigationRequestCallback: ({
+                required bool isForMainFrame,
+                required String url,
+              }) =>
+                  Future<bool>.value(false),
+              loadUrl: (String url, Map<String, String>? headers) {
+                fail(
+                    'loadUrl should not be called if onNavigationRequestCallback returns false.');
+              });
+
+      webViewClient.requestLoading(MockWebView(), mockRequest);
+    });
+  });
 }
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index 3385e79..78e60ca 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -286,6 +286,36 @@
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
 }
 
+/// A class which mocks [WebResourceRequest].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWebResourceRequest extends _i1.Mock
+    implements _i2.WebResourceRequest {
+  MockWebResourceRequest() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  String get url =>
+      (super.noSuchMethod(Invocation.getter(#url), returnValue: '') as String);
+  @override
+  bool get isForMainFrame => (super
+          .noSuchMethod(Invocation.getter(#isForMainFrame), returnValue: false)
+      as bool);
+  @override
+  bool get hasGesture =>
+      (super.noSuchMethod(Invocation.getter(#hasGesture), returnValue: false)
+          as bool);
+  @override
+  String get method =>
+      (super.noSuchMethod(Invocation.getter(#method), returnValue: '')
+          as String);
+  @override
+  Map<String, String> get requestHeaders =>
+      (super.noSuchMethod(Invocation.getter(#requestHeaders),
+          returnValue: <String, String>{}) as Map<String, String>);
+}
+
 /// A class which mocks [WebViewAndroidDownloadListener].
 ///
 /// See the documentation for Mockito's code generation for more information.