Looking at the StatefulWidget usage, I am wondering about the design decision to have circular dependency like this:
class YellowBird extends StatefulWidget {
const YellowBird({ Key key }) : super(key: key);
#override
_YellowBirdState createState() => _YellowBirdState();
}
class _YellowBirdState extends State<YellowBird> {
#override
Widget build(BuildContext context) {
return Container(color: const Color(0xFFFFE306));
}
}
example is taken from Flutter StatefulWidget docs
Any thoughts?
This is (one of the) correct way's to build a stateful widget and is properly in line with the flutter documentation.
This is not a circular dependency. You are defining a Stateful Widget class, and a corresponding State class.
The Widget contains a State object (or rather a sub-class of it), using the principle of composition-over-inheritance.
From the Stateful widget documentation:
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.
This way Flutter framework can better manage the manipulation and change of this Widget's data. For example (from the link above):
[...] multiple State objects might be associated with the same StatefulWidget if that widget has been inserted into the tree in multiple places.
This principle of composition-over-inheritance is a core-concept of Flutter framework and most IDE's can create this boilerplate code for you automatically (e.g. typing stful on AndroidStudio suggests the StatefulWidgets class and its State related class) so you don't have to worry about it.
Related
Using Flutter, I display a list of elements in an app.
I have a StatefulWidget (ObjectList) that holds a list of items in its State (ObjectListState).
The state has a method (_populateList) to update the list of items.
I want the list to be updated when a method (updateList) is called on the widget.
To achieve this, I save a reference (_state) to the state in the widget. The value is set in createState. Then the method on the state can be called from the widget itself.
class ObjectList extends StatefulWidget {
const ObjectList({super.key});
static late ObjectListState _state;
#override
State<ObjectList> createState() {
_state = ObjectListState();
return _state;
}
void updateList() {
_state._populateList();
}
}
class ObjectListState extends State<ObjectList> {
List<Object>? objects;
void _populateList() {
setState(() {
// objects = API().getObjects();
});
}
// ... return ListView in build
}
The problem is that this raises a no_logic_in_create_state warning. Since I'm not "passing data to State objects" I assume this is fine, but I would still like to avoid the warning.
Is there a way to do any of these?
Saving the reference to the state without violating no_logic_in_create_state.
Accessing the state from the widget, without saving the reference.
Calling the method of the state from the outside without going through the widget.
It make no sense to put the updateList() method in the widget. You will not be able to call it anyway. Widgets are part of a widget tree, and you do not store or use a reference to them (unlike in other frameworks, such as Qt).
To update information in a widget, use a StreamBuilder widget and create the widget to be updated in the build function, passing the updated list to as a parameter to the widget.
Then, you store the list inside the widget. Possibly this may then be implemented as a stateless widget.
This question already has an answer here:
Why am I getting TypeError at runtime with my generic StatefulWidget class?
(1 answer)
Closed last month.
I've got a simplified example below. In practice, I'm passing a list of data objects to a StatefulWidget. I want the Widget to copy the provided list into its state object which will then be filtered through future interactions. The filters use a type parameter so they know what fields they can work with in a callback, for example Filter<MyData>.
So, I'm trying to create a Widget that is aware of the MyData type so it can build the FilterChip Widgets using MyData fields. I tried to achieve this by adding a type parameter to both the StatefulWidget and its State class.
import 'package:flutter/material.dart';
/// Generic type information loss example
class MyWidget<T> extends StatefulWidget {
final List<T> things;
const MyWidget({Key? key, required this.things}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState<T>();
}
class _MyWidgetState<T> extends State<MyWidget> {
#override
Widget build(BuildContext context) {
List<T> things = widget.things; // Compiler error (IDE shows widget.things has a type of List<dynamic>)
return Container();
}
}
This code results in:
Error: A value of type 'List<dynamic>' can't be assigned to a variable of type 'List<T>'.
So what I don't understand is, why does things in the StatefulWidget class have a type of List<T>, but when referenced through the widget property of the class extendingState<MyWidget>, widget.things has a type of List<dynamic>.
And, as a result, any code in the state class that needs to be aware of the type now breaks. At runtime, the filter callbacks result in errors like:
type '(MyData) => bool' is not a subtype of type '(dynamic) => bool'
I think it is only class MyWidget extends StatefulWidget, without <T>.
Then you don‘t need to copy your list to preserve the list. Just use widget.things, this is considered best practice. The data remains during the rebuilds.
I have an unusual use case where I'd like to add a getter to a StatefulWidget class that accesses its State instance. Something like this:
class Foo extends StatefulWidget {
Foo({super.key});
int get bar => SomethingThatGetsFooState.bar;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int bar = 42;
#override
Widget build(BuildContext context) {
return Container();
}
}
Does SomethingThatGetsFooState exist?
I wonder, if your approach is the right way.
Flutter's way isn't 'Ask something about its state'
Flutter is much more like this: 'The consumer of a Widget passes something to another Widget, which the other Widget e.g. calls in case of certain situations (e.g. value change).'
Approach 1
You map pass a Callback Function to Foo and pass that along to _FooState.
If something special happens inside _FooState, you may call the callback and thus pass some value back to the provider of the Callback.
Approach 2
Probably use a state management solution like Flutter Redux. Using Flutter Redux, you establish a state store somewhere at the top of the widget tree, e.g. in MaterialApp.
Then you subscribe to the store at certain other locations, where dependent widgets exist, which need to update accordingly.
In one project I created certain Action classes, which I send to certain so called reducers of those states, to perform a change on the state:
StoreProvider.of<EModel>(context).dispatch( SaveToFileAction())
This call finds the relevant EModel state and asks it to perform the SaveToFileAction().
This way, a calling Widget not even needs to know, who is responsible for the Action.
The responsible Widget can even be moved around the widget tree - and the application still works. The initiator of SaveToFileAction() and the receiver are decoupled. The receiver you told a coordination 'Tell me, if someone tried to ask me for something.'
Could your provide some further details? Describe the usage pattern?
#SteAp is correct for suggesting there's a code smell in my OP. Typically there's no need to access State thru its StatefulWidget. But as I responded to his post, I'm fleshing out the first pass at a state management package, so it's an unusual case. Once I get it working, I'll revisit.
Below worked without Flutter complaining.
class _IntHolder {
int value;
}
class Foo extends StatefulWidget {
Foo({super.key});
final _intHolder = _IntHolder();
int get bar => _intHolder.value;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int value = 42;
#override
Widget build(BuildContext context) {
widget._intHolder.value = value;
return Container();
}
}
Im unsure about wether caching a widget instance and reusing it in the build() method makes a significant difference.
Suppose we have two widget classes:
class ParentWidget extends StatelessWidget {
ParentWidget({Key key})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container( // or some other widgets that define the ui
child: ChildWidget(/*...*/),
);
}
}
class ChildWidget extends StatelessWidget {
ChildWidget({Key key}) : super(key: key); // no constant
#override
Widget build(BuildContext context) {
/*
* returns some Widget
*/
}
}
From my understanding, everytime Flutter calls build() on the ParentWidget, a new ChildWidget is created and attached to the same Element in the element tree. Except if const ChildWidget is available (e.g. ChildWidget has a const constructor).
However, we can cache the child like so:
class ParentWidget extends StatelessWidget {
final Widget child;
ParentWidget({Key key})
: child = ChildWidget(/*...*/),
super(key: key);
#override
Widget build(BuildContext context) {
return Container(
// or some other widgets that define the ui
child: child, // <-- use child instead of ChildWidget()
);
}
}
From my understanding, the same widget will be used and no modification of the element tree has to be done by flutter. But I am unsure in which cases that matters.
Is there a significant difference between caching a widget and creating a new Widget with the same configuration?
If so, is there a rule on when to cache widgets instead of reconstructing them?
In the documentation of StatefulWidget, it is recommended to cache a widget if the subtree represented by that widget does not change:
If a subtree does not change, cache the widget that represents that subtree and re-use it each time it can be used. It is massively more efficient for a widget to be re-used than for a new (but identically-configured) widget to be created. Factoring out the stateful part into a widget that takes a child argument is a common way of doing this.
I therefore conclude that whenever a Widget changes State, subtrees that do not change based on the state should be cached and reused.
However, the example declared in the question is not a situation in which choosing to cache the widget is a good option, as only the root of a not-changing subtree should be cached by the parent of that subtree. But as ParentWidget is itself a StatelessWidget, it will be part of a not-changing subtree each time ChildWidget is. Therefore, some stateful Parent of ParentWidget should be responsible for caching.
There can be a difference, if for example the child widget allocates resources or has a large subtree of children.
The official flutter api documentation does recommend caching in certain situations. But generally, it should not be the first thing to do, when trying to optimise your views.
Read the documentation on Stateful and Stateless widgets, in particular the "Performance Considerations" section has a lot of relevant further information. For example:
If a subtree does not change, cache the widget that represents that subtree and re-use it each time it can be used.
Use const widgets where possible. (This is equivalent to caching a widget and re-using it.)
Here is an example of a StatefulWidget. But isn't it just boiler plate code? Why do we need two classes? Normally you copy&paste the extends StatefulWidget part but where is the purpose? Is there some hidden functionality? Or is it some kind of abstraction level? Why was this design chosen by the Flutter team?
class Counter extends StatefulWidget {
int someVar;
Counter(this.someVar);
#override
_CounterState createState() => new _CounterState();
}
And here is the State class. Wouldn't be this class sufficient? Why do we need two classes for a StatefulWidget?
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
#override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
Because all Widgets's are immutable. This means that your Counter class in this case is immutable and thus also all variables should be final. The State, however, is not mutable.
You can check out the documentation about the Widget class to read more about its purpose.
A widget is an immutable description of part of a user interface. [...] Widgets themselves have no mutable state (all their fields must be final). If you wish to associate mutable state with a widget, consider using a StatefulWidget, which creates a State object (via StatefulWidget.createState) whenever it is inflated into an element and incorporated into the tree.
Dividing a stateful widget in two is a requirement of some of flutter patterns. Widgets being the representation of a pure function you'd find in functional programming.
In fact React, a library from which flutter takes inspiration use two separate objects too. It's fairly obvious when using the following syntax :
class MyComponent extends React.Component<Props, State> {
state: State = {
foo: 42,
}
...
}
Okey, but why is it that State and the Stateful subclass are deeply linked ? Why can't I use any class as State ?
Ah, that's an interesting question ! For those who've used react before, this is something you can do out there. React can use any object as it's state. Flutter being so similar, why can't we too ?
After all, with a bit of tweaks there's nothing preventing the following syntax, not even the #immutable flag :
class MyStateful extends StatefulWidget<Video> {
#override
void dispose() {}
#override
build(BuildContext context) {
return new Text(state.title);
}
}
So why ?
A quote from flutter documentation :
You might wonder why StatefulWidget and State are separate objects. In Flutter, these two types of objects have different life cycles. Widgets are temporary objects, used to construct a presentation of the application in its current state. State objects on the other hand are persistent between calls to build(), allowing them to remember information.
This would mean that we'd have all the lifecycle methods override you can have on State move to the Stateful subclass. Such as initState, but also dispose or didUpdateWidget. Which doesn't make sense considering widgets are just throwable objets that never update.