How to use DrawerControllerState in a Drawer? - flutter

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.

Related

Creating a calendar in Second Route widget in Flutter

I am very new to using Flutter and can't figure out how to include a calendar on a widget I am using as the 'second route'.
class FirstRoute extends StatefulWidget {
FirstRoute({Key key, this.title}) : super(key: key);
final String title;
#override
_FirstRoute createState() => _FirstRoute();
}
class _FirstRoute extends State<FirstRoute> {
CalendarController _controller;
#override
void initState(){
super.initState();
_controller = CalendarController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Project'),
),
body: new Center(
child: new ListView(
children: <Widget>[
TableCalendar(calendarController: _controller,)
...
Currently, this works to show the calendar on the first page but I am wanting it on the second page which I have as:
class SecondRoute extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: SingleChildScrollView(
child: new ListView(
children: <Widget>[
...
And navigate to using:
...
ListTile(
title: Text('Calendar'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute())
);
},
),
...
Is there a way of using the TableCalendar(calendarController: _controller,) in the second route? I tried to add of the calendar code into the second route but this does not work as you can't use the initState() in a widget. Thank you!
In you want to use initState in the SecondRoute page, it must extend StatefulWidget as following
class SecondRoute extends StatefulWidget {
#override
_SecondRoute createState() => _SecondRoute();
}
class _SecondRoute extends State<SecondRoute> {
CalendarController _controller;
#override
void initState(){
super.initState();
_controller = CalendarController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Second Route"), ),
body: ListView(
children: [
TableCalendar(calendarController: _controller,)
],
),
),
}
}

Getting floating action button error in the flutter app

I am new to flutter and I am trying to make an app, but I got stuck in the initial phase only, and can't figure out what the problem is.
Below is my code:
import 'package:flutter/material.dart';
void main() => runApp(BMICalculator());
class BMICalculator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('BMI CALCULATOR'),
),
body: InputPage(),
),
);
}
}
class InputPage extends StatefulWidget {
#override
_InputPageState createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('Body Text'),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
);
}
}
I am getting error on my floating action button.
Below is the error message:
Compiler message:
lib/main.dart:29:7: Error: No named parameter with the name 'floatingActionButton'.
floatingActionButton: FloatingActionButton(
^^^^^^^^^^^^^^^^^^^^
../../../desktop/flutter/flutter/packages/flutter/lib/src/widgets/basic.dart:1870:9: Context: Found this candidate, but the arguments don't match.
const Center({ Key key, double widthFactor, double heightFactor, Widget child })
^^^^^^
I need Body text at the centre of the screen and the button at the bottom right corner.
The floatingActionbutton needs to be in a Scaffold widget.
I added a demo code(using your widget tree) below:
class BMICalculator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('BMI CALCULATOR'),
),
body: InputPage(),
// floating action button needs to be in the Scaffold widget
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
),
);
}
}
class InputPage extends StatefulWidget {
#override
_InputPageState createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('Body Text'),
);
}
}
Your code is almost right. The only thing wrong about is that you're trying to set the Floating Action Button (FAB) as a parameter to Center. Instead, put it in a column like this:
import 'package:flutter/material.dart';
void main() => runApp(BMICalculator());
class BMICalculator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('BMI CALCULATOR'),
),
body: InputPage(),
),
);
}
}
class InputPage extends StatefulWidget {
#override
_InputPageState createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Text('Body Text'),
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
],
),
);
}
}
While the code I just typed works fine, you might want to know that a FAB is typically used with a scaffold. It doesn't HAVE to be, but that's how most people use it. That's why there is a dedicated scaffold parameter for a FAB. You can do it like so:
Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {},
),
appBar: AppBar(
.......
)

How to reuse the same layout screen without creating a new widget tree branch

I am developing Flutter Web Application.
The object is to reuse the same layout screen widget(Drawer, AppBar) for most of route screen.
I have tried create a new Scaffold class and add body widget to each screen.
The problem is every time I navigate to a new screen. There is a new (MyScaffold) created on the widget tree. So it is not good for performance.
I also tried to use nested router, the problem is nested router is not supported by url that I can not navigate to the screen by typing the URL.
Is there any other proper way to deal with this problem.
Thanks
Add the code example :
import 'package:flutter/material.dart';
void main() => runApp(AppWidget());
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('Go back!'),
),
),
);
}
}
And I will try to explain the question better.
As you can see First Screen and Second Screen has Exactly same structure of widget tree. But every time flutter is remove the Screen Widget and create a new one.
I also tried to change the code to create a new MyScaffold and reuse the same Widget class :
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => MyScallfold(
bodyWidget: FirstScreen(),
),
'/second': (context) => MyScallfold(
bodyWidget: SecondScreen(),
),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
child: Text('To Screen 2!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('To Screen 1!'),
);
}
}
class MyScallfold extends StatelessWidget {
Widget bodyWidget;
MyScallfold({this.bodyWidget});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: bodyWidget,
);
}
}
Bus I noticed every time I use the navigation, all the widget of the tree is rebuilt (The renderObject #id is changed)
So is it possible to reuse the same RenderObject (AppBar, RichText) in flutter to optimise the performance ?
The quick answer is no, not yet anyway. Currently when you use Navigator it refreshes the page and rebuilds the full view.
The most efficient way on Flutter web currently would be to use a TabController with a TabBarView in a Stateful widget with SingleTickerProviderStateMixin.
It only loads what Widget is on screen, but doesn't require the page to reload to view other pages. Your example would look like this (I have added animation to transition to the next page, but you can remove it):
import 'package:flutter/material.dart';
TabController tabController;
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateMixin {
int activeTab = 0;
#override
void initState() {
tabController = TabController(length: 3, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
activeTab = tabController.index;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: Expanded(
child: TabBarView(
controller: tabController,
children: <Widget>[
FirstScreen(), //Index 0
SecondScreen(), //Index 1
ThirdScreen(), //Index 2
],
),
),
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(2);
},
child: Text('To Screen 3!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(0);
},
child: Text('To Screen 1!'),
);
}
}
class ThirdScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(1);
},
child: Text('To Screen 2!'),
);
}
}

How to open a drawer when the application starts?

My app load on started page, and when my page is loaded me need automacly show drawer menu, how can I open this? in void main(), have this:
new MaterialApp(
initialRoute: '/page',
builder: (context, widget) {
return new Padding(
child: widget,
padding: new EdgeInsets.only(bottom: 10.0),
);
void main() => runApp(MaterialApp(home: AppPage()));
class AppPage extends StatefulWidget {
#override
_AppPageState createState() => _AppPageState();
}
class _AppPageState extends State<AppPage> {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
void initState() {
super.initState();
Timer.run(() => _key.currentState.openDrawer());
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
appBar: AppBar(),
drawer: Drawer(),
);
}
}

Open drawer on clicking AppBar

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
),