Open drawer on clicking AppBar - flutter

If you create an Scafold there is an option for drawer. If you now create this drawer you get automaticly the menu icon on the leading position of the appbar. But i want an other icon there which opens the drawer. I tried to make an iconbutton myself on the leading position but this button can‘t open the drawer even with „Scafold.of(context).openDrawer()“ it can‘t open it.
Is there any option to replace the icon for the drawer button?

Use a Key in your Scaffold and show the drawer by calling myKey.currentState.openDrawer(), here is a working code:
import "package:flutter/material.dart";
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
drawer: new Drawer(),
appBar: new AppBar(
leading: new IconButton(
icon: new Icon(Icons.settings),
onPressed: () => _scaffoldKey.currentState.openDrawer(),
),
),
);
}
}

Alternative to the accepted answer which does not require a GlobalKey:
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: new Drawer(),
appBar: new AppBar(
leading: Builder(
builder: (context) => IconButton(
icon: new Icon(Icons.settings),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
),
);
}
}

Using GlobalKey:
final GlobalKey<ScaffoldState> _key = GlobalKey(); // Create a key
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key, // Assign the key to Scaffold.
drawer: Drawer(),
floatingActionButton: FloatingActionButton(
onPressed: () => _key.currentState!.openDrawer(), // <-- Opens drawer
),
);
}
Using Builder:
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: () => Scaffold.of(context).openDrawer(), // <-- Opens drawer.
);
}),
);
}

you need initialize scaffoldKey
after that,
Open drawer and close drawer
GestureDetector(
onTap: () {
if(scaffoldKey.currentState.isDrawerOpen){
scaffoldKey.currentState.openEndDrawer();
}else{
scaffoldKey.currentState.openDrawer();
}
},
child: LeadingIcon(icon: Icons.menu),//your button
),

Related

Flutter Navigator v2.0 how to pop a child nested route

I've been trying flutter new declarative Navigator v2.0, following this
johnpryan example, I've decide to change FlatButton pop() in the BookDetailsScreen:
class BookDetailsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
children: [
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Back'),
),
],
}
To the parent router AppBar() in the _AppShellState:
class _AppShellState extends State<AppShell> {
InnerRouterDelegate _routerDelegate;
ChildBackButtonDispatcher _backButtonDispatcher;
#override
Widget build(BuildContext context) {
var appState = widget.appState;
_backButtonDispatcher.takePriority();
return Scaffold(
appBar: appState.selectedBook != null
? AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => _routerDelegate.popRoute(),
),
actions: [
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () => null,
)
],
)
: AppBar(),
body: Router(
routerDelegate: _routerDelegate,
backButtonDispatcher: _backButtonDispatcher,
),
Fullcode
My question is, I'm using _routerDelegate.popRoute() it's working, but I not sure if it is right way to do it?
PS: If someone have a more complex example using Navigator v2.0, I'm new to Flutter and need to know best practices to how separate and organize my code, how to add more routes for instance an Edit and login screen? working with more objects like users, books, authors and etc...
I have a tweak to show the back button, kinda naturally, when the BookDetailsScreen is presented.
In the _AppShellState class
#override
Widget build(BuildContext context) {
...
return Scaffold(
// <- Comment out this appBar
// appBar: AppBar(
// leading: appState.selectedBook != null
// ? IconButton(
// icon: Icon(Icons.arrow_back),
// onPressed: () => _routerDelegate.popRoute(),
// )
// : null,
// ),
...
// Keep the rest part the same
);
}
Add the appBar property into Screens Scaffold in the build method, like this below. Have the appBar on both BooksListScreen and BookDetailsScreen,
class BooksListScreen extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(), // <- This is the key
...
);
}
}
Hope this makes sense.

How to close the Drawer while keeping the Dialog opened?

Minimal code:
void main() => runApp(MaterialApp(home: MainPage()));
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Navigator.of(context, rootNavigator: true).pop());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
On pressing the button, I am showing dialog and after 2s I want to close the Drawer while keeping the Dialog opened on the screen. For this I am using Timer and rootNavigator property of Navigator. However, my dialog is getting dismissed.
Is there any solution for closing the drawer besides using GlobalKey<DrawerControllerState> stuff?
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Scaffold.of(context).openEndDrawer());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
You can use ScaffoldState, to close the drawer. Just keep a track of the time and you are good to go. In this answer, I have told you on how to use the ScaffoldState with your drawer.
This code will help you achieve what you want. I have used the second option from my previous answer, that is, using everything in the MainPage only
class MainPage extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
// this will check for the drawer state and close it
// using _scaffoldKey
timer() {
return Future.delayed(Duration(seconds: 2), (){
// checking whether it is open
if(_scaffoldKey.currentState.isDrawerOpen){
// here how you close it
_scaffoldKey.currentState.openEndDrawer();
}
});
}
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog Title'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this message?'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Our drawer
Drawer _drawer(BuildContext context) => Drawer(
child: RaisedButton(
onPressed: (){
timer();
_showMyDialog(context);
},
child: Text('Show Dialog'),
)
);
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(),
drawer: _drawer(context)
);
}
}
Result
Please note: I have not clicked anywhere on the screen, to close the drawer. It goes automatically by the timer()

