What constitutes a widget configuration change that triggers didUpdateWidget()? - flutter

According to the docs, didUpdateWidget() is
Called whenever the widget configuration changes.
I took this to mean that didUpdateWidget() will be triggered if a widget is being rebuilt with an argument that is different than the argument it was given in the previous build. But after a simple test I seem to stand corrected:
Example :
class A extends StatefulWidget {
const A({Key key}) : super(key: key);
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTapDown: (_) => setState(() {}),
child: Container(
width: 300,
height: 300,
color: Colors.blue,
child: B(6),
),
),
);
}
}
class B extends StatefulWidget {
final int x;
const B(this.x, {Key key}) : super(key: key);
#override
_BState createState() => _BState();
}
class _BState extends State<B> {
#override
void didUpdateWidget(covariant B oldWidget) {
print('[${DateTime.now()}] widget has changed!');
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) => Container();
}
A has a child B that it rebuilds after every tap down gesture, and passes to it the int 6. Even though the argument remains the same, didUpdateWidget() gets invoked on every tap down.
On the other hand, if instead of 'B(6)' I give as an argument 'const B(6)', then didUpdateWidget() does not fire.
So am I to understand that an instance of a widget in and of itself is considered a configuration? Two instances of the same widget class with the exact same arguments are considered different configurations?

Related

how to give a choice for a parameter with an already created widget group(class)?

I have some some stless wrap widget:
class SomeWrap extends StatelessWidget {
// it's work, but i can put any widget, but I want only widget form class MyChoises
Widget MyValue
const SomeWrap({
required this.MyValue,
Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
// Here some settings for UI
// here my value as a widget
child: MyValue;
);
}
}
Here class MyChoises, that return couple widgets:
abstract class StatusTextOrder {
Text processing = Text('Processing',style: TextStyle(color:Colors.Yellow)));
Text delivered = Text('Delivered',style: TextStyle(color:Colors.Green)));
IconButton canceled = IconButton(icon: Text('Canceled', onPressed: ()=>{}))
}
What the correct way to use this "choices" for a value ?
usage:
SomeWrap(MyValue: StatusTextOrder.delivered)
Now into MyValue I can put any Widget, its not that im looking for.
I tried to use none abstract class, and put StatusTextOrder or Widget , but all of this gives me an errors.
Someone said that it will work :
import 'package:flutter/material.dart';
class StatusTextOrder {
static final processing =
Text('Processing', style: TextStyle(color: Colors.yellow));
static final delivered =
Text('Delivered', style: TextStyle(color: Colors.green));
static final canceled = IconButton(
onPressed: () {},
icon: Icon(
Icons.cancel,
color: Colors.red,
));
}
class Wrapper extends StatelessWidget {
StatusTextOrder widget;
Wrapper({Key? key, required this.widget}) : super(key: key);
#override
Widget build(BuildContext context) {
return Placeholder(
child: widget,
);
}
}
class ErrorPage extends StatelessWidget {
const ErrorPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child:Row(
children: [
Wrapper(widget: StatusTextOrder.delivered),
],
)),
);
}
}
no, it gives errors:
The argument type 'StatusTextOrder' can't be assigned to the parameter type 'Widget?'.
The argument type 'Text' can't be assigned to the parameter type 'StatusTextOrder'.
As comment section included desire behavior, It can be
wrapper class,
//wrapper class
class SomeWrap extends StatelessWidget {
final StatusTextOrder statusTextOrder;
const SomeWrap({required this.statusTextOrder, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return statusTextOrder;
}
}
//base class
abstract class StatusTextOrder extends Widget {
const StatusTextOrder({super.key});
}
/// concrete class
class Processing extends StatelessWidget implements StatusTextOrder {
const Processing({super.key});
#override
Widget build(BuildContext context) {
return const Text('processing');
}
}
And you need to use like
SomeWrap(statusTextOrder: Processing());
Also it can be
abstract class StatusTextOrder extends StatelessWidget {
const StatusTextOrder({super.key});
}
class Processing extends StatusTextOrder {
const Processing({super.key});
#override
Widget build(BuildContext context) {
return const Text('processing');
}
}
You can create another class with theses concrete Class as static variable and pass like old part.
old:
To use like StatusTextOrder.delivered, you need to make those variable as statics,
abstract class StatusTextOrder {
static Text processing = Text('Processing');
static Text delivered = Text('Delivered');
static IconButton canceled = IconButton(icon: Text('Canceled'), onPressed: () => {});
}
class SomeWrap extends StatelessWidget {
final Widget MyValue;
const SomeWrap({required this.MyValue, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: MyValue);
}
}
You can find more about class-variables-and-methods

Calling a stateful widget from another stateful widget does not work as expected

Calling a statefulwidget from a button click of another widget does not do anything. What I am trying to do is execute a print statement just to see if the calling works
I have this ElevatedButton below which calls the Stateful Widget Contacts, I dont see any errors but I dont see the print statement printed out in the console.
ElevatedButton(
onPressed: () {
const Contacts(name: 'Jimmy');
},
child: const Text('Fetch API data'),
),
class Contacts extends StatefulWidget {
final String name;
const Contacts({Key? key, required this.name}) : super(key: key);
#override
_ContactsState createState() => _ContactsState();
}
class _ContactsState extends State<Contacts> {
#override
Widget build(BuildContext context) {
print('Test2');
return Center(child: Text(widget.name));
}
}
State objects will not be created unless added to the widget tree.
Maybe you could run the print statement in constructor? Try this way:
class Contacts extends StatefulWidget {
final String name;
const Contacts({Key? key, required this.name}) : super(key: key);
#override
_ContactsState createState() => _ContactsState();
}
class _ContactsState extends State<Contacts> {
_ContactsState () {
print('Test2');
}
#override
Widget build(BuildContext context) {
return Center(child: Text(widget.name));
}
}

Call Navigator from outside state or call child state method from parent

I ended up with a parent calling a child method, which is fine, but I can't call Navigator outside the state class. My goal is either to move the child method in it's state and somehow access it, or to call Navigator form outside the state in a Stateful widget.
What is the best approach to this problem?
import 'package:flutter/material.dart';
class ParentClass extends StatefulWidget {
const ParentClass({Key? key}) : super(key: key);
#override
_ParentClassState createState() => _ParentClassState();
}
class _ParentClassState extends State<ParentClass> {
ChildClass childclass = ChildClass();
#override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('Button'),
onPressed: () => {
childclass.callNavigator(),//or call callNavigatorState
}),
);
}
}
class ChildClass extends StatefulWidget {
const ChildClass({Key? key}) : super(key: key);
void callNavigator() {
//call navigator from here
}
#override
_ChildClassState createState() => _ChildClassState();
}
class _ChildClassState extends State<ChildClass> {
void callNavigatorState(){
//access from parent widget?
}
#override
Widget build(BuildContext context) {
return Container();
}
}
you can access a stateful's state, using Globalkey.currentState...
check this sample code:
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
final child = ChildWidget(key: GlobalKey());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
child,
ElevatedButton(
onPressed: () {
(((child as ChildWidget).key as GlobalKey).currentState!
as _ChildWidgetState)
.someFunction();
},
child: Text("childWidget someFunction"))
],
),
);
}
}
class ChildWidget extends StatefulWidget {
const ChildWidget({Key? key}) : super(key: key);
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
void someFunction() {
print("childWidget someFunction");
}
#override
Widget build(BuildContext context) {
return Container();
}
}

