- Basic usage
- Limitations and notes
- Known issues and TODOs
- Create a
WebViewXControllerinside your stateful widget
- Add the WebViewX widget inside the build method, and set the
onWebViewCreatedcallback in order to retrieve the controller when the webview is initialized
WebViewX( initialContent: '<h2> Hello, world! </h2>', initialSourceType: SourceType.HTML, onWebViewCreated: (controller) => webviewController = controller, ... ... other options );
If you need to add other widgets on top of the webview (e.g. inside a Stack widget), you MUST wrap those widgets with a WebViewAware widget. This does nothing on mobile, but on web it allows widgets on top to intercept gestures. Otherwise, those widgets may not be clickable and/or the iframe will behave weird (unexpected refresh/reload - this is a well known issue).
Also, as a side note: If you happen to add widgets on top of the webview, wrap them and then you notice that the iframe still reloads unexpectedly, you should check if there are other widgets that sit on top without being noticed, or try to wrap InkWell, GestureRecognizer or Button widgets to see which one causes the problem.
- Interact with the controller (run the example app to see how does it work)
webviewController.loadContent( 'https://flutter.dev', SourceType.URL, ); webviewController.goBack(); webviewController.goForward(); ... ...
Note: For instructions on how to use theese features, please see each one's documentation, located inside it's coresponding class.
|Initial webview content|
|Initial webview content type (|
|User agent ( issues on web )|
|Widget's width (if null, it takes all available space)|
|Widget's height (if null, it takes all available space)|
|Callback that gets executed when the webview has initialized|
|Boolean value that specifies if the widget should ignore all gestures right after it is initialized|
|This specifies if media content should be allowed to autoplay when initialized (i.e when the page is loaded)|
|Callback that gets executed when a page starts loading (e.g. after you change the content)|
|Callback that gets executed when a page finishes loading|
|Callback that gets executed when there is an error when loading resources ( issues on web )|
|This is an object that contains web-specific options. Theese are not available on mobile (yet)|
|This is an object that contains mobile-specific options. Theese are not available on web (yet)|
|Load URL that allows iframe embedding||webviewController.|
|Load URL that doesnt allow iframe embedding||webviewController.|
|Load URL that doesnt allow iframe embedding, with headers||webviewController.|
|Load HTML from string||webviewController.|
|Load HTML from assets||webviewController.|
|Check if you can go back in history|
|Go back in history|
|Check if you can go forward in history|
|Go forward in history|
|Reload current content||webviewController.|
|Check if all gestures are ignored||webviewController.|
|Set ignore all gestures||webviewController.|
|Call a JS method|
|Retrieve webview's content|
Limitations and notes
While this package aims to put together the best of both worlds, there are differences between web and mobile.
Running and building
First, this package was being developed while the default
html. Now(Flutter 2, Dart 2.12), the default renderer is
From my experience, this package does behave a little bit weird on canvaskit, so you should use the
To do this, you have to run your ordinary
flutter run -d chromecommand with the
--web-renderer htmlextra argument, like this:
flutter run -d chrome --web-renderer html
for running and
flutter build web --web-renderer html
Behaviour when loading content
To make the web version (iframe) work as it is, I had to use this x-frame bypass in order to make a request to a pre-defined CORS proxy, which removes the headers that block iframe embeddings.
This might seem like a hack, and it really is, but I couldn't find any other way to make the iframe behave similar to the mobile webview (which is some kind of an actual browser, that's why everything works there by default).
Also, it might not work on Safari, as stated here.
On web, the history navigation stack is built from scratch because I couldn't handle iframe's internal history the right way.
Known issues and TODOs
SourceType.URL_BYPASS, and they only have effect the first time being used (
URL_BYPASSto a dart callback, which will then be sent through the
onWebResourceErrorcallback, just like on the mobile version (
MobileSpecificParamsshould merge and theese two objects may disappear
List open, there may be others
This package wouldn't be possible without the following:
- webview_flutter for the mobile version
- easy_web_view for ideas and starting point for the web version
- pointer_interceptor for fixing iframe issues when other widgets are on top of it (see above)
- x-frame-bypass for allowing the iframe to bypass websites' X-Frame-Options: deny/same-origin headers, thus allowing us to load any webpage (just like on mobile)
- https://cors.bridged.cc/ for the free CORS proxy
- https://api.codetabs.com/ for the free CORS proxy
- And last but not least, http://deversoft.ro (the company I work for) for motivating me throughout the development process