Flutter: how to get parent widget variable? - flutter

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

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.

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

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: how to create a non-final property for a StatefulWidget?

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

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?