Event Listeners in flutter

Is there a way I can listen to a button click in flutter? I want to know if the button is clicked in another class that creates a object of that button.
What i want to do is let MyButtonRow know when MyButton is clicked, since it involves changing variables in MyButtonRow.
The functionality i am looking for is similar to .setEventListener in JavaScript
So here's the code
class MyButtonRow extends StatefulWidget {
const MyButtonRow({Key? key}) : super(key: key);
#override
_MyButtonRowState createState() => _MyButtonRowState();
}
class _MyButtonRowState extends State<MyButtonRow> {
#override
Widget build(BuildContext context) {
return Container(width: MediaQuery.of(context).size.width,
child: Row(children: <Widget>[MyButtonTile()],));
}
}
class MyButtonTile extends StatefulWidget {
const MyButtonTile({Key? key}) : super(key: key);
#override
_MyButtonTileState createState() => _MyButtonTileState();
}
class _MyButtonTileState extends State<MyButtonTile> {
#override
Widget build(BuildContext context) {
return TextButton(onPressed: (){
//notify MyButtonRow about the click
}, child: Text("hello"));
}
}
Firstly you declare onTap function on your child widget and then just pass the onTap function from where you define the MyButtonTile
class MyButtonRow extends StatefulWidget {
const MyButtonRow({Key? key}) : super(key: key);
#override
_MyButtonRowState createState() => _MyButtonRowState();
}
class _MyButtonRowState extends State<MyButtonRow> {
#override
Widget build(BuildContext context) {
return Container(width: MediaQuery.of(context).size.width,
child: Row(children: <Widget>[MyButtonTile(onTap: (){
print("Notify me");
})],));
}
}
class MyButtonTile extends StatelessWidget {
final Function onTap;
MyButtonTile({this.onTap});
#override
Widget build(BuildContext context) {
return TextButton(onPressed:onTap,//notify MyButtonRow about the click
child: Text("hello"));
}
}

Why does setState change widget type?

Calling setState in a Stateful Widget changes the type of the widget to the parent's type. Why does this happen and is there a way to prevent it?
In the provided example, changing the Slider value calls setState which rebuilds the widget. By overriding didUpdateWidget, one sees that nothing happened and the widget is still the same widget. However, overriding == shows that the Slider Widget type changed from Container to Slider.
Output:
Runtime Type Old: Container New: SliderWidget
Expected output:
Runtime Type Old: SliderWidget New: SliderWidget
Why does this happen?
Screenshot:
Code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("Text Widget"),
SliderWidget(),
],
),
),
);
}
}
class SliderWidget extends StatefulWidget {
SliderWidget({Key key}) : super(key: key);
#override
_SliderWidgetState createState() => _SliderWidgetState();
#override
bool operator ==(other) {
print("RuntimeType Old: ${other.runtimeType} New: $runtimeType");
return this.runtimeType == other.runtimeType;
}
#override
int get hashCode => runtimeType.hashCode;
}
class _SliderWidgetState extends State<SliderWidget> {
int value = 10;
#override
void didUpdateWidget(SliderWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
#override
Widget build(BuildContext context) {
return Container(
child: SliderTheme(
data: SliderThemeData(
showValueIndicator: ShowValueIndicator.always,
),
child: Slider(
value: value.toDouble(),
min: 0,
max: 100,
label: value.toString(),
onChanged: (double value) {
setState(() {
this.value = value.toInt();
});
},
),
),
);
}
}
Demo:
https://dartpad.dev/5bae524391974eb12efa825a7db4e674