From StatefulWidget API:
A widget that has mutable state.
From State API:
The logic and internal state for a StatefulWidget.
My interpretation of state is basically instance fields of an object or properties of an object.
Based on the same APIs, StatefulWidget only has these 3 properties: hashcode, key, runtimeType.
Clearly, StatefulWidget does not have a 'has-a' relationship with State as mentioned by the API. Being very new to Flutter, the only way I know that StatefulWidget accesses State is via the createState() method, this seems to indicate a dependency relationship rather than 'has-a'.
Hence, my question: What is meant to "have a state" since StatefulWidget does not have a 'has-a' relationship with State
Put it simple, State stands for the data that can be changed/ manipulated. For example it can be Colors, Size or Text value of a Widget. Look at this simple widget:
class SampleWidget extends StatefulWidget {
#override
_SampleWidgetState createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> {
String label = 'Sample Button';
Color? labelColor = Colors.black;
#override
Widget build(BuildContext context) {
return SizedBox(
width: double.maxFinite,
height: 45,
child: TextButton(
child: Text(label),
style: TextButton.styleFrom(
primary: labelColor,
backgroundColor: Theme.of(context).accentColor,
textStyle: UITextStyle.buttonStyle()),
onPressed: () => setState(() {
labelColor = Colors.red;
label = 'Button Pressed';
}),
),
);
}
}
Here we have 2 states: The content of the button's label and the color of the label. By pressing the button, we change the content and color to different value, thus manipulating the State of this StatefulWidget.
There are 2 general types of State according to the documentation. This is a good start to understand what the State means within the Flutter framework.
Related
I have 2 instances of the same widget(w1 and w2) with a button and an onPressed function. I know to disable the onPress by setting that up with a null value, so far so good. The issue in hand is to solve how to disable w2 onPress when clicking w1 once and enable it again if it is clicked once again.
Even though I send a variable containing if widget has been pressed, disabling never happens because I can not trigger from outside(Widget containing my 2 instances) the setState of each widget separately.
You have a couple of solutions. You could use a more sophisticated state management system (such as Provider or Bloc), but it might be simpler to instead try "lifting state".
"Lifting state" refers to pulling state out of the children and moving it to the parent.
Instead of having the children be stateful, they can become stateless, and the widget that contains them will be made stateful, and will keep track of which buttons are enabled and which are not.
// example button, actual implementation may be different
class MyButton extends StatelessWidget {
final VoidCallback? onPressed;
final String text;
const MyButton({Key? key, this.onPressed, required this.text,}) : super(key: key);
#override
Widget build(BuildContext context) => ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
class ButtonContainer extends StatefulWidget {
// boilerplate
}
class ButtonContainerState extends State<ButtonContainer> {
bool isSecondButtonEnabled = true;
void toggleSecondButton() => setState(() => isSecondButtonEnabled = !isSecondButtonEnabled);
#override
Widget build(BuildContext context) => Row(
children: [
MyButton(
text: "Button 1",
onPressed: toggleSecondButton,
),
MyButton(
text: "Button 2",
onPressed: isSecondButtonEnabled ? someOtherFunction : null,
)
],
);
}
I am creating a button but I want it change the text every time I press it (e.g. on and off).
This is my button class.
class TextSend extends StatelessWidget {
String title;
Function onTap;
TextSend(this.title, this.onTap);
#override
Widget build(BuildContext context) {
return RaisedButton(
color: Colors.green,
disabledColor: Colors.grey,
textColor: Colors.white,
disabledTextColor: Colors.black38,
child: title,
onPressed: onTap,
);
}
}
This is the button instance, where I call it on another class:
TextSend('Button on', Navigator.push(context, MaterialPageRoute(builder: (context) => MQTTView()));),
In order to change the text , you need to change the Stateless widget to Stateful Widget, because Stateless widget is only for static pages that doesn't change content.
after that in order to have a text into the button you need to give a Text widget as a child of the RaisedButton ,and not a String .
in order to change the text you have to call a function when the button is pressed, that function is gonna change the title variable and after that call "setState" function which is responsible of refreshing the page so that your changes can be displayed
here is your code modified and should do the job you want. if you miss something just make me know . thanks
import 'package:flutter/material.dart';
class TextSend extends StatefulWidget {
String title;
Function onTTap;
TextSend(this.title, this.onTTap);
#override
_TextSendState createState() => _TextSendState();
}
class _TextSendState extends State<TextSend> {
#override
Widget build(BuildContext context) {
return RaisedButton(
color: Colors.green,
disabledColor: Colors.grey,
textColor: Colors.white,
disabledTextColor: Colors.black38,
child: Text(widget.title),
onPressed: changeText, //
);
}
// method responsible of changing the text
changeText(){
setState(() {
widget.title='text changed';
});
}
}
You need a StatefulWidget class. Flutter documentation has a really nice tutorial that shows an example similar to your problem.
Say I want to create a button that every time that it's clicked it changes color.
Its starting color is required in the constructor.
The following code works fine, but when I hover on top of TestButton I get this message: "This class (or a class which this class inherits from) is marked as '#immutable', but one or more of its instance fields are not final: TestButton.color".
If it should be final but I need it to change, what's the solution?
Why does it have be final if it works anyways?
class TestButton extends StatefulWidget {
TestButton({this.color});
Color color;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
setState(() {
widget.color = widget.color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Icon(
Icons.add,
size: 80,
),
color: widget.color,
);
}
}
You can have variables in the State object of the StatefulWidget, not in the StatefulWidget itself.
If you need to pass the value from another widget, you can pass it and reassign it to a State variable in the initState function.
Example:
class TestButton extends StatefulWidget {
TestButton({this.passedcolor});
final Color passedColor;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
Color color;
#override
initState(){
color = widget.passedColor;
super.initState()
}
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
setState(() {
color = color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Icon(
Icons.add,
size: 80,
),
color: color,
);
}
}
and then you can update it using setState to any color you wish.
As to why the value passed in the constructor has to be final or why you can't change it is because the StatefulWidget itself is immutable and holds immutable data, but it also holds a mutable State object which is a store for all the state aka mutable data the widget requires.
Quoting from Flutter docs:
StatefulWidget instances themselves are immutable and store their mutable state either in separate State objects that are created by the createState method, or in objects to which that State subscribes, for example Stream or ChangeNotifier objects, to which references are stored in final fields on the StatefulWidget itself.
You can read more on it here:
StatefulWidget class documentation on Flutter website
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
I am new to Flutter and still don't know how to do things the CORRECT way. I hope the title is clear enough because I don't know the proper keyword to address this. First snippet extends StatelessWidget:
class FloatingActionButtonBuilder extends StatelessWidget {
final Function function;
final String text;
final String toolTip;
final IconData icon;
const FloatingActionButtonBuilder({
Key key,
#required this.function,
#required this.text,
#required this.toolTip,
this.icon,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return FloatingActionButton.extended(
onPressed: function,
foregroundColor: Colors.white,
tooltip: '$toolTip',
icon: Icon(
icon,
),
label: Text(
'$text',
style: TextStyle(
fontSize: 16.0,
),
),
);
}
}
Second snippet a regular class:
class FloatingActionButtonBuilder2 {
final BuildContext context;
final Function function;
final String text;
const FloatingActionButtonBuilder2({
#required this.context,
#required this.function,
#required this.text,
});
Widget buildFAB(String toolTip, IconData icon) {
return FloatingActionButton.extended(
onPressed: function,
foregroundColor: Colors.white,
tooltip: '$toolTip',
icon: Icon(
icon,
),
label: Text(
'$text',
style: TextStyle(
fontSize: 16.0,
),
),
);
}
}
The one I've been using is the regular one. Initially, I made the extends StatelessWidget but then I decided not to because I assumed there's no difference anyway and didn't think much about it. Now, out of nowhere my brain wants to know what the experts think about this particular case, in depth suggestions are deeply appreciated. And I noticed that with override build function I don't need BuildContext as a dependency.
EDIT:
Snippet of page using extends StatelessWidget (floatingActionButton property of Scaffold):
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final String text = 'PUSH';
final IconData icon = Icons.add;
void push() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Test3(),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButtonBuilder(
function: push,
text: text,
toolTip: text,
icon: icon,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'PUSH PAGE',
style: TextStyle(
fontSize: 32.0,
),
),
Text(
'EXTENDS CLASS',
style: TextStyle(
fontSize: 32.0,
),
),
],
),
),
);
}
}
Snippet of page using regular class (floatingActionButton property of Scaffold):
class Test3 extends StatefulWidget {
#override
_Test3State createState() => _Test3State();
}
class _Test3State extends State<Test3> {
final String text = 'POP';
final IconData icon = Icons.remove;
void pop() {
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButtonBuilder2(
context: context,
function: pop,
text: text,
).buildFAB(text, icon),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'POP PAGE',
style: TextStyle(
fontSize: 32.0,
),
),
Text(
'REGULAR CLASS',
style: TextStyle(
fontSize: 32.0,
),
),
],
),
),
);
}
}
Per your edit, there is a key difference between your two approaches - the class in approach A is a widget, whereas the class in approach B merely includes a method that returns a widget.
To Flutter, this difference is very significant. When you define a new widget, Flutter uses that widget class to track visual elements in the widget tree for changes. It is capable of detecting when a widget is changed and needs to be redrawn, and it is very good at doing so without affecting any more of the widget tree than is absolutely necessary.
However, if you are creating widgets through a method call, Flutter cannot detect you are doing this, so you lose out on these kinds of optimizations. This is why when you are refactoring your UI code to break it into modular pieces, it is the official Flutter recommendation to break the UI code into new widget classes rather than into separate methods in the same widget class.
A more semantic description is this. In approach A, the widget class has a build method as an inherent effect of being a widget class. This build method is called by Flutter, and the widget it returns becomes a child of the widget class itself. (You can see this if you view the widget tree in the Dart DevTools.) In approach B, the build method is just another method that happens to return a widget. That widget will become the child of whatever other widgets you are passing it to where you call the method (in your case, the Scaffold). As such, there is no inherent relationship between the widget being built and the class itself. This lack of relationship will manifest in a fragile widget tree and an incredibly sloppy UI management as a whole, resulting in an app that is held together with twine and prayers.
Another reason to not use the second approach? It makes your code more verbose for no good reason. Compare the implementation of approach A with approach B - everything in the parentheses is identical, but approach B requires the additional call to the build method itself, and you would have to do this everywhere you used your "not-a-widget" class. You've traded conciseness of the code in the UI declaration for not having to type StatelessWidget in one place... a terrible trade.
Furthermore, if your class isn't a proper widget, it isn't capable of taking advantage of all the widget lifecycle events. Want to be notified when the widget is initialized? When it is in the process of updating? When it is being navigated to/away from? When it is being disposed? What about if you want to trigger an update? Assuming it's even possible, all that would be a royal pain to implement with a generic class, whereas all that functionality is readily available when your class extends StatefulWidget (plus in trying to force it to work in approach B, you'd likely just end up reinventing StatefulWidget from scratch anyway).
In short, there is literally almost never a good reason to have a generic non-widget class with a builder method that you manually call. If you have UI code, it belongs in a widget class (unless you have a very good reason otherwise).
The difference is that if you extend a class with Stateful or Stateless widget then, the class itself becomes a widget class which will returns a widget. In simple, if you extend a class with Stateful or Stateless widget you will need to override the build function which usually create a widget or we can returns a widget :D
I suggest you to first learn more about class widget(Stateful and Statless), I assure you that you will understand how it works.