Flutter: How to call openDrawer from other widget - flutter

Question: I want to call openDrawer from onPressed() of Header.dart
The Drawer.dart, Header.dart and Home.dart files are separate.
I have triedScaffold.of(context).openDrawer();
not worked.
I tried using cotroller but it not work.
Perhaps it was not used in the right way.
I would appreciate any advice you could give me.
code:
Codes are omitted.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: Header('App'),
endDrawer: Menu(),
)}}
class Header extends StatefulWidget implements PreferredSizeWidget {
const Header(this.heading, {Key? key});
final String heading;
#override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
#override
State<Header> createState() => _HeaderState(heading);
}
class _HeaderState extends State<Header> {
_HeaderState(this.heading);
final String heading;
#override
Widget build(BuildContext context) {
return AppBar(
centerTitle: false,
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu, size: 30.0),
tooltip: '',
onPressed: () {
// _key.currentState!.openDrawer();
},
),]
);
}
}
class Menu extends StatelessWidget {
const Menu({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Drawer()
}

You're using an end drawer, so instead of Scaffold.of(context).openDrawer(), you should use Scaffold.of(context).openEndDrawer():
class _HeaderState extends State<Header> {
_HeaderState(this.heading);
final String heading;
#override
Widget build(BuildContext context) {
return AppBar(
centerTitle: false,
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu, size: 30.0),
tooltip: '',
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),]
);
}
}
Side Note: You don't need to pass arguments from the StatefulWidget to the State - Every instance of State has a widget property, which you can use to access the properties of the parent widget.
In your case it would look like:
class Header extends StatefulWidget implements PreferredSizeWidget {
const Header(this.heading, {Key? key});
final String heading;
#override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
#override
State<Header> createState() => _HeaderState();
}
class _HeaderState extends State<Header> {
#override
Widget build(BuildContext context) {
return AppBar(
centerTitle: false,
// Assuming this is what the heading argument is for
title: Text(widget.heading),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu, size: 30.0),
tooltip: '',
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),
],
);
}
}

Related

How to delete a custom TextFormField in a list of custom TextFormFields without losing the information in the other TextFormFields?

I tried to create a list of custom TextformField which can be deleted using a button with the Map function but the Textformfield is not deleting properly.
It is always the last Widget that is deleted.
Does anyone have a solution?
Est-ce qu'il est possible de créer un objet qui peut se suprrimer lui même ?
Thanks !
Here is my test code
you can try this code in Darpad
enter image description here
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late List<int> _listID = [0, 1, 2, 3];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: _listID.map((id) {
print(id);
return TestWidget(
id: id,
onPressed: () {
setState(() {
_listID.remove(id);
print(_listID);
});
},
);
}).toList()),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class TestWidget extends StatefulWidget {
TestWidget({super.key, this.onPressed, required this.id});
late VoidCallback? onPressed;
late int id;
#override
State<TestWidget> createState() =>
_TestWidgetState(onPressed: this.onPressed, id: id);
}
class _TestWidgetState extends State<TestWidget> {
late VoidCallback? onPressed;
late int id;
_TestWidgetState({required this.id, this.onPressed});
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
labelText: id.toString(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear), onPressed: () => onPressed!()),
));
}
}
You dont need to pass data to state class, you can use widget.variableName
class TestWidget extends StatefulWidget {
const TestWidget({super.key, this.onPressed, required this.id});
final VoidCallback? onPressed;
final int id;
#override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
labelText: widget.id.toString(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear), onPressed: () => widget.onPressed!()),
));
}
}
you have to pass a key to your TestWidget to preserve the state object of every widget here is how:
return TestWidget(
key: ValueKey(id), // a value key with id as a value for every TestWidget
id: id,
onPressed: () {
setState(() {
_listID.remove(id);
print(_listID);
});
},
);
you can learn more about keys from the Flutter youtube channel
Finally for a perfect behavior of what I wanted to do I had to define a class TestWidgetData to contain the data of each widget TestWidget, including the unique identifier and the user name. When the user presses the "clear" button in one of the TestWidget widgets, I use the removeWhere method of the _listData list to remove the item corresponding to the given id, rather than removing an item from the index given. This keeps the state of existing TestWidget widgets when I delete an item from the list, rather than rebuilding them all.
Thank you for your answers they were very helpful!
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<TestWidgetData> _listData = [
TestWidgetData(id: 0, textValue: ""),
TestWidgetData(id: 1, textValue: ""),
TestWidgetData(id: 2, textValue: ""),
TestWidgetData(id: 3, textValue: ""),
];
void removeTestWidgetData(int id) {
setState(() {
_listData.removeWhere((data) => data.id == id);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: _listData.map((data) {
return TestWidget(
key: ValueKey(data.id),
data: data,
onPressed: () {
removeTestWidgetData(data.id);
},
);
}).toList()),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class TestWidget extends StatefulWidget {
final TestWidgetData data;
final VoidCallback? onPressed;
const TestWidget({
Key? key,
required this.data,
this.onPressed,
}) : super(key: key);
#override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
final TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
textController.text = widget.data.textValue;
}
#override
Widget build(BuildContext context) {
return TextFormField(controller: textController,
decoration: InputDecoration(
labelText: widget.data.id.toString(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear), onPressed: () => widget.onPressed!()),
));
}
}
class TestWidgetData {
final int id;
String textValue;
TestWidgetData({required this.id, required this.textValue});
}

