When to use Valuechanged<> versus Valuesetter<> in flutter - flutter

I've been following some of the beginner flutter tutorials on their website and was doing this tutorial for basic interactivity, specifically the part where a parent widget is used to manage the state of a child widget. There is a ParentWidget and _ParentWidgetState class the code for which is as follows:
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
TapboxB is a class which is a child of ParentWidget, the code for which is as follows:
class TapboxB extends StatelessWidget {
TapboxB({this.active: false, #required this.onChanged});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Column(
//aligns column in the centre vertically
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
//sets text depending on _active boolean
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 20.0, color: Colors.white),
),
Text(
'Tapbox B',
style: TextStyle(fontSize: 14.0, color: Colors.white),
),
],
),
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
//sets colour depending on _active boolean
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
The _handleTap method is called when the widget is clicked, which calls the onChanged callback, which toggles the active variable. In the definition for onChanged the type is ValueChanged<bool> which is documented as a "signature for callbacks that report that an underlying value has changed." If I change this however to ValueSetter<bool> the app works in the exact same way and nothing seems to change. So my question is what is the difference between the two of these? Is one better in this particular scenario?

I searched the documentation for you, using just flutter ValueChanged ValueSetter, and quickly found this:
void ValueSetter (T Value)
Signature for callbacks that report that a value has been set.
This is the same signature as ValueChanged, but is used when the callback is called even if the underlying value has not changed. For example, service extensions use this callback because they call the callback whenever the extension is called with a value, regardless of whether the given value is new or not.
typedef ValueSetter<T> = void Function(T value);
So they're just typedefs to the same underlying type, but one has a different semantic meaning, presumably used as self-documenting code despite the same underlying signature.
If you don't need the latter meaning of ValueSetter, then use ValueChanged, as the API already said.

Related

What happens when onChanged is triggered on a TextField? What is a callback?

I have problems following step by step what happens when onChanged is triggered on my TextField. Especially, I have a problem understanding where and why the variable value gets its actual value in the following example.
Example:
class felder extends StatefulWidget {
felder({super.key});
String textFieldName = "";
#override
State<felder> createState() => _felderState();
}
class _felderState extends State<felder> {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
obscureText: false,
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Name'),
onChanged: (value) => widget.textFieldName = value,
)
],
);
}
}
How I always imagined it: I think flutter passes a function in the background, which has a parameter value, that has the content of the TextField.
Actually TextField is a widget that has its own state.
Whenever user types something, the value in a TextField
changes.
At that time, a callback is fired from the TextField.
The changed value is also passed along with the
callback.
Using onChanged: (value){ print(value); } , we can
get the value from that callback and use it as per our needs.
From TextField source code,
The text field calls the [onChanged] callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the [onSubmitted] callback.
To get the value from a TextField, you can also use TexteditingController.
First declare TextEditingController controller = TextEditingController();.
Then inside your TextField, add the controller like this
TextField(
controller: controller,
),
Then to get the value from controller, you can use controller.value.text.
What is a callback?
From GeeksForGeeks:
Callback is basically a function or a method that we pass as an
argument into another function or a method to perform an action. In
the simplest words, we can say that Callback or VoidCallback are used
while sending data from one method to another and vice-versa
Creating a callback
To create your own callback, you can use ValueChanged.
Code example:
Let's create our own button, that when the onChanged is called, it will give us a new value:
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
Usage:
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
Complete example
You can run/paste this example in your editor, and take a look:
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
See also
How to pass callback in Flutter
What's in onChanged Docs ?
ValueChanged<String>? onChanged
onChanged is of type ValueChanged<String> and is called when the user initiates a change to the TextField's value: when they have inserted or deleted text.
This callback doesn't run when the TextField's text is changed programmatically, via the TextField's controller. Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself.
What is Callback ?
If we go by definition, the Callback is a function or a method which we pass as an argument into another function or method and can perform an action when we require it.
For Example, if you are working in any app and if you want any change in any value then what would you do?
Here you are in a dilemma that what you want to change either state() or a simple value/values. If you need to change states then you have various state-changing techniques but if you want to change simple values then you will use Callback.
Refer this article to understand the callback on event of textChange this will surely make you understand the core behind the mechanism

bind the data of two widgets on the same level to each other and also to a global model through provider

Goal
I'm trying to use a top-level provider to pass model data downstream.
Also, I want some widgets on the same level share that model and affect each other at the same time, without using setState().
Code
Here is what I do to bind a Slider to a TextField
class NumberEntry extends StatefulWidget {
var data = 0;
NumberEntry({Key? key}) : super(key: key);
#override
State<NumberEntry> createState() => _NumberEntryState();
}
class _NumberEntryState extends State<NumberEntry> {
final _controller = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var model = context.read<Model>();
return Row(
children: [
const Padding(
padding: EdgeInsets.all(4.0),
child: Text('Suffix'),
),
SizedBox(
width: 50,
child: TextField(
controller: _controller,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
onChanged: (input) {
var model = context.read<Model>();
model.setSuffix(int.parse(input));
},
)),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 100,
child: Slider(
min: 0,
max: 100,
value: model.suffix.toDouble(),
onChanged: (input) {
var model = context.read<Model>();
model.setSuffix(input.toInt());
})),
),
],
);
}
}
My model looks like this
class Model extends ModelBase with ChangeNotifier, DiagnosticableTreeMixin {
int suffix = 0;
void setSuffix(int sx) {
suffix = sx;
notifyListeners();
}
}
Expectation
I expected that both widgets can bind to model.suffix and affect each other upon interaction. They also affect model.suffix independently.
Observation
The Slider does not move at all. The TextField does not affect the Slider.
Workaround
I had to bind widget.data to the TextField's controller and Slider's value, then use setState() to update them simultaneously in the onChange() callbacks to make it work as expected.
Question
The workaround seems a lot of redundancy right there, i.e. three copies of the same data.
Is this by design, or am I missing something obvious?