How to use DrawerControllerState in a Drawer?

Minimal code:
void main() => runApp(MaterialApp(home: MainPage()));
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
final _drawerKey = GlobalKey<DrawerControllerState>();
#override
Widget build(BuildContext context) {
return Drawer(
key: _drawerKey,
child: RaisedButton(
onPressed: () => print(_drawerKey.currentState), // Prints null
child: Text('Show Dialog'),
),
);
}
}
When I press the button, it prints null, so what's the correct way of using the DrawerControllerState?
See, the problem is, you are not using your key correctly. Firstly, in order to get the state of the Drawer, you need to have ScaffoldState not the DrawerControllerState type key.
With the use of ScaffoldState.currentState, you will be getting the data. Also, if you want to see whether your drawer is open or closed. You can use it like this:
ScaffoldState.currentState.isDrawerOpen
There are two ways to do this:
1. Declaring the GlobalKey gloabally for accessing it anywhere
void main() => runApp(MaterialApp(home: MainPage()));
// declare it globally or make your drawer inside the MainPage only
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey, // <-- Use your key here not for drawer
appBar: AppBar(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () => print(_scaffoldKey.currentState.isDrawerOpen), // <-- prints true, when open
child: Text('Show Dialog'),
)
);
}
}
2. Make your drawer inside your MainPage only. Easily accessible
class MainPage extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Drawer get _drawer => Drawer(
child: RaisedButton(
onPressed: () => print(_scaffoldKey.currentState.isDrawerOpen), // <-- prints true when opened
child: Text('Show Dialog'),
)
);
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey, // <-- Using Key for Scaffold
appBar: AppBar(),
drawer: _drawer
);
}
}
Passing the GlobalKey from MainPage to MyDrawer won't help. You can play with that.

How to show alerts when using full_pdf_viewer

I am using flutter full_pdf_viewer package to view a pdf before saving. I have a save option which I would like to have a confirmation before saving. However, when I try to render the alert it does not show up. Currently, I have a workaround where I pop the PDFViewerScaffold before showing the alert however this is not the intended behavior. This is the code I currently have
class EditorPage extends StatefulWidget {
const EditorPage({
Key key,
}) : super(key: key);
#override
_EditorPageState createState() => _EditorPageState();
}
class _EditorPageState extends State<EditorPage> {
#override
Widget build(BuildContext context) {
final Map arguments = ModalRoute.of(context).settings.arguments as Map;
final pdfPath = arguments['pdfPath'];
return Scaffold(
body: PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
InkWell(
child: Icon(Icons.save),
onTap: () {
Navigator.of(context).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text('Enter the title of your notice'),
content: Text('This is a test'),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
},
),
],
),
path: pdfPath),
);
}
}
Try to add a key to your Scaffold:
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
And in your Scaffold:
return Scaffold(
key: _scaffoldKey,
This solve the problem here.

"How to open scaffold drawer from the custom AppBar ?"

I have made customized Drawer and AppBar. I want the Drawer to be opened when action widget in the AppBar is tapped. I want to know how to implement this for the custom AppBar
#override
Widget build(BuildContext context) {
return Scaffold(
endDrawer:buildProfileDrawer(),
appBar: setAppBar(),
body: HomeBody()
);
}
//custom widget
Widget setAppBar(){
return AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.account_circle,),
onPressed: () {
//Open the drawer
},
)
],
);
}
//Custom drawer
buildProfileDrawer() {
return Drawer(
//....drawer childs
);
}
You should use GlobalKey in Scaffold, and call openEndDrawer method on it.
GlobalKey<ScaffoldState> _key = GlobalKey(); // add this
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key, // set it here
endDrawer: buildProfileDrawer(),
appBar: setAppBar(),
body: Center(),
);
}
//custom widget
Widget setAppBar() {
return AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.account_circle),
onPressed: () {
_key.currentState.openEndDrawer(); // this opens drawer
},
)
],
);
}
//Custom drawer
buildProfileDrawer() {
return Drawer();
}
Update
GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
endDrawer: buildProfileDrawer(),
appBar: setAppBar(_key),
body: Center(),
);
}
Somewhere in some file.
Widget setAppBar(GlobalKey<ScaffoldState> globalKey) {
return AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.account_circle),
onPressed: () {
globalKey.currentState.openEndDrawer();
},
)
],
);
}
Somewhere in some other file
buildProfileDrawer() {
return Drawer();
}
You can use this in actions list:
StatefulBuilder(
builder: (BuildContext context, setState) {
return IconButton(
icon: Icon(Icons.format_align_right),
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
);
},
),
We can use
Scaffold.of(context).openDrawer();
on onPressed inside an IconButton in your CustomAppBar or wherever you want to call the drawer.
In the Scaffold just ensure to provide the drawer: property a Drawer Widget.