Flutter: how to create a non-final property for a StatefulWidget? - flutter

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

Related

Flutter changing State of a Widget from another Page

my problem in short: i'll try to change the state of a widget from another widget.
in long:
i have a page.
this page shows a colored container
from another page, this colored container should change its color
I would be grateful for any help
import 'package:flutter/material.dart';
class Display extends StatefulWidget {
Display({Key? key, required this.color}) : super(key: key);
Color color;
#override
State<StatefulWidget> createState() => DisplayState();
}
class DisplayState extends State<Display> {
#override
Widget build(BuildContext context) {
return Container(
color: widget.color,
);
}
changeColor(Color color) {
setState(() {
widget.color = color;
});
}
}
I don't know if I get correctly what do you want, but if I'm right you could do this:
Make a variable of type Color;
Make a function that change the state of this variable (setState);
Pass this function as argument to your widget, where you want to change the color.
Call the function in your other page.
Your first screen should have a variable to hold the Color value
Color containerColor;
A function to change its value
void _setContainerColor(Color newColor) {
setState(() {
containerColor = newColor;
});
}
And use the variable in your container
Container(
color: containerColor, // your variable state
child: SomeWidget(),
);
Pass your function as argument to your second page
MyOtherWidget(_setContainerColor);
And at your Other Widget, you declare a variable of Function type to receive your function:
const MyOtherWidget(this._setContainerColor) extends StatefulWidget {
final Function _setContainerColor;
#override
_MyOtherWidgetState createState() => _MyOtherWidgetState();
}
class _MyOtherWidgetState extends State<_MyOtherWidget> {
#override
Widget build(BuildContext context) {
return TextButton(
child: Text("Change Color"),
onPressed: () {
widget._setContainerColor(Colors.red);
},
)
}
}
Don't forget to use your containerColor as value in your Container background color.

What is meant by "have a state"?

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.

Flutter change state from related widget class

Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"
class SpecialButton extends StatefulWidget {
bool active = false;
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration:
BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
child: null);
}
}
In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).
My second idea was to introduce this flag as a propper state of the SpecialButtonState class
class SpecialButton extends StatefulWidget {
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
bool active;
#override
void initState() {
super.initState();
this.active = false;
}
activate() {
this.setState(() {
active = true;
});
}
deactivate() {
this.setState(() {
active = false;
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
child: null);
}
}
As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.
So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?
There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.
EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.
It is not neсessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:
class SomeParent extends StatefulWidget {
const SomeParent({Key key}) : super(key: key);
#override
State<SomeParent> createState() => SomeParentState();
}
class SomeParentState extends State<SomeParent> {
bool _button1IsActive = false;
bool _button2IsActive = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SpecialButton(
key: UniqueKey(),
active: _button1IsActive,
),
SizedBox(height: 8),
SpecialButton(
key: UniqueKey(),
active: _button2IsActive,
),
SizedBox(height: 16),
TextButton(
child: Text('Toggle button 1'),
onPressed: () {
setState(() {
_button1IsActive = !_button1IsActive;
});
},
),
SizedBox(height: 8),
TextButton(
child: Text('Toggle button 2'),
onPressed: () {
setState(() {
_button2IsActive = !_button2IsActive;
});
},
),
],
),
);
}
}
class SpecialButton extends StatelessWidget {
final bool active;
const SpecialButton({Key key, this.active = false}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
);
}
}
SomeParent is my fantasy, just for example. Don't know what your parent is.
Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.
Please try this approach, it should work.
As nvoigt says, your buttons could even be stateless widget , but their parent should be statefull and you should provide them with the corresponding value. e.g.:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
StateLessButton1(isEnabled: isEnabled),
StateLessButton1(isEnabled: !isEnabled),
FloatingActionButton(onPressed: (){
setState(() {
isEnabled = !isEnabled;
});
})
],
);
}
}
Now it just depends on when you want to change that value. If you want to change it inside your buttons, I would recommend you to use a class with ChangeNotifier and a function inside it that changes the value. Otherwise I would recommend not to separate your tree into multiple files

Flutter: how to get parent widget variable?

