Skip to content

FAQ request: why is the build() method on State, and not StatefulWidget ? #8794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sethladd opened this issue Mar 15, 2017 · 23 comments · Fixed by #8916
Closed

FAQ request: why is the build() method on State, and not StatefulWidget ? #8794

sethladd opened this issue Mar 15, 2017 · 23 comments · Fixed by #8916
Assignees
Labels
from: study Reported in a UX study

Comments

@sethladd
Copy link
Contributor

This comes up often when users are learning our framework. At our recent event, multiple Flutter team members didn't have a canned response to this FAQ.

Suggestion: Add this to our FAQ so for the next event, or next time this pops up in a chat or a conversation with a new user, we can point to our answer.

Can someone write up an answer to "Why is the build() method on State, and not StatefulWidget?" and we'll add it to the FAQ.

Thanks!

@sethladd sethladd added dev: docs - website from: study Reported in a UX study labels Mar 15, 2017
@abarth abarth self-assigned this Mar 15, 2017
@abarth
Copy link
Contributor

abarth commented Mar 15, 2017

If we put the build function on StatefulWidget rather than State, the build function would receive the state object as a parameter, similar to the following:

class MyStatefulWidget extends Widget {
  ...
  Widget build(BuildContext context, covariant State state);
}

If you defined a closure in that function, the closure would implicitly capture this, which is the current widget instance, and would have the (immutable) fields of that instance in scope:

class MyButton extends MyStatefulWidget {
  ...
  final Color color;

  @override
  Widget build(BuildContext context, MyButtonState state) {
    ... () { print("color: $color"); } ...
  }
}

For example, suppose the parent builds MyButton with color being blue, the $color in the print function refers to blue, as expected. Now, suppose the parent rebuilds MyButton with green. The closure created by the first build still implicitly refers to the original widget and the $color still prints blue even through the widget has been updated to green.

In contrast, with the build function on the state object, closures created during build implicitly capture the state object instead of the widget:

class MyButtonState extends State<MyButton> {
  ...
  @override
  Widget build(BuildContext context) {
    ... () { print("color: ${config.color}"); } ...
  }
}

Now when the parent rebuilds MyButton with green, the closure created by the first build still refers to state object, which is preserved across rebuilds, but the framework has updated that state object's config property to refer to the new MyButton instance and ${config.color} prints green, as expected.

@abarth abarth assigned sethladd and unassigned abarth Mar 15, 2017
@abarth
Copy link
Contributor

abarth commented Mar 15, 2017

Let me know if that answer makes sense.

@sethladd
Copy link
Contributor Author

sethladd commented Mar 15, 2017

Thanks! I'm going to challenge us to try to come up with the two-sentence explanation (maybe not the full reason, but enough to convince yourself that the difference is well-meaning, well thought out). I can start to understand that part of the reason is the way closures work, and understanding the lifecycle of our widgets (stateful and stateless).

Back to your example, I don't see why the problem of creating a closure that could implicitly keep a reference to an old value is unique to StatefulWidget. In your example, we don't refer to MyButtonState in the closure. So wouldn't we also have the problem with StatelessWidgets ?

class MyButton extends StatelessWidget {
  ...
  final Color color;

  @override
  Widget build(BuildContext context) {
    ... () { print("color: $color"); } ...   // <== this captures this.color
  }
}

