Flutter is a “new” cross-platform UI development framework that follows the React component-based approach, allowing developers to build a complete App interface from one component to another. React provides only one basic component class, react.component.ponent. Therefore, developers do not need to select the react.component.ponent class before writing component code. However, Flutter provides developers with two important basic components: a StatelessWidget and a StatefulWidget. Although the name makes sense, one is stateless and the other is stateful. But for a beginner to Flutter, a number of questions may arise:
1. What’s the difference?
2. How to choose?
3. Will improper use affect performance?
The following sections will focus on these three questions.
The difference between StatelessWidget and StatefulWidget
Before explaining the differences, let’s familiarize ourselves with the basic concepts and usage of StatelessWidgets and StatefulWidgets.
StatelessWidget
The concept of stateless components is very similar to the “presentation components” in React. Stateless means that no mutable state is maintained inside the component. The data that the component renders depends on is passed in through the component’s constructor and is immutable. Let’s take a look at an example of StatelessWidget use, with the following code:
class MyWidget extends StatelessWidget {
final String content;
const MyWidget(this.content);
@override
Widget build(BuildContext context) {
returnContainer( child: Text(content) ); }}Copy the code
In the code above, we define a stateless component called MyWidget that displays a piece of text from externally passed content. So why is the data immutable?
Note that the content variable is defined in the component with final modifier, so that the value cannot be changed after the first assignment in the constructor, and therefore the content of the component cannot be changed again after rendering. If we do not use final when defining variables, the editor will warn us, as shown in the figure below.
If you want to display other text content, you can only change it in a variable in the parent component. For example, we can wrap a stateful component around it and change the state value to change what the stateless child component shows (this is explained below). This component will be destroyed before the Flutter renders the next frame and a brand new component will be created for rendering.
StatefulWidget
The concept of stateful components is very similar to the “container components” in React. In addition to passing in immutable data from the outside, a stateful component can define mutable state within the component itself. Changing the value of these states through setState methods provided by StatefulWidget triggers component rebuild to display new content in the interface. We use a StatefulWidget to wrap the MyWidget above and use state to change the text content rendered by the MyWidget as follows:
class MyWidget extends StatelessWidget {
final String content;
const MyWidget(this.content);
@override
Widget build(BuildContext context) {
print('MyWidget build');
returnContainer( key: key, child: Text(content) ); }}class MyStateFulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return_FulWidgetStateWidgetState(); }}class _FulWidgetStateWidgetState extends State<MyStateFulWidget> {
String content = 'default';
@override
Widget build(BuildContext context) {
print('FulWidget build');
return GestureDetector(
onTap: (){
setState((){
content = 'text'; }); }, child: MyWidget(content), ); }}Copy the code
In the code above, we created a stateful MyStateFulWidget to manage the content. When we change the content value via setState, the update of the MyStateFulWidget child component will be triggered. The Flutter will create a MyWidget object with the new content value to render and display the new content in the interface.
The difference between
As you can see from the introduction of the two types of components above, the biggest difference between them, besides the implementation method, is whether there is a state that can be changed inside the component. For stateless components, there is no internal maintenance of mutable state, and all the data that render depends on comes from the constructor when the component is created. A stateless component can only trigger a build after a constructor is called in the parent, so a stateless component needs to rely on the parent to trigger a build. Stateful components maintain mutable state internally and can change state internally through setState to trigger a rebuild of themselves including child components.
So how does a stateful component change state to trigger child component updates? Let’s look at the source of setState:
// framework.dart Line:1048
@protected
voidSetState (VoidCallback fn) {...if(_debugLifecycleState == _StateLifecycle.created && ! mounted) {throwFlutterError. FromParts (< DiagnosticsNode > [...] ); }return true; } ());final Object? result = fn() as dynamic;
/ / the key_element! .markNeedsBuild(); }Copy the code
The last line of the setState method calls Element’s markNeedsBuild method. This method marks the current Element as dirty, telling Flutter that the component needs to be rebuilt before the next frame is rendered. To understand this process, we need to understand how Flutter transforms widgets into UI.
In Flutter, a Widget stores configuration information about the UI block it represents. That is, Flutter does not render the UI directly using widgets, but rather as a configuration item of the UI. It is the Element class that truly represents the UI. From Widget creation to UI rendering, the process is as follows:
Flutter generates three trees: Widget tree, Element tree, and RenderObject tree. The Flutter generates the Element tree from the Widght tree and the RenderObject tree from the Element tree. The Flutter UI system will eventually draw components onto the screen based on the layout information provided by the RenderObject tree.
When the three trees are first built, the UI appears on the screen. Flutter diff the trees layer by layer to see if they have changed (marked as dirty components) before each subsequent frame is rendered. If a node of the tree is marked as a dirty component, the node and its children are recreated to replace the original node with a new component reference. After the next frame is rendered, we can see the new content on the screen.
How does Flutter determine if a component node has been updated? The markNeedsBuild method mentioned earlier does just that. Components marked by markNeedsBuild are considered by Flutter to be updated and need to be rebuilt. So when we call the setState method, the component is rebuilt in the DIff phase.
Another interesting thing to note from the setState source code is that content = ‘text’ is the same for code that changes the state of a component, whether written inside or outside the setState callback. This is because whenever a component is marked for update, it is built with the latest state retrieved at rebuild time.
When we understand how setState works, we may wonder: since setState calls the markNeedsBuild method for stateful components to update, is there any way for stateless components to update themselves by calling the markNeedsBuild method? In fact, there is, let’s look at the following code:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget('wwww'),),),); }}class MyWidget extends StatelessWidget {
final String content;
const MyWidget(this.content);
@override
Widget build(BuildContext context) {
print('MyWidget build');
return GestureDetector(
onTap: (){
/ / the key
(context as Element).markNeedsBuild();
},
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Color.fromRGBO(255.255.255.1)))); }}Copy the code
In the code above, MyWidget is a white square with a width of 300 and a height on which Tap events are listened for through the GestureDetector. In the Tap event, we cast the context to Element (Element implements the BuildContext interface: framework.dart Line: 3004: Abstract Class Element extends DiagnosticableTree implements BuildContext), MyWidget is marked as a dirty component every time we click on a white area, Let Flutter reconstruct it. Run the demo above in DartPad and click on the white area to see the output of the MyWidget Build log in the console, indicating that the MyWidget has been rebuilt, as shown in the figure below.
Stateless components can also trigger their own rebuild in some way, contradicting the previous statement that a stateless component can only trigger a build after a constructor is called.
Personally, I don’t think you need to worry too much about this. StatelessWidget is designed to make it easier for developers to implement a non-self-managing component. StatelessWidget hides many of the methods in statefulWidgets. You don’t need to override various unwanted methods when using StatelessWidget, and you don’t need to care about internal state changes, just what data is passed in from the outside and displayed.
When using React, we need to use code specifications to define which components are container and which are presentation components. And you need to look at the component’s implementation code to know what type it is. Flutter is designed to separate these two concepts directly, making it more explicit, and reading the code can tell which component it is by simply looking at the definition at the beginning of the component.
When should you use a StatelessWidget? When should StatefulWidget be used?
Overall, Flutter expects developers to consider and decide whether stateless or stateful components need to be used before implementing them. This makes for a better design of the application’s components.
Let’s abstract a few scenarios to talk about how to make a selection, using a button as an example.
General button
The requirement is to implement a very common, generic button that will be used in multiple places and have the same style except for the text of the button. Therefore, we need to implement a generic button component that displays text in a button based on incoming content from the outside. Based on the requirements analysis, the button itself does not change what it displays, but is externally controlled, so the obvious choice is to use the StatelessWidget. The code is as follows:
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255.18.32.47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: CommonButtonWidget('sure'),),),); }}class CommonButtonWidget extends StatelessWidget {
final String buttonText;
const CommonButtonWidget(this.buttonText);
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
width: 120,
height: 60,
decoration: BoxDecoration(color: Color.fromRGBO(255.255.255.1)),
child: Text(
buttonText,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 2.5,
fontFamily: "Courier"(), (), (), (); }}Copy the code
The code running result is as follows:
Built-in countdown button
You must have used the SMS verification code sending function of each website. When the verification code is successfully sent, the send button will add a countdown function and modify the copy of the button. After the countdown begins, the button will not be clickable. According to the requirement analysis, there are three changes in the presentation form of buttons during the use of users:
1. Button copy changes
2. Display the countdown
3. Clickable status change
Based on these points, we need to determine whether the changes are externally or internally controlled before implementation, and then choose which component form makes more sense. Obviously, these change points are within the responsibility of the button itself. Based on the design principle of low coupling and high cohesion, this part of the code logic should be implemented inside the component code. Therefore, we need to use stateful components to do this, as follows:
import 'package:flutter/material.dart';
import 'dart:async';
final Color darkBlue = Color.fromARGB(255.18.32.47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: ButtonWidget(), ), ), ); }}class ButtonWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return_ButtonWidgetState(); }}class _ButtonWidgetState extends State<ButtonWidget> {
int count = 10;
int status = 0;
Map<int.String> textMap = {0: 'send'.1: 'Sent'};
Timer timer = Timer(new Duration(seconds: 0), () {});
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
print('FulWidget build');
return GestureDetector(
onTap: () {
if (status == 0) {
setState(() {
status = 1;
});
timer.cancel();
timer = Timer.periodic(new Duration(seconds: 1), (timer) {
count = count - 1;
if(count == 0){
timer.cancel();
setState(() {
status = 0;
count = 10;
});
}else{ setState(() { count = count; }); }}); } }, child: Container( width:120,
height: 60,
decoration: BoxDecoration(color: Color.fromRGBO(255.255.255.1)),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
textMap[status] ?? 'send',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 1.5,
fontFamily: "Courier",
),
),
status == 1? SizedBox( width:5
): Container(),
status == 1 ? Text(
count.toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 1.5,
fontFamily: "Courier", ), ): Container(), ]), ), ); }}Copy the code
The code running result is as follows:
Going back to the original question, how do developers choose?
The recommendation here is to use the stateful component StatefulWidget if the component to be implemented needs to internally manage the data on which the rendering depends and will be rerendered by changing state after the first rendering, or use the StatelessWidget if not. When we don’t know how to make a selection, we try using the StatelessWidget implementation first, then switch to the StatefulWidget when we run into problems.
Does improper use affect performance?
At the heart of this question is the situation under which statelessWidgets and StatefulWidgets are rebuilt.
For a StatelessWidget, the StatelessWidget itself is rebuilt whenever the state of its parent changes, or when an ancestor component changes state and causes its parent to be rebuilt. Due to the React PureCompoment concept, it is always assumed that a StatelessWidget will not be rebuilt if the parameters passed in to a Flutter do not change. Since there is no mechanism in Flutter to compare parameters during diff (this process is officially considered fast enough), the StatelessWidget will always be rebuilt in the above situations. A StatefulWidget is basically the same as a StatelessWidget in that it can trigger a rebuild itself in addition to being affected by a parent element. Therefore, there will be no performance difference with either.
We might as well write a demo to verify:
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255.18.32.47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false.home: Scaffold( body: Center( child: FulWidget(), ), ), ); }}class MyWidget extends StatelessWidget {
final String content;
MyWidget(this.content);
@override
Widget build(BuildContext context) {
print('MyWidget build');
return Container(
key: key,
child: Text(content, style: Theme.of(context).textTheme.headline4) ); }}class FulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return_FulWidgetStateWidgetState(); }}class _FulWidgetStateWidgetState extends State<FulWidget> {
int a = 0;
@override
Widget build(BuildContext context) {
print('FulWidget build');
return GestureDetector(
onTap: (){
setState((){
a = a + 1;
});
},
child: Row(
children: [
Container(
width: 300.height: 300.decoration: BoxDecoration(
color: Color.fromRGBO(255.255.255.1)
),
),
MyWidget('Test'))); }}Copy the code
In the above code, MyWidget is a stateless component, and its parent FulWidget is a stateful component. We trigger the rebuild of the FulWidget by changing the internal state A of the FulWidget in response to a click event. From the output after the click, the MyWidget component is rebuilt without changing the value passed in. At the same time, the FulWidget itself has been rebuilt, as shown below:
If a stateless component in an application scenario does not need to be rebuilt in any case, const can be added to the stateless component when declared and called, as shown in the following code:
class MyWidget extends StatelessWidget {
final String content;
// Add const to the constructor
const MyWidget(this.content); ... }class FulWidget extends StatefulWidget {
……
}
class _FulWidgetStateWidgetState extends State<FulWidget> {
int a = 0;
@override
Widget build(BuildContext context) {
print('FulWidget build');
returnGestureDetector (onTap: () {... }, child: Row(children: [...// Use const when called
const MyWidget('Test'))); }}Copy the code
When we click on the white square in the same way, we can see on the console that the MyWidget has not been rebuilt, only the FulWidget has been rebuilt, as shown below:
Although officials say the process is quick, it is frustrating to do so without the need to rebuild. Is there a way to have a shouComponentUpdate method like React that allows developers to control this behavior to optimize it to the utmost? Flutter itself is not provided, but can be implemented by other means. If you want to learn more about this topic, read the following article.
Developpaper.com/the-ultimat…