Skip to content

Commit ac7ed7c

Browse files
mvanbeusekomKyleFin
authored andcommitted
[webview_flutter] Android implementation of loadFile and loadHtmlString methods (flutter#4544)
1 parent a6cfb50 commit ac7ed7c

18 files changed

+794
-78
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 2.4.0
22

3+
* Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface.
34
* Updates to webview_flutter_platform_interface version 1.5.2.
45

56
## 2.3.1

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ public interface WebViewHostApi {
173173

174174
void dispose(Long instanceId);
175175

176+
void loadData(Long instanceId, String data, String mimeType, String encoding);
177+
178+
void loadDataWithBaseUrl(
179+
Long instanceId,
180+
String baseUrl,
181+
String data,
182+
String mimeType,
183+
String encoding,
184+
String historyUrl);
185+
176186
void loadUrl(Long instanceId, String url, Map<String, String> headers);
177187

178188
String getUrl(Long instanceId);
@@ -274,6 +284,96 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) {
274284
channel.setMessageHandler(null);
275285
}
276286
}
287+
{
288+
BasicMessageChannel<Object> channel =
289+
new BasicMessageChannel<>(
290+
binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.loadData", getCodec());
291+
if (api != null) {
292+
channel.setMessageHandler(
293+
(message, reply) -> {
294+
Map<String, Object> wrapped = new HashMap<>();
295+
try {
296+
ArrayList<Object> args = (ArrayList<Object>) message;
297+
Number instanceIdArg = (Number) args.get(0);
298+
if (instanceIdArg == null) {
299+
throw new NullPointerException("instanceIdArg unexpectedly null.");
300+
}
301+
String dataArg = (String) args.get(1);
302+
if (dataArg == null) {
303+
throw new NullPointerException("dataArg unexpectedly null.");
304+
}
305+
String mimeTypeArg = (String) args.get(2);
306+
if (mimeTypeArg == null) {
307+
throw new NullPointerException("mimeTypeArg unexpectedly null.");
308+
}
309+
String encodingArg = (String) args.get(3);
310+
if (encodingArg == null) {
311+
throw new NullPointerException("encodingArg unexpectedly null.");
312+
}
313+
api.loadData(instanceIdArg.longValue(), dataArg, mimeTypeArg, encodingArg);
314+
wrapped.put("result", null);
315+
} catch (Error | RuntimeException exception) {
316+
wrapped.put("error", wrapError(exception));
317+
}
318+
reply.reply(wrapped);
319+
});
320+
} else {
321+
channel.setMessageHandler(null);
322+
}
323+
}
324+
{
325+
BasicMessageChannel<Object> channel =
326+
new BasicMessageChannel<>(
327+
binaryMessenger,
328+
"dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl",
329+
getCodec());
330+
if (api != null) {
331+
channel.setMessageHandler(
332+
(message, reply) -> {
333+
Map<String, Object> wrapped = new HashMap<>();
334+
try {
335+
ArrayList<Object> args = (ArrayList<Object>) message;
336+
Number instanceIdArg = (Number) args.get(0);
337+
if (instanceIdArg == null) {
338+
throw new NullPointerException("instanceIdArg unexpectedly null.");
339+
}
340+
String baseUrlArg = (String) args.get(1);
341+
if (baseUrlArg == null) {
342+
throw new NullPointerException("baseUrlArg unexpectedly null.");
343+
}
344+
String dataArg = (String) args.get(2);
345+
if (dataArg == null) {
346+
throw new NullPointerException("dataArg unexpectedly null.");
347+
}
348+
String mimeTypeArg = (String) args.get(3);
349+
if (mimeTypeArg == null) {
350+
throw new NullPointerException("mimeTypeArg unexpectedly null.");
351+
}
352+
String encodingArg = (String) args.get(4);
353+
if (encodingArg == null) {
354+
throw new NullPointerException("encodingArg unexpectedly null.");
355+
}
356+
String historyUrlArg = (String) args.get(5);
357+
if (historyUrlArg == null) {
358+
throw new NullPointerException("historyUrlArg unexpectedly null.");
359+
}
360+
api.loadDataWithBaseUrl(
361+
instanceIdArg.longValue(),
362+
baseUrlArg,
363+
dataArg,
364+
mimeTypeArg,
365+
encodingArg,
366+
historyUrlArg);
367+
wrapped.put("result", null);
368+
} catch (Error | RuntimeException exception) {
369+
wrapped.put("error", wrapError(exception));
370+
}
371+
reply.reply(wrapped);
372+
});
373+
} else {
374+
channel.setMessageHandler(null);
375+
}
376+
}
277377
{
278378
BasicMessageChannel<Object> channel =
279379
new BasicMessageChannel<>(

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,30 @@ public void dispose(Long instanceId) {
352352
}
353353
}
354354

355+
@Override
356+
public void loadData(Long instanceId, String data, String mimeType, String encoding) {
357+
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
358+
webView.loadData(
359+
data, parseNullStringIdentifier(mimeType), parseNullStringIdentifier(encoding));
360+
}
361+
362+
@Override
363+
public void loadDataWithBaseUrl(
364+
Long instanceId,
365+
String baseUrl,
366+
String data,
367+
String mimeType,
368+
String encoding,
369+
String historyUrl) {
370+
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
371+
webView.loadDataWithBaseURL(
372+
parseNullStringIdentifier(baseUrl),
373+
data,
374+
parseNullStringIdentifier(mimeType),
375+
parseNullStringIdentifier(encoding),
376+
parseNullStringIdentifier(historyUrl));
377+
}
378+
355379
@Override
356380
public void loadUrl(Long instanceId, String url, Map<String, String> headers) {
357381
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
@@ -477,4 +501,13 @@ public void setWebChromeClient(Long instanceId, Long clientInstanceId) {
477501
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
478502
webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(clientInstanceId));
479503
}
504+
505+
@Nullable
506+
private static String parseNullStringIdentifier(String value) {
507+
if (value.equals(nullStringIdentifier)) {
508+
return null;
509+
}
510+
511+
return value;
512+
}
480513
}

packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,52 @@ public void releaseInputAwareWebViewDependents() {
156156
verify(mockJavaScriptChannel2).release();
157157
}
158158

159+
@Test
160+
public void loadData() {
161+
testHostApiImpl.loadData(
162+
0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64");
163+
verify(mockWebView)
164+
.loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64");
165+
}
166+
167+
@Test
168+
public void loadDataWithNullValues() {
169+
testHostApiImpl.loadData(
170+
0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "<null-value>", "<null-value>");
171+
verify(mockWebView).loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null);
172+
}
173+
174+
@Test
175+
public void loadDataWithBaseUrl() {
176+
testHostApiImpl.loadDataWithBaseUrl(
177+
0L,
178+
"https://flutter.dev",
179+
"VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==",
180+
"text/plain",
181+
"base64",
182+
"about:blank");
183+
verify(mockWebView)
184+
.loadDataWithBaseURL(
185+
"https://flutter.dev",
186+
"VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==",
187+
"text/plain",
188+
"base64",
189+
"about:blank");
190+
}
191+
192+
@Test
193+
public void loadDataWithBaseUrlAndNullValues() {
194+
testHostApiImpl.loadDataWithBaseUrl(
195+
0L,
196+
"<null-value>",
197+
"VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==",
198+
"<null-value>",
199+
"<null-value>",
200+
"<null-value>");
201+
verify(mockWebView)
202+
.loadDataWithBaseURL(null, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null, null);
203+
}
204+
159205
@Test
160206
public void loadUrl() {
161207
testHostApiImpl.loadUrl(0L, "https://www.google.com", new HashMap<>());

packages/webview_flutter/webview_flutter_android/example/lib/main.dart

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
import 'dart:async';
88
import 'dart:convert';
9+
import 'dart:io';
910
import 'package:flutter/foundation.dart';
1011
import 'package:flutter/material.dart';
12+
import 'package:path_provider/path_provider.dart';
1113
import 'package:webview_flutter_android/webview_surface_android.dart';
1214
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
1315

@@ -38,6 +40,25 @@ The navigation delegate is set to block navigation to the youtube website.
3840
</html>
3941
''';
4042

43+
const String kExamplePage = '''
44+
<!DOCTYPE html>
45+
<html lang="en">
46+
<head>
47+
<title>Load file or HTML string example</title>
48+
</head>
49+
<body>
50+
51+
<h1>Local demo page</h1>
52+
<p>
53+
This is an example page used to demonstrate how to load a local file or HTML
54+
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
55+
webview</a> plugin.
56+
</p>
57+
58+
</body>
59+
</html>
60+
''';
61+
4162
class _WebViewExample extends StatefulWidget {
4263
const _WebViewExample({Key? key}) : super(key: key);
4364

@@ -135,6 +156,8 @@ enum _MenuOptions {
135156
listCache,
136157
clearCache,
137158
navigationDelegate,
159+
loadLocalFile,
160+
loadHtmlString,
138161
}
139162

140163
class _SampleMenu extends StatelessWidget {
@@ -172,6 +195,12 @@ class _SampleMenu extends StatelessWidget {
172195
case _MenuOptions.navigationDelegate:
173196
_onNavigationDelegateExample(controller.data!, context);
174197
break;
198+
case _MenuOptions.loadLocalFile:
199+
_onLoadLocalFileExample(controller.data!, context);
200+
break;
201+
case _MenuOptions.loadHtmlString:
202+
_onLoadHtmlStringExample(controller.data!, context);
203+
break;
175204
}
176205
},
177206
itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -204,6 +233,14 @@ class _SampleMenu extends StatelessWidget {
204233
value: _MenuOptions.navigationDelegate,
205234
child: Text('Navigation Delegate example'),
206235
),
236+
const PopupMenuItem<_MenuOptions>(
237+
value: _MenuOptions.loadHtmlString,
238+
child: Text('Load HTML string'),
239+
),
240+
const PopupMenuItem<_MenuOptions>(
241+
value: _MenuOptions.loadLocalFile,
242+
child: Text('Load local file'),
243+
),
207244
],
208245
);
209246
},
@@ -281,6 +318,18 @@ class _SampleMenu extends StatelessWidget {
281318
await controller.loadUrl('data:text/html;base64,$contentBase64');
282319
}
283320

321+
Future<void> _onLoadLocalFileExample(
322+
WebViewController controller, BuildContext context) async {
323+
final String pathToIndex = await _prepareLocalFile();
324+
325+
await controller.loadFile(pathToIndex);
326+
}
327+
328+
Future<void> _onLoadHtmlStringExample(
329+
WebViewController controller, BuildContext context) async {
330+
await controller.loadHtmlString(kExamplePage);
331+
}
332+
284333
Widget _getCookieList(String cookies) {
285334
if (cookies == null || cookies == '""') {
286335
return Container();
@@ -294,6 +343,16 @@ class _SampleMenu extends StatelessWidget {
294343
children: cookieWidgets.toList(),
295344
);
296345
}
346+
347+
static Future<String> _prepareLocalFile() async {
348+
final String tmpDir = (await getTemporaryDirectory()).path;
349+
final File indexFile = File('$tmpDir/www/index.html');
350+
351+
await Directory('$tmpDir/www').create(recursive: true);
352+
await indexFile.writeAsString(kExamplePage);
353+
354+
return indexFile.path;
355+
}
297356
}
298357

299358
class _NavigationControls extends StatelessWidget {

packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,28 @@ class WebViewController {
363363

364364
WebView _widget;
365365

366+
/// Loads the file located on the specified [absoluteFilePath].
367+
///
368+
/// The [absoluteFilePath] parameter should contain the absolute path to the
369+
/// file as it is stored on the device. For example:
370+
/// `/Users/username/Documents/www/index.html`.
371+
///
372+
/// Throws an ArgumentError if the [absoluteFilePath] does not exist.
373+
Future<void> loadFile(String absoluteFilePath) {
374+
return _webViewPlatformController.loadFile(absoluteFilePath);
375+
}
376+
377+
/// Loads the supplied HTML string.
378+
///
379+
/// The [baseUrl] parameter is used when resolving relative URLs within the
380+
/// HTML string.
381+
Future<void> loadHtmlString(String html, {String? baseUrl}) {
382+
return _webViewPlatformController.loadHtmlString(
383+
html,
384+
baseUrl: baseUrl,
385+
);
386+
}
387+
366388
/// Loads the specified URL.
367389
///
368390
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will

packages/webview_flutter/webview_flutter_android/example/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ environment:
88
dependencies:
99
flutter:
1010
sdk: flutter
11+
12+
path_provider: ^2.0.6
13+
1114
webview_flutter_android:
1215
# When depending on this package from a real application you should use:
1316
# webview_flutter: ^x.y.z

0 commit comments

Comments
 (0)