WebView for Flutter
A Flutter plugin that provides a WebView widget.
On iOS the WebView widget is backed by a WKWebView. On Android the WebView widget is backed by a WebView.
Android | iOS | |
---|---|---|
Support | SDK 19+ or 20+ | 11.0+ |
Add webview_flutter
as a dependency in your pubspec.yaml file.
You can now display a WebView by:
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse('https://flutter.dev'));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Simple Example')),
body: WebViewWidget(controller: controller),
);
}
See the Dartdocs for WebViewController and WebViewWidget for more details.
This plugin uses Platform Views to embed the Android’s WebView within the Flutter app.
You should however make sure to set the correct minSdkVersion
in android/app/build.gradle
if it was previously lower than 19:
android {
defaultConfig {
minSdkVersion 19
}
}
Many classes have a subclass or an underlying implementation that provides access to platform-specific features.
To access platform-specific features, start by adding the platform implementation packages to your app or package:
Next, add the imports of the implementation packages to your app or package:
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
Now, additional features can be accessed through the platform implementations. Classes WebViewController, WebViewWidget, NavigationDelegate, and WebViewCookieManager pass their functionality to a class provided by the current platform. Below are a couple of ways to access additional functionality provided by the platform and is followed by an example.
fromPlatformCreationParams
constructor (e.g. WebViewController.fromPlatformCreationParams
, WebViewWidget.fromPlatformCreationParams
, etc.).platform
field (e.g. WebViewController.platform
, WebViewWidget.platform
, etc.).Below is an example of setting additional iOS and Android parameters on the WebViewController
.
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params);
// ···
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
See https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/webview_flutter_android-library.html for more details on Android features.
See https://pub.dev/documentation/webview_flutter_wkwebview/latest/webview_flutter_wkwebview/webview_flutter_wkwebview-library.html for more details on iOS features.
To use Material Components when the user interacts with input elements in the WebView, follow the steps described in the Enabling Material Components instructions.
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.
In version 3.0 and below, WebViewController
could only be retrieved in a callback after the WebView
was added to the widget tree. Now, WebViewController
must be instantiated and can be used before it is added to the widget tree. See Usage
section above for an example.
The WebView
class has been removed and its functionality has been split into WebViewController
and WebViewWidget
.
WebViewController
handles all functionality that is associated with the underlying web view provided by each platform. (e.g., loading a url, setting the background color of the underlying platform view, or clearing the cache).
WebViewWidget
takes a WebViewController
and handles all Flutter widget related functionality (e.g., layout direction, gesture recognizers).
See the Dartdocs for WebViewController and WebViewWidget for more details.
The PlatformView implementation for Android uses Texture Layer Hybrid Composition on versions 23+ and automatically fallbacks to Hybrid Composition for version 19-23. See section Platform-Specific Features
and AndroidWebViewWidgetCreationParams.displayWithHybridComposition to manually switch to Hybrid Composition on versions 23+.
Below is a non-exhaustive list of changes to the API:
WebViewController.clearCache
no longer clears local storage. Please use WebViewController.clearLocalStorage
.WebViewController.clearCache
no longer reloads the page.WebViewController.loadUrl
has been removed. Please use WebViewController.loadRequest
.WebViewController.evaluateJavascript
has been removed. Please use WebViewController.runJavaScript
or WebViewController.runJavaScriptReturningResult
.WebViewController.getScrollX
and WebViewController.getScrollY
have been removed and have been replaced by WebViewController.getScrollPosition
.WebViewController.runJavaScriptReturningResult
now returns an Object
and not a String
. This will attempt to return a bool
or num
if the return value can be parsed.WebView.initialCookies
has been removed. Use WebViewCookieManager.setCookie
before calling WebViewController.loadRequest
.CookieManager
is replaced by WebViewCookieManager
.NavigationDelegate.onWebResourceError
callback includes errors that are not from the main frame. Use the WebResourceError.isForMainFrame
field to filter errors.WebView
have been moved to NavigationDelegate
. They can be added to a WebView with WebViewController.setNavigationDelegate
.WebView.navigationDelegate
-> NavigationDelegate.onNavigationRequest
WebView.onPageStarted
-> NavigationDelegate.onPageStarted
WebView.onPageFinished
-> NavigationDelegate.onPageFinished
WebView.onProgress
-> NavigationDelegate.onProgress
WebView.onWebResourceError
-> NavigationDelegate.onWebResourceError
WebView
have been moved to WebViewController
:WebView.javascriptMode
-> WebViewController.setJavaScriptMode
WebView.javascriptChannels
-> WebViewController.addJavaScriptChannel
/WebViewController.removeJavaScriptChannel
WebView.zoomEnabled
-> WebViewController.enableZoom
WebView.userAgent
-> WebViewController.setUserAgent
WebView.backgroundColor
-> WebViewController.setBackgroundColor
Platform-Specific Features
for details on accessing Android platform-specific features.WebView.debuggingEnabled
-> static AndroidWebViewController.enableDebugging
WebView.initialMediaPlaybackPolicy
-> AndroidWebViewController.setMediaPlaybackRequiresUserGesture
Platform-Specific Features
for details on accessing iOS platform-specific features.WebView.gestureNavigationEnabled
-> WebKitWebViewController.setAllowsBackForwardNavigationGestures
WebView.initialMediaPlaybackPolicy
-> WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction
WebView.allowsInlineMediaPlayback
-> WebKitWebViewControllerCreationParams.allowsInlineMediaPlayback
Run this command:
With Flutter:
$ flutter pub add webview_flutter
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_flutter: ^4.2.2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter/webview_flutter.dart';
// 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.
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:webview_flutter/webview_flutter.dart';
// #docregion platform_imports
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
// #enddocregion platform_imports
void main() => runApp(const MaterialApp(home: WebViewExample()));
const String kNavigationExamplePage = '''
<!DOCTYPE html><html>
<head><title>Navigation Delegate Example</title></head>
<body>
<p>
The navigation delegate is set to block navigation to the youtube website.
</p>
<ul>
<ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul>
<ul><a href="https://www.google.com/">https://www.google.com/</a></ul>
</ul>
</body>
</html>
''';
const String kLocalExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
''';
const String kTransparentBackgroundPage = '''
<!DOCTYPE html>
<html>
<head>
<title>Transparent background test</title>
</head>
<style type="text/css">
body { background: transparent; margin: 0; padding: 0; }
#container { position: relative; margin: 0; padding: 0; width: 100vw; height: 100vh; }
#shape { background: red; width: 200px; height: 200px; margin: 0; padding: 0; position: absolute; top: calc(50% - 100px); left: calc(50% - 100px); }
p { text-align: center; }
</style>
<body>
<div id="container">
<p>Transparent background test</p>
<div id="shape"></div>
</div>
</body>
</html>
''';
class WebViewExample extends StatefulWidget {
const WebViewExample({super.key});
@override
State<WebViewExample> createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
// #docregion platform_features
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params);
// #enddocregion platform_features
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
),
)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..loadRequest(Uri.parse('https://flutter.dev'));
// #docregion platform_features
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
// #enddocregion platform_features
_controller = controller;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
appBar: AppBar(
title: const Text('Flutter WebView example'),
// This drop down menu demonstrates that Flutter widgets can be shown over the web view.
actions: <Widget>[
NavigationControls(webViewController: _controller),
SampleMenu(webViewController: _controller),
],
),
body: WebViewWidget(controller: _controller),
floatingActionButton: favoriteButton(),
);
}
Widget favoriteButton() {
return FloatingActionButton(
onPressed: () async {
final String? url = await _controller.currentUrl();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Favorited $url')),
);
}
},
child: const Icon(Icons.favorite),
);
}
}
enum MenuOptions {
showUserAgent,
listCookies,
clearCookies,
addToCache,
listCache,
clearCache,
navigationDelegate,
doPostRequest,
loadLocalFile,
loadFlutterAsset,
loadHtmlString,
transparentBackground,
setCookie,
}
class SampleMenu extends StatelessWidget {
SampleMenu({
super.key,
required this.webViewController,
});
final WebViewController webViewController;
late final WebViewCookieManager cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<MenuOptions>(
key: const ValueKey<String>('ShowPopupMenu'),
onSelected: (MenuOptions value) {
switch (value) {
case MenuOptions.showUserAgent:
_onShowUserAgent();
break;
case MenuOptions.listCookies:
_onListCookies(context);
break;
case MenuOptions.clearCookies:
_onClearCookies(context);
break;
case MenuOptions.addToCache:
_onAddToCache(context);
break;
case MenuOptions.listCache:
_onListCache();
break;
case MenuOptions.clearCache:
_onClearCache(context);
break;
case MenuOptions.navigationDelegate:
_onNavigationDelegateExample();
break;
case MenuOptions.doPostRequest:
_onDoPostRequest();
break;
case MenuOptions.loadLocalFile:
_onLoadLocalFileExample();
break;
case MenuOptions.loadFlutterAsset:
_onLoadFlutterAssetExample();
break;
case MenuOptions.loadHtmlString:
_onLoadHtmlStringExample();
break;
case MenuOptions.transparentBackground:
_onTransparentBackground();
break;
case MenuOptions.setCookie:
_onSetCookie();
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
const PopupMenuItem<MenuOptions>(
value: MenuOptions.showUserAgent,
child: Text('Show user agent'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.addToCache,
child: Text('Add to cache'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.listCache,
child: Text('List cache'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.clearCache,
child: Text('Clear cache'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.navigationDelegate,
child: Text('Navigation Delegate example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.doPostRequest,
child: Text('Post Request'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.loadHtmlString,
child: Text('Load HTML string'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.loadLocalFile,
child: Text('Load local file'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.loadFlutterAsset,
child: Text('Load Flutter Asset'),
),
const PopupMenuItem<MenuOptions>(
key: ValueKey<String>('ShowTransparentBackgroundExample'),
value: MenuOptions.transparentBackground,
child: Text('Transparent background example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.setCookie,
child: Text('Set cookie'),
),
],
);
}
Future<void> _onShowUserAgent() {
// Send a message with the user agent string to the Toaster JavaScript channel we registered
// with the WebView.
return webViewController.runJavaScript(
'Toaster.postMessage("User Agent: " + navigator.userAgent);',
);
}
Future<void> _onListCookies(BuildContext context) async {
final String cookies = await webViewController
.runJavaScriptReturningResult('document.cookie') as String;
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Cookies:'),
_getCookieList(cookies),
],
),
));
}
}
Future<void> _onAddToCache(BuildContext context) async {
await webViewController.runJavaScript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";',
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Added a test entry to cache.'),
));
}
}
Future<void> _onListCache() {
return webViewController.runJavaScript('caches.keys()'
// ignore: missing_whitespace_between_adjacent_strings
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
'.then((caches) => Toaster.postMessage(caches))');
}
Future<void> _onClearCache(BuildContext context) async {
await webViewController.clearCache();
await webViewController.clearLocalStorage();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Cache cleared.'),
));
}
}
Future<void> _onClearCookies(BuildContext context) async {
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(message),
));
}
}
Future<void> _onNavigationDelegateExample() {
final String contentBase64 = base64Encode(
const Utf8Encoder().convert(kNavigationExamplePage),
);
return webViewController.loadRequest(
Uri.parse('data:text/html;base64,$contentBase64'),
);
}
Future<void> _onSetCookie() async {
await cookieManager.setCookie(
const WebViewCookie(
name: 'foo',
value: 'bar',
domain: 'httpbin.org',
path: '/anything',
),
);
await webViewController.loadRequest(Uri.parse(
'https://httpbin.org/anything',
));
}
Future<void> _onDoPostRequest() {
return webViewController.loadRequest(
Uri.parse('https://httpbin.org/post'),
method: LoadRequestMethod.post,
headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
body: Uint8List.fromList('Test Body'.codeUnits),
);
}
Future<void> _onLoadLocalFileExample() async {
final String pathToIndex = await _prepareLocalFile();
await webViewController.loadFile(pathToIndex);
}
Future<void> _onLoadFlutterAssetExample() {
return webViewController.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadHtmlStringExample() {
return webViewController.loadHtmlString(kLocalExamplePage);
}
Future<void> _onTransparentBackground() {
return webViewController.loadHtmlString(kTransparentBackgroundPage);
}
Widget _getCookieList(String cookies) {
if (cookies == '""') {
return Container();
}
final List<String> cookieList = cookies.split(';');
final Iterable<Text> cookieWidgets =
cookieList.map((String cookie) => Text(cookie));
return Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: cookieWidgets.toList(),
);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File(
<String>{tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator));
await indexFile.create(recursive: true);
await indexFile.writeAsString(kLocalExamplePage);
return indexFile.path;
}
}
class NavigationControls extends StatelessWidget {
const NavigationControls({super.key, required this.webViewController});
final WebViewController webViewController;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () async {
if (await webViewController.canGoBack()) {
await webViewController.goBack();
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No back history item')),
);
}
}
},
),
IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: () async {
if (await webViewController.canGoForward()) {
await webViewController.goForward();
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No forward history item')),
);
}
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: () => webViewController.reload(),
),
],
);
}
}
Download details:
Author: flutter.dev
Source: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter