I implemented a custom DropdownButton widget, but I don't know how to implement it's setState. I would like to pass items and selectedItem to the widget, and let it to handle it's own state. And retrieve selected item when needed by myDropdownButton.selectedItem. How I could implement it?
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
MyDropdownButton({Key key, this.selected, this.items})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: widget.selected,
onChanged: (String value) {
widget.selected = value;
},
But the selected is final and cannot be modified. How to implement it?
Thank you!
There are two questions here:
Updating the widget, using setState.
Passing the value back to the widget that is using the Dropdown with a callback. Medium Article on callbacks
Firstly to have the dropdown update, you need to call a setstate on the value change. But first, you'll need to receive the value passed, usually this is done in initstate.
Second, you need to use a callback function. The class that calls this widget/class can then receive and process that value
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
final Function(String) valueReturned; //callback function
MyDropdownButton({Key key, this.selected, this.items, this.valueReturned})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
String sel;
#override
void initState() {
super.initState();
sel = widget.selected; //get the value passed
}
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: sel
onChanged: (String value) {
setState() {
sel = value;
widget.valueReturned(value); //this will trigger the callback function
},
}
In the code that calls the widget, you will need to listen to and handle the response.
Container(
child: MyDropdownButton(items: items, selected: selected, valueReturned: _handleValueReturned))
_handleValueReturned(String value) {
thingToUpdate = value;
}
Define a local variable and initialize it in initState():
String _selected;
#override
void initState() {
super.initState();
_selected = widget.selected;
}
use setState to update your local variable as the selection changes:
onChanged: (String value) {
setState(() {_selected = value;})
}
To retrieve the value, define a getter in your class:
String get selectedItem => _selected;
You can then access the selected item using myDropdownButton.selectedItem.
For more detailed explanation on implicit and explicit getters/setters see How do getters and setters change properties in Dart?
Related
I got a ListView.builder that generates n number of elements and I am looking at adding a controller for each of them. I have seen some approaches of adding a controller to a list of controllers and then access them by the index however I am just wondering how will this impact the performance of the screen if lets say you have 20 controllers? Are there some best practices for this scenario? Should you even go down this line or avoid it?
I suggest to introduce a Widget for all items in list.
Make sure you dispose in a correct place for the performance.
Also I request to store the user entered value with the object of item will help to restore on scrolls.
Eg:
class YourWidget extends StatefulWidget {
const YourWidget({Key? key, required this.item}) : super(key: key);
final YourItem item;
#override
State<YourWidget> createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
final controller = TextEditingController();
#override
void initState() {
super.initState();
controller.text = widget.item.enteredValue;
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: (value){
widget.item.enteredValue = value;
},
...
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
class YourItem {
String? id;
...
String enteredValue = '';
}
I have a statefull widget where am passing an integer to. I would like to execute a method when the value passed to the widget changes
class Test extends StatefulWidget {
final String item;
const Test({
Key? key,
required this.item,
}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
List<String> hh = [];
#override
void initState() {
super.initState();
print("init state value is ${widget.item}");
}
#override
Widget build(BuildContext context) {
return Container();
}
void getDataItems() {
print("value passed is ${widget.item}");
setState(() {
hh = [widget.item];
});
}
}
So on the above i would like to detect when the value of string item passed to the widget changes and call the method getDataItems which will later update the list hh.
How can i detect when the value passed statefull widget has changed?
You can check the useEffect, which is based on the React hook useEffect.
Or you can hardcode it.
Create other integer: late int lastCalledInteger;
And check this in the build:
#override
Widget build(BuildContext context) {
if(lastCalledInteger != integer) {
getDataItems();
lastCalledInteger = integer;
}
return Container();
}
You can simply override didUpdateWidget on State to be informed of every change of passed in arguments. If you have multiple arguments you of course need to change what changed. For this didUpdateWidget provides you with the oldWidget as only parameter.
From the documentation:
Called whenever the widget configuration changes.
[...]
Override this method to respond when the widget changes (e.g., to
start implicit animations).
How do I update my UI when the value change in another stateful widget?.
I created a stateful class (Test2) which build a widget. Then in my main class, I have a list of Test2 widget. Finally, I am iterating thru the list and rendering the Test2 widget.
However, I want to update a value in one of Test2 widget and then have the main UI update accordingly.
How can I do that?
FILE:test2.dart
class Test2 extends StatefulWidget {
Test2({this.responseId, this.message});
final String responseId;
final String message;
bool strike =false;
#override
_Test2State createState() => _Test2State();
}
class _Test2State extends State<Test2> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(
widget.responseId + widget.message,
style: (widget.strike)?TextStyle(decoration: TextDecoration.none):TextStyle(decoration: TextDecoration.underline)
),
);
}
}
File :home.dart
MAIN CLASS
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<Test2> _counter =List();
int cnt =0;
#override
void initState() {
super.initState();
_counter.add(Test2(message: "message",responseId:"1"));
_counter.add(Test2(message: "message",responseId:"2"));
_counter.add(Test2(message: "message",responseId:"3"));
}
In my BUILD METHOD
for(int i=0;i<_counter.length;i++)...[
_counter[i]
],
BUTTON CLICK
void _incrementCounter() {
for(int i=0;i<_counter.length;i++){
if(_counter[i].responseId =="1"){
_counter[i].strike =true;
}
}
setState(() {
});
}
You can use ValueNotifier to rebuild the listeners.
Decalre a ValueNotifier<bool> in the Text2 class and initialize it.
ValueNotifier<bool> strikeNotifier = ValueNotifier(false);
then use ValueListenableBuilder to wrap the widget that you want to rebuild when value of strike changes.
ValueListenableBuilder(valueListenable: strikeNotifier, builder: (_, result, widget) => Text(...),)
And create a method for updating the value of strike, also for comparing the old and new values and update the value of ValueNotifier with the comparison result.
void updateStrike(bool value){
var result = (strike == value);
strike = value;
strikeNotifier.value = result;
}
So you can update the value of strike with updateStrike() in _incrementCounter() to notify the Text2 widgets for rebuilding.
I'm developing a mobile app in Flutter and have encountered a problem while trying to pass a function as a parameter to a widget.
To be more precise:
class Test extends StatefulWidget {
final Function(bool) onChanged;
const Test({Key key, this.onChanged}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool switchValue = false;
#override
Widget build(BuildContext context) {
return Container(
child: Switch(
value: switchValue,
onChanged: (bool value) {
setState(() => switchValue = value);
widget.onChanged(value);
}));
}
}
It throws NoSuchMethodError: "The method 'call' was called on null" when the widget is used without defining the onChanged function.
How to define a default function for the onChanged parameter? The parameter should be optional.
I have tried with:
() {} - A value of type 'Null Function( )' can't be assigned to a variable of type 'dynamic Function(bool)'.
(bool) {} - The default value of an optional parameter must be constant.
Solutions without using default value are:
to check if onChange parameter is not null before calling it, or
to define it every time when the widget is used - onChanged: (bool val) {}
Any suggestions would be appreciated.
You can define a default function like this.
void emptyFunction(bool value) {
if (value) {
// do something
} else {
// do something
}
}
const Test({Key key, this.onChanged = emptyFunction}) : super(key: key);
you can check a sample code showing this in action on dartpad. https://dartpad.dev/30fc0fdc02bec673779eebc733753c05
I used dropdownformfield to get the gender of user in the sign up page, I created the widget in other class, I wanna know how can I control the field or retrieve the value when it changes because it doesnt have a controller unlike textformfield.
I would suggest adding an onChanged callback that can be passed into the constructor of the class containing the dropdown field.
class DropdownField extends StatelessWidget {
final Function(dynamic) onChanged;
DropdownField({#required this.onChanged});
#override
Widget build(BuildContext context) {
return DropdownButtonFormField(
onChanged: this.onChanged,
);
}
}
Then when you instantiate the class, have it manipulate something on callback.
class _OtherClassState extends State<OtherClass> {
var _selectedItem;
#override
Widget build(BuildContext context) {
return DropdownField(
onChanged: (newItem) => setState(() => this._selectedItem = newItem),
);
}
}