Creating widgets
Learning objectives
- You know the basic idea of how Flutter applications are composed.
- You know how to create Flutter widgets.
Applications are composed of widgets
Applications constructed with Flutter are composed of widgets. In the previous part, we looked into building a "Hello world!" application, which looked as follows.
When we look at the application and how it looks like when we run it, we see that some of the widgets are concretely visible to the user (e.g. the Text widget), while some widgets are not (e.g. the Center widget).
This observation highlights how Flutter applications are composed. Applications are composed of widgets, which are used to define most if not all aspects of an application. These aspects include: (1) the layout of the user interface, (2) the components shown in the interface, (3) the styles used in the interface, and (4) the interactive functionality of the interface.
Creating widgets
Widgets are created by inheriting (extending) a class called StatelessWidget. The StatelessWidget class has a method build
, which the child class overrides. The method build
must return an object that extends the Widget class (effectively any Flutter widget).
The following example demonstrates how a widget HelloWorldWidget
used for showing the text "Hello world!" is created from the StatelessWidget and used as a part of an application.
Stateless widgets to be exact..
In this part, when creating widgets, we are working with stateless widgets. That is, the widgets do not contain a state that could change and consequently would refresh the shown content.
Widget properties
The defined stateless widgets are classes similar to other classes, with the exception that they inherit functionality from StatelessWidget. Thus, defining properties to widgets is straightforward, pending that we follow expectations set in StatelessWidget.
There are three key expectations: (1) when defining constructors, we use named arguments with default values (or required named arguments); (2) we define a method Widget build(BuildContext context)
that returns a widget, and (3) if we define properties, we define them using the final
keyword that states that their value cannot be re-set once set,
The following example shows a GreetingWidget
that takes a String greeting
as a named constructor argument and uses it in the widget. By default, the greeting is "Hello!". The constructor of GreetingWidget
is also set as constant (using const
).
Following the same idea, a widget can also contain an instance of a class. The following example shows a widget that shows the name of a person. Properties cannot be null due null safety and thus we use required
when defining the named parameter for the constructor.
A peek at build context
When we create a new widget, we extend the StatelessWidget
class and define a method Widget build(BuildContext context)
. In the previous examples, we have simply returned a widget from the build
-method, like in the following example.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(home: Scaffold(body: HelloWorldWidget())));
}
class HelloWorldWidget extends StatelessWidget {
Widget build(BuildContext context) {
return const Text("Hello world!");
}
}
The method build
takes a BuildContext object as an argument. The BuildContext provides a handle to the present widget in the widget tree, and is managed by Flutter.
The widget tree represents the present Flutter application widgets as a tree.
The BuildContext is injected to widgets being created by Flutter; through the BuildContext, we can infer properties of the widget. For example, the method visitAncestorElements allows us to iterate parent nodes in the widget tree, while the method visitChildElements allows us to iterate child nodes in the widget tree.
As it is managed by Flutter, we mostly can ignore its presence.