[webview_flutter] controller headers loadurl (#1516)

* Modify loadUrl to allow headers

* Add test for headers

* pubspec and changelog

* fix analyze errors

* Add a test for webview headers

* analyze errors

* Add print to debug tests

* fix test
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 8985c7b..5048781 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.6
+
+* Add an optional `headers` field to the controller.
+
 ## 0.3.5+5
 
 * Fixed error in documentation of `javascriptChannels`.
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index 21720cf..06cbb39 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -18,6 +18,7 @@
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
 import io.flutter.plugin.common.MethodChannel.Result;
 import io.flutter.plugin.platform.PlatformView;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -100,9 +101,15 @@
     }
   }
 
+  @SuppressWarnings("unchecked")
   private void loadUrl(MethodCall methodCall, Result result) {
-    String url = (String) methodCall.arguments;
-    webView.loadUrl(url);
+    Map<String, Object> request = (Map<String, Object>) methodCall.arguments;
+    String url = (String) request.get("url");
+    Map<String, String> headers = (Map<String, String>) request.get("headers");
+    if (headers == null) {
+      headers = Collections.emptyMap();
+    }
+    webView.loadUrl(url, headers);
     result.success(null);
   }
 
diff --git a/packages/webview_flutter/example/test_driver/webview.dart b/packages/webview_flutter/example/test_driver/webview.dart
index 03de967..8b56878 100644
--- a/packages/webview_flutter/example/test_driver/webview.dart
+++ b/packages/webview_flutter/example/test_driver/webview.dart
@@ -54,6 +54,39 @@
     final String currentUrl = await controller.currentUrl();
     expect(currentUrl, 'https://www.google.com/');
   });
+
+  test('loadUrl with headers', () async {
+    final Completer<WebViewController> controllerCompleter =
+        Completer<WebViewController>();
+    await pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebView(
+          key: GlobalKey(),
+          initialUrl: 'https://flutter.dev/',
+          onWebViewCreated: (WebViewController controller) {
+            controllerCompleter.complete(controller);
+          },
+          javascriptMode: JavascriptMode.unrestricted,
+        ),
+      ),
+    );
+    final WebViewController controller = await controllerCompleter.future;
+    final Map<String, String> headers = <String, String>{
+      'test_header': 'flutter_test_header'
+    };
+    await controller.loadUrl('https://flutter-header-echo.herokuapp.com/',
+        headers: headers);
+    final String currentUrl = await controller.currentUrl();
+    expect(currentUrl, 'https://flutter-header-echo.herokuapp.com/');
+
+    // wait for the web page to load.
+    await Future<dynamic>.delayed(const Duration(seconds: 5));
+
+    final String content = await controller
+        .evaluateJavascript('document.documentElement.innerText');
+    expect(content.contains('flutter_test_header'), isTrue);
+  });
 }
 
 Future<void> pumpWidget(Widget widget) {
diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m
index ea3b192..afc2f99 100644
--- a/packages/webview_flutter/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m
@@ -123,11 +123,11 @@
 }
 
 - (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
-  NSString* url = [call arguments];
-  if (![self loadUrl:url]) {
-    result([FlutterError errorWithCode:@"loadUrl_failed"
-                               message:@"Failed parsing the URL"
-                               details:[NSString stringWithFormat:@"URL was: '%@'", url]]);
+  if (![self loadRequest:[call arguments]]) {
+    result([FlutterError
+        errorWithCode:@"loadUrl_failed"
+              message:@"Failed parsing the URL"
+              details:[NSString stringWithFormat:@"Request was: '%@'", [call arguments]]]);
   } else {
     result(nil);
   }
@@ -256,13 +256,36 @@
   }
 }
 
+- (bool)loadRequest:(NSDictionary<NSString*, id>*)request {
+  if (!request) {
+    return false;
+  }
+
+  NSString* url = request[@"url"];
+  if ([url isKindOfClass:[NSString class]]) {
+    id headers = request[@"headers"];
+    if ([headers isKindOfClass:[NSDictionary class]]) {
+      return [self loadUrl:url withHeaders:headers];
+    } else {
+      return [self loadUrl:url];
+    }
+  }
+
+  return false;
+}
+
 - (bool)loadUrl:(NSString*)url {
+  return [self loadUrl:url withHeaders:[NSMutableDictionary dictionary]];
+}
+
+- (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*)headers {
   NSURL* nsUrl = [NSURL URLWithString:url];
   if (!nsUrl) {
     return false;
   }
-  NSURLRequest* req = [NSURLRequest requestWithURL:nsUrl];
-  [_webView loadRequest:req];
+  NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:nsUrl];
+  [request setAllHTTPHeaderFields:headers];
+  [_webView loadRequest:request];
   return true;
 }
 
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index 49063a2..2f5bffc 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -414,13 +414,19 @@
   /// `url` must not be null.
   ///
   /// Throws an ArgumentError if `url` is not a valid URL string.
-  Future<void> loadUrl(String url) async {
+  Future<void> loadUrl(
+    String url, {
+    Map<String, String> headers,
+  }) async {
     assert(url != null);
     _validateUrlString(url);
     // 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 _channel.invokeMethod('loadUrl', url);
+    return _channel.invokeMethod('loadUrl', <String, dynamic>{
+      'url': url,
+      'headers': headers,
+    });
   }
 
   /// Accessor to the current URL that the WebView is displaying.
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index 943f624..183a11d 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
 name: webview_flutter
 description: A Flutter plugin that provides a WebView widget on Android and iOS.
-version: 0.3.5+5
+version: 0.3.6
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
 
diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart
index d66f56d..67a5547 100644
--- a/packages/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/test/webview_flutter_test.dart
@@ -106,6 +106,25 @@
     expect(await controller.currentUrl(), isNull);
   });
 
+  testWidgets('Headers in loadUrl', (WidgetTester tester) async {
+    WebViewController controller;
+    await tester.pumpWidget(
+      WebView(
+        onWebViewCreated: (WebViewController webViewController) {
+          controller = webViewController;
+        },
+      ),
+    );
+
+    expect(controller, isNotNull);
+
+    final Map<String, String> headers = <String, String>{
+      'CACHE-CONTROL': 'ABC'
+    };
+    await controller.loadUrl('https://flutter.io', headers: headers);
+    expect(await controller.currentUrl(), equals('https://flutter.io'));
+  });
+
   testWidgets("Can't go back before loading a page",
       (WidgetTester tester) async {
     WebViewController controller;
@@ -722,8 +741,8 @@
   Future<dynamic> onMethodCall(MethodCall call) {
     switch (call.method) {
       case 'loadUrl':
-        final String url = call.arguments;
-        _loadUrl(url);
+        final Map<dynamic, dynamic> request = call.arguments;
+        _loadUrl(request['url']);
         return Future<void>.sync(() {});
       case 'updateSettings':
         if (call.arguments['jsMode'] != null) {