Flutter is a UI toolkit created by Google intended to help developers to easily create pixel-perfect apps for Android and iOS from a single code base.
No one can deny that the React philosophy inspired the creators of Flutter, and the concept of component, or widget, is central for understanding how applications are designed and build. Each one is part of a component tree and receives its data from a parent widget. Also, a widget describes its look based on the application's state. Therefore, if data changes, the framework will determine the affected widgets and repaint them accordingly.
Simple Example
import 'package:flutter/material.dart';
void main() => runApp(
Container(
child: Center(
child: Text('Hello, dev.to'),
),
),
);
As shown by the example above, each Flutter app requires the package 'package:flutter/material.dart'
, which contains all functions needed for the app creation. Next, the runApp
function sets up the initial settings for the app and defines which is the root widget of the application. Finally, the framework forces the root widget to cover all the screen, so, when executed, the example will show a text widget centered into a blank canvas.
As React programmers know well, flutters components are classified into two types: stateless and stateful widgets.
Stateless Widget
It is a type of widget that does not require an inner state to render its child tree. Therefore, the parent widget is the source of all data required by the stateless rendering loop.
To create a stateless widget, the developer must extend the StatelessWidget class and implement the build method. This method is the one responsible for creating the widget's children based on the input data.
Example
import 'package:flutter/material.dart';
class BackgroundTitle extends StatelessWidget {
final String titleText;
final Color backgroundColor.
BackgroundTitle({
this.titleText,
this.backgroundColor,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
child: Text(
titleText,
style: Theme.of(context).primaryTextTheme.title.copyWith(
fontSize: 32,
fontWeight: FontWeight.w600,
),
),
decoration: BoxDecoration(
color: this.backgroundColor.withOpacity(0.9),
borderRadius: BorderRadius.all(
Radius.circular(4),
),
),
);
}
}
Performance
A stateless widget depends solely on the configuration data provided to it. Consequently, the widget is rendered in two main occasions: when the widget is inserted into a widget tree and when that configuration data changes.
If the parent widget is constantly changing the widget configuration's data, so measures must be taken to avoid performance issues. The developer must guarantee that the widget will continue to render smoothly.
According to Google's documentation, there are several techniques to be considered to make a widget renders well.
Use
const
widgets where possible, and provide aconst
constructor for a custom widget, too. So, users can apply this same method to the custom widget.Consider refactoring the stateless widget into a stateful widget, and applying the adequated performance methods.
Minimize the use o transient widgets to build complex layouts. Instead of many levels o widget layering, consider using a
SinglePaint
widget.
For more information about stateless widget, check the official documentation.
Stateful Widgets
It is a type of widget that owns an inner and mutable state. As in React, state data can be read synchronously and might change through time. Consequently, stateful widgets are responsible for implementing their inner state, notifying the framework about state changes.
A stateful widget instance is itself immutable, but its mutable part is stored apart, into state objects. When the widget is created, it subscribes to the state objects and repaints the widget elements according to changes notified by State.setState function.
Finally, a stateful widget can keep the same state data if moved from one location to another one inside the component tree. But, this behavior is only possible if the developer provides a global key to that widget. Therefore, the framework can track the associated subtree and graft it in another widget tree.
Example
import 'package:flutter/material.dart';
import 'package:my_app/src/widgets/components/email_field.dart';
import 'package:my_app/src/widgets/components/password_field.dart';
import 'package:my_app/src/widgets/components/submit_button.dart';
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
String _email;
String _password;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
EmailField(
onDataChange: (String value) => setState(() => _email = value);
),
PasswordField(
onDataChange: (String value) => setState(() => _password = v);
),
SubmitButton(
onSubmit: _onSubmit
),
],
),
),
),
);
}
void _onSubmit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print(_email);
print(_password);
}
}
}
Performance
In terms of performance, we classify stateful widgets into two categories: cheap and expensive.
The cheap stateful widgets are those that allocate resources in the creation time and dispose of them when the widget is discarded. They are computationally cheap because they consume little CPU and GPU time since they never are redrawn due to updates. These widgets are commonly created as the root elements in the widget tree.
The expensive stateful widgets are those that allocate resources at creating time, but are subject to redrawing during its lifespan. They are the most common type of stateful widgets and will depend on State.setState
to trigger changes in response to events.
To avoid performance issues regarding stateful widgets, the developer must take some simple measures:
Try to place stateful widgets in the leaf nodes of the widget tree.
Avoid the use of many transient widgets and complex layouts, especially for widgets that have a high update rate or a big widget subtree.
Re-using a widget is a way more performant then rebuilding it. So, if the widget's subtree does not change, try to cache that. At first, extract the widget subtree to a stateless widget, then make it available as a child argument of the stateful widget.
Use
const
widgets wherever possible.If in-depth changes are necessary for some reason, the subtrees must use a global key object, avoiding superfluous updates.
For more information on stateful widgets, check the documentation
Also, you can check this video:
Conclusions
Widgets are a key role for a Flutter developer since each application is structured as a widget tree. As React, Flutter uses the concept of one-way data binding, meaning that data flows in a single direction, i.e., from a parent widget to its children. That creates a situation where there are widgets that holds data as an inner state, reacting to its changes, and others that do not. Those are stateful and stateless widgets, respectively.
Due to state operations, stateful widgets consume more computational resources than the stateless ones. Therefore, developers might take some actions to prevent performance lags and deliver a smooth experience to the user.
Finally, stateless widgets can decrease performance due to constant repaints, mainly because they cannot cache widgets when they have a complex widget subtree. To avoid those problems, developers must be aware of the performance tips from Google's documentation to build applications that render smoothly.
Top comments (0)