Nov 15, 2023
By Alberto Méndez

Widgets, the cornerstone of Flutter

Serie: Flutter

In the previous chapter, we explored the origins of the Flutter framework, who created it, a bit about its community, the Dart language, Skia, etc. We took a tour on the surface of this profound technology involving multiple actors. This time, we will continue digging a bit deeper into what could be considered the basic essence of Flutter: its widgets, how they work, and what to keep in mind when using them to build our applications.

Flutter is based on the idea that everything is a widget. From the text displayed on the screen, buttons, images, to the layouts that organize other widgets; all are widgets. These are immutable, meaning they do not change once created. Instead, new widgets are created if a change is needed. This immutability helps Flutter to be more efficient and provide a smoother user experience. Unlike other frameworks that might separate views, controllers, layouts, and other components, Flutter simplifies everything into widgets.

What is a Widget?

A widget can be considered as a description of a part of the user interface. Instead of building a user interface by directly modifying elements on the screen, you build a series of widgets and Flutter takes care of the rest. Imagine widgets like Lego blocks: each one has a specific function and shape, and you can combine them in various ways to build something bigger.

The framework differentiates two types of Widget that will be used depending on the context conditions, the * StatelessWidget* and StatefulWidget. While the former is immutable, once defined it cannot change, the latter is dynamic and allows the data it contains to change over time, that is, it has a ‘state’. This concept of state is fundamental to understand how Flutter manages interactivity and dynamics in an application. Let’s delve into the nature of the state and how it influences the construction of applications in Flutter.

What do we mean by ‘state’?

When we talk about interactive applications, we refer to applications that can respond to actions or events, either by user interaction or by external events, such as receiving data from a network. To handle this dynamic behavior, Flutter introduces the concept of “state” in its widgets.

State can be described as the information that can change during the lifetime of a widget. For example, if you have a widget that displays a counter, the number representing the counter is its state. When the number changes, the state of the widget has changed, and Flutter would redraw the widget to reflect that change.

Not all widgets need to have a state. Some widgets are static by nature (like a text widget that always displays the same value). Other widgets, like a checkbox or a slider, have a value that can change and, therefore, have an associated state.

When the state of a widget changes, the widget is redrawn. However, it’s important to understand that you’re not directly modifying the original widget; instead, you’re creating a new instance of the widget with the new state. This approach ensures that user interfaces are highly responsive and update efficiently.

The state associated with a StatefulWidget is closely related to the life cycle of this type of widget. Methods like initState() and dispose() allow you to initialize resources when the state is created and clean up when it is no longer needed, respectively.

Lifecycle

The concept of Lifecycle is essential in programming and development, as it encompasses the different phases or states that an element goes through, allowing developers to manage how it is initialized, updated, and disposed of. In native Android, for example, there are several key points in the execution of an Activity, such as the onCreate() method that is called the first time it is created, where the views of our user interface and other elements are usually initialized. Or the onResume() method that is executed when an Activity becomes the focus again after being paused or stopped, ideal for resuming specific tasks.

When the user taps the application icon, the operating system invokes the main() function. This function is the entry point of all Flutter applications. Before any widget can be displayed, it must be linked with the native platform through a WidgetsBinding instance. The runApp() function takes the root widget of the application and turns it into the widget tree that will be displayed on the screen.

Stateless widgets do not have a “lifecycle” in the traditional sense as they are immutable, meaning once they are given a particular set of properties, those properties cannot change. Each time you need to change the appearance of the widget, you must create a new one with the desired properties.

StatefulWidgets have a lifecycle, as they have the ability to change over their existence, providing interactivity and dynamism to the component in construction. This lifecycle of a Stateful widget in Flutter includes several key points:

  • createState(): It is the first method called when we create a StatefulWidget. It is responsible for creating the State object that will be used to manage the widget’s state.
  • initState(): Once the State object has been created, the initState() method is called. This is where you can perform state-related initializations.
  • build(): This method defines its interface and is invoked each time it needs to be rebuilt, for example, when the state changes.
  • didUpdateWidget(): Invoked when the parent widget changes and needs to update the current child widget. It is useful for when you need to perform specific operations in response to changes in parent widgets.
  • dispose(): It is the last point in the lifecycle of a StatefulWidget. When the widget is no longer needed and is going to be removed from the tree, this method is called to allow cleanup and resource release.

Understanding these concepts is essential to discern what happens in our application at each moment, allowing us to identify the different phases our components go through during their execution. Let’s continue looking at the different types of Widget that exist in Flutter now that we know what the state is and the lifecycles involved.

Stateful VS Stateless Widgets

Stateless Widget (StatelessWidget)

Stateless widgets in Flutter are the most basic and represent a portion of the user interface that is immutable. This means that if you want to make any change to a Stateless widget, instead of updating it directly, you will need to create a new instance of the widget with the desired modifications. A good example of this is a Text widget that displays a fixed message; being unalterable, if we wanted to change the message, we would need to create a new Text widget with the desired content.


  class MyStatelessText extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return const Text('Hello, Flutter!');
    }
  }
  

