Best way to access variable in StatefulWidget Flutter - 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.

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

Creating custom widgets with multiple constructors

So I am trying to create a custom widget in flutter that has multiple constructors.
Essentially I am creating an arrow button but want to be able to create the button with arrow icons facing in different directions.
Currently my code looks like this:
class RotateButton extends StatefulWidget {
RotateButton.left() {
_RotateButtonState createState() => _RotateButtonState.left();
}
RotateButton.right() {
_RotateButtonState createState() => _RotateButtonState.right();
}
#override
_RotateButtonState createState() => _RotateButtonState();
}
class _RotateButtonState extends State<RotateButton> {
IconData icon;
_RotateButtonState();
_RotateButtonState.left() {
icon = Icons.arrow_back;
}
_RotateButtonState.right() {
icon = Icons.arrow_forward;
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Icon(
icon,
size: 70,
),
),
);
}
}
Every time I use my widget it just defaults to the default constructor and shows no Icon child.
Is there a way to build a class without making a default constructor.
Also is there a way I can build this widget without using a stateful widget as it kind of just overcomplicates it.
I am getting a message that says:
The declaration 'createState' isn't referenced
This message is coming up next to the named constructors in the rotatebutton class.
Any help is very much appreciated.
Use something like this:
class SomeWidget extends StatelessWidget {
final bool isRight;
const SomeWidget({Key key, this.isRight}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Icon(isRight?Icons.arrow_forward:Icons.arrow_back),
);
}
}

When to use Valuechanged<> versus Valuesetter<> in 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.

How to build another Widget inside a button's onTap function

