Reputation: 3460
Im working on flutter responsive web UI. And I want to close the opened drawer on a specific screen width for mobile and desktop screen width, so if I stretch my browser, the drawer should close.
For example I opened the drawer (screen width less than 500)
And when the screen width is greater than 500, I want the opened drawer to automatically close.
Note: When the Drawer is opened. I have a code already that checked the screen width that show a button menu drawer or not. But basically, when the user open the drawer then suddenly stretch the browser the drawer should closed.
Code below. Thanks for the help
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size.width;
return Scaffold(
drawer: Drawer(),
body: CustomNavBar(screenSize: size),
);
}
}
class CustomNavBar extends StatefulWidget {
final double screenSize;
const CustomNavBar({Key key, this.screenSize}) : super(key: key);
@override
_CustomNavBarState createState() => _CustomNavBarState();
}
class _CustomNavBarState extends State<CustomNavBar> {
@override
Widget build(BuildContext context) {
if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 500) {
print("Drawer is Opened");
Scaffold.of(context).openEndDrawer(); //animation error
setState(() {});
}
return widget.screenSize > 500
? Container(color: Colors.red) //desktop screen
: Center(
//mobile screen
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
);
}
}
Upvotes: 5
Views: 2837
Reputation: 4098
With Flutter 2.0 here is proper way to detect drawer open/close:
Scaffold(
onDrawerChanged: (isOpened) {
//listener left drawer
},
onEndDrawerChanged: (isOpened) {
//listener right drawer
},
)
Upvotes: 1
Reputation: 316
You shouldn't have to close the drawer manually. Why not just get rid of the drawer when the screen width is less than 500?
class SampleDrawer extends StatelessWidget {
final GlobalKey<ScaffoldState> k = GlobalKey();
@override
Widget build(BuildContext context) {
// new
final size = MediaQuery.of(context).size.width;
if (k.currentState.isDrawerOpen && size < 500) {
Navigator.pop(context); // close drawer
}
return Scaffold(
key: k,
drawer: size > 500 ? Drawer() : null,
body: CustomNavBar(),
);
}
}
class CustomNavBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size.width;
return size > 500
? Container(color: Colors.red) //desktop screen
: Center( //mobile screen
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
);
}
}
The Scaffold will be rebuilt whenever the width of the device changes, and the drawer will automatically be omitted if the width is less than 500.
Upvotes: 6
Reputation: 1215
You're Code is enough. Just a few changes to your code
Wrap this Scaffold.of(context).openEndDrawer();
in
WidgetsBinding.instance.addPostFrameCallback((_) {
Scaffold.of(context).openEndDrawer(); //No Error
///The Error was coming, As you're trying to build a widget when it is
///rebuilding widget Tree due to the change in the width of the browser.
///Wrapping it inside ensures that the code will run after the build.
});
Don't Use setState(() {});
Use 520 instead of 500
The error was coming, As you're trying to build a widget when it is rebuilding Widget Tree due to the change in the width of the browser. Wrapping this
Scaffold.of(context).openEndDrawer();
insideWidgetsBinding.instance.addPostFrameCallback((_) {});
ensures that the code will run after the widget get's build.
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size.width;
print(size);
return Scaffold(
drawer: Drawer(),
body: CustomNavBar(
screenSize: size,
),
);
}
}
class CustomNavBar extends StatefulWidget {
final double screenSize;
const CustomNavBar({
Key key,
this.screenSize,
}) : super(key: key);
@override
_CustomNavBarState createState() => _CustomNavBarState();
}
class _CustomNavBarState extends State<CustomNavBar> {
@override
Widget build(BuildContext context) {
if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 520) {
print("Drawer is Opened");
WidgetsBinding.instance.addPostFrameCallback((_) {
Scaffold.of(context).openEndDrawer(); //No Error
///The error was coming, As you're trying to build a widget when it is
///rebuilding widget Tree due to the change in the width of the browser.
///Wrapping it inside ensure that the code will run after the build.
});
// Don't call setState((){}); Not Required;
// as every time you change the width it rebuilds all the widget again
// setState(() {});
}
return widget.screenSize > 520
? Container(color: Colors.red) //desktop screen
: Center(
//mobile screen
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
);
}
}
The following assertion was thrown while notifying status listeners for
AnimationController:
setState() or markNeedsBuild() called during build.
This Scaffold widget cannot be marked as needing to build because the framework is
already in the
process of building widgets. A widget can be marked as needing to be built during
the build phase
only if one of its ancestors is currently building. This exception is allowed
because the framework
builds parent widgets before children, which means a dirty descendant will always be
built.
Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
Scaffold
The widget which was currently being built when the offending call was made was:
CustomNavBar
Use the Layout Builder for Responsive UI.
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 800) {
return XScreen();
} else if (constraints.maxWidth < 1200 && constraints.maxWidth > 800) {
return Yscreen()?? ZScreen();
} else {
return XScreen()?? ZScreeen();
}
},
);
}
Upvotes: 3