Problem: build() depends in StatefulWidget's state
Recently I moved a property inspector widget from being an always visible part of the UI to a Drawer.
Once I moved the property inspector, the app rendered random update operations regarding the object when the property inspector was about to edit values: The property inspector updated a random other object, not the current 'focus' object.
Solution : Make build() depend on the State<T> of StatefulWidget
I solved the issue by making the State<PropertyInspector> solely depend on state variables defined inside State<PropertyInspector> itself - instead of defining state variables in StatefulWidget and accessing these variables using widget.<someVariable>.
Root cause of the problem seems to be the fact that the Property Inspector in the Drawer gets more updates from the framework than the permanently visible alternative.
General advice
In general, Flutter needs to insert / remove / update StatefulWidget at various moments of time. While StatefulWidget might change, its State<T> object persists.
Thus, the build() method of a StatefulWidget should depend on State<T> - and not on the changing StatefulWidget.
Rules of thumb
All instance variables of StatefulWidget should be final
Thus, if an instance variable of StatefulWidget can't be marked as final, the design of the StatefulWidget is wrong.
From within State<T>, access to 'widget.*' instance variables are fine, as long as those variables are final.
Instance variables of State<T> may be non-final
Is my description correct?
This site doesn't warn about using state as widget.*. Isn't that wrong? or at least dangerous / bad style?
As far as I understand, the proposed state in StatefulWidget might work fine for some time. But, under different conditions, it is likely to render problems (as mine).
Helpful video How Stateful Widgets Are Used Best - Flutter Widgets 101 Ep. 2 in Flutter's StatefulWidget class documentation.
Contradiction
This lint error contradicts my explanation: no_logic_in_create_state
Update
I'd be happy if answers would explain, if this description / practice is correct or not.
I established this practice since certain widgets didn't behave as expected in some projects.
If I have understood your description properly then I would say your rules of thumb are correct. The state class and fields persist across renders, therefore any mutable state should be enclosed within the state class not within the widget class.
The lint no_logic_in_create_state doesn't contradict this, the lint is saying no logic in the create state method, which is in the widget class.
Any mutable field in the widget class could be reset when the widget re-renders and therefore no mutable state should be kept in any widget.
I will also add that just because an instance variable in a widget is final doesn't mean it wont change within the lifecycle of the app. If you pass a prop into the widget then the instance variable would update if the prop updates (the widget would be recreated but the state wouldn't).
Related
Started recently using the BLoC approach for building apps, and one thing that is not clear is where to "keep" BLoC variables. I guess we can have these two options:
Declare a variable in the BLoC class; for example in my class I can do the following:
class ModulesBloc extends Bloc<ModulesEvent, ModulesState> {
late String myString;
}
And access it in my UI as follows:
BlocProvider.of<ModulesBloc>(context).myString;
Keep it as a state variable; for example I can declare my state class as follows:
class ModulesState extends Equatable {
const ModulesState({required this.myString});
final String myString;
#override
List<Object> get props => [myString];
}
And access it in my UI as follows:
BlocBuilder<ModulesBloc, ModulesState>(
builder: (BuildContext context, ModulesState modulesState) {
modulesState.myString;
}
)
Are there any performance penalties / state stability issues with any of the above approaches?
Thanks!
I am not sure there is an absolute answer but I can at least give my opinion.
In bloc you have 3 objects: bloc, event, state.
The state is the mutable part while the bloc is a description of the your problem (what states to emit for each event). As such, an immutable variable to describe your problem should be, in my opinion, placed inside the bloc. However, anything which might change is the state of your bloc (same as the state of your widget) and should as such be stored in the state.
Example:
You want to create an app where you can set timers. In this app you can have multiple timers, each of which will be identified by a name.
In this case:
your state will be an object containing a double variable called timeCount, which will be incremented each seconds for example.
You bloc will have a final field called name which will have to be set during the creation of the stopwatch.
Interestingly enough, if you want the bloc to also handle the stopwatch creation, you will have 2 states: the first empty, the second with a name and timeCount. See how naturally name became variable and is therefore found in the state now.
How to manage to force a Widget to rebuild immediately after one of the property value has changed?
Some pseudo code:
class Live extends StatefulWidget {
String name;
Live(this.name);
#override
_LiveState createState() => _LiveState();
}
class _LiveState extends State<Live> {
// some turbo logic I don't want to move to Live class
..
#override
Widget build(BuildContext context) {
return Column(childrens: [
Text(widget.name),
Card(content calculated based on turbo logic),
]
);
}
}
When String name property has updated (based on parent's setState call), everything is happening in real time. The change is reflected immediately in Text widget. The value is visible immediately only because i am using widget.name call so in built() method I am using property from Live class instead of State.
The problem is that another widget wrapped in Card is calculated in place marked as // some turbo logic I don't want to move to Live class. Due to this fact when I want to see updates in this section I need to switch tab and go to e.g Setting and then return to Live tab to see changes related to Card content. I believe it trigger build() method again.
Golas:
Once the name value is updated in Live widget, a State widget rebuilds immediately.
do not move turbo logic to Live class and keep it in State class
First off, your StatefulWidget should be immutable and therefore only contain immutable fields. I'd suggest you move the name field into State and change it there using a setter. The setter should call setState(), this will cause the desired rebuild.
See this introduction for more information - specifically the "Bird" sample code to see how to code a setter.
I use a lot StatelessWidgets when I have to create "templates" of widgets that are used multiple times inside my app because the docs say so:
Stateless widget are useful when the part of the user interface you
are describing does not depend on anything other than the
configuration information in the object itself and the BuildContext in
which the widget is inflated.
Here is an example:
class StepInputButton extends StatelessWidget {
final int pos;
final String value;
const StepInputButton({
this.pos,
this.value
});
#override
Widget build(BuildContext context) {
return Row(
// Text, Icon and a tiny button
);
}
}
The above is good because I can use const StepInputButton(val, "val"), in the code with CONST which improves performances.
PROBLEM
I am using the famous Provider widget to manage the state and the page of my apps usually look like this:
class SuccessPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var prov = Provider.of<Type>(context);
return Scaffold(...);
}
}
That's a page of my app with Scaffold that has a Drawer, a float action button and an appTitle.
Here I use a StatelessWidget because I do not use setState() since provider does all the work for me. But still in the official flutter doc they say:
For compositions that can change dynamically, e.g. due to having an
internal clock-driven state, or depending on some system state,
consider using StatefulWidget.
So do I have to change class SuccessPage extends StatelessWidget to class SuccessPage extends StatefulWidget? Do I get advantages?
Note: if you want to put the question in another way: should I use StatefulWidgets to create "app pages" whose state is going to change and StatelessWidgets for "reusable widgets" whose state doesn't change?
StatefulWidget is necessary for when the widget itself is maintaining its own state. In the example you gave, the Provider package is handling the state for you, assuming you're using the correct provider type higher up the widget tree (for example, ChangeNotifierProvider). There also doesn't seem to be anything in this code that would benefit from having access to the widget's lifecycle, so you wouldn't need access to methods like initState or dispose.
As such, there's nothing for the widget itself to manage, so converting your class to be stateful is unnecessary.
One thing I might suggest, though, is to use a Consumer instead of calling Provider.of directly. A Consumer handles the call for you and removes any ambiguity on whether your widget will get updated when the Provider detects a state change.
You use StatelessWidget for widgets that don't change their state, that will stay the same all the time. Example, appBar is stateless.. The build(...) function of the StatelessWidget is called only once and no amount of changes in any Variable(s), Value(s) or Event(s) can call it again.
Therefore, when you need to change state(ex value) then use StatefulWidgets, basically StatelessWidget is used for building UI widgets that are static
Keeping it simple:
If you have non-final global variables in your widget then you need a StatefulWidget
If all global variables are final then you should use StatelessWidget;
Reason:
If your global variable is non final that means it is allowed to change and if it's value is changed that means state of your object(Widget) is changed (basic oops concept I am talking about). In such case you would like to call build method of your widget so that your changes get applied on the UI (if it matters for your UI). We do it by calling setState(); and so we use StatefulWidget for such use-case.
If it is enough that once you initialize your global variable in constructor, you don't need to assign it any value in future then in such case use StatelessWidget.
I have tried to keep it very simple and not technical enough so, if you still have any doubts please comment on this answer.
We can use StatefulBuilder to update a specific element on the UI of a StatelessWidget and in other hand, we can archive similar result moving the StatelessWidget to a StatefulWidget and getting rid of the StatefulBuilder widget.
I prefer StatefulBuilder because it makes the code much easier and cleaner, but what's the best in terms of performance?
A: Wrap everything on a StatefulBuilder using StatelessWidget
B: Use StatefulWidget
There is effectively no difference in performance between the two. Both involve the creation of a State object.
Given that your using global state, there is no real reason to use a StatefulBuilder relating to part of that existing global state. The StatefulBuilder provides a mechanism to have its own associated State object with state you define and can mutate. In your case (global data), your state is already in memory, so you might as well just mutate it in your StatefulBuilder builder callback, e.g.
setState(() => myGlobalCounter.increment());
You are likely aware, that one would normally prefer an InheritedWidget based state solution. While this can be considered equivalent in some ways to just moving global state to a global tree location, it more easily allows for all interested Widgets to be notified of changes to that state.
I am wondering what will happen if I define a StatelessWidget but return a stateful Widget from its build method? I've tried it and everything seems to be working, but I just want to know what is going on behind the scene so that I can be sure nothing will break when I ship it into production. Specifically:
1) I am wondering if every rebuild of the parent StatelessWidget will trigger a rebuild of the StatefulWidget it returns? If so, is it saying I am effectively returning a StatelessWidget?
2) I am wondering if the parent StatelessWidget will still be in the widget tree, given it is merely a wrapper and does not have any visual element to be rendered?
3) If I want to give the child StatefulWidget a Key, should I give the parent StatelessWidget the same key? Or, should I just put the key on the parent StatelessWidget?
Mixing Stateless and Stateful is a very, very, very common use-case.
The answer is relatively simple: Nothing special happens.
Stateless+Stateful is the same as Stateless*2 or Stateful*2. There's no behavior change and no extra code needed.
I am wondering if every rebuild of the parent StatelessWidget will trigger a rebuild of the StatefulWidget it returns? If so, is it saying I am effectively returning a StatelessWidget?
No. Each widget is independent and can rebuild without forcing other widgets to rebuild.
A child rebuilding won't make the parent rebuild. Similarly, a parent rebuilding doesn't necessarily force the child to rebuild either.
I am wondering if the parent StatelessWidget will still be in the widget tree, given it is merely a wrapper and does not have any visual element to be rendered?
Yes, a StatelessWidget is still in the tree.
No, it is not "just a wrapper." A StatelessWidget can use InheritedWidgets and override ==.
These can cause the widget to rebuild independently from other widgets. And as such, this widget must say in the tree.
It even has a setState equivalent; it's just not public.
If I want to give the child StatefulWidget a Key, should I give the parent StatelessWidget the same key? Or, should I just put the key on the parent StatelessWidget?
No. That's not needed.
If the key is on a widget, this will impact its entire subtree. So there's no need to put it on descendants too.
In fact, you can't, depending on the Key. GlobalKey, for example, requires to be unique.