Hey there ๐
So far in this series, we've covered what Flutter is, how to set up the development environment, and dissected the starter project file by file. So the next logical step would be to get a better understanding of Widgets, as they are the foundation of Flutter's UI.
In this post, we will cover what Widgets are, the different types available, and finally how to use and create them.
There is a repo that complements this post here.
What are Widgets?
Flutter emphasizes widgets as a unit of composition. This means that they are the building blocks of a Flutter app interface. From a text or image, to how they are laid out on the page, almost everything is composed of widgets.
They form a hierarchy based on composition, also known as the widget tree. Each widget nests inside its parent and each widget receives "context" from its parent (the context is just some information about the location of the widget in the tree). This tree structure goes all the way up to the root widget. You could also imagine the widget tree as similar to the DOM but not the exactly the same (read more).
This is how a tree of widgets looks like:
ListView(
children: const [
Text('ListView is my parent'),
ListTile(
title: Text('My parent is ListTile'),
trailing: Icon(Icons.check),
)
],
);
Categories of widgets
From the research I've done, these are the main categories people seem to combine widgets in:
- Accessibility
- Animation and Motion
- Assets, Images, and Icons
- Async
- Basics
- Input
- Interactive
- Layout/structural
- Painting and effects
- Scrolling
- Styling
- Text
Flutter provides a set of pre-made Widgets, that help you build apps that follow Material Design or Cupertino style.
Let's break down some of them:
Layout/structural
They have no visual representation, they just control some aspect of the layout of one or more widgets. For example a Column, Row, Padding, etc...
Example of a structural Widget:
Column(
children: [
Text('Element 1'),
Text('Element 2'),
]
);
The Column
widget, will position the child widgets in a column or vertical direction.
Interactive
These are widgets that provide interactivity, for example, a button, a text field, a scrollable list, etc...
Example of an Interactive Widget:
ElevatedButton(
onPressed: () {
print('Click!');
},
child: const Text('A button'),
),
The ElevatedButton
widget, will render a button that responds to events, like onPressed
. This event will execute whenever the user presses/clicks the button.
Accessibility
These are widgets that help with making our apps more accessible.
Types of widgets
When writing flutter apps, we will use two kinds of widgets, depending on whether the widget has a state or not:
Stateless Widget
These are widgets that have no mutable state, meaning they will not change over time (for example a label or an icon)
const Text('I am a stateless widget!');
The Text
widget is stateless, as it does not need to keep any state, no value will change internally.
Stateful Widget
These types of Widgets do have a mutable state, meaning they will react whenever the state changes, like a counter widget or in the following example a text field (or text input):
TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
);
The TextField
widget is stateful because it has to keep track of the value of the field and react whenever the user types on the keyboard.
How do widgets update?
Flutter apps update their UI in response to events, such as a user interaction like a tap/click, or a state change.
These events tell Flutter that they need to replace a widget in the hierarchy with another widget. Flutter compares both the old and new widgets and efficiently updates the UI. Similar to VDOM if you're familiar with that, though not exactly the same (more info here).
Using widgets
Using widgets is rather easy, we just instantiate them wherever we like, like any other class. Note that using the new keyword is optional in Dart, which makes for cleaner code as loads of classes are instantiated all over the code.
For example, if we want to create a pieceย of text centered on the page, we could compose it like this:
build(context){
return Center(
child: Text('I see 4 widgets up my tree!'),
);
}
Or like this:
build(context){
return Column(
mainAxisAligment: MainAxisAligment.center,
srossAxisAligment: CrossAxisAligment.center,
children: [Text('I see 4 widgets up my tree!')],
);
}
Most widgets accept either child
or children
arguments, and each widget will manipulate or position those children in some way. Some will put them side by side, whilst others will make your widget contortionate in such ways that defy physics...
Let's see a complete example:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Home Page'),
),
body: Center(
child: Column(
children: [
const Text('Hello World'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print('Click!');
},
child: const Text('A button'),
),
],
),
),
),
);
}
}
We can see that MyApp
is a widget that returns more widgets nested hierarchically. Flutter apps are composed by combining premade widgets like Scaffold
or AppBar
, and custom widgets created by ourselves (like the one created further down) or by third-party developers.
The above example will result in a widget tree like this:
And would render like this, when run on a mobile device (Android in this example):
Creating Widgets
Widgets are usually composed of many smaller, single-purpose widgets that combine to create more powerful ones. Flutter emphasized the importance of keeping the class hierarchy shallow, meaning that whenever possible, the widgets should be small, composable, and do one thing well.
As we've seen at the start of the post, Flutter apps are built by composing widgets. But we are not restricted to the widgets Flutter offers. We can create custom widgets too, let's see a basic example of how to do that.
To create a widget we need to create a class that extends from either StatelessWidget
or StatefulWidget
, and override the build method. What the build method returns will determine the visual representation of your widget.
For the custom widget, let's create a to-do list item widget. It will have text on the left, and an icon on the right.
class TodoListItem extends StatelessWidget {
final bool isComplete;
final String label;
const TodoListItem({
Key? key,
this.isComplete = false,
this.label = 'TODO',
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Icon(isComplete ? Icons.check : Icons.close),
],
),
);
}
}
After that, we can use it anywhere in our app, like this:
Column(
children: const [
TodoListItem(label: 'Make coffee', isComplete: true),
TodoListItem(label: 'Drink it', isComplete: true),
TodoListItem(label: 'Sleep', isComplete: false),
],
),
The example above would result in something that looks like this:
Considerations
There are some important considerations to know when building widgets:
-
build()
method should be free os side effects. Meaning that the function should return a new widget tree, regardless of what was returned previously. All that heavy work is done for you by Flutter. - The
build()
method should also be fast and returns quickly. Heavy computation should always be done asynchronously.
Summary
Widgets are the building block of Flutter's UI. There are different widgets for various kinds of use. Some of them respond to user input, like pressing a button, while others make our app more accessible or dynamic.
We can also create and compose widgets of our own in really simple and declarative ways. There is still much more to cover, more complementary posts will be heading this way to hopefully fill in all the gaps :P
Learning more
As always, thanks for reading. Hopefully, you now have a better understanding of what Widgets are.
And remember to comment if you have any suggestions or something you would like me to cover in these posts.
< Previous Post ... Next Post >
Top comments (1)
In case you missed it, all the code mentioned in the post is available here