Our example, simplified to the extreme, would show us a text box with the message “Hello, Flutter!” written in it. It wouldn’t provide much utility to the user other than displaying specific information. It does not allow interaction or change, it has no state, which makes its reconstruction more direct and often more efficient. Other advantages are that we are talking about intuitive components that are simple to understand and use, which facilitates development. Because they only depend on immutable properties, their behavior is predictable each time they are constructed, ensuring a uniform experience. Additionally, their stateless design allows for easy reuse in different parts of an application, improving cohesion and reducing redundancy in the code.

Stateful Widget (StatefulWidget)

These can change during their lifetime. They have an associated “state” that can vary and cause the widget to be redrawn. They are ideal for parts of the user interface that need to be dynamic, such as counters, clocks, calendars, or any widget that requires user interaction.

Stateful widgets, known as StatefulWidgets, have characteristics that distinguish them and make them essential in the creation of dynamic applications in Flutter. They hold data that can change during the execution of the application, have an internal state that, when modified, triggers a reconstruction of the widget, allowing changes to be reflected in real time in the interface.

The ‘state’ of a StatefulWidget is essentially a snapshot of the data at a particular moment. Each time this state changes, the build() method is invoked again, redrawing the widget with the updated information. This is especially useful for widgets that must respond to user inputs or changes in data in real time.

A good example of the utility of this type of widgets is found in applications that require an interactive response. If you are developing a form, for example, where users can enter and modify information, or a game where elements change in response to the player’s actions, these will be your tool of choice.


  class StatefulCounter extends StatefulWidget {
    @override
    StatefulCounterState createState() => StatefulCounterState();
  }
  
  class StatefulCounterState extends State<StatefulCounter> {
    int _counter = 0;
  
    @override
    Widget build(BuildContext context) {
      return Column(
        children: <Widget>[
          Text('You have pressed the button $_counter times.'),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _counter++;
              });
            },
            child: Text('Press here'),
          ),
        ],
      );
    }
  }
  

This source code defines a Stateful widget called StatefulCounter. In reality, Stateful widgets are composed of two classes. The first is an extension of StatefulWidget, which is essentially a factory for its state counterpart. The second is an extension of State, which contains the mutable state of the widget and defines how the widget looks and behaves based on that state.

The StatefulCounter class extends StatefulWidget and overrides the createState() method. This method does not directly construct the widget with its logic and state, but rather returns a new instance of **StatefulCounterState **, which is the one that actually manages the state and logic. Thus, the StatefulCounter class acts as a factory that creates an instance of the associated state when necessary. This separation allows the widget itself to be immutable (as is desirable in Flutter to optimize UI reconstruction), while the associated state can be mutable and change over time, as we see with the _counter variable. In the Dart programming language, used by Flutter, the underscore “_” at the beginning of a variable or method name indicates that it is private to its current library.

The combination of different types of widgets, both Stateful and Stateless, allows us to build unique and customized interfaces for our applications. It is crucial to understand that they are a cornerstone in the construction of the structure and allow developers to have detailed control over how and when the user interface is updated, providing dynamic and reactive experiences to the end users.

Customizable and Reusable Design

For example, we could combine a custom widget CustomCard with several widgets provided by Flutter: Card, * ListTile*, Icon, and ElevatedButton. The beauty of this approach is that although we have combined multiple widgets, the experience remains fluid and optimized. In addition, we will design our CustomCard to be reusable, allowing customization of the title and icon through its properties. A simple Widget that we could then reuse anywhere in our application without needing to duplicate code, by simply importing it with a line at the beginning of our class.


  class CustomCard extends StatelessWidget {
    final String title;
    final IconData icon;
  
    CustomCard({required this.title, required this.icon});
  
    @override
    Widget build(BuildContext context) {
      return Card(
        elevation: 5.0,
        child: ListTile(
          leading: Icon(this.icon),
          title: Text(this.title),
          trailing: ElevatedButton(
            onPressed: () {
              // Action when pressed
            },
            child: Text('Press'),
          ),
        ),
      );
    }
  }
  

Widget reuse is fundamental for keeping the code clean, modular, and efficient. It not only speeds up the development process but also facilitates long-term code maintainability. For example, if we wanted to change the design of all the cards in our application, we would simply need to modify the CustomCard widget, and all the places where it is used would automatically reflect those changes.

Another advantage is design consistency. By reusing custom widgets, we can ensure that similar elements in different parts of the application maintain the same look and behavior, contributing to a more coherent user experience.

With the right combination of widgets, whether provided by the framework or created from scratch, unique UIs can be designed that stand out and provide memorable experiences to users.

We have learned the most basic concepts of Flutter, essential concepts to work comfortably with the framework and not get overwhelmed at the first attempt. In the next part, we will learn about the Dart language, its main characteristics, and some code examples. If you liked it and want to receive the next chapters directly in your inbox, don’t hesitate to subscribe for free and remember, Happy Coding!

Related posts

That may interest you

Flutter client

We have seen the basic functionalities to manage our data that GraphQL offers us. Operations like …

read more

Did you like it?

Your support helps us continue to share updated knowledge. If you find value in our articles, please consider helping us keep the blog running. 🤗
comments powered by Disqus

Our work

Simplifying digital experiences

project-thumb
Seamlessly bridge language gaps with just a single button press for effortless communication. BuddyLingo
project-thumb
AI-generated adventures where your choices will create endless possibilities. LAIA
project-thumb
Book a table at your favorite restaurant with MesaParaTi. MesaParaTi
project-thumb
Keep track of the hours dedicated to your job and/or projects. Own your time WorkIO