// Copyright 2013 The Flutter Authors // 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_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { runApp(const MaterialApp(home: WebViewExample())); } const String kNavigationExamplePage = ''' Navigation Delegate Example

The navigation delegate is set to block navigation to the pub.dev website.

'''; const String kLocalExamplePage = ''' Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

'''; const String kTransparentBackgroundPage = ''' Transparent background test

Transparent background test

'''; const String kLogExamplePage = ''' Load file or HTML string example

Local demo page

This page is used to test the forwarding of console logs to Dart.

'''; const String kAlertTestPage = '''

Click the following button to see the effect

'''; const String kViewportMetaPage = ''' Viewport meta example

Viewport meta example

'''; class WebViewExample extends StatefulWidget { const WebViewExample({super.key, this.cookieManager}); final PlatformWebViewCookieManager? cookieManager; @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { late final PlatformWebViewController _controller; @override void initState() { super.initState(); _controller = PlatformWebViewController(AndroidWebViewControllerCreationParams()) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x80000000)) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnProgress((int progress) { debugPrint('WebView is loading (progress : $progress%)'); }) ..setOnPageStarted((String url) { debugPrint('Page started loading: $url'); }) ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) ..setOnHttpError((HttpResponseError error) { debugPrint( 'HTTP error occured on page: ${error.response?.statusCode}', ); }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: code: ${error.errorCode} description: ${error.description} errorType: ${error.errorType} isForMainFrame: ${error.isForMainFrame} url: ${error.url} '''); }) ..setOnNavigationRequest((NavigationRequest request) { if (request.url.contains('pub.dev')) { debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; } debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }) ..setOnUrlChange((UrlChange change) { debugPrint('url change to ${change.url}'); }) ..setOnHttpAuthRequest((HttpAuthRequest request) { openDialog(request); }) ..setOnSSlAuthError((PlatformSslAuthError error) { debugPrint( 'SSL error from ${(error as AndroidSslAuthError).url}', ); error.cancel(); }), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'Toaster', onMessageReceived: (JavaScriptMessage message) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message.message))); }, ), ) ..setOnPlatformPermissionRequest(( PlatformWebViewPermissionRequest request, ) { debugPrint( 'requesting permissions for ${request.types.map((WebViewPermissionResourceType type) => type.name)}', ); request.grant(); }) ..loadRequest( LoadRequestParams(uri: Uri.parse('https://flutter.dev')), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF4CAF50), appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ NavigationControls(webViewController: _controller), SampleMenu( webViewController: _controller, cookieManager: widget.cookieManager, ), ], ), body: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: _controller), ).build(context), floatingActionButton: favoriteButton(), ); } Widget favoriteButton() { return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Favorited $url'))); } }, child: const Icon(Icons.favorite), ); } Future openDialog(HttpAuthRequest httpRequest) async { final TextEditingController usernameTextController = TextEditingController(); final TextEditingController passwordTextController = TextEditingController(); return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( decoration: const InputDecoration(labelText: 'Username'), autofocus: true, controller: usernameTextController, ), TextField( decoration: const InputDecoration(labelText: 'Password'), controller: passwordTextController, ), ], ), actions: [ TextButton( onPressed: () { httpRequest.onProceed( WebViewCredential( user: usernameTextController.text, password: passwordTextController.text, ), ); Navigator.of(context).pop(); }, child: const Text('Authenticate'), ), ], ); }, ); } } enum MenuOptions { showUserAgent, listCookies, clearCookies, addToCache, listCache, clearCache, navigationDelegate, doPostRequest, loadLocalFile, loadFlutterAsset, loadHtmlString, transparentBackground, setCookie, videoExample, logExample, basicAuthentication, javaScriptAlert, viewportMeta, } class SampleMenu extends StatelessWidget { SampleMenu({ super.key, required this.webViewController, PlatformWebViewCookieManager? cookieManager, }) : cookieManager = cookieManager ?? PlatformWebViewCookieManager( const PlatformWebViewCookieManagerCreationParams(), ); final PlatformWebViewController webViewController; late final PlatformWebViewCookieManager cookieManager; @override Widget build(BuildContext context) { return PopupMenuButton( key: const ValueKey('ShowPopupMenu'), onSelected: (MenuOptions value) { switch (value) { case MenuOptions.showUserAgent: _onShowUserAgent(); case MenuOptions.listCookies: _onListCookies(context); case MenuOptions.clearCookies: _onClearCookies(context); case MenuOptions.addToCache: _onAddToCache(context); case MenuOptions.listCache: _onListCache(); case MenuOptions.clearCache: _onClearCache(context); case MenuOptions.navigationDelegate: _onNavigationDelegateExample(); case MenuOptions.doPostRequest: _onDoPostRequest(); case MenuOptions.loadLocalFile: _onLoadLocalFileExample(); case MenuOptions.loadFlutterAsset: _onLoadFlutterAssetExample(); case MenuOptions.loadHtmlString: _onLoadHtmlStringExample(); case MenuOptions.transparentBackground: _onTransparentBackground(); case MenuOptions.setCookie: _onSetCookie(); case MenuOptions.videoExample: _onVideoExample(context); case MenuOptions.logExample: _onLogExample(); case MenuOptions.basicAuthentication: _promptForUrl(context); case MenuOptions.javaScriptAlert: _onJavaScriptAlertExample(context); case MenuOptions.viewportMeta: _onViewportMetaExample(); } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: MenuOptions.showUserAgent, child: Text('Show user agent'), ), const PopupMenuItem( value: MenuOptions.listCookies, child: Text('List cookies'), ), const PopupMenuItem( value: MenuOptions.clearCookies, child: Text('Clear cookies'), ), const PopupMenuItem( value: MenuOptions.addToCache, child: Text('Add to cache'), ), const PopupMenuItem( value: MenuOptions.listCache, child: Text('List cache'), ), const PopupMenuItem( value: MenuOptions.clearCache, child: Text('Clear cache'), ), const PopupMenuItem( value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), const PopupMenuItem( value: MenuOptions.doPostRequest, child: Text('Post Request'), ), const PopupMenuItem( value: MenuOptions.loadHtmlString, child: Text('Load HTML string'), ), const PopupMenuItem( value: MenuOptions.loadLocalFile, child: Text('Load local file'), ), const PopupMenuItem( value: MenuOptions.loadFlutterAsset, child: Text('Load Flutter Asset'), ), const PopupMenuItem( value: MenuOptions.setCookie, child: Text('Set cookie'), ), const PopupMenuItem( key: ValueKey('ShowTransparentBackgroundExample'), value: MenuOptions.transparentBackground, child: Text('Transparent background example'), ), const PopupMenuItem( value: MenuOptions.logExample, child: Text('Log example'), ), const PopupMenuItem( value: MenuOptions.videoExample, child: Text('Video example'), ), const PopupMenuItem( value: MenuOptions.basicAuthentication, child: Text('Basic Authentication Example'), ), const PopupMenuItem( value: MenuOptions.javaScriptAlert, child: Text('JavaScript Alert Example'), ), const PopupMenuItem( value: MenuOptions.viewportMeta, child: Text('Viewport meta example'), ), ], ); } Future _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 _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: [const Text('Cookies:'), _getCookieList(cookies)], ), ), ); } } Future _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 _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 _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Cache cleared.'))); } } Future _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 _onNavigationDelegateExample() { final String contentBase64 = base64Encode( const Utf8Encoder().convert(kNavigationExamplePage), ); return webViewController.loadRequest( LoadRequestParams(uri: Uri.parse('data:text/html;base64,$contentBase64')), ); } Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything', ), ); await webViewController.loadRequest( LoadRequestParams(uri: Uri.parse('https://httpbin.org/anything')), ); } Future _onVideoExample(BuildContext context) { final AndroidWebViewController androidController = webViewController as AndroidWebViewController; // #docregion fullscreen_example androidController.setCustomWidgetCallbacks( onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => widget, fullscreenDialog: true, ), ); }, onHideCustomWidget: () { Navigator.of(context).pop(); }, ); // #enddocregion fullscreen_example return androidController.loadRequest( LoadRequestParams( uri: Uri.parse('https://www.youtube.com/watch?v=4AoFA19gbLo'), ), ); } Future _onDoPostRequest() { return webViewController.loadRequest( LoadRequestParams( uri: Uri.parse('https://httpbin.org/post'), method: LoadRequestMethod.post, headers: const { 'foo': 'bar', 'Content-Type': 'text/plain', }, body: Uint8List.fromList('Test Body'.codeUnits), ), ); } Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); await webViewController.loadFile(pathToIndex); } Future _onLoadFlutterAssetExample() { return webViewController.loadFlutterAsset('assets/www/index.html'); } Future _onLoadHtmlStringExample() { return webViewController.loadHtmlString(kLocalExamplePage); } Future _onTransparentBackground() { return webViewController.loadHtmlString(kTransparentBackgroundPage); } Future _onJavaScriptAlertExample(BuildContext context) { webViewController.setOnJavaScriptAlertDialog(( JavaScriptAlertDialogRequest request, ) async { await _showAlert(context, request.message); }); webViewController.setOnJavaScriptConfirmDialog(( JavaScriptConfirmDialogRequest request, ) async { final bool result = await _showConfirm(context, request.message); return result; }); webViewController.setOnJavaScriptTextInputDialog(( JavaScriptTextInputDialogRequest request, ) async { final String result = await _showTextInput( context, request.message, request.defaultText, ); return result; }); return webViewController.loadHtmlString(kAlertTestPage); } Widget _getCookieList(String cookies) { if (cookies == '""') { return Container(); } final List cookieList = cookies.split(';'); final Iterable cookieWidgets = cookieList.map( (String cookie) => Text(cookie), ); return Column( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: cookieWidgets.toList(), ); } static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; final File indexFile = File( {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator), ); await indexFile.create(recursive: true); await indexFile.writeAsString(kLocalExamplePage); return indexFile.path; } Future _onLogExample() { webViewController.setOnConsoleMessage(( JavaScriptConsoleMessage consoleMessage, ) { debugPrint( '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}', ); }); return webViewController.loadHtmlString(kLogExamplePage); } Future _promptForUrl(BuildContext context) { final TextEditingController urlTextController = TextEditingController(); return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Input URL to visit'), content: TextField( decoration: const InputDecoration(labelText: 'URL'), autofocus: true, controller: urlTextController, ), actions: [ TextButton( onPressed: () { if (urlTextController.text.isNotEmpty) { final Uri? uri = Uri.tryParse(urlTextController.text); if (uri != null && uri.scheme.isNotEmpty) { webViewController.loadRequest(LoadRequestParams(uri: uri)); Navigator.pop(context); } } }, child: const Text('Visit'), ), ], ); }, ); } Future _showAlert(BuildContext context, String message) async { return showDialog( context: context, builder: (BuildContext ctx) { return AlertDialog( content: Text(message), actions: [ TextButton( onPressed: () { Navigator.of(ctx).pop(); }, child: const Text('OK'), ), ], ); }, ); } Future _showConfirm(BuildContext context, String message) async { return await showDialog( context: context, builder: (BuildContext ctx) { return AlertDialog( content: Text(message), actions: [ TextButton( onPressed: () { Navigator.of(ctx).pop(false); }, child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.of(ctx).pop(true); }, child: const Text('OK'), ), ], ); }, ) ?? false; } Future _showTextInput( BuildContext context, String message, String? defaultText, ) async { return await showDialog( context: context, builder: (BuildContext ctx) { return AlertDialog( content: Text(message), actions: [ TextButton( onPressed: () { Navigator.of(ctx).pop('Text test'); }, child: const Text('Enter'), ), ], ); }, ) ?? ''; } Future _onViewportMetaExample() { return webViewController.loadHtmlString(kViewportMetaPage); } } class NavigationControls extends StatelessWidget { const NavigationControls({super.key, required this.webViewController}); final PlatformWebViewController webViewController; @override Widget build(BuildContext context) { return Row( children: [ 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(), ), ], ); } }