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);
},
),
Related
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 have the image shown using network to local file
Hero(
tag: "customWidget",
child: Container(
width: context.screenWidth / 2,
height: context.screenHeight / 4,
child: NetworkToLocal(
mediaURL: element.payload.uri,
mediaType: 'image',
),
),
),
I want to add functionality that after tapping on the image I can present this image in fullscreen mode. How it can be done with the hero widget?
In the above screenshot, an error occurs highlighting elements when adding
child: NetworkToLocal(
mediaURL: element.payload.uri,
mediaType: 'image',
),
You use the Hero Animation
import 'package:flutter/material.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Transition Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png',
),
),
),
);
}
}
class DetailScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png',
),
),
),
),
);
}
}
Is it possible for me to open a page with icons when I click on the place I drew in red in the picture, like the bottomnavbar on Instagram, but I want mine to be like in the picture
What you have to do is:
add a drawer to your scaffold
add a GlobalKey to your Widget which you will use to open the Drawer
Remove the AppBar Menu button if you don't want it there
Add a the button and Position it in your body
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Drawer Demo';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
//Add GlobalKey which you will use to open the drawer
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
//Set GlobalKey
key: _scaffoldKey,
appBar: AppBar(title: Text(title),
//This will remove the AppBar menu button
automaticallyImplyLeading: false,),
body:
Center(child:
Container(
width: double.infinity,
alignment: Alignment.centerLeft,
child: InkWell(
//This function will open the Side Menu
onTap: ()=> _scaffoldKey.currentState?.openDrawer()
,
child: Icon(
Icons.menu,
size: 20,
),
),
),
),
//Add Drawer to your Scaffold
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: Text('Item 1'),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
I have Flutter App with nested Navigator and I want to override "onBackPressed" using WillPopScope in my nested screen.
Let's say I have MainScreen, PrimaryScreen, and SecondaryScreen.
PrimaryScreen is nested Navigator, it have several screen like PrimaryOneScreen, PrimaryTwoScreen, and PrimaryThreeScreen.
SecondaryScreen is also a nested Navigator, just like PrimaryScreen, it have 3 screens.
Here the illustration of what i want to achieve
MainScreen -> PrimaryScreen(PrimaryOneScreen -> PrimaryTwoScreen -> PrimaryThreeScreen)
When my position on PrimaryTwoScreen, I want to get back to PrimaryOneScreen with overriding "onBackPressed" using WillPopScope Widget. But onWillPop never called when I press back button, and my screen go back directly to MainScreen.
Here are my codes
MainScreen.dart
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MainApp(),
onGenerateRoute: (settings){
WidgetBuilder builder;
switch(settings.name){
case 'primary' :
builder = (BuildContext _) => PrimaryScreen();
break;
case 'secondary' :
builder = (BuildContext _) => SecondaryScreen();
break;
case 'main' :
builder = (BuildContext _) => MainApp();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
);
}
}
class MainApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary');
},
child: Text('To Page Primary'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'secondary');
},
child: Text('To Page Secondary'),
)
],
),
);
}
}
PrimaryScreen.dart
class PrimaryScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Navigator(
initialRoute: 'primary/pageone',
onGenerateRoute: (RouteSettings settings){
WidgetBuilder builder;
switch(settings.name){
case 'primary/pageone' :
builder = (BuildContext _) => PrimaryOneScreen();
break;
case 'primary/pagetwo' :
builder = (BuildContext _) => PrimaryTwoScreen();
break;
case 'primary/pagethree' :
builder = (BuildContext _) => PrimaryThreeScreen();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
);
}
}
PrimaryOneScreen.dart
class PrimaryOneScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Primary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagetwo');
},
child: Text('To Page Two'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagethree');
},
child: Text('To Page Three'),
),
],
),
),
);
}
}
PrimaryTwoScreen.dart
class PrimaryTwoScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
print('willPopScope');
Navigator.of(context, rootNavigator: false).pop();
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text('Secondary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pageone');
},
child: Text('To Page One'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagethree');
},
child: Text('To Page Three'),
),
],
),
),
),
);
}
}
PrimaryThreeScreen.dart
class PrimaryThreeScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Primary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pageone');
},
child: Text('To Page One'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagetwo');
},
child: Text('To Page Two'),
)
],
),
),
);
}
}
EDIT
I added an image to illustrate what I want to achieve.
How to pop only on nested navigator?
Thanks!
When pressing the back button, I think it will use the main navigator of your app, because it doesn't know the nested navigator what you want to interact with.
To solve it, you have to assign a key for your navigator to be able to use it later. And adding a WillPopScope at widget which you use your nested navigator to handle user action.
Based on your example, I added some code. Noting, the maybePop() function will call the WillPopScope of your widget of the nested navigator. If you call pop(), it will pop directly, ignoring the WillPopScope of your widget.
class PrimaryScreen extends StatelessWidget{
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_navigatorKey.currentState != null) {
_navigatorKey.currentState!.maybePop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
initialRoute: 'primary/pageone',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch(settings.name){
case 'primary/pageone' :
builder = (BuildContext _) => PrimaryOneScreen();
break;
case 'primary/pagetwo' :
builder = (BuildContext _) => PrimaryTwoScreen();
break;
case 'primary/pagethree' :
builder = (BuildContext _) => PrimaryThreeScreen();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
),
);
}
}
Try with this link https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31
Check Use-case : pushReplacementNamed tag description
I have added a flat button to my project which pops alert message with a text field in it. I want That Flat button text to update with the text entered by user on alert message.
This is my function to create alert dialog box.
Future<String> createAlertDialog(BuildContext context) {
TextEditingController customController = TextEditingController();
return showDialog(context: context, builder: (context){
return AlertDialog(
title: Text("Your Name"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("Submit"),
onPressed: () {
Navigator.of(context).pop(customController.text.toString());
},
)
],
);
});
}
This is the function where i want to receive it and update the button text which currently says "Enter Your Name".
List<Widget> codeField() {
return [
Container(
child: Center(
child: new FlatButton(
child: new Text('Enter Your Name',
style: new TextStyle(fontSize: 10.0)),
onPressed: () {
createAlertDialog(context).then((onValue) {
SnackBar mySnackbar = SnackBar(content: Text("$onValue"));
Scaffold.of(context).showSnackBar(mySnackbar);
});
}
),
),
)
];
}
You can copy paste run full code below
You can wrap FlatButton with StatefulBuilder
code snippet
String name = 'Enter Your Name';
List<Widget> codeField() {
return [
Container(
child: Center(child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return FlatButton(
child: new Text('$name', style: new TextStyle(fontSize: 10.0)),
onPressed: () {
createAlertDialog(context).then((onValue) {
name = onValue;
print(onValue);
SnackBar mySnackbar = SnackBar(content: Text("$onValue"));
_scaffoldKey.currentState.showSnackBar(mySnackbar);
setState(() {});
});
});
})),
)
];
}
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final _scaffoldKey = GlobalKey<ScaffoldState>();
Future<String> createAlertDialog(BuildContext context) {
TextEditingController customController = TextEditingController();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Your Name"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("Submit"),
onPressed: () {
Navigator.of(context).pop(customController.text.toString());
},
)
],
);
});
}
String name = 'Enter Your Name';
List<Widget> codeField() {
return [
Container(
child: Center(child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return FlatButton(
child: new Text('$name', style: new TextStyle(fontSize: 10.0)),
onPressed: () {
createAlertDialog(context).then((onValue) {
name = onValue;
print(onValue);
SnackBar mySnackbar = SnackBar(content: Text("$onValue"));
_scaffoldKey.currentState.showSnackBar(mySnackbar);
setState(() {});
});
});
})),
)
];
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, children: codeField()),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}