CH 3
CH 3
FLUTTER
53
54 Building User Interfaces with Flutter
1 Introduction to UI Design
At the heart of Flutter's UI design is the concept of widgets. In Flutter, everything you
see on the screen is a widget, including layout elements, buttons, and text. Widgets are
immutable and can be combined to create complex UIs. They follow a hierarchical structure,
where parent widgets contain child widgets to build the overall interface.
Flutter is unique in the way that the user interface is expressed. Developers use the same
Dart language to express an app's graphical user interface as well as the behavior. Table
3.1, copied from from the book "Beginning App Development with Flutter" for educational
purposes, shows a comparison between dierent frameworks in terms of languages used in
behaviour and UI expressions.
Table 3.1: Frameworks and Their Corresponding Languages for Behavior and UI
Similar to many other frameworks and languages, a Flutter application begins with a main
function. In Flutter, this main function invokes runApp(), which takes a single argument:
the root widget. While the root widget can be named anything, it must be a class that
extends StatelessWidget from Flutter.
Figure 3.1 shows an example of a root widget declared in a dart code.
Flutter's core widgets serve as the fundamental building blocks for everything we create,
with around 160 dierent widgets available as of the latest count. Keeping track of so many
widgets can be quite challenging.
Building User Interfaces with Flutter 55
https://docs.utter.dev/reference/widgets
Widgets can be categorized based on state conditions into two main types:
Stateless Widgets: These are used for parts of the UI that do not change over time.
They are ideal for static content such as text and images. A common example is the
Text widget used to display text on the screen.
Stateful Widgets: These are used for parts of the UI that can change dynamically.
They maintain state information that can be updated over time, such as user input
elds or animations. An example is the TextField widget, which updates its content
based on user interaction.
Flutter's ocial list of base Widgets includes 12 widgets that support a range
of common rendering options like input, layout, and text. These widgets include:
Assets, Images, and Icons: Manage assets, display images, and show icons.
Input: Take user input in addition to input widgets in Material components and
Cupertino.
Interaction Models: Respond to touch events and route users to dierent views.
Layout: Arrange other widgets in columns, rows, grids, and many other layouts.
Painting and Eects: These widgets apply visual eects to the children without
changing their layout, size, or position.
Styling: Manage the theme of your app, make your app responsive to screen sizes, or
add padding.
56 Building User Interfaces with Flutter
Layout widgets: Layout widgets oer extensive control over arranging elements
within our interface. They allow us to position widgets side by side or stack them
vertically, make them scrollable, enable wrapping, and manage the spacing around
widgets to ensure they don't appear cramped.
Navigation widgets: When your application features multiple scenes (or "screens,"
"pages," etc.), you'll need a method to transition between them. This is where Naviga-
tion widgets come into play. They manage how users move from one scene to another,
typically triggered by tapping a button. Navigation controls might also be found on a
tab bar or within a drawer that slides in from the left side of the screen.
Table3.2 shows examples of the mentioned 4 types. these examples are copied from the
book "Beginning App Development with Flutter" for educational purposes.
createState()
initState()
didChangeDependencies()
build()
didUpdateWidget()
setState()
deactivate()
dispose()
createState(): This method creates the state object for the widget. When we create a
stateful widget, our framework calls a createState() method and it must be overridden.
initState(): This method is called after the state object is created. It is used to initialise
the state of the widget.
build(): This method is called after the state object is initialised. It is used to build
the widget tree. This gets called each time the widget is rebuilt; this can happen after
initState, didChangeDependencies, didUpdateWidget, or when the state is changed via a
call to setState.
58 Building User Interfaces with Flutter
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
appBar: AppBar(
title: const Text("Lifecycle Demo"),
),
body: Container(
child: Column(
children: [
Text(_counter.toString()),
ElevatedButton(onPressed: _increment, child: const Text("Increment"))
],
),
),
);
}
@override
void didChangeDependencies() {
print("didChangeDependencies");
super.didChangeDependencies();
}
didUpdateWidget(): This method is called when the widget is updated with new prop-
erties. A typical case is when a parent passes some variable to the child widget via the
constructor.
@override
void didUpdateWidget(covariant MyPage oldWidget) {
print("didUpdateWidget");
super.didUpdateWidget(oldWidget);
}
deactivate(): This method is invoked when the State is removed from subtree A and
reinserted to subtree B with the use of a GlobalKey.
@override
void deactivate() {
print("deactivate");
super.deactivate();
}
Building User Interfaces with Flutter 59
dispose(): This method is called when the widget is about to be destroyed permanently.
It is used to release any resources used by the widget, such as closing network connections
or stopping animations.
@override
void dispose() {
print("dispose");
super.dispose();
}
3.1 AppBar
The AppBar widget provides a container that displays content and actions at the top of the
screen. It is commonly used to show titles, icons, and actions like search buttons or menus.
An app bar consists of a toolbar and potentially other widgets, such as a TabBar and a
FlexibleSpaceBar. App bars typically expose one or more common actions with IconButtons
which are optionally followed by a PopupMenuButton for less common operations (sometimes
called the "overow menu").
App bars are typically used in the Scaold.appBar property, which places the app bar as
a xed-height widget at the top of the screen. For a scrollable app bar, see SliverAppBar,
which embeds an AppBar in a sliver for use in a CustomScrollView.
60 Building User Interfaces with Flutter
The AppBar displays the toolbar widgets, leading, title, and actions, above the bottom
(if any). The bottom is usually used for a TabBar. If a exibleSpace widget is specied
then it is stacked behind the toolbar and the bottom widget. The following diagram shows
where each of these slots appears in the toolbar when the writing language is left-to-right
(e.g. English):
The leading widget is in the top left, the actions are in the top right, the title is between
them. The bottom is, naturally, at the bottom, and the exibleSpace is behind all of them.
If the leading widget is omitted, but the AppBar is in a Scaold with a Drawer, then
a button will be inserted to open the drawer. Otherwise, if the nearest Navigator has any
previous routes, a BackButton is inserted instead. This behavior can be turned o by setting
the automaticallyImplyLeading to false. In that case a null leading widget will result in the
middle/title widget
3.2 Column
The Column widget arranges its child widgets in a vertical layout. It's ideal for organizing
content in a linear, top-to-bottom ow. Child widgets inside the Column can be aligned
and stretched using various properties. It plays a signicant role in arranging the layout in
Flutter apps. To cause a child to expand to ll the available vertical space, wrap the child
in an Expanded widget.
The Column widget does not scroll (and in general it is considered an error to have more
children in a Column than will t in the available room). If you have a line of widgets and
want them to be able to scroll if there is insucient room, consider using a ListView.
If you only have one child, then consider using Align or Center to position the child.
3.3 Container
Container is a versatile widget that combines common painting, positioning, and sizing. It
can be used to add padding, margins, borders, or backgrounds to its child widgets.
A container rst surrounds the child with padding (inated by any borders present in the
decoration) and then applies additional constraints to the padded extent (incorporating the
width and height as constraints, if either is non-null). The container is then surrounded by
additional empty space described from the margin.
During painting, the container rst applies the given transform, then paints the decoration
to ll the padded extent, then it paints the child, and nally paints the foregroundDecoration,
also lling the padded extent.
Containers with no children try to be as big as possible unless the incoming constraints are
unbounded, in which case they try to be as small as possible. Containers with children size
themselves to their children. The width, height, and constraints arguments to the constructor
override this.
By default, containers return false for all hit tests. If the color property is specied,
the hit testing is handled by ColoredBox, which always returns true. If the decoration or
foregroundDecoration properties are specied, hit testing is handled by Decoration.hitTest.
Building User Interfaces with Flutter 61
If the widget has no child and no alignment, but a height, width, or constraints are
provided, then the Container tries to be as small as possible given the combination of
those constraints and the parent's constraints.
If the widget has an alignment, and the parent provides unbounded constraints, then
the Container tries to size itself around the child.
If the widget has an alignment, and the parent provides bounded constraints, then the
Container tries to expand to t the parent, and then positions the child within itself
according to the alignment.
Otherwise, if the widget has a child but no height, no width, no constraints, and no
alignment, the Container passes the constraints from the parent to the child and sizes
itself to match the child.
The margin and padding properties also aect the layout, as described in the docu-
mentation for those properties. Their eects merely augment the rules described above.
The decoration can implicitly increase the padding (e.g., borders in a BoxDecoration
contribute to the padding); see Decoration.padding.
3.4 ElevatedButton
The ElevatedButton widget represents a Material Design elevated button. It is a lled
button whose material elevates when pressed, providing tactile feedback to the user.
Use elevated buttons to add dimension to otherwise mostly at layouts, e.g. in long busy
lists of content, or in wide spaces. Avoid using elevated buttons on already-elevated content
such as dialogs or cards.
An elevated button is a label child displayed on a Material widget whose Material.elevation
increases when the button is pressed. The label's Text and Icon widgets are displayed
in style's ButtonStyle.foregroundColor and the button's lled background is the Button-
Style.backgroundColor.
The elevated button's default style is dened by defaultStyleOf. The style of this elevated
button can be overridden with its style parameter. The style of all elevated buttons in a
subtree can be overridden with the ElevatedButtonTheme, and the style of all of the elevated
buttons in an app can be overridden with the Theme's ThemeData.elevatedButtonTheme
property.
The static styleFrom method is a convenient way to create a elevated button ButtonStyle
from simple values.
If onPressed and onLongPress callbacks are null, then the button will be disabled.
62 Building User Interfaces with Flutter
3.5 FlutterLogo
The FlutterLogo widget displays the Flutter logo. It can be used in dierent parts of the
app to signify that a widget or a section is part of a Flutter app.
3.6 Icon
The Icon widget displays a Material Design icon. Icons are visual elements that represent
actions or concepts, and they can be customized in size, color, and appearance.
3.7 Image
The Image widget is used to display images in the app. It supports various image formats
and allows customization such as scaling and tting into the layout.
3.8 Placeholder
The Placeholder widget draws a box that represents where other widgets will eventually be
placed. This is particularly useful when designing a layout and some elements are not yet
dened.
3.9 Row
Similar to Column, the Row widget arranges its child widgets in a horizontal layout. It's
helpful for displaying multiple widgets side by side.
3.10 Scaold
The Scaffold widget is the backbone of any Material Design app layout. It provides APIs
to show drawers, snack bars, bottom sheets, and more. It's a versatile widget that makes it
easy to build a structure that adheres to Material Design principles.
Fig. 3.3 shows an example of a Scaold with an appBar, a body and FloatingActionButton.
The body is a Text placed in a Center in order to center the text within the Scaold. The
FloatingActionButton is connected to a callback that increments a counter.
3.11 Text
The Text widget is one of the most commonly used widgets in Flutter. It allows you to
display a run of text in a customizable style. You can adjust properties such as font size,
color, and weight. The style argument is optional. When omitted, the text will use the style
from the closest enclosing DefaultTextStyle. If the given style's TextStyle.inherit property is
true (the default), the given style will be merged with the closest enclosing DefaultTextStyle.
This merging behavior is useful, for example, to make the text bold while using the default
font family and size.
Key characteristics
The UI is described as a series of declarations (i.e., widgets in Flutter).
UI updates are automatic: when the state changes, the framework re-renders the UI
to match the new state.
The developer doesn't need to manage the exact steps to update the UI. Instead, they
describe the nal state of the UI, and the framework handles transitions.
Example
In Flutter, you dene the UI by returning widgets inside a build() method. When the state
changes, Flutter calls build() again to rebuild the UI based on the new state.
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: () => setState(() {
counter++;
}),
child: Text('Increment'),
),
],
);
}
64 Building User Interfaces with Flutter
Key characteristics
The UI is updated through a series of commands or instructions that directly manip-
ulate UI elements.
Developers need to manually keep track of the UI state and explicitly tell the framework
when and how to update it.
This style requires ne-grained control over UI changes, but it can also lead to more
complex code as developers must manage the ow of state and UI updates themselves.
Example
In an imperative framework like Android's findViewById with setText, you must explicitly
update the UI when an event occurs.
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
counter++;
textView.setText("Counter: " + counter);
}
});
View B, which is nested inside View A, transitions from containing two views, c1 and
c2, to containing only view c3.
In an imperative approach, you would typically reference ViewB's owner, retrieve the
instance of b using selectors such as findViewById or similar, and then modify it directly.
You would also implicitly invalidate it. For instance:
Building User Interfaces with Flutter 65
Moreover, you might need to replicate this conguration in the constructor of ViewB, as
the source of truth for the UI might outlive the specic instance of b.
In contrast, the declarative style operates dierently. UI components (like Flutter's
Widgets) are immutable and serve as lightweight "blueprints." To modify the UI, a widget
initiates a rebuild (usually by invoking setState() on StatefulWidgets in Flutter) and
constructs a new subtree of Widgets. Here's how this is done:
Rather than mutating the previous instance b when the UI changes, Flutter creates new
Widget instances. The framework manages many of the traditional UI object responsibilities
(like maintaining layout state) behind the scenes through RenderObjects. These Render-
Objects persist between frames, while Flutter's lightweight Widgets inform the framework
to adjust the RenderObjects during state transitions. Flutter handles the remaining details
internally.
In Summary:
Declarative (Flutter): You declare what the UI should look like based on the current
state, and the framework handles the UI updates automatically.
Imperative: You directly control how the UI updates step-by-step, explicitly manag-
ing the changes and UI state transitions.
In Flutter it's okay to rebuild parts of your UI from scratch instead of mod-
ifying it. Flutter is fast enough to do that, even on every frame if needed.
Flutter is declarative. This means that Flutter builds its user interface to
reect the current state of your app. When the state of your app changes
(for example, the user ips a switch in the settings screen), you change the
state, and that triggers a redraw of the user interface. There is no imperative
changing of the UI itself (like widget.setText)you change the state, and the
UI rebuilds from scratch.
66 Building User Interfaces with Flutter
5.5 Navigation
Flutter Scaold: Navigation in Flutter involves pushing and popping Scaffold wid-
gets using Navigator.push() and Navigator.pop().
Single-child Layout Widgets: These widgets allow you to place one widget inside
another and control its size and position.
Multi-child Layout Widgets: These widgets enable you to manage and arrange
multiple child widgets within a parent widget.
Understanding how to use these layout widgets eectively is crucial for creating responsive
and well-structured UIs in Flutter applications.
7.1 Container
The Container widget is one of the most versatile layout widgets in Flutter. It allows you
to specify the width, height, padding, margin, and decoration for a single child widget. The
Container can also be used to apply constraints and align the child.
Container(
width: 100,
height: 100,
padding: EdgeInsets.all(10),
margin: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text('Single Child'),
)
7.2 Align
The Align widget positions its child within itself based on the alignment property. This
widget is useful for aligning a single widget to a specic part of its parent.
Align(
alignment: Alignment.topRight,
child: Text('Aligned to top-right'),
)
7.3 Center
The Center widget centers its child within itself. It is a simple way to place a widget in the
center of its parent.
Center(
child: Text('Centered Text'),
)
8.1 Column
The Column widget arranges its children vertically in a column. It is useful for stacking
widgets on top of each other.
Column(
children: <Widget>[
Text('First Child'),
Text('Second Child'),
Text('Third Child'),
],
)
8.2 Row
The Row widget arranges its children horizontally in a row. It is used for placing widgets side
by side.
Row(
children: <Widget>[
Text('Left Child'),
Text('Center Child'),
Text('Right Child'),
],
)
8.3 Stack
The Stack widget allows you to overlay multiple children on top of each other. This is useful
for creating complex designs where widgets need to be layered.
Stack(
children: <Widget>[
Container(color: Colors.red, width: 100, height: 100),
Positioned(
top: 20,
left: 20,
child: Container(color: Colors.blue, width: 50, height: 50),
),
],
)
8.4 ListView
The ListView widget provides a scrollable list of widgets. It is used when you have a large
number of items that need to be displayed vertically.
70 Building User Interfaces with Flutter
ListView(
children: <Widget>[
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
)
9 Aligning Widgets
You control how a row or column aligns its children using the mainAxisAlignment and
crossAxisAlignment properties. For a row, the main axis runs horizontally and the cross
axis runs vertically. For a column, the main axis runs vertically and the cross axis runs
horizontally.
Start by identifying the larger components of your UI. For example, you might have an
image, a title, buttons, and a description, all arranged in a column. Once you've diagrammed
each row or section of your layout, think about how you will implement it in code.
Next, consider how you will organize your code:
Will you write everything in a single class, or break it down into multiple classes, each
handling dierent parts of the layout?
Building User Interfaces with Flutter 71
Following Flutter's best practices, it's recommended to create individual classes (or Wid-
gets) for each part of your layout. This approach leverages Flutter's ecient rendering
system, where only the smallest part of the UI that changes gets updated. For instance,
if only the text in a Text widget changes, Flutter will redraw just that text. This is why
the framework follows the principle of "everything is a widget", ensuring that updates are as
minimal and ecient as possible.
Push and Pop: Navigator works on a stack of routes, allowing you to push a new
route onto the stack or pop it o to return to the previous screen.
Examples:
72 Building User Interfaces with Flutter
Good for: Basic navigation, such as transitioning between screens using a back button
or passing data between screens.
Pros:
Cons:
Less control over advanced navigation ows (e.g., deep linking or custom animations).
Not well-suited for managing complex route structures, such as nested navigation.
Good for: Apps that require advanced routing, such as deep linking, nested navigation,
URL-based navigation (web apps), or complex route management.
Pros:
Ideal for handling deep linking, nested routes, and URL parsing (useful for web appli-
cations).
Cons:
Imperative vs Declarative: Navigator is imperative, where you push and pop routes
manually, whereas Router follows a declarative model where navigation state is dened
and Flutter updates the UI accordingly.
Web and Deep Linking: Router is better suited for web applications that require
deep linking or URL-based routing, while Navigator suces for mobile apps without
these advanced requirements.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Text(data),
);
}
}
return Scaffold(
appBar: AppBar(),
body: Text(data),
);
}
}
void increment() {
_count++;
notifyListeners();
}
}
],
),
);
}
}
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
Building User Interfaces with Flutter 77
// Stream of data
Stream<String> dataStream = Stream<String>.periodic(
Duration(seconds: 1),
(count) => 'Data $count',
);
Refhttps://docs.utter.dev/cookbook/navigation/passing-data
78 Building User Interfaces with Flutter
2. Modify the following dart code to change the text of the ElevatedButton in the following
utter app based on whether the _count value is odd or even. When the _count value
is odd, the text should appear as "Odd <number>". When the text value is even, the
text should appear as "Even <number>". Fig. 3.6 shows a screenshot of the utter
app after the update. Identify the line numbers where changes will be applied.
1 class _ElevatedButtonExampleState extends
State < ElevatedButtonExample > {
2 int _count = 0;
3
4 @override
5 Widget build ( BuildContext context ) {
6 final ButtonStyle style =
7 ElevatedButton . styleFrom ( textStyle : const
TextStyle ( fontSize : 20) ) ;
8
9 return Center (
Building User Interfaces with Flutter 79
10 child : Column (
11 mainAxisSize : MainAxisSize . min ,
12 children : < Widget >[
13 ElevatedButton (
14 style : style ,
15 onPressed : null ,
16 child : const Text ( ' Disabled ') ,
17 ),
18 const SizedBox ( height : 30) ,
19 ElevatedButton (
20 style : style ,
21 onPressed : () = > setState (() = > _count ++) ,
22 child : const Text ( ' Enabled ') ,
23 ),
24 ],
25 ),
26 );
27 }
28 }
Use the following table template to specify the modications made to the Dart code.
Line Number Code Modications/Notes
5. Describe the purpose of the static styleFrom method in the context of ElevatedButton.
(a) If the widget has no child, no height, no width, no constraints, and the parent
provides unbounded constraints.
(b) If the widget has no child and no alignment, but a height, width, or constraints
are provided.
80 Building User Interfaces with Flutter