| // 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_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 = ''' |
| <!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: #FF0000; width: 200px; height: 100%; margin: 0; padding: 0; position: absolute; top: 0; bottom: 0; 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, this.cookieManager}); |
| |
| final PlatformWebViewCookieManager? cookieManager; |
| |
| @override |
| State<WebViewExample> createState() => _WebViewExampleState(); |
| } |
| |
| class _WebViewExampleState extends State<WebViewExample> { |
| 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'); |
| }) |
| ..setOnWebResourceError((WebResourceError error) { |
| debugPrint(''' |
| Page resource error: |
| code: ${error.errorCode} |
| description: ${error.description} |
| errorType: ${error.errorType} |
| isForMainFrame: ${error.isForMainFrame} |
| '''); |
| }) |
| ..setOnNavigationRequest((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; |
| }) |
| ..setOnUrlChange((UrlChange change) { |
| debugPrint('url change to ${change.url}'); |
| }), |
| ) |
| ..addJavaScriptChannel(JavaScriptChannelParams( |
| name: 'Toaster', |
| onMessageReceived: (JavaScriptMessage message) { |
| ScaffoldMessenger.of(context).showSnackBar( |
| SnackBar(content: Text(message.message)), |
| ); |
| }, |
| )) |
| ..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: <Widget>[ |
| 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 (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, |
| PlatformWebViewCookieManager? cookieManager, |
| }) : cookieManager = cookieManager ?? |
| PlatformWebViewCookieManager( |
| const PlatformWebViewCookieManagerCreationParams(), |
| ); |
| |
| final PlatformWebViewController webViewController; |
| late final PlatformWebViewCookieManager cookieManager; |
| |
| @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>( |
| value: MenuOptions.setCookie, |
| child: Text('Set cookie'), |
| ), |
| const PopupMenuItem<MenuOptions>( |
| key: ValueKey<String>('ShowTransparentBackgroundExample'), |
| value: MenuOptions.transparentBackground, |
| child: Text('Transparent background example'), |
| ), |
| ], |
| ); |
| } |
| |
| 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( |
| LoadRequestParams( |
| uri: 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(LoadRequestParams( |
| uri: Uri.parse('https://httpbin.org/anything'), |
| )); |
| } |
| |
| Future<void> _onDoPostRequest() { |
| return webViewController.loadRequest(LoadRequestParams( |
| uri: Uri.parse('https://httpbin.org/post'), |
| method: LoadRequestMethod.post, |
| headers: const <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 == null || 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 PlatformWebViewController 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(), |
| ), |
| ], |
| ); |
| } |
| } |