I used the example below to test and learn how to call parent functions from child widget.
When you click the TestButton, the countRefresh function is called and the variable count increases by 1.
Right now the button changes color each time it is clicked (either blue or red).
QUESTION: say that I want the color to change based on some logic around the count variable, how can I access the count variable from within the TestButton widget?
E.g. if count is a multiple of three then the button should be red, otherwise blue.
I read about InheritedWidgets, but it seems like variables must be final inside InheritedWidgets (if I don't put final before int count = 0; I get the 'this class is marked as immutable' message error).
But based on this example I need count to change each time the button is clicked.
What's the alternative to InheritedWidgets?
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
static const String id = 'test';
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int count = 0;
Color color = Colors.red;
void refreshCount() {
setState(() {
count += 1;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Text('The button was pressed $count time${count == 1 ? '' : 's'}'),
),
floatingActionButton: TestButton(
color: color,
notifyParent: refreshCount,
),
);
}
}
class TestButton extends StatefulWidget {
TestButton({
#required this.color,
#required this.notifyParent,
});
final Color color;
final void Function() notifyParent;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
Color color;
void initState() {
color = widget.color;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.notifyParent();
setState(() {
color = color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Container(
child: Icon(
Icons.add,
size: 80,
),
color: color,
),
);
}
}
I want the color to change based on some logic around the count variable, how can I access the count variable from within the TestButton widget? E.g. if count is a multiple of three then the button should be red, otherwise blue.
You need to pass count down to the child and let the child to build itself based on that value.
Don't try to access the parent widget. In declarative programming you can only update states and rebuild trees.
Here's a good video about state management in Flutter: https://www.youtube.com/watch?v=HrBiNHEqSYU

Passing paramaters to initState Flutter/Dart?

I am new to Flutter so I am not sure if this is possible...
I am trying to customise a statefulWidget, building upon the MaterialDesignIcon RaisedButton.
I would like to simply pass in two parameters on the instantiation of the raisedButton.
So when I create the RaisedButton I can do something like below.....
RaisedButton(backgroundColor: Colors.grey, text: 'Press me')
Please see the code I am trying to make work below.
class CustomRaisedButton extends StatefulWidget {
#override
CustomRaisedButtonState createState() => CustomRaisedButtonState();
}
void buttonPressed() {
print('A FUNCTION WOULD GO HERE');
}
class CustomRaisedButtonState extends State<CustomRaisedButton> {
var _backgroundColor = Colors.transparent;
var _text = String;
var _hoverColor = Colors.transparent;
#override
void initState(backgroundColor, text) { < ---- // Can I put the parameters required here?
super.initState();
this._backgroundColor = backgroundColor;
this._text = text;
if (_backgroundColor != Colors.grey) {
_textColor = Colors.white;
}
if (_backgroundColor == Colors.grey) {
_hoverColor = Colors.black54;
}
if (_backgroundColor == Colors.red) {
_hoverColor = Colors.deepOrangeAccent;
}
if (_backgroundColor == Colors.lightGreen) {
_hoverColor = Colors.green;
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return RaisedButton(
onPressed: buttonPressed,
color: _backgroundColor,
textColor: Colors.black,
disabledColor: Colors.black38,
disabledTextColor: _textColor,
disabledElevation: 4,
elevation: 4,
hoverColor: _hoverColor,
padding: const EdgeInsets.all(8.0),
child: Text('$_text', style: CustomTextStyle.display1(context))
);
}
}
This may seem a stupid question as I know you can access those properties in the parameters of the custom widget instantiation anyway. But I would like to change the different properties of the button depending on the backgroundColor. Thanks in advance.
You can access the widgets properties in a stateful widget using widget.:
class ExampleWidget extends StatefulWidget {
final String data;
const ExampleWidget({Key key, this.data}) : super(key: key);
#override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
String text;
#override
void initState() {
super.initState();
text = widget.data.substring(0,2).toUpperCase();
}
#override
Widget build(BuildContext context) {
return Text(text);
}
}
no question is stupid! :D. There is not this possibility, initState doesn't receive any parameters. But as you said yourself you can always access the Widget properties.
What do you want to accomplish that you are not able to using the widgets properties approach?