What is ? It’s a Flutter plugin that allows you to incorporate into your Flutter app, to use , or to use . flutter_inappwebview WebView widgets headless WebViews In-App browsers So, what’s the difference between (Official flutter plugin) and ? webview_flutter flutter_webview_plugin Compared to all other WebView plugins, it is : a lot of , , and to control WebViews. Furthermore, they do not have good documentation about their API or, at least, it is not complete. Instead, every feature of is almost all documented (just check the ). feature-rich events methods options flutter_inappwebview API Reference on pub.dev In this article, I’m going to present the main classes and some examples of the widget that people were asking about on the official (issue section) and on StackOverflow. InAppWebView flutter_inappwebview repository Main Classes Overview This is a list of the main classes that the plugin offers: : Flutter Widget for adding an inline native WebView integrated into the flutter widget tree. InAppWebView : This class represents the WebView context menu. ContextMenu : Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an to the widget tree. HeadlessInAppWebView InAppWebView : In-App Browser using native WebView. InAppBrowser : In-App Browser using on Android / on iOS. ChromeSafariBrowser Chrome Custom Tabs SFSafariViewController : This class allows you to create a simple server on . The default value is . InAppLocalhostServer http://localhost:[port]/ port 8080 : This class implements a singleton object (shared instance) which manages the cookies used by WebView instances. CookieManager : This class implements a singleton object (shared instance) that manages the shared HTTP auth credentials cache. HttpAuthCredentialDatabase : This class implements a singleton object (shared instance) which manages the web storage used by WebView instances. WebStorageManager In this article, I’m going to show in particular the widget, that is the most used/requested one. InAppWebView InAppWebView is a Widget like any other! Adding the widget into your app is very simple. It’s just a widget like any other Flutter widget: . InAppWebView InAppWebView(initialUrl: 'https://github.com/flutter') : To use it on iOS, you need to opt-in for the embedded views preview by adding a boolean property to the app's file, with the key and the value . NOTE Info.plist io.flutter.embedded_views_preview YES This widget has a set of initial attributes that you can use to initialize the WebView: : Initial URL that will be loaded; initialUrl : Initial WebView options that will be used; initialOptions : specifies which gestures should be consumed by the WebView; gestureRecognizers : Initial InAppWebViewInitialData that will be loaded, such as an HTML string; initialData : Initial asset file that will be loaded (check the “ ” Section); initialFile Load a file inside assets folder : Initial headers that will be used; initialHeaders : Context menu which contains custom menu items. contextMenu The list of all available WebView options is quite long, for example, you can enable/disable JavaScript using the option or enable/disable cache using the option. The full list of all options is available . javascriptEnabled cacheEnabled here Use InAppWebViewController to control your WebView Instead, to control the WebView, you have the class. This controller is returned by the callback when the WebView is ready to be used. InAppWebViewController onWebViewCreated Through it, you can control your WebView or access its properties, such as the current URL using method. Other methods, for example, are to load a new URL, to load a given URL with custom data using POST method, to evaluate JavaScript code into the WebView, and to get the result of the evaluation, to take the screenshot (in PNG format) of the WebView’s visible viewport, to get the for the main top-level page or if there is no certificate. getUrl loadUrl postUrl evaluateJavascript takeScreenshot getCertificate SSL certificate null The full list of all methods you can use is quite long and available . here InAppWebView Events The widget offers a ! Here’s a few of them: InAppWebView variety of events : event fired when the WebView starts to load an URL; onLoadStart : event fired when the WebView finishes loading an URL; onLoadStop event fired when the WebView main page receives an HTTP error; onLoadHttpError: : event fired when the WebView receives a JavaScript console message (such as , , etc.); onConsoleMessage console.log console.error : gives the host application a chance to take control when a URL is about to be loaded in the current WebView; shouldOverrideUrlLoading : event fired when WebView recognizes a downloadable file; onDownloadStart : event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request; onReceivedHttpAuthRequest : event fired when the WebView need to perform server trust authentication (certificate validation); onReceivedServerTrustAuthRequest : event fired when is called from JavaScript side; onPrint window.print() : event fired when the InAppWebView requests the host application to create a new window, for example when trying to open a link with or when is called by JavaScript side; onCreateWindow target="_blank" window.open() and ! I recommend checking the API Reference to get more details. As for the WebView options and methods, the full list of all WebView events is quite long and available . many many more here InAppWebView Simple Example Here is a simple example that shows an InAppWebView widget, its current URL, and 3 buttons: one to go back, one to go forward, and another one to reload the current page. This is the full code example: ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); runApp( MyApp()); } { _MyAppState createState() => _MyAppState(); } { InAppWebViewController _webViewController; url = ; progress = ; Widget build(BuildContext context) { MaterialApp( home: Scaffold( appBar: AppBar( title: Text( ), ), body: Container( child: Column(children: <Widget>[ Container( padding: EdgeInsets.all( ), child: Text( ), ), Container( padding: EdgeInsets.all( ), child: progress < ? LinearProgressIndicator(value: progress) : Container()), Expanded( child: Container( margin: EdgeInsets.all( ), decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent)), child: InAppWebView( initialUrl: , initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , ) ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; }, onLoadStart: (InAppWebViewController controller, url) { setState(() { .url = url; }); }, onLoadStop: (InAppWebViewController controller, url) { setState(() { .url = url; }); }, onProgressChanged: (InAppWebViewController controller, progress) { setState(() { .progress = progress / ; }); }, ), ), ), ButtonBar( alignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Icon(Icons.arrow_back), onPressed: () { (_webViewController != ) { _webViewController.goBack(); } }, ), RaisedButton( child: Icon(Icons.arrow_forward), onPressed: () { (_webViewController != ) { _webViewController.goForward(); } }, ), RaisedButton( child: Icon(Icons.refresh), onPressed: () { (_webViewController != ) { _webViewController.reload(); } }, ), ], ), ])), ), ); } } import 'dart:async' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' async new class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp String "" double 0 @override return const 'InAppWebView Example' 20.0 "CURRENT URL\n " ${(url.length > ) ? url.substring( , ) + : url} 50 0 50 "..." 10.0 1.0 const 10.0 "https://flutter.dev/" true String this String async this int this 100 if null if null if null JavaScript Handlers (Channels) You can communicate with the JavaScript side and vice-versa. To add a JavaScript handler, you can use method, where you define the and a to be invoked when it is called by the JavaScript side. The can return data to be sent on the JavaScript side. _webViewController.addJavaScriptHandler handlerName callback callback Instead, on the JavaScript side, to execute the callback handler and send data to Flutter, you need to use method, where is a string that represents the handler name that your calling and are optional arguments that you can send to the Flutter side. window.flutter_inappwebview.callHandler(handlerName <String>, ...args) handlerName args In order to call properly, you need to wait and listen to the JavaScript event . window.flutter_inappwebview.callHandler(handlerName <String>, ...args) flutterInAppWebViewPlatformReady This event will be dispatched as soon as the platform (Android or iOS) is ready to handle the method. callHandler Here is an example: ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); runApp( MyApp()); } { _MyAppState createState() => _MyAppState(); } { InAppWebViewController _webViewController; Widget build(BuildContext context) { MaterialApp( home: Scaffold( appBar: AppBar( title: Text( ), ), body: Container( child: Column(children: <Widget>[ Expanded( child:InAppWebView( initialData: InAppWebViewInitialData( data: ), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , ) ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; _webViewController.addJavaScriptHandler(handlerName: , callback: (args) { { : , : }; }); _webViewController.addJavaScriptHandler(handlerName: , callback: (args) { (args); }); }, onConsoleMessage: (controller, consoleMessage) { (consoleMessage); }, ), ), ])), ), ); } } import 'dart:async' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' async new class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp @override return const 'InAppWebView Example' """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> </head> <body> <h1>JavaScript Handlers (Channels) TEST</h1> <script> window.addEventListener("flutterInAppWebViewPlatformReady", function(event) { window.flutter_inappwebview.callHandler('handlerFoo') .then(function(result) { // print to the console the data coming // from the Flutter side. console.log(JSON.stringify(result)); window.flutter_inappwebview .callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}, result); }); }); </script> </body> </html> """ true 'handlerFoo' // return data to JavaScript side! return 'bar' 'bar_value' 'baz' 'baz_value' 'handlerFooWithArgs' print // it will print: [1, true, [bar, 5], {foo: baz}, {bar: bar_value, baz: baz_value}] print // it will print: {message: {"bar":"bar_value","baz":"baz_value"}, messageLevel: 1} WebRTC in InAppWebView At this moment, is supported only on Android, because, unfortunately, on iOS doesn’t implement all the WebRTC API (you can follow this issue: ). WebRTC WKWebView #200 I’m going to show an example using to test WebRTC feature. It’s a video chat demo app based on WebRTC ( ). https://appr.tc/ https://github.com/webrtc/apprtc To request permissions about the camera and microphone, you can use the plugin. Also, you need to set the WebView option to in order to autoplay HTML5 audio and video. permission_handler mediaPlaybackRequiresUserGesture false Furthermore, on Android, you need to implement the event (it’s an Android-specific event), that is an event fired when the WebView is requesting permission to access a specific resource (that is the Android native ). androidOnPermissionRequest WebChromeClient.onPermissionRequest event In this case, this event is used to grant permissions for the WebRTC API. Also, you need to add these permissions in the : AndroidManifest.xml < = /> uses-permission android:name "android.permission.INTERNET" < = /> uses-permission android:name "android.permission.CAMERA" < = /> uses-permission android:name "android.permission.RECORD_AUDIO" < = /> uses-permission android:name "android.permission.MODIFY_AUDIO_SETTINGS" < = /> uses-permission android:name "android.permission.VIDEO_CAPTURE" < = /> uses-permission android:name "android.permission.AUDIO_CAPTURE" Here is the full code example: ; ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); Permission.camera.request(); Permission.microphone.request(); runApp(MyApp()); } { _MyAppState createState() => _MyAppState(); } { Widget build(BuildContext context) { MaterialApp( home: InAppWebViewPage() ); } } { _InAppWebViewPageState createState() => _InAppWebViewPageState(); } { InAppWebViewController _webViewController; Widget build(BuildContext context) { Scaffold( appBar: AppBar( title: Text( ) ), body: Container( child: Column(children: <Widget>[ Expanded( child: Container( child: InAppWebView( initialUrl: , initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( mediaPlaybackRequiresUserGesture: , debuggingEnabled: , ), ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; }, androidOnPermissionRequest: (InAppWebViewController controller, origin, < > resources) { PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT); } ), ), ), ])) ); } } import 'dart:async' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' import 'package:permission_handler/permission_handler.dart' async await await class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp @override return class InAppWebViewPage extends StatefulWidget @override new < > class _InAppWebViewPageState extends State InAppWebViewPage @override return "InAppWebView" "https://appr.tc/r/704328056" false true String List String async return How to enable download files in InAppWebView can recognize downloadable files in both Android and iOS platforms. To be able to recognize downloadable files, you need to set the option, and then you can listen to the event. InAppWebView useOnDownloadStart: true onDownloadStart On Android you need to add write permission inside your file: AndroidManifest.xml < = /> uses-permission android:name "android.permission.WRITE_EXTERNAL_STORAGE" Then, you need to ask permission using the plugin. Instead, to effectively download your file, you can use the plugin. permission_handler flutter_downloader Here is a complete example using (in particular, the as URL) to test the download: http://ovh.net/files/ http://ovh.net/files/1Mio.dat ; ; ; ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); FlutterDownloader.initialize( debug: ); Permission.storage.request(); runApp(MyApp()); } { _MyAppState createState() => _MyAppState(); } { InAppWebViewController _webViewController; Widget build(BuildContext context) { MaterialApp( home: Scaffold( appBar: AppBar( title: Text( ), ), body: Container( child: Column(children: <Widget>[ Expanded( child: InAppWebView( initialUrl: , initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , useOnDownloadStart: ), ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; }, onDownloadStart: (controller, url) { ( import 'dart:async' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' import 'package:flutter_downloader/flutter_downloader.dart' import 'package:path_provider/path_provider.dart' import 'package:permission_handler/permission_handler.dart' async await true // optional: set false to disable printing logs to console await class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp @override return const 'InAppWebView Example' "http://ovh.net/files/1Mio.dat" true true async print "onDownloadStart $url "); final taskId = await FlutterDownloader.enqueue( url: url, savedDir: (await getExternalStorageDirectory()).path, showNotification: true, // show download progress in status bar (for Android) openFileFromNotification: true, // click on notification to open downloaded file (for Android) ); }, )) ])), ), ); } } As you can see, I’m using also the plugin to get the folder where I want to save the file. path_provider Allow Self-signed SSL Certificates To allow self-signed SSL certificates, you can use the event and simply return to proceed with the request: onReceivedServerTrustAuthRequest onReceivedServerTrustAuthRequest: (controller, challenge) { ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED); }, async return How to manage popup windows opened with target=”_blank” or “window.open” To manage popup windows when a user clicks on a link with or through JavaScript code using , you can use the event. On Android, to be able to allow this event, you need to set the option to . target="_blank" window.open onCreateWindow supportMultipleWindows true Also, in order to be able to allow the usage of JavaScript, you need to set the to . javaScriptCanOpenWindowsAutomatically true If you want to manage these requests, you should return from this event, otherwise, the default implementation of this event does nothing and hence returns . true false The represents the navigation request which contains a that can be used to create, for example, a new instance. This is used by the native code to map the request and the WebView to be used to manage that request. CreateWindowRequest windowId InAppWebView windowId Also, contains the of the request (on Android, if the popup is opened using JavaScript with , it will be ), but if you need to maintain the Window JavaScript object reference (created using method), for example, to call method, then you should create the new WebView with the , without using the . CreateWindowRequest url window.open null window.open window.close windowId url Here is a simple example that shows an when the user clicks on the link: AlertDialog ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); runApp( MyApp()); } { _MyAppState createState() => _MyAppState(); } { Widget build(BuildContext context) { MaterialApp( home: InAppWebViewPage() ); } } { _InAppWebViewPageState createState() => _InAppWebViewPageState(); } { InAppWebViewController _webViewController; InAppWebViewController _webViewPopupController; initState() { .initState(); } dispose() { .dispose(); } Widget build(BuildContext context) { MaterialApp( home: Scaffold( appBar: AppBar( title: Text( ), ), body: SafeArea( child: Container( child: InAppWebView( initialData: InAppWebViewInitialData( data: ), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , javaScriptCanOpenWindowsAutomatically: ), android: AndroidInAppWebViewOptions( supportMultipleWindows: ) ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; }, onCreateWindow: (controller, createWindowRequest) { ( ); showDialog( context: context, builder: (context) { AlertDialog( content: Container( width: MediaQuery.of(context).size.width, height: , child: InAppWebView( windowId: createWindowRequest.windowId, initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , ), ), onWebViewCreated: (InAppWebViewController controller) { _webViewPopupController = controller; }, onLoadStart: (InAppWebViewController controller, url) { ( import 'dart:async' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' async new class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp @override return class InAppWebViewPage extends StatefulWidget @override new < > class _InAppWebViewPageState extends State InAppWebViewPage @override void super @override void super @override return const 'InAppWebView Example' """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Flutter InAppWebView</title> </head> <body> <a style="margin: 50px; background: #333; color: #fff; font-weight: bold; font-size: 20px; padding: 15px; display: block;" href="https://github.com/flutter" target="_blank"> Click here to open https://github.com/flutter in a popup! </a> </body> </html> """ true // set this to true if you are using window.open to open a new window with JavaScript true // on Android you need to set supportMultipleWindows to true, // otherwise the onCreateWindow event won't be called true async print "onCreateWindow" return 400 // Setting the windowId property is important here! true String print "onLoadStart popup onLoadStop popup $url "); }, onLoadStop: (InAppWebViewController controller, String url) { print(" $url "); }, ), ), ); }, ); return true; }, ), ), ), ), ); } } Manage platform URLs such as whatsapp:, fb:, tel:, mailto:, etc. Generally, a WebView knows nothing on how to manage , or protocol/scheme. To capture the requests made with these custom protocols/schemes, you can use the event (you need to enable it with option). whatsapp: tel: fb: shouldOverrideUrlLoading useShouldOverrideUrlLoading: true This way you can cancel the request made for the WebView and, instead, open the App, for example, using the plugin: url_launcher initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: , useShouldOverrideUrlLoading: ), ), shouldOverrideUrlLoading: (controller, request) { url = request.url; uri = .parse(url); (![ , , , , , , ].contains(uri.scheme)) { ( canLaunch(url)) { launch( url, ); ShouldOverrideUrlLoadingAction.CANCEL; } } ShouldOverrideUrlLoadingAction.ALLOW; }, true true async var var Uri if "http" "https" "file" "chrome" "data" "javascript" "about" if await // Launch the App await // and cancel the request return return Manage WebView Cookies To manage WebView cookies, you can use the class, which implements a singleton object (shared instance). On Android, it is implemented using the class. On iOS, it is implemented using the class. CookieManager CookieManager WKHTTPCookieStore Here is an example of how to set a cookie: CookieManager _cookieManager = CookieManager.instance(); expiresDate = .now().add( (days: )).millisecondsSinceEpoch; _cookieManager.setCookie( url: , name: , value: , domain: , expiresDate: expiresDate, isSecure: , ); final DateTime Duration 3 "https://flutter.dev/" "session" "54th5hfdcfg34" ".flutter.dev" true Custom context menus You can customize WebView’s context menu adding custom menu items, and/or hiding the default system menu items. For each custom menu item, you can declare a callback to be invoked when the user clicks on it. As an example, I will add a custom menu item named and I will define a callback action that shows a JavaScript to the user with the text selected. action Special window.alert Here is the full code example: ; ; ; ; Future main() { WidgetsFlutterBinding.ensureInitialized(); runApp(MyApp()); } { _MyAppState createState() => _MyAppState(); } { Widget build(BuildContext context) { MaterialApp( home: InAppWebViewPage() ); } } { _InAppWebViewPageState createState() => _InAppWebViewPageState(); } { InAppWebViewController _webViewController; ContextMenu contextMenu; initState() { .initState(); contextMenu = ContextMenu( menuItems: [ ContextMenuItem(androidId: , iosId: , title: , action: () { ( ); selectedText = _webViewController.getSelectedText(); _webViewController.clearFocus(); _webViewController.evaluateJavascript(source: import 'dart:async' import 'dart:io' import 'package:flutter/material.dart' import 'package:flutter_inappwebview/flutter_inappwebview.dart' async class MyApp extends StatefulWidget @override new < > class _MyAppState extends State MyApp @override return class InAppWebViewPage extends StatefulWidget @override new < > class _InAppWebViewPageState extends State InAppWebViewPage @override void super 1 "1" "Special" async print "Menu item Special clicked!" var await await await "window.alert('You have selected: $selectedText ')"); }) ], options: ContextMenuOptions( hideDefaultSystemContextMenuItems: false ), onCreateContextMenu: (hitTestResult) async { print("onCreateContextMenu"); print(hitTestResult.extra); print(await _webViewController.getSelectedText()); }, onHideContextMenu: () { print("onHideContextMenu"); }, onContextMenuActionItemClicked: (contextMenuItemClicked) async { var id = (Platform.isAndroid) ? contextMenuItemClicked.androidId : contextMenuItemClicked.iosId; print("onContextMenuActionItemClicked: " + id.toString() + " " + contextMenuItemClicked.title); } ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InAppWebView") ), body: Container( child: Column(children: <Widget>[ Expanded( child: Container( child: InAppWebView( initialUrl: "https://github.com/flutter", contextMenu: contextMenu, initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( debuggingEnabled: true, ), ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; }, ), ), ), ])) ); } } Conclusion In this article, I made a little introduction to the plugin, in particular, about the InAppWebView widget. The plugin is in continuous development (at the time of this writing, the latest release is ) and I recommend you check out the API Reference to find out all the features. For any new feature request/bug fix, you can use the issue section of the repository. flutter_inappwebview 4.0.0+4 The next article will be on how to implement a using this plugin. Full-Featured Browser That’s all for today! I hope it has opened new use cases for your Flutter apps.