Stateful and Stateless Widgets — The Lego blocks of Flutter

·

4 min read

Nearly all things in Flutter are widgets. Buttons are widgets. Texts are widgets. Even animations are defined by widgets. Think of widgets as Lego Blocks — the foundation of which all Flutter applications are built.

The anatomy of a widget

import 'package:flutter/material.dart';

class BigButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      child: Text("Click me"),
    );
  }
}

In simple terms, widgets are Dart classes. Their job is to describe how Flutter should paint elements on the screen. For example, above we have defined a BigButton — a stateless Widget — that paints a Raised button with text.

As shown on line 5, every widget has a build method. This takes a context as an argument and returns another widget of your choosing. Here, we return a ‘text’ widget that displays the phrase ‘Click me’.

Stateful vs Stateless Widgets

At this point, you might look to line 3 and wonder what we mean by a StatelessWidget, and why our button inherits from this widget. There are two main types of widgets in Flutter — Stateless and Stateful. Like a React component, a stateful widget holds an internal state that can be managed.

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter;

  @override
  void initState() {
    super.initState();
    _counter = 0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
          Text(_counter.toString()),
          RaisedButton(
            onPressed: () {
              setState(() {
                _counter++;
              });
            },
            child: Text('Press me'),
          )
        ]),
      ),
    );
  }
}

In the example above, We have created a new widget by defining a new Dart class called ‘CounterWidget’ that extends from ‘StatefulWidget’. We have code that increments a counter when we click a button. On line 15, we set the initial state of the counter to 0 within the ‘initState’ method.

The initState() method is called when the widget mounts the tree. The method initialises any data within the block before Flutter tries to paint to the screen — like backstage preparations at a theatre. In line 19, we have the build method that takes in the buildContext. The buildContext is responsible for tracking where a widget is located on the widget tree. For instance, if you were to call Theme.of(buildContext).primaryColour, it first finds what is located at that point in the widget tree. It then grabs the theme of the widget and returns the data saved as the primaryColour in the Theme class.

Line 29 is where the magic happens. In order to increment a counter, we must always keep in mind the current value of the counter before adding one to it. The setState() method tells Flutter to execute all the code in the callback (in our case, line 30), and then to repaint the widgets that rely on this information (the Text widget). However, it is important to note that the setState method should not contain async code. This is to make sure the data is resolved before the widgets are being repainted. Hence, the initState() method might be a better place to execute this, or generally before setting the state.

How does this compare to a stateless widget?

On the other hand, a stateless widget holds no internal state — a dummy widget with its own set of configurations. I do however, use the phrase dummy loosely. A stateless widget can hold its own methods and have a set of configurations that is passed down to it from its parent. The only difference, is that a stateless widget cannot change its own configurations — not without the help of observables. They are immutable.

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class CounterWidget extends StatelessWidget {

  int _counter = 0;  //The class is immutable so this instance field should be final.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_counter.toString()),
              RaisedButton(
                onPressed: () {
                  _counter++; // Will not increment because there is no internal state.
                },
                child: Text('Press me'),
              )
            ]),
      ),
    );
  }
}

If we attempted to create a counter with a stateless widget, in the same manner as a stateful widget, it would fail. Firstly, there is no internal state, so the setState method is not readily available to us, and the widget would not be repainted to register changes to an instance field that should be final in an immutable class.