| // 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 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:webview_flutter_android/webview_surface_android.dart'; |
| import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; |
| |
| import 'web_view.dart'; |
| |
| void main() { |
| // Configure the [WebView] to use the [SurfaceAndroidWebView] |
| // implementation instead of the default [AndroidWebView]. |
| WebView.platform = SurfaceAndroidWebView(); |
| |
| runApp(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> |
| '''; |
| |
| class _WebViewExample extends StatefulWidget { |
| const _WebViewExample({Key? key}) : super(key: key); |
| |
| @override |
| _WebViewExampleState createState() => _WebViewExampleState(); |
| } |
| |
| class _WebViewExampleState extends State<_WebViewExample> { |
| final Completer<WebViewController> _controller = |
| Completer<WebViewController>(); |
| |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| 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(_controller.future), |
| _SampleMenu(_controller.future), |
| ], |
| ), |
| // We're using a Builder here so we have a context that is below the Scaffold |
| // to allow calling Scaffold.of(context) so we can show a snackbar. |
| body: Builder(builder: (context) { |
| return WebView( |
| initialUrl: 'https://flutter.dev', |
| onWebViewCreated: (WebViewController controller) { |
| _controller.complete(controller); |
| }, |
| javascriptChannels: _createJavascriptChannels(context), |
| javascriptMode: JavascriptMode.unrestricted, |
| userAgent: 'Custom_User_Agent', |
| ); |
| }), |
| floatingActionButton: favoriteButton(), |
| ); |
| } |
| |
| Widget favoriteButton() { |
| return FutureBuilder<WebViewController>( |
| future: _controller.future, |
| builder: (BuildContext context, |
| AsyncSnapshot<WebViewController> controller) { |
| if (controller.hasData) { |
| return FloatingActionButton( |
| onPressed: () async { |
| final String url = (await controller.data!.currentUrl())!; |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar( |
| SnackBar(content: Text('Favorited $url')), |
| ); |
| }, |
| child: const Icon(Icons.favorite), |
| ); |
| } |
| return Container(); |
| }); |
| } |
| } |
| |
| Set<JavascriptChannel> _createJavascriptChannels(BuildContext context) { |
| return { |
| JavascriptChannel( |
| name: 'Snackbar', |
| onMessageReceived: (JavascriptMessage message) { |
| ScaffoldMessenger.of(context) |
| .showSnackBar(SnackBar(content: Text(message.message))); |
| }), |
| }; |
| } |
| |
| enum _MenuOptions { |
| showUserAgent, |
| listCookies, |
| clearCookies, |
| addToCache, |
| listCache, |
| clearCache, |
| navigationDelegate, |
| } |
| |
| class _SampleMenu extends StatelessWidget { |
| _SampleMenu(this.controller); |
| |
| final Future<WebViewController> controller; |
| |
| @override |
| Widget build(BuildContext context) { |
| return FutureBuilder<WebViewController>( |
| future: controller, |
| builder: |
| (BuildContext context, AsyncSnapshot<WebViewController> controller) { |
| return PopupMenuButton<_MenuOptions>( |
| onSelected: (_MenuOptions value) { |
| switch (value) { |
| case _MenuOptions.showUserAgent: |
| _onShowUserAgent(controller.data!, context); |
| break; |
| case _MenuOptions.listCookies: |
| _onListCookies(controller.data!, context); |
| break; |
| case _MenuOptions.clearCookies: |
| _onClearCookies(controller.data!, context); |
| break; |
| case _MenuOptions.addToCache: |
| _onAddToCache(controller.data!, context); |
| break; |
| case _MenuOptions.listCache: |
| _onListCache(controller.data!, context); |
| break; |
| case _MenuOptions.clearCache: |
| _onClearCache(controller.data!, context); |
| break; |
| case _MenuOptions.navigationDelegate: |
| _onNavigationDelegateExample(controller.data!, context); |
| break; |
| } |
| }, |
| itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[ |
| PopupMenuItem<_MenuOptions>( |
| value: _MenuOptions.showUserAgent, |
| child: const Text('Show user agent'), |
| enabled: controller.hasData, |
| ), |
| 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'), |
| ), |
| ], |
| ); |
| }, |
| ); |
| } |
| |
| void _onShowUserAgent( |
| WebViewController controller, BuildContext context) async { |
| // Send a message with the user agent string to the Snackbar JavaScript channel we registered |
| // with the WebView. |
| await controller.runJavascript( |
| 'Snackbar.postMessage("User Agent: " + navigator.userAgent);'); |
| } |
| |
| void _onListCookies( |
| WebViewController controller, BuildContext context) async { |
| final String cookies = |
| await controller.runJavascriptReturningResult('document.cookie'); |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar(SnackBar( |
| content: Column( |
| mainAxisAlignment: MainAxisAlignment.end, |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| const Text('Cookies:'), |
| _getCookieList(cookies), |
| ], |
| ), |
| )); |
| } |
| |
| void _onAddToCache(WebViewController controller, BuildContext context) async { |
| await controller.runJavascript( |
| 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar(const SnackBar( |
| content: Text('Added a test entry to cache.'), |
| )); |
| } |
| |
| void _onListCache(WebViewController controller, BuildContext context) async { |
| await controller.runJavascript('caches.keys()' |
| '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' |
| '.then((caches) => Snackbar.postMessage(caches))'); |
| } |
| |
| void _onClearCache(WebViewController controller, BuildContext context) async { |
| await controller.clearCache(); |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar(const SnackBar( |
| content: Text("Cache cleared."), |
| )); |
| } |
| |
| void _onClearCookies( |
| WebViewController controller, BuildContext context) async { |
| final bool hadCookies = await WebView.platform.clearCookies(); |
| String message = 'There were cookies. Now, they are gone!'; |
| if (!hadCookies) { |
| message = 'There are no cookies.'; |
| } |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar(SnackBar( |
| content: Text(message), |
| )); |
| } |
| |
| void _onNavigationDelegateExample( |
| WebViewController controller, BuildContext context) async { |
| final String contentBase64 = |
| base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); |
| await controller.loadUrl('data:text/html;base64,$contentBase64'); |
| } |
| |
| 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(), |
| ); |
| } |
| } |
| |
| class _NavigationControls extends StatelessWidget { |
| const _NavigationControls(this._webViewControllerFuture) |
| : assert(_webViewControllerFuture != null); |
| |
| final Future<WebViewController> _webViewControllerFuture; |
| |
| @override |
| Widget build(BuildContext context) { |
| return FutureBuilder<WebViewController>( |
| future: _webViewControllerFuture, |
| builder: |
| (BuildContext context, AsyncSnapshot<WebViewController> snapshot) { |
| final bool webViewReady = |
| snapshot.connectionState == ConnectionState.done; |
| final WebViewController? controller = snapshot.data; |
| |
| return Row( |
| children: <Widget>[ |
| IconButton( |
| icon: const Icon(Icons.arrow_back_ios), |
| onPressed: !webViewReady |
| ? null |
| : () async { |
| if (await controller!.canGoBack()) { |
| await controller.goBack(); |
| } else { |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar( |
| const SnackBar(content: Text("No back history item")), |
| ); |
| return; |
| } |
| }, |
| ), |
| IconButton( |
| icon: const Icon(Icons.arrow_forward_ios), |
| onPressed: !webViewReady |
| ? null |
| : () async { |
| if (await controller!.canGoForward()) { |
| await controller.goForward(); |
| } else { |
| // ignore: deprecated_member_use |
| Scaffold.of(context).showSnackBar( |
| const SnackBar( |
| content: Text("No forward history item")), |
| ); |
| return; |
| } |
| }, |
| ), |
| IconButton( |
| icon: const Icon(Icons.replay), |
| onPressed: !webViewReady |
| ? null |
| : () { |
| controller!.reload(); |
| }, |
| ), |
| ], |
| ); |
| }, |
| ); |
| } |
| } |
| |
| /// Callback type for handling messages sent from JavaScript running in a web view. |
| typedef void JavascriptMessageHandler(JavascriptMessage message); |