Complete newbie, so bear with me. Just a question on how I could refactor my code using the DRY principle. I'm sure it can be done on such a simple example so here goes.... My code below shows three 'switchListTiles'. I've managed to work out how to create 3 switchListTiles on top of one another, and also how to get them to turn on/off individually. Its just that this means I'm creating the following function 3 times:
bool _value3 = false;
void _onChanged3(bool value3) {
setState(() {
_value3 = value3;
});
}
I'm sure there is a way I could change this so I don't have the same code three times.
Any help would be greatly appreciated
Many thanks in advance
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: "Switch Widget Demo",
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _value = false;
void _onChanged(bool value) {
setState(() {
_value = value;
});
}
bool _value2 = false;
void _onChanged2(bool value2) {
setState(() {
_value2 = value2;
});
}
bool _value3 = false;
void _onChanged3(bool value3) {
setState(() {
_value3 = value3;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Switch Demo"),
backgroundColor: Colors.redAccent,
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Column(
children: <Widget>[
Switch(value: _value,
onChanged: (bool value) {_onChanged(value);}),
SwitchListTile(value: _value,
title: Text("Click Me"),
activeColor: Colors.red,
secondary: Icon(Icons.home),
subtitle: Text("For my small print"),
onChanged: (bool value) {_onChanged(value);}),
SwitchListTile(value: _value2,
title: Text("Click Me Again Please"),
activeColor: Colors.lightGreen,
secondary: Icon(Icons.perm_identity),
onChanged: (bool value2) {_onChanged2(value2);}),
SwitchListTile(value: _value3,
title: Text("And Again Please"),
activeColor: Colors.blueGrey,
subtitle: Text("Some more small print"),
secondary: Icon(Icons.person),
onChanged: (bool value) {_onChanged3(value);}),
],
),
),
);
}
}
You just need to refactor the SwitchListTiles into its separate class, then make List<SwitchListTiles> in the parent widget.
Here I create 20 of them with lesser code:
class MySwitchListTilesContainer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[800],
body: ListView(
children: List.generate(20, (i)=>MySwitchListTile(
)),
),
);
}
}
class MySwitchListTile extends StatefulWidget {
#override
_MySwitchListTileState createState() => new _MySwitchListTileState();
}
class _MySwitchListTileState extends State<MySwitchListTile> {
bool _v = false;
#override
Widget build(BuildContext context) {
return SwitchListTile(
value:_v,
onChanged: (value)=>setState((){
_v=value;
}),
);
}
}
Related
I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter App",
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark
),
home: Scaffold(
appBar: AppBar(
title: Text("Main Dart"),
),
body: HomeScreen(),
),
routes: <String, WidgetBuilder>{
'/homeScreen': (buildContext)=>HomeScreen(),
'/second': (buildContext)=>Second()
},
);
}
}
//HomeScreen or Second Widget on different dart file
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//here I want to change the title of Main Dart to HomeScreen
return Container(
child: Center(
child: FlatButton(
child: new Text("Home screen"),
onPressed: (){
Route route = MaterialPageRoute(builder: (context) => Second());
Navigator.push(context, route);
},
),
),
);
}
}
or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?
Have a BLoC for app properties in app_properties_bloc.dart
final appBloc = AppPropertiesBloc();
class AppPropertiesBloc{
StreamController<String> _title = StreamController<String>();
Stream<String> get titleStream => _title.stream;
updateTitle(String newTitle){
_title.sink.add(newTitle);
}
dispose() {
_title.close();
}
}
Use stream builder in AppBar like this:
AppBar(
title: StreamBuilder<Object>(
stream: appBloc.titleStream,
initialData: "Main Dart",
builder: (context, snapshot) {
return Text(snapshot.data);
}
),
),
Use this to update title on button's onPressed()
onPressed: () {
appBloc.updateTitle('new title');
},
Just in case you are changing only the title of Scaffold then this will work.
I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
}
}
class DefaultScaffold extends StatelessWidget {
String title;
Widget body;
DefaultScaffold(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
class SOMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultScaffold(
"Main Screen",
Center(
child: RaisedButton(
child: Text("Go to second screen"),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
}),
),
);
}
}
class SOSecond extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Go the 3rd screen"),
onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
),
);
}
}
class SOThird extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: Text("You are on last screen"));
}
}
Note: This is a simple workaround and may not be the best way to do this.
You can accomplish updating the state of the parent from a child by using a callback function.
Parent Class:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String title = "Old Title";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DaysFragmentView(onTitleSelect: (String value) {
setTitle(value);
}
),
);
}
void setTitle(String value) {
setState(() {
title = value;
});
}
}
Child Class
typedef TitleCallback = void Function(Title color);
class DaysFragmentView extends StatelessWidget {
const DaysFragmentView({this.onTitleSelect});
final TitleCallback onTitleSelect;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
RaisedButton(
child: Text('One'),
onPressed: () {
onTitleSelect("TITLE ONE");
},
),
RaisedButton(
child: Text('Two'),
onPressed: () {
onTitleSelect("TITLE TWO");
},
)
],
);
}
}
Reference:
call-method-in-one-stateful-widget-from-another-stateful-widget-flutter
working-with-callback-in-flutter
Using ValueListenableBuilder is an option.
Use an instance variable
String appTitle;
Then set the app bar as in the following block:
appBar: AppBar(
ValueListenableBuilder<String>(
valueListenable: appTitle,
builder: (context, value, child) {
return Text(appTitle.value);
},
),
After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.
appTitle.value = "Home Screen";
Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.
You can run the example in dart pad
import 'package:flutter/material.dart';
void main() {
runApp(const MyHomePage(title: 'init title'));
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: ValueListenableBuilder<String?>(
builder: (BuildContext context, String? value, Widget? child) {
return Text(value ?? widget.title);
},
valueListenable: _appBarTitleNotifier,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
],
),
),
),
);
}
}
class ChildDemoTitleBar extends StatefulWidget {
final ValueNotifier<String?> titleNotifier;
const ChildDemoTitleBar({Key? key, required this.titleNotifier})
: super(key: key);
#override
State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}
class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: InkWell(
onTap: () {
_counter++;
widget.titleNotifier.value = "title updated $_counter";
},
child: const Text("tap to update title")));
}
}
How do i create a gridview-layout with multi-select feature in Flutter, like android photo app? I was looking for an existing widget but couldn't find one.
What I have at the moment: a gridview-layout with n rows and 2 columns. The cells contain a GridTile-widget with some information and a header text. Now i want to have a functionality like in android photo app, after a long press on one of these tiles, a check-circle appears on the left top corner for all items.
Do i have to build this on my own, or is there an existing Flutter-widget which i didn't find so far?
I also don't know an existing widget, but perhaps this will help you:
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
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> {
List<String> _imageList = List();
List<int> _selectedIndexList = List();
bool _selectionMode = false;
#override
Widget build(BuildContext context) {
List<Widget> _buttons = List();
if (_selectionMode) {
_buttons.add(IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_selectedIndexList.sort();
print('Delete ${_selectedIndexList.length} items! Index: ${_selectedIndexList.toString()}');
}));
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: _buttons,
),
body: _createBody(),
);
}
#override
void initState() {
super.initState();
_imageList.add('https://picsum.photos/800/600/?image=280');
_imageList.add('https://picsum.photos/800/600/?image=281');
_imageList.add('https://picsum.photos/800/600/?image=282');
_imageList.add('https://picsum.photos/800/600/?image=283');
_imageList.add('https://picsum.photos/800/600/?image=284');
}
void _changeSelection({bool enable, int index}) {
_selectionMode = enable;
_selectedIndexList.add(index);
if (index == -1) {
_selectedIndexList.clear();
}
}
Widget _createBody() {
return StaggeredGridView.countBuilder(
crossAxisCount: 2,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
primary: false,
itemCount: _imageList.length,
itemBuilder: (BuildContext context, int index) {
return getGridTile(index);
},
staggeredTileBuilder: (int index) => StaggeredTile.count(1, 1),
padding: const EdgeInsets.all(4.0),
);
}
GridTile getGridTile(int index) {
if (_selectionMode) {
return GridTile(
header: GridTileBar(
leading: Icon(
_selectedIndexList.contains(index) ? Icons.check_circle_outline : Icons.radio_button_unchecked,
color: _selectedIndexList.contains(index) ? Colors.green : Colors.black,
),
),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Colors.blue[50], width: 30.0)),
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
),
onLongPress: () {
setState(() {
_changeSelection(enable: false, index: -1);
});
},
onTap: () {
setState(() {
if (_selectedIndexList.contains(index)) {
_selectedIndexList.remove(index);
} else {
_selectedIndexList.add(index);
}
});
},
));
} else {
return GridTile(
child: InkResponse(
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
onLongPress: () {
setState(() {
_changeSelection(enable: true, index: index);
});
},
),
);
}
}
}
I used staggered grid view to show a grid and grid tiles with a header to have a space for the selection icon. Hope that helps!
This is a plugin from flutter package You can use this
https://pub.dev/packages/drag_select_grid_view
I am trying to display some widgets like a CheckBox or a Switch in an OverlayEntry. The overlay is built in a tap event.
The problem is that the bool _value is updated only the first time I tap the CheckBox inside the overlay, but that CheckBox doesn't update its State. The strange thing is that (only the first tap) the tap updates the CheckBox outside the overlay instead of the one inside it.
What I have missed here?
Below a full snippet to reproduce this.
Thanks for your time!
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _value = true;
_buildOverlay() {
OverlayEntry _overlayEntry;
OverlayState _overlayState = Overlay.of(context);
_overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return Stack(
children: <Widget>[
Material(
child: Container(
padding: EdgeInsets.all(100),
color: Colors.lightBlue,
child: Checkbox(
value: _value,
onChanged: (bool value) { print("$value $_value"); setState(() => _value = value); },
),
),
),
],
);
},
);
_overlayState.insert(_overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(""),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
FloatingActionButton(
onPressed: () {_buildOverlay();},
child: Icon(Icons.add),
),
Checkbox(
value: _value,
onChanged: (bool value) { print("$value $_value"); setState(() => _value = value); },
),
],
),
],
),
),
);
}
}
UPDATE:
In addition to the anmol.majhail solution, using ValueListenableBuilder could be another solution without the need to do a StatefulWidget.
The declaration of _value becomes:
var _value = ValueNotifier<bool>(false);
and here the _overlayEntry in the _buildOverlay() function:
_overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return Material(
child: Container(
padding: EdgeInsets.all(100),
color: Colors.lightBlue,
child: ValueListenableBuilder<bool>(
valueListenable: _value,
builder: (context, value, child) {
return Checkbox(
value: _value.value,
onChanged: (bool value) {
print("$value $_value");
setState(() => _value.value = value);
},
);
},
),
),
);
},
);
Issue here is Overlay entry have different context. In Order to make it work you need to separate the overlay entry in a separate stateful widget - through which you can manage the checkbox state.
working Code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _value = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(""),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
FloatingActionButton(
onPressed: () {
OverlayState _overlayState = Overlay.of(context);
_overlayState.insert(OverlayEntry(
builder: (BuildContext context) {
return OverlayClass(
val: _value,
);
},
));
// _buildOverlay(context);
},
child: Icon(Icons.add),
),
Checkbox(
value: _value,
onChanged: (bool value) {
print("$value $_value");
setState(() => _value = value);
},
),
],
),
],
),
),
);
}
}
class OverlayClass extends StatefulWidget {
final bool val;
OverlayClass({this.val});
#override
_OverlayClassState createState() => _OverlayClassState();
}
class _OverlayClassState extends State<OverlayClass> {
bool _value;
#override
void initState() {
super.initState();
_value = widget.val;
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Material(
child: Container(
padding: EdgeInsets.all(100),
color: Colors.lightBlue,
child: Checkbox(
value: _value,
onChanged: (bool value) {
print("$value $_value");
setState(() => _value = value);
},
),
),
),
],
);
}
}
I am developing a stateful widget and I did not yet understand the difference between useing setState and a normal variable assignment.
This is the code I am useing:
import ...
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _state_var;
#override
void initState() {
super.initState();
setState(() {
_state_var = "test";
});
}
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppState>(
converter: (store) => store.state,
builder: (context, appState) {
return Scaffold(
appBar: AppBar(
title: Text("TITLE"),
),
body: Container(
padding: EdgeInsets.all(10.0),
child: ListView(
children: [
Text(_state_var),
Center(
child: FlatButton(
child: Text('CHANGE VALUE'),
onPressed: () async {
_state_var = "SND TEST";
// OR
setState(() {
_state_var = "SND TEST";
});
},
),
),
])
),
);
},
);
}
}
Whats the difference between:
_state_var = "SND TEST";
and
setState(() {
_state_var = "SND TEST";
});
I mean, in both cases the Text gets new value.
As title. It since that we can detect the drawer is opened, but is this possible to check it is closed or not? Thanks.
I have added this feature in Flutter 2.0.0. Make sure you are using Flutter SDK version >= 2.0.0 to use this.
Simply use a callback in Scaffold
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: NavDrawer(),
onDrawerChanged: (isOpen) {
// write your callback implementation here
print('drawer callback isOpen=$isOpen');
},
endDrawer: NavDrawerEnd(),
onEndDrawerChanged: (isOpen) {
// write your callback implementation here
print('end drawer callback isOpen=$isOpen');
},
body:
...
Pull request merged in 2.0.0: https://github.com/flutter/flutter/pull/67249
Happy coding!
Declare a GlobalKey to reference your drawer:
GlobalKey _drawerKey = GlobalKey();
Put the key in your Drawer:
drawer: Drawer(
key: _drawerKey,
Check if your drawer is visible:
final RenderBox box = _drawerKey.currentContext?.findRenderObject();
if (box != null){
//is visible
} else {
//not visible
}
You can copy paste run full code below
You can wrap Drawer with a StatefulWidget and put callback in initState() and dispose()
initState() will call widget.callback(true); means open
dispose() will call widget.callback(false); means close
Slide also work in this case
code snippet
drawer: CustomDrawer(
callback: (isOpen) {
print("isOpen ${isOpen}");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_isDrawerOpen = isOpen;
});
});
},
...
class CustomDrawer extends StatefulWidget {
CustomDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.callback,
}) : assert(elevation != null && elevation >= 0.0),
super(key: key);
final double elevation;
final Widget child;
final String semanticLabel;
final DrawerCallback callback;
#override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
#override
void initState() {
if (widget.callback != null) {
widget.callback(true);
}
super.initState();
}
#override
void dispose() {
if (widget.callback != null) {
widget.callback(false);
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
key: widget.key,
elevation: widget.elevation,
semanticLabel: widget.semanticLabel,
child: widget.child);
}
}
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> {
bool _isDrawerOpen = false;
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: CustomDrawer(
callback: (isOpen) {
print("isOpen ${isOpen}");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_isDrawerOpen = isOpen;
});
});
},
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app.
// ...
},
),
],
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.centerRight,
child: Text(
_isDrawerOpen.toString(),
),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class CustomDrawer extends StatefulWidget {
CustomDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.callback,
}) : assert(elevation != null && elevation >= 0.0),
super(key: key);
final double elevation;
final Widget child;
final String semanticLabel;
final DrawerCallback callback;
#override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
#override
void initState() {
if (widget.callback != null) {
widget.callback(true);
}
super.initState();
}
#override
void dispose() {
if (widget.callback != null) {
widget.callback(false);
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
key: widget.key,
elevation: widget.elevation,
semanticLabel: widget.semanticLabel,
child: widget.child);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("route test"),
),
body: Text("SecondRoute"));
}
}
You can simply use onDrawerChanged for detecting if the drawer is opened or closed in the Scaffold widget.
Property :
{void Function(bool)? onDrawerChanged}
Type: void Function(bool)?
Optional callback that is called when the Scaffold.drawer is opened or closed.
Example :
#override Widget build(BuildContext context) {
return Scaffold(
onDrawerChanged:(val){
if(val){
setState(() {
//foo bar;
});
}else{
setState(() {
//foo bar;
});
}
},
drawer: Drawer(
child: Container(
)
));
}
When you click a Drawer Item where you will navigate to a new screen, there in the Navigator.push(..) call, you can add a .then(..) clause, and then know when the Drawer item Screen has been popped.
Here is the ListTile for a Drawer item which makes the Navigator.push(..) call when clicked , and the the associated .then(..) callback block:
ListTile(
title: Text('About App'),
onTap: () {
Navigator.push(
_ctxt,
MaterialPageRoute(builder: (context) => AboutScreen()),
).then(
(value) {
print('Drawer callback for About selection');
if (_onReadyCallback != null) {
_onReadyCallback();
}
},
);
}),
_onReadyCallback() represents a Function param you can pass in.
I found this is approach - of leveraging the .then() callback from a .push() call - to be a very useful concept to understand with Flutter in general.
Big thanks to the main 2 answers here:
Force Flutter navigator to reload state when popping
Here's the complete Drawer code:
Drawer drawer = Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Color(0xFF7FAD5F),
),
child: Text(App.NAME_MENU),
),
ListTile(
title: Text('About App'),
onTap: () {
Navigator.push(
_ctxt,
MaterialPageRoute(builder: (context) => AboutScreen()),
).then(
(value) {
print('Drawer callback for About selection');
if (_onReadyCallback != null) {
_onReadyCallback();
}
},
);
}),
],
),
);
I would recommend that you use this package : https://pub.dev/packages/visibility_detector.
Afterwards you should assign a GlobalKey, like _drawerKey for instance, to the Drawer widget, after which you would be able to detect when the drawer is closed like this:
VisibilityDetector(
key: _drawerKey,
child: Container(),
onVisibilityChanged: (info) {
if (info.visibleFraction == 0.0) {
// drawer not visible.
}
},
)