diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/HttpRequestManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/HttpRequestManager.java new file mode 100644 index 000000000000..ab08455a58ae --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/HttpRequestManager.java @@ -0,0 +1,203 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.os.Handler; +import androidx.annotation.VisibleForTesting; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.Executor; + +/** Defines callback methods for the HttpRequestManager. */ +interface HttpRequestCallback { + void onComplete(String result); + + void onError(Exception error); +} + +/** + * Works around on Android WebView postUrl method to accept headers. + * + *

Android WebView does not provide a post request method that accepts headers. Only method that + * is provided is {@link android.webkit.WebView#postUrl(String, byte[])} and it accepts only URL and + * HTTP body. CustomHttpPostRequest is implemented to provide this feature since adding a header to + * post requests is a feature that is likely to be wanted. + * + *

In the implementation, {@link HttpURLConnection} is used to create a post request with the + * HTTP headers and the HTTP body. + */ +public class HttpRequestManager { + private final Executor executor; + private final Handler resultHandler; + + HttpRequestManager(Executor executor, Handler resultHandler) { + this.executor = executor; + this.resultHandler = resultHandler; + } + + /** + * Executes the given HTTP request in a background thread. See https://developer.android.com/guide/background/threading. + * + * @param request {@link WebViewRequest} to execute. + * @param callback methods to invoke after the HTTP request has completed. + */ + public void requestAsync(final WebViewRequest request, final HttpRequestCallback callback) { + executor.execute( + new Runnable() { + @Override + public void run() { + try { + String responseResult = request(request); + notifyComplete(responseResult, callback); + } catch (IOException e) { + notifyError(e, callback); + } + } + }); + } + + /** + * Executes the given HTTP request synchronously. + * + * @param request {@link WebViewRequest} to execute. + * @return The response body as a String. + */ + public String request(WebViewRequest request) throws IOException { + URL url = URLFactory.create(request.getUri()); + HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); + try { + // Basic request configuration + httpURLConnection.setConnectTimeout(5000); + httpURLConnection.setRequestMethod(request.getMethod().getValue().toUpperCase()); + + // Set HTTP headers + for (Map.Entry entry : request.getHeaders().entrySet()) { + httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue()); + } + + // Set HTTP body + if (request.getBody() != null && request.getBody().length > 0) { + // Used to enable streaming of a HTTP request body without internal buffering, + // when the content length is known in advance. It improves the performance + // because otherwise HTTPUrlConnection will be forced to buffer the complete + // request body in memory before it is transmitted, wasting (and possibly exhausting) + // heap and increasing latency. + httpURLConnection.setFixedLengthStreamingMode(request.getBody().length); + + httpURLConnection.setDoOutput(true); + OutputStream os = BufferedOutputStreamFactory.create(httpURLConnection.getOutputStream()); + os.write(request.getBody()); + os.flush(); + os.close(); + } + + // Collect and return response body + String line = ""; + StringBuilder contentBuilder = new StringBuilder(); + BufferedReader rd = + BufferedReaderFactory.create( + InputStreamReaderFactory.create(httpURLConnection.getInputStream())); + while ((line = rd.readLine()) != null) { + contentBuilder.append(line); + } + return contentBuilder.toString(); + } finally { + httpURLConnection.disconnect(); + } + } + + private void notifyComplete(final String responseResult, final HttpRequestCallback callback) { + resultHandler.post( + new Runnable() { + @Override + public void run() { + callback.onComplete(responseResult); + } + }); + } + + private void notifyError(final Exception error, final HttpRequestCallback callback) { + resultHandler.post( + new Runnable() { + @Override + public void run() { + callback.onError(error); + } + }); + } + /** Factory class for creating a {@link URL} */ + static class URLFactory { + /** + * Creates a {@link URL}. + * + *

Important: This method is visible for testing purposes only and should + * never be called from outside this class. + * + * @param url to create the instance for. + * @return The new {@link URL} object. + */ + @VisibleForTesting + public static URL create(String url) throws MalformedURLException { + return new URL(url); + } + } + /** Factory class for creating a {@link BufferedOutputStream} */ + static class BufferedOutputStreamFactory { + /** + * Creates a {@link BufferedOutputStream}. + * + *

Important: This method is visible for testing purposes only and should + * never be called from outside this class. + * + * @param stream to create the instance for. + * @return The new {@link BufferedOutputStream} object. + */ + @VisibleForTesting + public static BufferedOutputStream create(OutputStream stream) { + return new BufferedOutputStream(stream); + } + } + /** Factory class for creating a {@link BufferedReader} */ + static class BufferedReaderFactory { + /** + * Creates a {@link BufferedReader}. + * + *

Important: This method is visible for testing purposes only and should + * never be called from outside this class. + * + * @param stream to create the instance for. + * @return The new {@link BufferedReader} object. + */ + @VisibleForTesting + public static BufferedReader create(InputStreamReader stream) { + return new BufferedReader(stream); + } + } + /** Factory class for creating a {@link InputStreamReader} */ + static class InputStreamReaderFactory { + /** + * Creates a {@link InputStreamReader}. + * + *

Important: This method is visible for testing purposes only and should + * never be called from outside this class. + * + * @param stream to create the instance for. + * @return The new {@link InputStreamReader} object. + */ + @VisibleForTesting + public static InputStreamReader create(InputStream stream) { + return new InputStreamReader(stream); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewRequest.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewRequest.java new file mode 100644 index 000000000000..f5e80c3a178e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewRequest.java @@ -0,0 +1,107 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import java.util.Collections; +import java.util.Map; + +/** + * Defines the supported HTTP methods for loading a page in the {@link android.webkit.WebView} and + * the {@link HttpRequestManager}. + */ +enum WebViewLoadMethod { + GET("get"), + + POST("post"); + + private final String value; + + WebViewLoadMethod(String value) { + this.value = value; + } + + /** Converts to WebViewLoadMethod to String format. */ + public String serialize() { + return getValue(); + } + + /** Returns the enum value. */ + public String getValue() { + return value; + } + + /** Converts to String to WebViewLoadMethod format. */ + public static WebViewLoadMethod deserialize(String value) { + for (WebViewLoadMethod webViewLoadMethod : WebViewLoadMethod.values()) { + if (webViewLoadMethod.value.equals(value)) { + return webViewLoadMethod; + } + } + throw new IllegalArgumentException("No enum value found for '" + value + "'."); + } +} + +/** + * Creates a HTTP request object. + * + *

Defines the parameters that can be used to load a page in the {@link android.webkit.WebView} + * and the {@link HttpRequestManager}. + */ +public class WebViewRequest { + private final String uri; + private final WebViewLoadMethod method; + private final Map headers; + private final byte[] body; + + WebViewRequest(String uri, WebViewLoadMethod method, Map headers, byte[] body) { + this.uri = uri; + this.method = method; + this.headers = headers == null ? Collections.emptyMap() : headers; + this.body = body; + } + + /** + * Deserializes the request and the url to WebViewRequest instance. + * + * @param requestObject is the {@link io.flutter.plugin.common.MethodCall#arguments} to build + * WebViewRequest instance. + */ + @SuppressWarnings("unchecked") + static WebViewRequest fromMap(Map requestObject) { + String uri = (String) requestObject.get("uri"); + if (uri == null) { + return null; + } + + Map headers = (Map) requestObject.get("headers"); + + WebViewLoadMethod invokedMethod = + WebViewLoadMethod.deserialize((String) requestObject.get("method")); + + byte[] httpBody = (byte[]) requestObject.get("body"); + + return new WebViewRequest(uri, invokedMethod, headers, httpBody); + } + + /** Returns HTTP method in WebViewLoadMethod format. */ + public WebViewLoadMethod getMethod() { + return method; + } + + /** Returns base url. */ + public String getUri() { + return uri; + } + + /** Returns HTTP headers. */ + public Map getHeaders() { + return headers; + } + + /** Returns HTTP body. */ + public byte[] getBody() { + return body; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/HttpRequestManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/HttpRequestManagerTest.java new file mode 100644 index 000000000000..b2653097d471 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/HttpRequestManagerTest.java @@ -0,0 +1,296 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Handler; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class HttpRequestManagerTest { + + Executor mockExecutor; + Handler mockHandler; + HttpRequestManager httpRequestManager; + MockedStatic mockedURLFactory; + URL mockUrl; + MockedStatic mockedBufferedOutputStreamFactory; + BufferedOutputStream mockBufferedOutputStream; + MockedStatic mockedBufferedReaderFactory; + BufferedReader mockBufferedReader = mock(BufferedReader.class); + MockedStatic mockedInputStreamReaderFactory; + InputStreamReader mockInputStreamReader = mock(InputStreamReader.class); + + @Before + public void setup() { + mockExecutor = mock(Executor.class); + mockHandler = mock(Handler.class); + httpRequestManager = spy(new HttpRequestManager(mockExecutor, mockHandler)); + + mockUrl = mock(URL.class); + mockedURLFactory = mockStatic(HttpRequestManager.URLFactory.class); + mockedURLFactory + .when(() -> HttpRequestManager.URLFactory.create(ArgumentMatchers.any())) + .thenReturn(mockUrl); + + mockBufferedOutputStream = mock(BufferedOutputStream.class); + mockedBufferedOutputStreamFactory = + mockStatic(HttpRequestManager.BufferedOutputStreamFactory.class); + mockedBufferedOutputStreamFactory + .when( + () -> + HttpRequestManager.BufferedOutputStreamFactory.create( + ArgumentMatchers.any())) + .thenReturn(mockBufferedOutputStream); + + mockBufferedReader = mock(BufferedReader.class); + mockedBufferedReaderFactory = mockStatic(HttpRequestManager.BufferedReaderFactory.class); + mockedBufferedReaderFactory + .when( + () -> + HttpRequestManager.BufferedReaderFactory.create( + ArgumentMatchers.any())) + .thenReturn(mockBufferedReader); + + mockInputStreamReader = mock(InputStreamReader.class); + mockedInputStreamReaderFactory = mockStatic(HttpRequestManager.InputStreamReaderFactory.class); + mockedInputStreamReaderFactory + .when( + () -> + HttpRequestManager.InputStreamReaderFactory.create( + ArgumentMatchers.any())) + .thenReturn(mockInputStreamReader); + } + + @After + public void tearDown() { + mockedURLFactory.close(); + mockedBufferedOutputStreamFactory.close(); + mockedBufferedReaderFactory.close(); + mockedInputStreamReaderFactory.close(); + } + + @Test + public void request_shouldBuildAndExecuteRequest() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + Map headers = + new HashMap() { + { + put("3", "3"); + } + }; + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(new byte[] {0x02}); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(headers); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockUrl.openConnection()).thenReturn(mockConnection); + InputStream mockInputStream = mock(InputStream.class); + when(mockConnection.getInputStream()).thenReturn(mockInputStream); + when(mockBufferedReader.readLine()) + .thenAnswer( + new Answer() { + private int count = 0; + + public String answer(InvocationOnMock invocation) { + if (count++ == 3) { + return null; + } + return "*"; + } + }); + + // Execute + String resp = httpRequestManager.request(request); + + // Validation + mockedURLFactory.verify(() -> HttpRequestManager.URLFactory.create("1")); + // Verify setting of basic request properties + verify(mockConnection, times(1)).setConnectTimeout(5000); + verify(mockConnection, times(1)).setRequestMethod("post"); + // Verify header is being set + verify(mockConnection, times(1)).setRequestProperty("3", "3"); + // Verify request body is set + verify(mockConnection, times(1)).setFixedLengthStreamingMode(1); + verify(mockConnection, times(1)).setDoOutput(true); + verify(mockBufferedOutputStream, times(1)).write(new byte[] {0x02}, 0, 1); + verify(mockBufferedOutputStream, times(1)).flush(); + // Verify response body is being collected and returned + mockedInputStreamReaderFactory.verify( + () -> HttpRequestManager.InputStreamReaderFactory.create(mockInputStream)); + mockedBufferedReaderFactory.verify( + () -> HttpRequestManager.BufferedReaderFactory.create(mockInputStreamReader)); + verify(mockBufferedReader, times(4)).readLine(); + assertEquals("***", resp); + // Verify cleanup + verify(mockConnection, times(1)).disconnect(); + } + + @Test + public void request_shouldNotSetHeadersWhenNoneAreProvided() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(new byte[] {0x02}); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(Collections.emptyMap()); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockUrl.openConnection()).thenReturn(mockConnection); + + // Execute + httpRequestManager.request(request); + + // Validation + verify(mockConnection, never()).setRequestProperty(anyString(), anyString()); + } + + @Test + public void request_shouldNotSetBodyWhenNoneIsProvided() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(null); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(Collections.emptyMap()); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockUrl.openConnection()).thenReturn(mockConnection); + + // Execute + httpRequestManager.request(request); + + // Validation + verify(mockConnection, never()).setFixedLengthStreamingMode(anyInt()); + verify(mockConnection, never()).setDoOutput(anyBoolean()); + verify(mockBufferedOutputStream, never()).write(any(), anyInt(), anyInt()); + verify(mockBufferedOutputStream, never()).flush(); + } + + @Test + public void requestAsync_shouldScheduleRequest() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(null); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(Collections.emptyMap()); + HttpRequestCallback mockCallback = mock(HttpRequestCallback.class); + + // Execute + httpRequestManager.requestAsync(request, mockCallback); + + // Validation + verify(mockExecutor, times(1)).execute(any()); + } + + @Test + public void requestAsync_shouldCallOnCompleteCallbackOnSuccess() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(null); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(Collections.emptyMap()); + HttpRequestCallback mockCallback = mock(HttpRequestCallback.class); + doAnswer( + (Answer) + invocationOnMock -> { + Runnable runnable = invocationOnMock.getArgument(0, Runnable.class); + runnable.run(); + return null; + }) + .when(mockExecutor) + .execute(any()); + doAnswer( + (Answer) + invocationOnMock -> { + Runnable runnable = invocationOnMock.getArgument(0, Runnable.class); + runnable.run(); + return null; + }) + .when(mockHandler) + .post(any()); + doReturn("RESPONSE").when(httpRequestManager).request(any()); + + // Execute + httpRequestManager.requestAsync(request, mockCallback); + + // Validation + verify(mockHandler, times(1)).post(any()); + verify(mockCallback, never()).onError(any()); + verify(mockCallback, times(1)).onComplete("RESPONSE"); + } + + @Test + public void requestAsync_shouldCallOnErrorCallbackOnIOException() throws IOException { + // Preparation + WebViewRequest request = mock(WebViewRequest.class); + when(request.getUri()).thenReturn("1"); + when(request.getBody()).thenReturn(null); + when(request.getMethod()).thenReturn(WebViewLoadMethod.POST); + when(request.getHeaders()).thenReturn(Collections.emptyMap()); + HttpRequestCallback mockCallback = mock(HttpRequestCallback.class); + doAnswer( + (Answer) + invocationOnMock -> { + Runnable runnable = invocationOnMock.getArgument(0, Runnable.class); + runnable.run(); + return null; + }) + .when(mockExecutor) + .execute(any()); + doAnswer( + (Answer) + invocationOnMock -> { + Runnable runnable = invocationOnMock.getArgument(0, Runnable.class); + runnable.run(); + return null; + }) + .when(mockHandler) + .post(any()); + IOException exception = new IOException(); + doThrow(exception).when(httpRequestManager).request(any()); + + // Execute + httpRequestManager.requestAsync(request, mockCallback); + + // Validation + verify(mockHandler, times(1)).post(any()); + verify(mockCallback, never()).onComplete(any()); + verify(mockCallback, times(1)).onError(exception); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewRequestTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewRequestTest.java new file mode 100644 index 000000000000..c3ec42151dc3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewRequestTest.java @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class WebViewRequestTest { + + @Test + public void webViewLoadMethod_serialize_shouldReturnValue() { + assertEquals("get", WebViewLoadMethod.GET.serialize()); + assertEquals("post", WebViewLoadMethod.POST.serialize()); + } + + @Test + public void webViewLoadMethod_deserialize_shouldReturnEnumValue() { + assertEquals(WebViewLoadMethod.GET, WebViewLoadMethod.deserialize("get")); + assertEquals(WebViewLoadMethod.POST, WebViewLoadMethod.deserialize("post")); + } + + @Test(expected = IllegalArgumentException.class) + public void webViewLoadMethod_deserialize_shouldThrowIllegalArgumentExceptionForUnknownValue() { + WebViewLoadMethod.deserialize("fakeMethod"); + } + + @Test + public void webViewRequest_shouldConstructWithGivenParams() { + Map headers = + new HashMap() { + { + put("3", "3"); + } + }; + byte[] body = {0x04}; + WebViewRequest req = new WebViewRequest("1", WebViewLoadMethod.POST, headers, body); + + assertEquals(req.getUri(), "1"); + assertEquals(req.getMethod(), WebViewLoadMethod.POST); + assertEquals(req.getHeaders(), headers); + assertEquals(req.getBody(), body); + } + + @Test + public void webViewRequest_shouldConstructFromMap() { + final Map headers = + new HashMap() { + { + put("3", "3"); + } + }; + final byte[] body = {0x04}; + WebViewRequest req = + WebViewRequest.fromMap( + new HashMap() { + { + put("url", "1"); + put("method", "post"); + put("headers", headers); + put("body", body); + } + }); + + assertEquals(req.getUri(), "1"); + assertEquals(req.getMethod(), WebViewLoadMethod.POST); + assertEquals(req.getHeaders(), headers); + assertEquals(req.getBody(), body); + } +} 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 bc43c16d4498..4af98d11b8f9 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; @@ -158,6 +159,7 @@ enum _MenuOptions { navigationDelegate, loadLocalFile, loadHtmlString, + doPostRequest, } class _SampleMenu extends StatelessWidget { @@ -201,6 +203,9 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.loadHtmlString: _onLoadHtmlStringExample(controller.data!, context); break; + case _MenuOptions.doPostRequest: + _onDoPostRequest(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -241,6 +246,10 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.loadLocalFile, child: Text('Load local file'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.doPostRequest, + child: Text('Post Request'), + ), ], ); }, @@ -330,6 +339,17 @@ class _SampleMenu extends StatelessWidget { await controller.loadHtmlString(kExamplePage); } + void _onDoPostRequest( + WebViewController controller, BuildContext context) async { + WebViewRequest request = WebViewRequest( + uri: Uri.parse('https://httpbin.org/post'), + method: WebViewRequestMethod.post, + headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + await controller.loadRequest(request); + } + Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index 654abbd960cd..b7e3b3ee66ca 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 @@ -402,6 +402,11 @@ class WebViewController { return _webViewPlatformController.loadUrl(url, headers); } + // Loads a page by making the specified request. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`.