(There's something here about Dart I probably don't understand, forgive me if I'm missing the obvious thing. :)

@sethladd sethladd assigned abarth and unassigned sethladd Mar 16, 2017
@abarth
Copy link
Contributor

abarth commented Mar 17, 2017

With a stateful widget, it's common to make closures whose lifecycle are tied to the state's lifecycle, which lasts through multiple widgets. With a stateless widget, it's common to make closures whose lifecycle are tied to the widget's lifecycle, which doesn't cause a problem.

@abarth
Copy link
Contributor

abarth commented Mar 17, 2017

Why is the build() method on State, and not StatefulWidget?

Putting a Widget build(BuildContext context) method on State rather putting a Widget build(BuildContext context, State state) method on StatefulWidget gives developers more flexibility when subclassing StatefulWidget.

For example, AnimatedWidget is a subclass of StatefulWidget that introduces an abstract Widget build(BuildContext context) method for its subclasses to implement. If StatefulWidget already had a build method that took a State argument, AnimatedWidget would be forced to provide its State object to subclasses even though its State object is an internal implementation detail of AnimatedWidget.

Conceptually, StatelessWidget could also be implemented as a subclass of StatefulWidget in a similar manner. If the build method were on StatefulWidget rather than State, that would not be possible anymore.

@Hixie
Copy link
Contributor

Hixie commented Mar 19, 2017

We should include all the above explanations in a ## Design discussion section on State.build.

abarth added a commit to abarth/flutter that referenced this issue Mar 20, 2017
abarth added a commit that referenced this issue Mar 20, 2017
sethladd added a commit to flutter/website that referenced this issue Mar 21, 2017
sethladd added a commit to flutter/website that referenced this issue Mar 21, 2017
@PandaGeek1024
Copy link

PandaGeek1024 commented Jul 8, 2018

Hi, @abarth . Im pretty new to this framework so even though you have provided very detailed information, im still confused about some part of it. For example, in the below quote

For example, suppose the parent builds MyButton with color being blue, the $color in the print function refers to blue, as expected. Now, suppose the parent rebuilds MyButton with green. The closure created by the first build still implicitly refers to the original widget and the $color still prints blue even through the widget has been updated to green.

My understanding is that when the parent is updated, a new instance of MyButton should be recreated as well as the new closure. That should refer to the new $color as my understanding. But why is it still maintaining the old closure and capturing the old $color? This is really confusing to me, maybe it is a dart specific thing?

@abarth
Copy link
Contributor

abarth commented Jul 8, 2018

But why is it still maintaining the old closure and capturing the old $color?

That depends on what happened to the old closure. The old closure might still be retained by another object. If called, it does the wrong thing. You're right that the old closure will often be replaced by a new closure that captures the new color, but nothing guarantees that will happen. With the build method on the State, you always get the right behavior.

@fenduru
Copy link

fenduru commented Aug 19, 2018

The naming here is the part that is confusing to me as someone learning Flutter. The class MyState isn't really the state - its pretty much the whole thing. It has variables (the actual state), methods, and a build that describes how to render the variables.

This is so much more than "state", so coming from pretty much any other paradigm this is going to be confusing.

I think it would be better for the docs to have an explanation of what the conceptual responsibilities of the pieces are. Saying "because of the closures, and lifetimes of things, etc." may help someone interested in the nitty gritty details, but is not helpful for someone trying to understand the concepts.

@exotfboy
Copy link

exotfboy commented Jan 5, 2019

I am really confused with the concept too when I read the documents for flutter.

As far as I can understand from other framework, especially some front framework like redux or flux.
State should be a pure class to store the attributes and properties of your widget, it should have no UI related logic just like the adapter in android. That's what make me really hard to understand when I found the State HAVE TO construct the ui components by the build function.

If it have to, I think it is better to rename the State to something else.

@developerlaoz
Copy link

I' m thinking about the result of method createState in statefulWidget, why not build in the statefulWidget using this result?

@KamiShikkaku
Copy link

I agree the naming convention makes this unnecessary confusing, especially coming from frameworks like React where the boundaries of what we call "state" are so clearly defined. I really hope a name change will at least be considered.

@InMatrix
Copy link

InMatrix commented Feb 6, 2019

@KamiShikkaku What would you call "state" in Flutter ideally? I'm curious about potential alternatives.

@ofan
Copy link

ofan commented Feb 23, 2019

Perhaps State should be called ViewModel, it seems to me that StatefulWidget doesn't serve much purpose except being a glue in the framework.

@patniemeyer
Copy link

Way too late but I'm going to chime in anyway in case this helps someone in the future.

The problem here is that in order to support the hot reload functionality at development time without any actual specialized platform support for this feature (i.e. runtime magic) the API designers have had to jump through hoops and create a double indirection that allows what should be otherwise immutable state to be swapped out on demand. Unfortunately developers have to live with this awkwardness in their code permanently.

The briefest way I can summarize the situation is: A subclass of StatefulWidget is (counterintuitively) an immutable Widget that holds configuration state along with a factory for a mutable widget state object that itself serves as a factory for the actual widget tree to be displayed by the application.

It helps a little to mentally rename:

StatefulWidget => StatefulWidgetFactory
State => ViewModel

But these are really incorrect and should be something more like:

StatefulWidget => StatefulWidget(ViewModelAndWidgetFactory)Factory
State => ViewModelAndWidgetFactory

It begs the question of whether this could have been made clearer by simply introducing explicit classes for the immutable widget config and a widget factory. The separation would have eliminated any ambiguity about closures. Perhaps they tried this and decided it was just too much boilerplate.

Personally I think this is so confusing as is that it's going to be a barrier to some developers getting started and an ongoing annoyance for everyone else. I'm disappointed that Google didn't put more effort into this and create a real platform solution for hot reload that doesn't impose this extremely counterintuitive pattern on developers.

@linvain
Copy link

linvain commented May 10, 2019

It's like fighting your own language, paradigm (OOP) and design decisions. Why not expose higher level API to hide the inconsistencies? So that for a developer StatelessWidget and StatefulWidget were similar except that StatefulWidget would have additional methods for dealing with state. Or maybe merge both into one Widget and use hooks to add state like they do in React.

@nikhilk
Copy link

nikhilk commented May 22, 2019

I too got confused by the naming -- when I saw StatefulWidget and State, I expected the following (thinking this would be MVVM built-in into Flutter):

class MyFooState extends State {
// properties, methods etc.
}

class MyFooWidget extends StatefulWidget<MyFooState> {
// method to build view/widget tree
}

but unfortunately, no. It looks inverted, but not quite.

Based on what I have read so far State -> WidgetController
and StatefulWidget -> Widget

I am new to this, so don't have it all thought out, but from past UI frameworks, having a better MVVM model would be interesting.

@yash1195
Copy link

This issue should be addressed. I feel like it is making a not so difficult thing to understand, extremely difficult.

@Hixie
Copy link
Contributor

Hixie commented Dec 21, 2019

It's addressed in the API docs: https://api.flutter.dev/flutter/widgets/State/build.html

@Hixie
Copy link
Contributor

Hixie commented Dec 21, 2019

Also, I did a whole video about it: https://www.youtube.com/watch?v=dkyY9WCGMi0

@champ96k
Copy link

champ96k commented Dec 7, 2020

The reason why StatefulWidget uses a separate State class and not having build method inside its body is because all fields inside a Widget are immutable, and this includes all its sub-classes.

You might have noticed that StatelessWidget has its build and other associated methods defined inside it, but that was possible due to the nature of StatelessWidget which is rendered completely using the provided info, and doesn't expect any future change in its State.

In the case of StatefulWidget, State information occasionally changes (or expected to change) during the course of the app, thus this information isn't suitable for storage in a final field (build) to satisfy Widget class conditions (all fields are immutable). That's why State class is introduced. You just have to override the createState function to attach your defined State to your StatefulWidget, and let all that change happens in a separate class.

I hope it help!
Thank you

@lordzsolt
Copy link

@champ96k The problem is not why is there a separate object.

The problem is, the term "State" implies, to most people, that it's a plain old object that holds only logic and has nothing to do with UI. It's like a fancy new supermarket called their meat aisle "Produce area". Every new customer would expect to find fruit and vegetables there, only to have an FAQ next to it explaining why it's not the case...

A term like WidgetBuilder would be infinitely more understandable. Then you only need to explain to people that "WidgetBuilder" holds the state of the Widget, and rebuilds the widget when this changes.

@github-actions
Copy link

github-actions bot commented Aug 6, 2021

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 6, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
from: study Reported in a UX study
Projects
None yet
Development

Successfully merging a pull request may close this issue.