How does the "event" parameter contain data? What should I do if I want to create my "onHover"?

I have a simple flutter application. It's ok, but I'm trying to understand how onHover: (event){...} works, why "event" contains data? How can I make my own widget have function parameters like that?
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double dx = 0, dy = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
home: Scaffold(
body: MouseRegion(
onHover: (event) {
setState(() {
dx = event.localPosition.dx;
dy = event.localPosition.dy;
});
},
child: Center(
child: Text('$dx'),
),
),
),
);
}
}
To create your own onChange, or the like we can use ValueChanged.
For example, taking a look at the code for a TextButton() we see:
const TextButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
ValueChanged<bool>? onHover,
the onHover uses a ValueChanged.
You can implement your own valueChanged using this example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Buttons(
onHover: (value) {
// Do something
print(value);
},
),
),
),
);
}
}
class Buttons extends StatelessWidget {
final ValueChanged<String> onHover;
Buttons({Key? key, required this.onHover}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: () {
onHover('Pressed');
},
child: Text("Click me")),
Text('hi')
],
);
}
}
So this how we pass the data from the widget which is at the bottom of the widget tree.
It's more related to passing the value from bottom to top using callback functions.
Below is the simple example to demonstrate this data sharing.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _parentData = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
"Parent State Value: " + _parentData.toString(),
),
ChildWidgetExample(
callbackFn: (data) {
setState(() {
_parentData = data;
});
},
)
],
);
}
}
class ChildWidgetExample extends StatefulWidget {
final Function(int) callbackFn;
const ChildWidgetExample({
Key? key,
required this.callbackFn,
}) : super(key: key);
#override
State<ChildWidgetExample> createState() => _ChildWidgetExampleState();
}
class _ChildWidgetExampleState extends State<ChildWidgetExample> {
int data = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
data.toString(),
),
const SizedBox(
height: 30,
),
ElevatedButton(
onPressed: () {
setState(() {
data++;
});
widget.callbackFn(data);
},
child: const Text("Press"),
)
],
);
}
}
In Flutter you can declare Functions with parameters.
void Function(String foo) myFunction;
So you declare in as a variable in your widget component.
MyWidget({required this.myFunction});
Then when you have to call this component you can write :
...
child : MyWidget(myFunction: (String foo) {},),

Show list items in a stateful widget linked to the item