change variable value of other instances in flutter

I try to create custom dropdown for my app. And the dropdown can have three duplicate on the same screen. When each dropdown tapped, there is a variable called isDropdownOpened set to true. The case, when one of the dropdown opened then I wan't the others have to set it's isDropdownOpened variable to false again. So, how to change the isDropdownOpened value automatically when other instances of dropdown tapped?
should i use state management like provider, or bloc and cubit? Or even i can do it with setState.
here is the code.
class SearchDropdownButton extends StatefulWidget {
const SearchDropdownButton({
Key? key,
required this.text,
}) : super(key: key);
final String text;
#override
State<SearchDropdownButton> createState() => _SearchDropdownButtonState();
}
class _SearchDropdownButtonState extends State<SearchDropdownButton> {
late OverlayEntry _categoryBottomBar;
bool isDropdownOpened = false;
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
if (isDropdownOpened) {
_categoryBottomBar.remove();
} else {
Overlay.of(context)!.insert(_categoryBottomBar);
}
isDropdownOpened = !isDropdownOpened;
});
},
and the instances on a row.
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
SizedBox(width: 20),
SearchDropdownButton(text: "Consume"),
SizedBox(width: 20),
SearchDropdownButton(text: "Gadget"),
SizedBox(width: 20),
SearchDropdownButton(text: "Fashion"),
SizedBox(width: 20),
],
),
the complete code : https://pastebin.com/QtfDfXzU
Your case is not specific to flutter (React is same). Basic way to do this is moving isDropdownOpened state to parent stateful widget. Corresponding react tutorial is here.
If you want to do this in implicit way then yes, you should use state management library for inter-component state sharing.

ERROR: "Exception caught by gesture" in Flutter

I am trying to store data in a class to store this information and use them later, but I am getting this error message and can't find out what I have to change to make it work. Here is a simplified version of my code where I get the same error.
class LUI {
String lui;
LUI({
this.lui,
});
}
class Test extends StatefulWidget {
final LUI data;
Test({
Key key,
this.data,
});
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
TextEditingController _controller = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Container(
child: TextField(
controller: _controller,
keyboardType: TextInputType.number,
))),
TextButton(
child: Text('add information'),
onPressed: () {
_sendresult(context);
print(widget.data.lui);
},
),
],
),
),
);
}
void _sendresult(BuildContext context) {
final result = _controller.text;
print(result);
setState(() {
widget.data.lui = result;
});
}
}```
The problem is that, the Test class expects a value to be passed for the data argument. However as you are not passing any value to that while creating the instance, which makes the field to remain null.
Later when you access the same field in the print(widget.data.lui); it results in a null pointer exception. As you already ahve figured, the soultion is to pass a value to data.
However, you can write your code in a better way so that you can catch this error in the compile time rather than waiting till the runtime.
Approach 1: (With Null-Safety enabled)
Use the keyword required to specify the filed is a must e.g.
Test({
Key key,
required this.data,
});
Now, with this, creating an instance like Test() with arguments input will throw compilation errors compalining about the missing input argument.
Approach 2: (Without Null-Safety enabled)
Use the annotation #required to specify the filed is a required e.g.
Test({
Key key,
#required this.data,
});
In this case, failing to pass an argument will throw a compile time warning (yellow line highlighter) which unlike the above case will inform you but will not block your execution.
I had to send a value for the data parameter from where I create the instance of the Test class. Thanks, Sisir for your answer.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Basis',
theme: ThemeData(
primaryColor: Colors.red,
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
home: Test(data: new LUI()));
}
}

Best way to access variable in StatefulWidget Flutter

I'm trying to create a radio button widget.
Everything is fine until I have to get the state of the button. (if it's clicked or not)
I made something really simple to get it, I created a bool variable in the StatefulWidget and I'm using it in the state class to update the widget when it gets clicked, and I return this variable with a function in the StatefulWidget.
It works well but it gives me this warning :
This class (or a class that this class inherits from) is marked as '#immutable', but one or more of its instance fields aren't final: RadioButton._isSelecteddart(must_be_immutable)
But how am I supposed to access variable if I declare them in the state class?
I saw a lot of ways to deal with this problem but they all look way too much for a simple problem like this.
Here is my StatefulWidget :
class RadioButton extends StatefulWidget{
bool _isSelected = false;
bool isSelected() {
return _isSelected;
}
#override
_RadioButtonState createState() => new _RadioButtonState();
}
And my State :
class _RadioButtonState extends State<RadioButton> {
void _changeSelect(){
setState(() {
widget._isSelected = !widget._isSelected;
});
}
#override
Widget build(BuildContext context){
return GestureDetector(
onTap: _changeSelect,
child: Container(
width: 16.0,
height: 16.0,
padding: EdgeInsets.all(2.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(width: 2.0, color: Colors.black)),
child: widget._isSelected
? Container(
width: double.infinity,
height: double.infinity,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.black),
)
: Container(),
)
);
}
}
and to access the variable, I declare the widget as a variable of my app class and I use the function isSelected on it :
RadioButton myRadio = new RadioButton();
...
print(myRadio.isSelected()); //For exemple
I could let this code but I want to learn the best way to do this.
The proposed way is also correct but preferred way is to have them in your State class as it maintains the State of your widget
Example :
class _RadioButtonState extends State<RadioButton> {
bool _isSelected = false;
bool isSelected() {
return _isSelected;
}
// other code here
}
The Widgets are Immutable in flutter, So you cannot modify the
fields in the Radio widget.
you can only change the values in State class
you better assign the boolean value inside the State class and then use it.