Make Your Webview app look real and attractive— Flutter

Hey there… In this article, I am assuming you already know how to implement a WebView in Flutter. If you do not, no worries, you can read this article to get started.

So, you have your WebView app set up but for some reason, you feel like it looks more like a website than a mobile application. Here are some tips on how to make it look more like an app than a website.

Add a loading screen.
A loading screen that probably shows a spinner while the app fetches content from the website would be nice. To achieve this, you’d have to first wrap your WebView with a Stack widget.
The stack widget has a “children” property which stacks widgets on top of each other. Just think of it as putting books on top of each other. You’d most likely put the one you need at the moment on top. That’s exactly what Stack does here.
For our loading screen, we would be adding two children to our stack widget.
i. Our WebView
ii. a Container.
The container would take a background colour and its height and width would be set to the height and width of the screen.

Stack(
children: <Widget>[
Webview(...),
Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: const Center(
child: CircularProgressIndicator(
color: Colors.teal,
)),
),
]
)

In order to get remove the loading screen when our webpage loads up, we would need a boolean which checks if loading is still in progress.

On initialization, the loading state should be set to true, and with a conditional expression, we should be able to check if the bool is true or false and apply changes accordingly.

late bool isLoading;@override
void initState() {
super.initState();
...
isLoading = true;
}

Now, the second child in our stack should look more like this:

isLoading
? Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: const Center(
child: CircularProgressIndicator(
color: Colors.teal,
)),
)
: Container(),

So, when loading is done, an empty container is shown. For this to happen though, we need to ensure that the onPageFinished property of our WebView changes our boolean to false once the WebView is completely created.

onPageFinished: (done) {
setState(() {
isLoading = false;
});
},

Add an error page

I am certain you wouldn’t want to show your user a 404 error on your mobile application. For starters, mobile apps are not supposed to have those. What then do you do when your user does not have an internet connection but is trying to use the mobile app?

Thankfully, our webview_flutter package has a property that checks for errors from the webpage. All we need to do now is leverage on that to show our users an interactive error screen whenever there’s an error that prevents our app from loading up. We will be needing the loading boolean we created earlier as well as a new boolean which checks whether the error page should be shown.

onWebResourceError: (error) {
setState(() {
showErrorPage = true;
isLoading = false;
});
}
...
@override
void initState() {
super.initState();
...
showErrorPage = false;
isLoading = true;
}

We can now go on to add a third child to our stack. This would be our error page. We’d have a conditional expression that would check if the showErrorPage bool is true or not, to determine whether we should display the error page.

showErrorPage
? Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,

children: [
const Text(
'An error occurred. \nPlease check your internet and try again',
textAlign: TextAlign.center,
),
const SizedBox(
height: 20.0,
),
],
),
),
),
)
: Container(),

We would need to give our user the ability to reload the page from the error page and for us to do that, we would need to tap into the controller of our WebView and link it to the controller obtained from the WebView when it gets created.

WebViewController? webViewController;
.
.
.
onWebViewCreated: (controller){
webViewController = controller;
}

Now to our reload Button.

Under the Text indicating that there was an error, a reload button allows the user to refresh the page. While refreshing, we call back our loading screen by setting the loading boolean to true.

Button(
buttonText: 'Reload',
onPressed: () {
setState(() {
isLoading = true;
showErrorPage = false;
});
webViewController!.reload();
},
color: Colors.white,
textColor: kMainColor,
borderColor: kMainColor,
borderRadius: 0,
buttonHeight: 40,
),

You may have noticed by now that after clicking a link on the first page, a user is unable to return to the previous screen. This is because the back button currently sees just one screen so when it is pressed, it closes that screen. Let’s fix that.

Adding backward navigation

To add back navigation to our webView app, we will be adding the WillPopScope widget to our build method. We will also be making it the topmost widget in our widget tree.

Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
...
),
),
}

WillPopScope has two required properties: a child (our scaffold) and an “onWillPop” property which takes a future function.

Let’s start by creating a function that we would be passing into the onWillPop property. In the function, we check to see if the current webViewController can go back and if it can, we call the goBack method. If it can’t, we return true to pop the current screen and close the app.

Future<bool> _exitApp(BuildContext context) async {
if (await webViewController!.canGoBack()) {
webViewController!.goBack();
return false;
}
return true;
}

Edit: To ensure that a user gets a loading screen every time they tap a button, you need to use the navigationDelegate property of the webview, set the isLoading boolean to true and then return the navigation decision when you’re done. Shoutout to Soukainader for asking the question that led to this edit.

navigationDelegate: (NavigationRequest request) {
setState(() {
isLoading = true;
});
return NavigationDecision.navigate;
}

An extra tip that you may find useful:

Use bottom or drawer navigation with parts of your website or web app embedded into various parts of your navigation buttons.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store