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

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

Closed
sethladd opened this Issue Mar 15, 2017 · 9 comments

Comments

Projects
None yet
5 participants
@sethladd
Contributor

sethladd commented Mar 15, 2017

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!

@abarth

This comment has been minimized.

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

This comment has been minimized.

Contributor

abarth commented Mar 15, 2017

Let me know if that answer makes sense.

@sethladd

This comment has been minimized.

Contributor

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

Hixie commented Mar 19, 2017

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

@sethladd sethladd added the dev: docs label Mar 20, 2017

abarth added a commit to abarth/flutter that referenced this issue Mar 20, 2017

@abarth abarth closed this in #8916 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

@gagaJay

This comment has been minimized.

gagaJay 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

This comment has been minimized.

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

This comment has been minimized.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment