DEV Community

Cover image for Flutter Widgets 101
Francisco Souza
Francisco Souza

Posted on

Flutter Widgets 101

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 a const 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)