Why build method isn't defined inside StatefulWidget?
Asked Answered
W

2

8

I'm currently learning Flutter. I tried to deep dive into Flutter Widget life-cycle, and I wonder why StatefulWidget are written like this :

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  // initState
  // setState
  // ...
  @override
  Widget build(BuildContext build) {
    ...
  }
}

but not :

class Example extends StatefulWidget {
  // initState
  // setState
  // ...
  @override
  Widget build(BuildContext build) {
    ...
  }
}

I think the latter makes the source simple. But I don't know why they're using the former style ?

Wein answered 16/8, 2019 at 2:30 Comment(1)
Official answer here: github.com/flutter/flutter/issues/8794Reign
O
18

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 change (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.

Orthohydrogen answered 16/8, 2019 at 7:13 Comment(1)
Could it also be because the state of a Stateful widget is not just the data element but also UI elements. Meaning, a stateful widget may show one text field and, upon a state change, it may show two text fields. So basically, upon a state change, the UI of a widget may also change and not just the data. Note: I really don't know if this is true. I am just looking for some more clarifiacation.Bewitch
P
0

In official file framework.dart they define reason as below.

  • Putting a Widget build(BuildContext context) method on [State] rather than putting a Widget build(BuildContext context, State state) method on [StatefulWidget] gives developers more flexibility when subclassin [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.
  • Putting the [build] function on [State] rather than [StatefulWidget] also helps avoid a category of bugs related to closures implicitly capturing this. If you defined a closure in a [build] function on a [StatefulWidget], that 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 StatefulWidgetX {
   MyButton({super.key, required this.color});
   final Color color;
   @override
   Widget build(BuildContext context, State state) {
     return SpecialWidget(
      handler: () { print('color: $color'); },
       );
    }
  }
  • Above code is invalid because 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; should that closure outlive its widget, it would print outdated information.
  • In contrast, with the [build] function on the [State] object, closures created during [build] implicitly capture the [State] instance instead of the widget instance:
class MyButton extends StatefulWidget {
   const MyButton({super.key, this.color = Colors.teal});

   final Color color;
   
 }
class MyButtonState extends State<MyButton> {
  @override
  Widget build(BuildContext context) {
     return SpecialWidget(
       handler: () { print('color: ${widget.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 [widget] property to refer to the new MyButton instance and ${widget.color} prints green, as expected.
Possessed answered 16/4, 2023 at 16:36 Comment(2)
it would have been much simpler to take the state as an argument of the build function no ?Rubinrubina
@Rubinrubina if you use state as an argument then once the widget is rebuild it will take outdated information means it will not update the data it take previous data not update them.Possessed

© 2022 - 2024 — McMap. All rights reserved.