I have a list of integers. Each of this item is displayed in a statefull widget by iterating the list in the build method.
import 'package:flutter/material.dart';
import 'package:widget_list/ItemWidget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Item list state demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Item list state demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static int itemsCount = 0;
final List<int> _items = List.empty(growable: true);
void _add() {
setState(() {
_items.add(itemsCount++);
});
}
void _remove() {
setState(() {
_items.removeAt(0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () => _add(),
child: const Text('Add item'),
),
TextButton(
onPressed: () => _items.isNotEmpty ? _remove() : null,
child: const Text('Remove item'),
),
],
),
for (var item in _items) ItemWidget(item: item),
],
),
),
);
}
}
Each of this widget, has a statically incremented integer "id" in it's state. Both the item and the widget id are displayed.
import 'package:flutter/material.dart';
var widgetCount = 0;
class ItemWidget extends StatefulWidget {
final int item;
const ItemWidget({
required this.item,
Key? key,
}) : super(key: key);
#override
State<ItemWidget> createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
final int widgetId = widgetCount++;
#override
Widget build(BuildContext context) {
print("Item ${widget.item} / Widget $widgetId");
return Text("Item ${widget.item} / Widget $widgetId");
}
}
When I add an item in the list, it is displayed in a newly generated widget. E.g. first item 0 is displayed in widget 0.
But if I remove an item at the beginning of the list (e.g. item 0), it's not the first widget that is destoyed, but the last one. The item 1 is then displayed in widget 0.
The widget item is final, so it cannot change. The widget ids are still the same, so the states were not rebuild. Then, why are the states no more consistent with the widgets?
This is done in FLutter desktop for Linux, v3.0.1
In the itemWidget you are creating a value from 0 so for each element that is rendered it will start from 0. please check the code below
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Item list state demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Item list state demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static int itemsCount = 0;
final List<ItemInfo> _items = List.empty(growable: true);
void _add() {
setState(() {
itemsCount++;
_items.add(ItemInfo(itemsCount, itemsCount));
});
}
void _remove() {
setState(() {
_items.removeAt(0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () => _add(),
child: const Text('Add item'),
),
TextButton(
onPressed: () => _items.isNotEmpty ? _remove() : null,
child: const Text('Remove item'),
),
],
),
for (var item in _items) ItemWidget(item: item),
],
),
),
);
}
}
and Itemwidget to be like this
class ItemWidget extends StatefulWidget {
final ItemInfo item;
const ItemWidget({
required this.item,
Key? key,
}) : super(key: key);
#override
State<ItemWidget> createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
#override
Widget build(BuildContext context) {
return Text(
"Item ${widget.item.itemVal} / Widget ${widget.item.itemIndex}");
}
}
also I created a class named ItemInfo which will hold both the value and its index.
class ItemInfo {
int itemVal;
int itemIndex;
ItemInfo(this.itemVal, this.itemIndex);
}

Flutter - Update child state from parent

I would like to update a child's state when a button is clicked in the parent, so for example:
class Parent extends StatelessWidget{
Widget build(context){
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () => //somehow increment the child's counter,
icon: const Icon(Icons.add),
),
],
),
body: const Child(),
);
}
}
class Child extends StatefulWidget {
const Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
...
int counter = 0; //to be incremented when parent's button is clicked on.
...
}
Is there a common way to implement this? From the other posts I've read, people usually use the child to update the parent's state via callback, so if there is a way to refactor my code to acheive the same effect, that would help too.
You can create the field counter in the parent and pass it down to the child widget and update the child widget from the parent.
You can check the demo that I made here..
DartPad Demo Link
statemanagement Method
You can use provider,bloc,cubit,getx... package to update the child and parent value
setstate callback (here i mention)
Change you widget like this .your parent widget to stateful.
int counter = 0;
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
SampleCod Dartpad live code check here
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
int counter = 0;
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
}
Try this:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
const Parent({Key? key}) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
tooltip: "Increment counter",
onPressed: incrementCounter,
icon: const Icon(
Icons.add,
),
),
],
),
body: Child(
counter: counter,
),
);
}
}
class Child extends StatefulWidget {
const Child({
Key? key,
required this.counter,
}) : super(key: key);
final int counter;
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
widget.counter.toString(),
style: const TextStyle(
fontSize: 30,
),
),
);
}
}

Flutter best practices - How to call a dynamic method of a stateful widget from its state?

I am wondering what are the best practices when you need to call a dynamic method that is inside a stateful widget from its state.
I mean, I have a generic stateful widget that receives a function in parameter. And when I call it from its state Lint tells me that I have to avoid calls on a dynamic target. So how am I supposed to do it ?
Here is how my class is currently implemented
class MyStatefulWidget<T> extends extends StatefulWidget {
final Function buttonFunction;
final T functionParameter;
const MyStatefulWidget(
{Key key,
this.buttonFunction,
this.functionParameter})
: super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.ac_unit),
onPressed: () {
widget.buttonFunction(widget.functionParameter);
},
),
title: Text("Some text"),
),
),
body:...
}
Thanks a lot for advising me.
I think much better if you will pass just a callback inside widget and then call your function with parameter from outside
class MyStatefulWidget extends StatefulWidget {
final VoidCallback onTap;
const MyStatefulWidget({Key key, this.onTap}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.ac_unit),
onPressed: widget.onTap,
),
title: Text("Some text"),
),
);
}
}
and somewhere in parent class add:
void _onTap() {
// call your function with parameter
}
or, as mentioned in comment #dees91
class MyStatefulWidget<T> extends StatefulWidget {
final ValueChanged<T> onTap;
final T parameter;
const MyStatefulWidget({Key key, #required this.onTap, this.parameter}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.ac_unit),
onPressed: () => widget.onTap(widget.parameter),
),
title: Text("Some text"),
),
);
}
}
and somewhere in parent widget
MyStatefulWidget<String>(onTap: _onTap, parameter: 'lets say its string')
and tap handler
void _onTap(String parameter) {
// call your function with parameter
}