The app has a nested navigator as below
import 'package:aa/routes.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
initialRoute: root,
onGenerateRoute: AppRouter.mainRouteSettings,
navigatorKey: RouteConfig().appRouteKey,
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
RouteConfig().main.currentState!.maybePop();
return false;
},
child: Scaffold(
body: Container(
margin: const EdgeInsets.all(100),
decoration: BoxDecoration(
color: Colors.grey[500], borderRadius: BorderRadius.circular(20)),
child: Navigator(
key: RouteConfig().main,
initialRoute: one,
onGenerateRoute: AppRouter.generateRoute,
),
),
),
);
}
}
import 'package:aa/main.dart';
import 'package:flutter/material.dart';
//Pre
const String root = '/';
const String preRoute = '/preRoute';
const String one = '/';
const String two = '/two';
const String three = '/three';
class RouteConfig {
static final RouteConfig _routeConfig = RouteConfig._internal();
factory RouteConfig() {
return _routeConfig;
}
RouteConfig._internal();
///App Navigator Key
GlobalKey<NavigatorState> appRouteKey = GlobalKey<NavigatorState>();
///Pre Auth Key
GlobalKey<NavigatorState> main = GlobalKey<NavigatorState>();
}
class AppRouter {
static Route mainRouteSettings(RouteSettings settings) {
late Widget page;
switch (settings.name) {
case root:
page = Scaffold(
body: Container(
child: TextButton(
onPressed: () => RouteConfig()
.appRouteKey
.currentState!
.pushReplacementNamed(preRoute),
child: Center(child: Text('Click Me')))),
);
break;
case preRoute:
page = Test();
break;
default:
page = const Center(child: Text('Not Found'));
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
}
static Route generateRoute(RouteSettings settings) {
late Widget page;
print(settings.name);
switch (settings.name) {
case one:
page = Builder(builder: (context) {
return WillPopScope(
onWillPop: () async => !Navigator.of(context).userGestureInProgress,
child: Container(
color: Colors.pink,
margin: const EdgeInsets.all(3),
child: Center(
child: Column(
children: [
TextButton(
onPressed: () {
RouteConfig().main.currentState!.pop();
},
child: Text('pop'),
),
TextButton(
onPressed: () {
RouteConfig().main.currentState!.pushNamed(two);
},
child: Text('dcdf'),
),
],
),
),
),
);
});
break;
case two:
page = const Text('Two');
break;
case three:
page = const Text('Three');
break;
default:
page = const Center(child: Text('Not Found'));
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
}
}
I am able to swipe back in the nested navigator.
for example, after I tap on the pop button it pops the initial route of the nested navigator how can the Nestednavigator go back when it's the initial route of the app how to prevent this behavior.
Refer the Video for example
The workaround was
Programmatically we can navigate back it's a bug currently in flutter
to prevent browser back and hardware back add a willpopscope with return false to prevent back or swipe for back in ios
Related
Background
I have a Navigator widget on my InitialPage and I am pushing two routes ontop of it (NestedFirstRoute and NestedSecondRoute). When I press the physical back button on Android, Both the routes in Navigator are popped (which is expected).
Use case
So I would like to handle this case when the back button is pressed only the top route (NestedSecondRoute) must be popped.
Solution I tried
To deal with this issue I have wrapped the Navigator widget in WillPopScope to handle the back button press events and assigned keys to nested routes so as to use them when popping routes in the willPop scope.
I get an exception on this line
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
Exception has occurred.
_CastError (Null check operator used on a null value)
Heres the minimal and complete code sample
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case NestedFirstPage.route:
return MaterialPageRoute(
builder: (context) {
return WillPopScope(
onWillPop: () async {
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
NestedFirstPage.firstPageKey.currentState!.pop();
return false;
} else if (NestedSecondPage.secondPageKey.currentState!
.canPop()) {
NestedSecondPage.secondPageKey.currentState!.pop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
switch (settings.name) {
case Navigator.defaultRouteName:
return MaterialPageRoute(
builder: (context) => const NestedFirstPage(),
settings: settings,
);
case NestedSecondPage.route:
return MaterialPageRoute(
builder: (context) => const NestedSecondPage(),
settings: settings,
);
}
},
),
);
},
settings: settings,
);
}
},
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const InitialPage(title: 'Initial Page'),
);
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
);
}
}
class NestedFirstPage extends StatelessWidget {
const NestedFirstPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
Navigator.pushNamed(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Here's a slightly modified version of the above code which will allow pushing nested routes and can be popped via android back button
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/nested/first': (context) => const NestedFirstPage(),
'/nested/first/second': (context) => const NestedSecondPage()
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const RouteManager());
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
static const String route = '/';
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState != null &&
navigatorKey.currentState!.canPop()) {
navigatorKey.currentState!.pop();
return true;
}
return false;
},
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
navigate(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
),
);
}
}
Future<void> navigate(BuildContext context, String route,
{bool isDialog = false, bool isRootNavigator = true}) =>
Navigator.of(context, rootNavigator: isRootNavigator).pushNamed(route);
final navigatorKey = GlobalKey<NavigatorState>();
class RouteManager extends StatelessWidget {
const RouteManager({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/nested/first':
builder = (BuildContext _) => const NestedFirstPage();
break;
case '/nested/first/second':
builder = (BuildContext _) => const NestedSecondPage();
break;
default:
builder =
(BuildContext _) => const InitialPage(title: 'Initial Page');
}
return MaterialPageRoute(builder: builder, settings: settings);
});
}
}
class NestedFirstPage extends StatelessWidget {
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
const NestedFirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
navigate(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Heres a real world example with a Nested Bottomavigationbar.
I need a navigator inside each tab, so when I push a new Widget, the tab bar keeps on screen. The Code is working very well, but the android back button is closing the app instead of running Navigator.pop()
import 'package:flutter/material.dart';
void main() {
runApp(const TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
const TabBarDemo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 1,
child: Scaffold(
bottomNavigationBar: const BottomAppBar(
color: Colors.black,
child: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
],
),
),
body: TabBarView(
children: [
Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => IconButton(
icon: Icon(Icons.directions_car),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => newPage()))),
);
},
),
],
),
),
),
);
}
}
class newPage extends StatelessWidget {
const newPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("new page"),
),
body: Center(child: Icon(Icons.add)),
);
}
}
the code is also available here but on dartpad you cannot test the android back button.
First you should create a key for your navigator
final GlobalKey<NavigatorState> homeNavigatorKey = GlobalKey();
then add this key to your navigator
Navigator(
key: homeNavigatorKey,
then wrap your Navigator in a WillPopScope widget and add the onWillPop as follows
child: WillPopScope(
onWillPop: () async {
return !(await homeNavigatorKey.currentState!.maybePop());
},
child: Navigator(
key: homeNavigatorKey,
this will check if the navigatorKey can pop a route or not, if yes it will pop this route only if no it will pop itself thus closing the app
I need your help.
I'm making a function for my app that has the user add something by pressing the add button, it will then navigate to an adding page and then from the adding page it will add a new listtile in the listview. But I don't know why the text that was input by the user cannot be shown. Can anyone help me?
import 'package:flutter/material.dart';
import 'storage for each listview.dart';
import 'package:provider/provider.dart';
import 'adding page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Storage(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage()
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final provider = Provider.of<Storage>(context, listen: false);
final storageaccess = provider.storage;
return Scaffold(
appBar: AppBar(
title: Text('app'),
),
body: ListView.builder(
itemCount: storageaccess.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(storageaccess[index].title),
subtitle: Text(storageaccess[index].titlediary.toString()),
onTap: () {},
onLongPress: () {
//delete function here
},
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => addpage()));
}, //void add
tooltip: 'add diary',
child: Icon(Icons.add),
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
/// this one I did not do anything first this one for later today just make UI
class Things {
String title;
DateTime titlediary;
Things({required this.title, required this.titlediary});
}
class addpage extends StatefulWidget {
#override
_addpageState createState() => _addpageState();
}
class _addpageState extends State<addpage> {
String title = '';
#override
Widget build(BuildContext context) {
final TextEditingController titleController=TextEditingController(text: title);
final formKey = GlobalKey<FormState>();
return Scaffold(
appBar: AppBar(
title: Text('enter page ',style: TextStyle(fontSize: 30),),
),
body:Form(
key: formKey,
child: Column(
children: [
TextFormField(
controller: titleController,
autofocus: true,
validator: (title) {
if (title!.length < 0) {
return 'enter a title ';
} else {
return null;
}
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'title',
),
),
SizedBox(height: 8),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black),
),
onPressed: () {
if (formKey.currentState!.validate()) {
final accessthing = Things(
title: title,
titlediary: DateTime.now(),
);
final provideraccess = Provider.of<Storage>(context, listen: false);
provideraccess.add(accessthing);
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>MyHomePage()));
}
},
child: Text('Save'),
),
],
),),);
}
}
class Storage extends ChangeNotifier {
List<Things> storage = [
Things(
title: 'hard code one ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
];
void add(Things variablethings) {
storage.add(variablethings);
} notifyListeners();
}
after the user clicks the addbutton, it will send them to an adding page, then after clicking save, the data will be saved into a storage page and then the provider will add the data provided by the user, but the text will not show on the listtile.
I suspect this is happening because you are Navigating to HomePage again using,
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>MyHomePage()));
but this time it is not connected to your provider context.
[
In detail: As you have used ChangeNotifierProvider() in MyApp then connected the MyHomePage() there. But if you push again in Navigator, then fluter creates a separate instance of MyHomePage() widget. Which will not be connected to ChangeNotifierProvider() in MyApp
].
In place of this, use Navigator.of(context).pop();
And in onPressed() use this,
floatingActionButton: FloatingActionButton(
---> onPressed: () async {
---> await Navigator.push(
context, MaterialPageRoute(builder: (context) => addpage()));
---> setState((){});
},
I have 4 bottomNavigationBar that each one has a webview.
but when tap in a one bottomNavigationBar and select a section in webview page, I can't back to previous page in webview by button back device.
I want back in webview just by back button device.
it is now when the press back button exit the app.
but I use double_back_to_close_app.dart to exit the app.
the home class :
import 'package:webview_flutter/webview_flutter.dart';
import 'package:fancy_bar/fancy_bar.dart';
import 'placeholder_widget.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.grey[200],
elevation: 0.0,
)
),
body: DoubleBackToCloseApp(
child: _children[_currentIndex],
snackBar: const SnackBar(
content: Text(
'برای خروج دو بار کلیک کنید',
style: TextStyle(fontFamily: "Shabnam"),
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
),
),
),
bottomNavigationBar: FancyBottomBar(
onItemSelected: onTabTapped,
selectedIndex: _currentIndex, // this will be set when a new tab is tapped
items: [
FancyItem(
textColor: Colors.green,
title: 'حساب',
icon: Icon(
LineIcons.user,
size: 29,
),
),
FancyItem(
textColor: Colors.green,
title: 'وبلاگ',
icon: Icon(
LineIcons.file_text,
size: 27,
),
),
FancyItem(
textColor: Colors.green,
title: 'سبد خرید',
icon: Icon(
LineIcons.shopping_cart,
size: 31,
),
),
FancyItem(
textColor: Colors.green,
title: 'صفحه اصلی',
icon: Icon(
LineIcons.home,
size: 28,
),
),
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
and the webview class in placeholder_widget.dart:
WebViewController controller;
class MyPlaceholderWidget extends StatelessWidget {
var url = 'https://www.mayehtaj.ir' ;
final key = UniqueKey();
MyPlaceholderWidget(String url){
this.url = url ;
}
#override
Widget build(BuildContext context) {
return WebView(
key: key,
javascriptMode: JavascriptMode.unrestricted,
initialUrl: url,
onWebViewCreated: (WebViewController webViewController){
controller = webViewController;
});
}
}
How to detect press back button device and go back to previous page in one webview ?
thank you for help !
You can copy paste run full code below
You can wrap Scaffold with WillPopScope
When user click device back button, you can 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();
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
}
}
working demo
full code
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>https://www.youtube.com/</ul>
<ul>https://www.google.com/</ul>
<ul>https://nodejs.org/en</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();
},
),
],
);
},
);
}
}
I have a material designed Navigation Drawer in my very first flutter app. This work's fine but I did't find any way to close the Navigation Drawer on Back Button Press if it's open when use WillPopScope to show AlertDialog. The application just show AlertDialog instead of close the Drawer when back press. I want Drawer should close if already open and show AlertDialog otherwise.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: onBackPressed,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
),
drawer: Drawer(
),
body: Center(
child: Text("Home"),
),
),
);
}
onBackPressed shows dialog to close the app.
Future<bool> onBackPressed() {
return showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Do you want to exit?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context, false),
child: Text("No")),
FlatButton(
onPressed: () => Navigator.pop(context, true),
child: Text("Yes"))
],
));
}
Can anybody guide me how can I achieve this?
You can copy paste run full code below
Step 1: You need Scaffold key to controll Drawer so you need GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
Step 2: In onWillPop You can check isDrawerOpen and do Navigator.pop
code snippet
GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_key.currentState.isDrawerOpen) {
Navigator.of(context).pop();
return false;
}
return true;
},
child: Scaffold(
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
print("My App Page");
//return false;
},
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: "test",),
),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_key.currentState.isDrawerOpen) {
Navigator.of(context).pop();
return false;
}
return true;
},
child: Scaffold(
key: _key,
appBar: AppBar(title: Text(title)),
body: Center(child: Text('My Page!')),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('page 2'),
onTap: () {
Navigator.push(
context, new MaterialPageRoute(builder: (context) => Page2()));
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
print("Page2");
_popNavigationWithResult(context, 'from_back');
return false;
},
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
_popNavigationWithResult(context, 'from_button');
},
),
body: Container(
child: Center(
child: Text('Page 2',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
),
),
),
);
}
void _popNavigationWithResult(BuildContext context, dynamic result) {
Navigator.pop(context, result);
}
}
my Back button was not working on drawer, because of my home screen. Remove WillPopScope from drawer and put following code in the home screen instead.
WillPopScope(
onWillPop: () async {
return Navigator.of(context).canPop();
},
child: Scaffold(
Simply add Navigator.pop(context);
ListTile(
title: const Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),