Diving Deep into WebViews
Learn how to handle webpages in your Flutter application using WebViews. By Michael Malak.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Diving Deep into WebViews
20 mins
- Getting Started
- Setting up the Starter Project
- Understanding WebViews
- Viewing the WebView
- Controlling the WebView
- Understanding Futures and Completers
- Loading URLs with WebViewController
- Adding Bottom Navigation Controls
- Handling Navigation to Certain Domains
- Sending Data to a Webpage
- Listening to Javascript Channel
- Working with Gesture Recognizer
- Understanding the GestureArena
- Viewing a List of Horizontal WebViews
- Where to Go From Here?
Do you have a feature on your website that you don’t want to implement again for your app? Does your app need to view a webpage without having to open a browser? Have you integrated a third-party service that requires you to use their webpages in your app? WebView
can accomplish all that for you in your Flutter app!
In this tutorial, you’ll play with WebView
as you build a simple app that randomly displays webpages and lets you save the ones you like.
By the end of this tutorial, you’ll know:
- What
WebView
is. - How to send and receive data with a webpage opened in
WebView
. - When to use
GestureRecognizer
.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
You’ll work on URL Gems App, a single-page app that displays a WebView
of a random website in one tab and a list of saved websites in another. Here’s what you’ll be able to do:
-
Discover: Reload the
WebView
with a random URL, navigate backward and forward, save a webpage, prevent navigation outside the displayed page’s domain, and display and communicate with a help page in HTML format. - Saved: View a vertical list of the saved webpages.
Here’s how the pages will look when you’re done:
Now, it’s time to take a look at the project.
Setting up the Starter Project
The starter project already contains the logic to get random URLs, save the URLs to the cache and read them from the cache.
Open Android Studio and choose Open an Existing Project. Then, choose the starter folder from the downloaded materials.
Before you fetch the dependencies declared in pubspec.yaml, you’ll add the WebView
dependency to the file. This is the plugin you’ll use to demonstrate WebView
functionalities in this project. Replace # TODO: Add dependency for WebView Plugin
with:
webview_flutter: ^2.0.8
Click on Pub get at the top of the pane when you’re in pubspec.yaml to fetch the dependencies declared in the file.
For this tutorial, the most important files in the project are:
- lib/presentation/discover_page/discover_page.dart: The page that displays a single webpage and allows you to save it or reload it.
- lib/presentation/discover_page/widgets/navigation_controls_widget.dart: The widget class that is part of the footer where you can navigate forward and backward and reload.
- assets/help_page.html: An HTML page that loads the instructions for using the app when you route to an invalid URL.
- lib/presentation/saved_urls_page/saved_urls_page.dart: The page where you display a vertical list of saved webpages.
Build and run. The app launches with the Discover tab selected. It shows a placeholder for now.
Now that you know what the starter project contains, you’ll take a deeper look at WebView
.
Understanding WebViews
Flutter provides you with a way to view webpages inside your app without having to open a browser. Using WebView
, you can simply pass a URL and it loads as a widget inside your app. Flutter WebViews use a technology known as Platform Views. On iOS, the WebView
These are special widgets that embed native views into Flutter. Platform Views can be expensive so use them carefully. On iOS, the WebViews uses the native WKWebView
and on Android, the standard native Android WebView
is used.
WebView
plugin doesn’t support Flutter web, since everything is already a web view. For that, you might use HtmlElementView
.Viewing the WebView
Start by going to lib/presentation/discover_page/discover_page.dart, and add an import to the WebView
package at the top of the file:
import 'package:webview_flutter/webview_flutter.dart';
Now you can replace the body of buildWebView
in the same file with:
return WebView(
// 1
initialUrl: url,
// 2
javascriptMode: JavascriptMode.unrestricted,
// 3
onProgress: onProgressWebView,
onPageFinished: onPageFinishedWebView,
// 4
gestureNavigationEnabled: true,
);
Here’s what you did:
- You specified an
initialUrl
for what page to display first. -
javascriptMode
allows you to control what kind of JavaScript can run in your web view. By default, JavaScript execution is disabled. You set it toJavascriptMode.unrestricted
to enable it. -
onProgress
andonPageFinished
are callback functions that you trigger when the page is loading and finished loading, respectively. You passedonProgressWebView
to set theisLoading
state, andonPageFinishedWebView
to check if the URL is valid and toggleisLoading
. - By setting
gestureNavigationEnabled
to true, you can use horizontal swipe gestures to trigger back-forward list navigations on theWebView
for iOS.
Build and run. You’ll see a webpage displayed inside the Discover tab. You’ve implemented your first WebView
!
Now you know how to load webpages with WebView
. Next, you’ll look at controlling it.
Controlling the WebView
There’s a lot of different things you can do with a WebView
, but before you surf through all of the options you’ll have to learn how to exert a bit more control over WebView
.
Understanding Futures and Completers
A Future
in Flutter is an object that represents delayed computation — a value that will be available sometime in the future. For example, you can wait for the Future
to get a random URL from the cache as follows:
final fetchedUrl = await widget.repository.getRandomUrl(exclude: url);
getRandomUrl
returns a Future
that you’ll wait for to get your fetchedUrl
A Completer
is a way provided by Flutter to produce Future
s and to complete them later. It’s mostly used when you want to convert a callback-based API into a Future
. To use a Completer
, you have to:
- Create a new completer
- Use its
Future
- Invoke either
complete
orcompleteError
You’ll need to know about Completer
s to work with WebViewController
s. The WebView
package provides you with a WebViewController
. However, WebView
doesn’t take the controller as an argument. In the next section, you’ll learn how to use WebViewController
and how to specify when the WebViewController
has completed loading.
Loading URLs with WebViewController
Now that you’ve added a WebView
to DiscoverPage
, you want to load URLs when the user clicks on I’m feeling lucky!. Add WebViewController
to _DiscoverPageState
by replacing //TODO: add WebViewController here
with
final Completer<WebViewController> _controller =
Completer<WebViewController>();
You’ll trigger the controller you added. In the WebView
you added earlier, pass complete
and completeError
as follows:
return WebView(
..
onWebViewCreated: _controller.complete,
onWebResourceError: _controller.completeError,
);
At last, you want to use the controller to load the URL. Replace //TODO: load url to be displayed on WebView
with:
final controller = await _controller.future;
controller.loadUrl(url);
Here, loadUrl
triggers the WebView
to load the new URL you pass.
Build and run. You can click I’m feeling lucky!, and the WebView
loads the new URL.
Adding Bottom Navigation Controls
Now, you can load URLs using WebViewController
. You can use the same controller to navigate forward, backward and reload the webpages.
Add an import to NavigationControls
at the top of lib/presentation/discover_page/discover_page.dart:
import 'widgets/navigation_controls_widget.dart';
Replace the Placeholder
widget at the FooterWidget
as follows:
navigationControls: NavigationControls(
webViewController: _controller.future,
isLoading: isLoading,
),
Here, you pass the WebViewController
to the NavigationControls
. You still want to trigger the respective functions in the controller inside the NavigationControls
.
In lib/presentation/discover_page/widgets/navigation_controls_widget.dart, replace the onPressed
block where the //TODO: Refresh page using WebViewController
text is with the following:
onPressed: !isReady ? null : controller.reload,
controller.reload
allows you to reload the page in your WebView
.
Similarly, replace //TODO: Route Forward using WebViewController
with:
await controller.goForward();
and //TODO: Route Backward using WebViewController
with
await controller.goBack();
Now, you can navigate backward and forward using the WebViewController
callback functions.
Build and run. When you click backward, forward or reload, they function as you’d expect.
What if you want to prevent navigation to certain domains for your WebView
? You’ll handle that next.
Handling Navigation to Certain Domains
You have a WebView
that you can control. However, by default, all navigation actions are allowed. If you want to decide which pages to allow or prohibit, you’ll need to make some modifications. Fortunately, WebView
provides NavigationDelegate
.
Say you want to navigate only to URLs that contain the domain name you opened. Otherwise, you’ll consider the URL as invalid and navigate to the help page.
To implement that behavior, in DiscoverPage
, replace ///TODO: Add Navigation Delegate to prevent navigation to certain domains
with:
Future<NavigationDecision> getNavigationDelegate(
NavigationRequest request) async {
if (request.url.contains(domainName)) {
// 1
return NavigationDecision.navigate;
}
if (!request.isForMainFrame) {
// 2
return NavigationDecision.prevent;
}
// 3
setState(() {
invalidUrl = request.url;
});
// 4
await routeToHelpPage();
// 5
return NavigationDecision.prevent;
}
Here’s what’s happening above:
- In case the URL was valid, you return
NavigationDecision.navigate
. You give the green light to theWebView
to proceed with the navigation. - Some pages you view change the URL in order to open modals or ads. When this happens, the
request.isForMainFrame
boolean isfalse
. To prevent navigation in that case, you returnNavigationDecision.prevent
. - You set
invalidUrl
with the URL you get from theNavigationRequest
. - Since you detected navigation to an invalid URL, you force navigating to the help page.
- You prevent navigation to the requested invalid URL.
Now that you specified your NavigationDelegate
, you can pass it to your WebView
.
return WebView(
..
navigationDelegate: getNavigationDelegate,
);
Build and run. You navigate automatically to the help page when you try to navigate outside the domain of the open webpage.
Would it be possible to trigger JavaScript functions from Flutter side? You’ll learn more in the next section.
Sending Data to a Webpage
Sometimes, you’ll want to evaluate JavaScript expressions of a webpage that you’re viewing in a WebView
. You can do that using WebViewController
by passing data to the page you are viewing.
A user might find it confusing to see the help page while navigating. To avoid this confusion, you can pass the invalid URL to the help page and display it as the reason for why the navigation was interrupted.
In DiscoverPage
, replace //TODO: Send data to webpage function
with:
void sendUrlToHelpPageJavascriptFunction(
String invalidUrlToBeDisplayed, String urlToBeDisplayed) async {
_controller.future.then((controller) {
// 1
controller.evaluateJavascript(
// 2
'''displayInvalidUrl('$invalidUrlToBeDisplayed', '$urlToBeDisplayed')''',
).then((result) {});
});
}
Here, you:
- Called
evaluateJavascript
, which evaluates the passed string as JavaScript inside the HTML page that you load. - Passed a raw string that calls a JavaScript function named displayInvalidUrl. You passed both the invalid URL and the domain name that you should navigate within.
WebView
, it’s best practice to wait for the WebView.onPageFinished
callback to guarantee that all the JavaScript embedded in the main frame HTML has been loaded. The Future
completes with an error if a JavaScript error occurred.You trigger sendUrlToHelpPageJavascriptFunction
when the page is loaded with an invalid URL. Replace //TODO: Send url to webpage
with:
sendUrlToHelpPageJavascriptFunction(invalidUrl!, url!);
Since you call the JavaScript function, displayInvalidUrl
in the help page, you’ll implement this function in JavaScript.
In assets/help_page.html, replace <!--TODO: Display Invalid URL function -->
with a little bit of JavaScript:
function displayInvalidUrl(invalidUrl, validUrl) {
document.getElementById("invalid-url").innerHTML = "It looks like you routed to an invalid url: "
+ invalidUrl + ", while you should keep browsing: " + validUrl;
document.getElementById("invalid-url").style.visibility = "visible";
}
Here, you display the passed invalid and valid URLs and toggle the visibility of the div
.
Hot restart. You can now see the invalid URL when you automatically navigate to the help page.
Next, you’ll learn how to trigger functions from the webpage in your Flutter app.
Listening to Javascript Channel
You can call JavaScript functions from your Flutter app. Guess what? you can do the opposite as well using JavascriptChannel
from WebView
package. You want the Discover a new website and Check your saved websites buttons in the Help Page to function as you’d expect.
In assets/help_page.html, replace <!--TODO: Trigger reload url Flutter function -->
with:
RefreshUrl.postMessage("");
and similarly replace <!--TODO: Trigger route to saved websites Flutter function -->
with:
RouteToSavedWebsites.postMessage("");
Here, you trigger two channels, RefreshUrl
and RouteToSavedWebsites
, in order for the WebView
to listen to them. You call postMessage
on each of the two objects so it passes a message to the WebView
. Since you’re only triggering the channels, you pass empty messages.
In lib/presentation/discover_page/discover_page.dart, replace //TODO: receive RefreshUrl message from webpage
with:
JavascriptChannel refreshUrlJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'RefreshUrl',
onMessageReceived: (_) {
onImFeelingLuckyPressed();
});
}
Here, you specify a JavascriptChannel
with name RefreshUrl
. When you call postMessage
, You pass the message to JavascriptChannel.onMessageReceived
and it triggers onImFeelingLuckyPressed();
Similarly, replace //TODO: receive RouteToSavedWebsites message from webpage
with:
JavascriptChannel routeToSavedWebsitesJavascriptChannel(
BuildContext context) {
return JavascriptChannel(
name: 'RouteToSavedWebsites',
onMessageReceived: (_) {
widget.routeToSavedUrlsTab();
});
}
Now, when postMessage
for RouteToSavedWebsites
is called, it triggers widget.routeToSavedUrlsTab();
and it routes to the saved URLs tabs.
You have two JavascriptChannel
s that you’ll pass as a set to the WebView
as follows:
return WebView(
...
javascriptChannels: <JavascriptChannel>{
refreshUrlJavascriptChannel(context),
routeToSavedWebsitesJavascriptChannel(context),
},
);
Build and run. From the help page, you can trigger JavaScript functions to refetch random URLs and route to another tab.
Working with Gesture Recognizer
Sometimes you want to act on your WebView
with different gestures, like swipes. Luckily, the WebView
package has you covered! In this section, you’ll learn about using gestures with WebView
s, starting with GestureArena
.
Understanding the GestureArena
In order to understand GestureArena
, you first need to understand the gesture system in Flutter. It consists of two elements: Pointer
and Gesture
.
- Pointer: Represents raw data about your interaction with your screen. It describes the location and movement of various interactions, like touches or mouse movements.
-
Gesture: Represents actions of different combinations of
Pointer
s. MultiplePointer
s could represent, for instance, a tap or a drag.
Most Material Components respond to Gesture
and claim part of the screen to detect these Gesture
s. You may have multiple Gesture detectors listening to the stream of Pointer
s.
GestureArena
is like a battle between different Gesture recognizers — only one will win, and it depends on the priority of its widget and the behavior of the stream of Pointer events.
When you find that by default the Gesture
s that are recognized are not what you expect, you can claim your own Gesture recognizer. This changes the prioritization of your widget in GestureArena
.
Viewing a List of Horizontal WebViews
You want to view all the saved websites in a vertical list. By default, a WebView
only responds to a gesture if no other widgets claimed it. In this case, ListView
widget claims vertical drag gestures and you can’t scroll the pages in your WebView
. To change this behavior, you’ll pass Gesture recognizers to the WebView
.
In lib/presentation/saved_urls_page/widgets/saved_website_card.dart, you’ll add the following import:
import 'package:flutter/gestures.dart';
Next, you’ll replace //TODO: Add Gesture Recognizers
with:
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
// 2
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
},
Here’s what’s happening above:
- You passed a set of
Factory
ofOneSequenceGestureRecognizer
. This class is a base class for gesture recognizers that can only recognize one gesture at a time. - By specifying
VerticalDragGestureRecognizer
as the gesture recognizer, you want Flutter to prioritize this gesture in the GestureArena.
Build and run. Now, you can scroll each WebView
without affecting the scrollability of the vertical ListView
.
Where to Go From Here?
Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.
You now have a deeper understanding of WebView
, and more importantly, when and how to use it. When you find the need, you can control the WebView
or specify your own NavigationDelegate
. And, you can send data to and receive data from a webpage.
Check out the following links to learn more about some of the concepts in this tutorial:
- Incorporating Web View into Your App (The Boring Flutter Development Show, Ep. 14).
- Flutter docs on Gestures.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more