EDIT (2nd):
I made a change the state from Stateful widgets to Stateless widget, and it turns out, I can solve the problem.
EDIT:
So I made a mistake, I shouldn't be making a widget inside onTap function, Instead, I should've instantiate CardMatcher somewhere and then access CardMatcher, send the button's keyword, and let CardMatcher check the keyword for me when the button is clicked.
Any Idea how to do that? Can someone make a simple code for me?
In other words, I want to make a widget that can check if there are two buttons that have been clicked. That widget, should be in another file so it may be reused.
ーーーー
So I made a custom button that will pass a keyword to another widget (CardMatcher) that will check the keyword. If two buttons have the same keywords, then the widget (CardMatcher) will do something about it.
The button will pass the keyword when it is clicked. Sadly, nothing happens. There's no error detected, but the app didn't build the CardMatcher as well. Here's the code for the button:
import 'package:flutter/material.dart';
import 'package:fluttermatchcard/cardMatcher.dart';
import 'package:fluttermatchcard/cardMatcher.dart';
import 'package:fluttermatchcard/testerState.dart';
class CardButton extends StatefulWidget {
final Widget child;
//final GestureTapCallback onPressed;
final double widthBut;
final double heightBut;
final Color colorInitial;
final Color colorClicked ;
final Color textColorInitial ;
final Color textColorClicked ;
final Alignment alignment;
final Text text;
final String keyword;
CardButton({
//#required this.onPressed,
this.child,
#required this.keyword,
this.heightBut =40,
this.widthBut = 75,
this.colorClicked = Colors.white,
this.colorInitial=Colors.amber,
this.textColorClicked = Colors.amber,
this.textColorInitial = Colors.white,
this.alignment = Alignment.center,
this.text = const Text(
"Card",
style: TextStyle(
fontSize: 20,
),
),
});
#override
_CardButtonState createState() => _CardButtonState(
keyword,
widthBut,
heightBut,
colorClicked,
colorInitial,
textColorClicked,
textColorInitial,
alignment,
text,
);
}
class _CardButtonState extends State<CardButton> {
String _keyword;
double _widthBut ;
double _heightBut;
Color _colorInitial;
Color _colorClicked ;
Color _textColorInitial;
Color _textColorClicked ;
Alignment _alignment ;
Text _text;
_CardButtonState(
this._keyword,
this._widthBut,
this._heightBut,
this._colorClicked,
this._colorInitial,
this._textColorClicked,
this._textColorInitial,
this._alignment,
this._text,
);
Color _colorNow;
Color _textColorNow;
bool isClicked = false;
void initState() {
_colorNow=_colorInitial;
_text = Text(_text.data, style: TextStyle(color: _textColorInitial, fontSize: _text.style.fontSize),);
super.initState();
}
void ChangeButton(){
setState(() {
isClicked= !isClicked;
if(isClicked){
_colorNow=_colorClicked;
_text = Text(_text.data, style: TextStyle(color: _textColorClicked, fontSize: _text.style.fontSize),);
}
else{
_colorNow=_colorInitial;
_text = Text(_text.data, style: TextStyle(color: _textColorInitial, fontSize: _text.style.fontSize),);
}
});
//super.initState();//no idea
}
#override
Widget build(BuildContext context) {
return Container(
width: _widthBut,
height: _heightBut,
child: InkWell(
onTap: (){ChangeButton();
CardMatcher(_keyword);
print("onTap");},
child: Container(
padding: EdgeInsets.all(3),
alignment: _alignment,
decoration: BoxDecoration(
color: _colorNow,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 5,
offset: Offset(0,2),
spreadRadius: 2
)
],
border: Border.all(
color: Colors.amberAccent
)
),
child: _text,
),
),
);
//widget.onPressed();
}
}
for the CardMatcher:
import 'package:flutter/material.dart';
class CardMatcher extends StatefulWidget {
final String keyword_now;
CardMatcher(this.keyword_now);
#override
_CardMatcherState createState() {
print("cardMatch");
_CardMatcherState(keyword_now);
}
}
class _CardMatcherState extends State<CardMatcher> {
String _keyword_1;
String _keyword_2;
String _keyword_now;
_CardMatcherState(
this._keyword_now,
);
void _collectKeywords(){
print("EnterCollect");
setState(() {
if(_keyword_1==null)
{
print("key1");
_keyword_1=_keyword_now;
}
else{
_keyword_2=_keyword_now;
_matchKeyword(_keyword_1,_keyword_2);
}
});
}
void _matchKeyword(_keyWord_one, _keyWord_two){
if(_keyWord_one==_keyWord_two){
//Lock the But
print("MATCH!!!!");
}
}
#override
Widget build(BuildContext context) {
print("BUILD");
_collectKeywords();
return null;
}
}
Save me, please
You try to create the CardMatcher widget in your onTap function!
What you need to do:
1. onTap must just call ChangeButton (but don't create CardMatcher here)
2. ChangeButton must call setState() AND change the _keyword field
3. Use a CardMatcher instance in your build tree with _keyword as constructor parameter
When calling setState, you indicate to flutter to rebuild (i.e. call build()) the widget. If you change a state (i.e. _keyword), then the build method will use the new state's value to build the widget accordling
The only thing that seems to be missing in your code is for you to use the widget. prefix to access the Stateful widgets constructor variables, like this:
class CardMatcher extends StatefulWidget {
final String keyword_now;
CardMatcher(this.keyword_now);
#override
_CardMatcherState createState() {
print("cardMatch");
_CardMatcherState(keyword_now);
}
}
class _CardMatcherState extends State<CardMatcher> {
String _keyword_1;
String _keyword_2;
void _collectKeywords(){
print("EnterCollect");
setState(() {
if(_keyword_1 == null)
{
print("key1");
_keyword_1 = widget.keyword_now;
}
else{
_keyword_2 = widget.keyword_now;
_matchKeyword(_keyword_1,_keyword_2);
}
});
}
void _matchKeyword(_keyWord_one, _keyWord_two){
if(_keyWord_one==_keyWord_two){
//Lock the But
print("MATCH!!!!");
}
}
#override
Widget build(BuildContext context) {
print("BUILD");
_collectKeywords();
return null;
}
}

flutter running a function from another widget. When i press the button 4, the userAnswer won't change its value

so this is a widget in my main screen. KeypadButton is imported from KeypadButton.dart
KeypadButton(
number: 4,
userAnswer: _userAnswer,
),
this is keypadbutton.dart
import 'package:flutter/material.dart';
class KeypadButton extends StatefulWidget {
KeypadButton({this.number, this.userAnswer});
The this.number is 4 and userAnswer is _userAnswer (they are both from main screen)
final int number;
String userAnswer;
#override
_KeypadButtonState createState() => _KeypadButtonState();
}
class _KeypadButtonState extends State<KeypadButton> {
#override
Widget build(BuildContext context) {
return Container(
child: FlatButton(
child: Text(
widget.number.toString(),
style: TextStyle(
color: Colors.white,
),
),
onPressed: () => setState(() {
widget.userAnswer += widget.number.toString();
}),
),
);
}
}
You can't change your props directly inside State Part of your Widget. So Widget can depend on external props and can't change them if you pass a property as a variable.
So one of the approaches is using callback as property for your widget, and call it inside this widget when you want. This callback needs to change the state of your main widget that uses KeypadButton.
Or you can use a controller like in standard TextEdit widget from the Flutter SDK. For example, you can use some model class that will keep some information, in KeypadButton you change this model via API of this model class. And in the main widget, you need to get this info from model class