Reputation: 263
I'm trying to use a webview in flutter as my home page for my application. Everything loads just fine, however hitting the back button doesn't send me to the previous web page in the webview, it just exits the application.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class _StudentPortalState extends State<StudentPortal>{
_launchURL(url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
Widget build (BuildContext context){
return Scaffold(
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: 'https://sites.google.com/ttmsa.org/ttmsa/home',
navigationDelegate: (NavigationRequest request) {
if(request.url.contains("intent:")) {
_launchURL('https://forms.gle/XfNbn1Ph9xFdUaKeA');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
}
}`
Upvotes: 26
Views: 54848
Reputation: 728
'WillPopScope'
is deprecated and shouldn't be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre. (Documentation)
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
onPopScopePressBackShowAlertDialog();
},
child: Scaffold()
);
}
exit(0);
for leave the appalication
Future onPopScopePressBackShowAlertDialog() async {
if (await webViewController.canGoBack()) {
webViewController.goBack();
} else {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Are you sure you want to close the application?',
textAlign: TextAlign.center,
),
contentPadding:
const EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
actions: <Widget>[
TextButton(
child: Text('Leave'),
onPressed: () {
exit(0);
},
),
ElevatedButton(
style: ElevatedButtonNormal(context),
child: Text('Stay'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
});
}
}
Upvotes: 3
Reputation: 1
I found a really cool way to forward and back in a simple and functional way.
This will be done by appbar.
appBar: AppBar(
backgroundColor: Colors.red[900],
actions: <Widget>[
ElevatedButton(onPressed: (){
print("Voltando");
controller.goBack();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
child: Image.network('https://raw.githubusercontent.com/HeroRickyGAMES/Projeto-CPJ-Consulta-Juridica-de-Processos/master/src/voltar.png')
),
ElevatedButton(onPressed: (){
print("Indo pra frente");
controller.goForward();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
child: Image.network('https://raw.githubusercontent.com/HeroRickyGAMES/Projeto-CPJ-Consulta-Juridica-de-Processos/master/src/irprafrente.png')
)
],
title: Text("Minha Pagina Web"),
),
Upvotes: 0
Reputation: 5018
Wrap WillPopScope
around Webview widget and use canGoBack()
method to check whether going to back webpage is possible, if yes use goBack()
method from the Webview controller to goto last web page.
class TheScreen extends StatelessWidget{
WebViewController? _controller;
Widget build(BuildContext context){
return WillPopScope(
onWillPop: () async {
bool? goBack = _controller?.canGoBack();
if(goBack != true) return true;
else {
_controller.goBack();
return false;
}
},
child: WebView(
initialUrl: "www.flutter.com",
onWebViewCreated: (controller)=> _controller = controller),
}
}
}
Upvotes: 3
Reputation: 63
The answer of chunhunghan worked for me after I changed
return Future.value(true);
in the if block of _exitApp() to
return Future.value(false);
With true the webview was going back to the last site and after this the 'default device back button handler' (I dont know how it is exactly named) closed the app. But with false the app isnt closed after the site of goBack is opened.
I am posting this as extra answer because my reputation is to low to comment on answers. But I spend some hours to find this out and hope this information is as helpful to others as it was to me.
Upvotes: 2
Reputation: 363
Code for onbackpressed Webview
Future for handling the back button
WebViewController controllerGlobal;
Future<bool> browserBack(BuildContext context) async{
print('activated');
if (await controllerGlobal.canGoBack()) {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("Munching....")),
);
print("onwill goback");
controllerGlobal.goBack();
}
else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
return Future.value(false);
}
}
WillPopScope widget
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()=> browserBack(context),
child:Scaffold(
body: Builder(builder: (BuildContext context){
return WebView(
initialUrl: 'https://flutter.dev/docs/deployment/android',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
// _controller.complete(webViewController);
controllerGlobal = webViewController;
},
);
},),
),
);
Upvotes: 1
Reputation: 1468
perfectly working code
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
class Homepage extends StatefulWidget {
@override
HomepageState createState() => HomepageState();
}
class HomepageState extends State<Homepage> {
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
),
);
}
Future<bool> _goBack(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
FlatButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
return Future.value(true);
}
}
}
Upvotes: 16
Reputation: 627
You have to control the logic in WillPopScope component:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home', url: 'https://flutter.dev/'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.url});
final String title;
final String url;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(context),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: WebView(
key: UniqueKey(),
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
initialUrl: widget.url,
)),
),
);
}
}
Upvotes: 7
Reputation: 2033
Here is the must better answer(100% working)-
// Use any webview plugin
// WebView _controller;
Future<void> _handleBack(context) async {
var status = await _controller.canGoBack();
if (status) {
_controller.goBack();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
FlatButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
}
}
Here is calling WillPopScope
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _handleBack(context),
child: SafeArea(
top: true,
child: Scaffold(......)))
Upvotes: 3
Reputation: 54367
Wrap Scaffold with WillPopScope and when user click device back button execute WebView Controller goback
code snippet onwillpop
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: 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),
],
),
code snippet for exit app
WebViewController controllerGlobal;
Future<bool> _exitApp(BuildContext context) async {
if (await controllerGlobal.canGoBack()) {
print("onwill goback");
controllerGlobal.goBack();
return Future.value(true);
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
}
}
full code
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => 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><a href="https://www.google.com/">https://nodejs.org/en</a></ul>
</ul>
</body>
</html>
''';
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
WebViewController controllerGlobal;
Future<bool> _exitApp(BuildContext context) async {
if (await controllerGlobal.canGoBack()) {
print("onwill goback");
controllerGlobal.goBack();
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
}
}
class _WebViewExampleState extends State<WebViewExample> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: 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: (BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
// ignore: prefer_collection_literals
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
if (request.url.startsWith('https://flutter.dev/docs')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
onPageFinished: (String url) {
print('Page finished loading: $url');
},
);
}),
floatingActionButton: favoriteButton(),
),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
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();
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('Favorited $url')),
);
},
child: const Icon(Icons.favorite),
);
}
return Container();
});
}
}
enum MenuOptions {
showUserAgent,
listCookies,
clearCookies,
addToCache,
listCache,
clearCache,
navigationDelegate,
}
class SampleMenu extends StatelessWidget {
SampleMenu(this.controller);
final Future<WebViewController> controller;
final CookieManager cookieManager = CookieManager();
@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(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 Toaster JavaScript channel we registered
// with the WebView.
controller.evaluateJavascript(
'Toaster.postMessage("User Agent: " + navigator.userAgent);');
}
void _onListCookies(
WebViewController controller, BuildContext context) async {
final String cookies =
await controller.evaluateJavascript('document.cookie');
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.evaluateJavascript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text('Added a test entry to cache.'),
));
}
void _onListCache(WebViewController controller, BuildContext context) async {
await controller.evaluateJavascript('caches.keys()'
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
'.then((caches) => Toaster.postMessage(caches))');
}
void _onClearCache(WebViewController controller, BuildContext context) async {
await controller.clearCache();
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text("Cache cleared."),
));
}
void _onClearCookies(BuildContext context) async {
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(message),
));
}
void _onNavigationDelegateExample(
WebViewController controller, BuildContext context) async {
final String contentBase64 =
base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
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;
controllerGlobal = controller;
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: !webViewReady
? null
: () async {
if (await controller.canGoBack()) {
controller.goBack();
} else {
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()) {
controller.goForward();
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text("No forward history item")),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: !webViewReady
? null
: () {
controller.reload();
},
),
],
);
},
);
}
}
working demo. I click desktop page and then press device back button and you can see information display "onwill goback". and goback to previous page
Upvotes: 39