Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

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

Merged
merged 23 commits into from Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6bc4fa8
Add android implementations for loadRequest.
BeMacized Dec 1, 2021
2557862
Update changelog and pubspec.
BeMacized Dec 1, 2021
986217b
Fix comment.
BeMacized Dec 1, 2021
493ad5d
Fix comment.
BeMacized Dec 1, 2021
ab49b54
Add tests
BeMacized Dec 2, 2021
8ca591a
Add back removed license headers
BeMacized Dec 2, 2021
78f9968
Fix analysis error
BeMacized Dec 2, 2021
425251e
Merge branch 'webview_flutter/request_url_native_android' into webvie…
BeMacized Dec 3, 2021
be39a75
Add support for loadRequest to app facing package
BeMacized Dec 3, 2021
706df11
Add test
BeMacized Dec 3, 2021
f024a18
Comment pending dependency in pubspec
BeMacized Dec 3, 2021
7e0a385
Remove workaround for supporting custom headers when making post requ…
BeMacized Dec 6, 2021
ecd7027
Merge branch 'webview_flutter/request_url_native_android' into webvie…
BeMacized Dec 6, 2021
bd65812
Document android workaround in dart doc
BeMacized Dec 6, 2021
6110116
Enforce uri scheme
BeMacized Dec 6, 2021
b5cb337
Merge branch 'webview_flutter/request_url_native_android' into webvie…
BeMacized Dec 6, 2021
885abad
Merge remote-tracking branch 'upstream/master' into webview_flutter/r…
BeMacized Dec 6, 2021
b3f50e7
Update loadRequest dartdoc
BeMacized Dec 6, 2021
712e5ae
Document android workaround in readme
BeMacized Dec 6, 2021
f02ea2a
Processed PR feedback.
BeMacized Dec 6, 2021
a4d2340
Merge branch 'webview_flutter/request_url_native_android' into webvie…
BeMacized Dec 6, 2021
37ce0f8
Merge remote-tracking branch 'upstream/master' into webview_flutter/r…
BeMacized Dec 7, 2021
3434a01
Updated dependency, version and changelog.
BeMacized Dec 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/webview_flutter/webview_flutter/README.md
Expand Up @@ -92,3 +92,8 @@ android {

To use Material Components when the user interacts with input elements in the WebView,
follow the steps described in the [Enabling Material Components instructions](https://flutter.dev/docs/deployment/android#enabling-material-components).

### Setting custom headers on POST requests

Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android.
If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHTMLString` instead.
20 changes: 20 additions & 0 deletions packages/webview_flutter/webview_flutter/example/lib/main.dart
Expand Up @@ -7,6 +7,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
Expand Down Expand Up @@ -133,6 +134,7 @@ enum MenuOptions {
listCache,
clearCache,
navigationDelegate,
doPostRequest,
}

class SampleMenu extends StatelessWidget {
Expand Down Expand Up @@ -171,6 +173,9 @@ class SampleMenu extends StatelessWidget {
case MenuOptions.navigationDelegate:
_onNavigationDelegateExample(controller.data!, context);
break;
case MenuOptions.doPostRequest:
_onDoPostRequest(controller.data!, context);
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
Expand Down Expand Up @@ -203,6 +208,10 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.navigationDelegate,
child: Text('Navigation Delegate example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.doPostRequest,
child: Text('Post Request'),
),
],
);
},
Expand Down Expand Up @@ -279,6 +288,17 @@ class SampleMenu extends StatelessWidget {
await controller.loadUrl('data:text/html;base64,$contentBase64');
}

Future<void> _onDoPostRequest(
WebViewController controller, BuildContext context) async {
final WebViewRequest request = WebViewRequest(
uri: Uri.parse('https://httpbin.org/post'),
method: WebViewRequestMethod.post,
headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
body: Uint8List.fromList('Test Body'.codeUnits),
);
await controller.loadRequest(request);
}

Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
Expand Down
Expand Up @@ -21,4 +21,6 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte
WebSetting,
WebSettings,
WebResourceError,
WebResourceErrorType;
WebResourceErrorType,
WebViewRequest,
WebViewRequestMethod;
20 changes: 20 additions & 0 deletions packages/webview_flutter/webview_flutter/lib/src/webview.dart
Expand Up @@ -513,6 +513,26 @@ class WebViewController {
return _webViewPlatformController.loadUrl(url, headers);
}

/// Makes a specific HTTP request ands loads the response in the webview.
BeMacized marked this conversation as resolved.
Show resolved Hide resolved
///
/// [WebViewRequest.method] must be one of the supported HTTP methods
/// in [WebViewRequestMethod].
///
/// If [WebViewRequest.headers] is not empty, its key-value pairs will be
/// added as the headers for the request.
///
/// If [WebViewRequest.body] is not null, it will be added as the body
/// for the request.
///
/// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme.
///
/// Android only:
/// When making a POST request, headers are ignored. As a workaround, make
/// the request manually and load the response data using [loadHTMLString].
Future<void> loadRequest(WebViewRequest request) async {
return _webViewPlatformController.loadRequest(request);
}

/// Accessor to the current URL that the WebView is displaying.
///
/// If [WebView.initialUrl] was never specified, returns `null`.
Expand Down
1 change: 1 addition & 0 deletions packages/webview_flutter/webview_flutter/pubspec.yaml
Expand Up @@ -19,6 +19,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
# TODO(BeMacized): Update dependency once it has been merged and published (#4563)
webview_flutter_android: ^2.3.1
webview_flutter_platform_interface: ^1.5.2
webview_flutter_wkwebview: ^2.4.0
Expand Down
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:flutter/src/foundation/basic_types.dart';
import 'package:flutter/src/gestures/recognizer.dart';
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -154,6 +156,29 @@ void main() {
));
});

testWidgets('loadRequest', (WidgetTester tester) async {
WebViewController? controller;
await tester.pumpWidget(
WebView(
onWebViewCreated: (WebViewController webViewController) {
controller = webViewController;
},
),
);
expect(controller, isNotNull);

final WebViewRequest req = WebViewRequest(
uri: Uri.parse('https://flutter.dev'),
method: WebViewRequestMethod.post,
headers: <String, String>{'foo': 'bar'},
body: Uint8List.fromList('Test Body'.codeUnits),
);

await controller!.loadRequest(req);

verify(mockWebViewPlatformController.loadRequest(req));
});

testWidgets('Clear Cache', (WidgetTester tester) async {
WebViewController? controller;
await tester.pumpWidget(
Expand Down
Expand Up @@ -8,19 +8,19 @@

import 'dart:async' as _i9;

import 'package:flutter/foundation.dart' as _i7;
import 'package:flutter/foundation.dart' as _i3;
import 'package:flutter/gestures.dart' as _i8;
import 'package:flutter/widgets.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart'
as _i6;
as _i7;
import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform.dart'
as _i3;
as _i4;
import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_callbacks_handler.dart'
as _i5;
as _i6;
import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_controller.dart'
as _i10;
import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i4;
import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i5;

// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
Expand All @@ -33,26 +33,26 @@ import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i4;

class _FakeWidget_0 extends _i1.Fake implements _i2.Widget {
@override
String toString({_i2.DiagnosticLevel? minLevel = _i2.DiagnosticLevel.info}) =>
String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) =>
super.toString();
}

/// A class which mocks [WebViewPlatform].
///
/// See the documentation for Mockito's code generation for more information.
class MockWebViewPlatform extends _i1.Mock implements _i3.WebViewPlatform {
class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform {
MockWebViewPlatform() {
_i1.throwOnMissingStub(this);
}

@override
_i2.Widget build(
{_i2.BuildContext? context,
_i4.CreationParams? creationParams,
_i5.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler,
_i6.JavascriptChannelRegistry? javascriptChannelRegistry,
_i3.WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
Set<_i7.Factory<_i8.OneSequenceGestureRecognizer>>?
_i5.CreationParams? creationParams,
_i6.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler,
_i7.JavascriptChannelRegistry? javascriptChannelRegistry,
_i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>?
gestureRecognizers}) =>
(super.noSuchMethod(
Invocation.method(#build, [], {
Expand Down Expand Up @@ -81,13 +81,29 @@ class MockWebViewPlatformController extends _i1.Mock
_i1.throwOnMissingStub(this);
}

@override
_i9.Future<void> loadFile(String? absoluteFilePath) =>
(super.noSuchMethod(Invocation.method(#loadFile, [absoluteFilePath]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i9.Future<void> loadHtmlString(String? html, {String? baseUrl}) =>
(super.noSuchMethod(
Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i9.Future<void> loadUrl(String? url, Map<String, String>? headers) =>
(super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i9.Future<void> updateSettings(_i4.WebSettings? setting) =>
_i9.Future<void> loadRequest(_i5.WebViewRequest? request) =>
(super.noSuchMethod(Invocation.method(#loadRequest, [request]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i9.Future<void> updateSettings(_i5.WebSettings? setting) =>
(super.noSuchMethod(Invocation.method(#updateSettings, [setting]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
Expand Down
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
## 2.5.0

* Adds support for the `loadRequest` method from the platform interface.

## 2.4.0

* Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface.
Expand Down
Expand Up @@ -185,6 +185,8 @@ void loadDataWithBaseUrl(

void loadUrl(Long instanceId, String url, Map<String, String> headers);

void postUrl(Long instanceId, String url, byte[] data);

String getUrl(Long instanceId);

Boolean canGoBack(Long instanceId);
Expand Down Expand Up @@ -407,6 +409,39 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.postUrl", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
Map<String, Object> wrapped = new HashMap<>();
try {
ArrayList<Object> args = (ArrayList<Object>) message;
Number instanceIdArg = (Number) args.get(0);
if (instanceIdArg == null) {
throw new NullPointerException("instanceIdArg unexpectedly null.");
}
String urlArg = (String) args.get(1);
if (urlArg == null) {
throw new NullPointerException("urlArg unexpectedly null.");
}
byte[] dataArg = (byte[]) args.get(2);
if (dataArg == null) {
throw new NullPointerException("dataArg unexpectedly null.");
}
api.postUrl(instanceIdArg.longValue(), urlArg, dataArg);
wrapped.put("result", null);
} catch (Error | RuntimeException exception) {
wrapped.put("error", wrapError(exception));
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
Expand Down
Expand Up @@ -382,6 +382,12 @@ public void loadUrl(Long instanceId, String url, Map<String, String> headers) {
webView.loadUrl(url, headers);
}

@Override
public void postUrl(Long instanceId, String url, byte[] data) {
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
webView.postUrl(url, data);
}

@Override
public String getUrl(Long instanceId) {
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
Expand Down
Expand Up @@ -208,6 +208,12 @@ public void loadUrl() {
verify(mockWebView).loadUrl("https://www.google.com", new HashMap<>());
}

@Test
public void postUrl() {
testHostApiImpl.postUrl(0L, "https://www.google.com", new byte[] {0x01, 0x02});
verify(mockWebView).postUrl("https://www.google.com", new byte[] {0x01, 0x02});
}

@Test
public void getUrl() {
when(mockWebView.getUrl()).thenReturn("https://www.google.com");
Expand Down
Expand Up @@ -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';
Expand Down Expand Up @@ -158,6 +159,7 @@ enum _MenuOptions {
navigationDelegate,
loadLocalFile,
loadHtmlString,
doPostRequest,
}

class _SampleMenu extends StatelessWidget {
Expand Down Expand Up @@ -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) => <PopupMenuItem<_MenuOptions>>[
Expand Down Expand Up @@ -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'),
),
],
);
},
Expand Down Expand Up @@ -330,6 +339,16 @@ class _SampleMenu extends StatelessWidget {
await controller.loadHtmlString(kExamplePage);
}

Future<void> _onDoPostRequest(
WebViewController controller, BuildContext context) async {
final WebViewRequest request = WebViewRequest(
uri: Uri.parse('https://httpbin.org/post'),
method: WebViewRequestMethod.post,
body: Uint8List.fromList('Test Body'.codeUnits),
);
await controller.loadRequest(request);
}

Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
Expand Down
Expand Up @@ -402,6 +402,11 @@ class WebViewController {
return _webViewPlatformController.loadUrl(url, headers);
}

/// Loads a page by making the specified request.
Future<void> loadRequest(WebViewRequest request) async {
return _webViewPlatformController.loadRequest(request);
}

/// Accessor to the current URL that the WebView is displaying.
///
/// If [WebView.initialUrl] was never specified, returns `null`.
Expand Down