0% found this document useful (0 votes)
55 views30 pages

CH 3

Uploaded by

Habiba Yasser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
55 views30 pages

CH 3

Uploaded by

Habiba Yasser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 30

CHAPTER 3

BUILDING USER INTERFACES WITH

FLUTTER

Flutter is a powerful framework for build-


1 Introduction to UI Design . . 54
ing natively compiled applications for mo-
2 What are Widget Lifecycle
bile, web, and desktop from a single code-
Methods . . . . . . . . . . . . 57
base. One of its most notable features is its
3 Introduction to Basic Flutter
capability to create rich and highly customiz-
Widgets . . . . . . . . . . . . 59
able user interfaces (UIs). Understanding UI
4 UI Styles: Declarative style vs
design in Flutter involves grasping its core
Imperative style . . . . . . . . 63
concepts, widgets, and layout mechanisms.
5 Comparison between Flut-
ter's Scaold and Android's
Native Activities/Fragments . 66
6 Flutter Layout Widgets . . . 67
7 Single-child Layout Widgets . 67
8 Multi-child Layout Widgets . 68
9 Aligning Widgets . . . . . . . 70
10 Designing Your Layout in
Flutter . . . . . . . . . . . . . 70
11 CodeLab: Creating layouts in
Flutter . . . . . . . . . . . . . 71
12 Navigation and routing . . . . 71
13 Options to Pass Data Be-
tween Screens in Flutter . . . 73
14 Chapter 3 Review Questions . 78

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.

Framework Behavior expressed in UI expressed in


Xamarin C# XAML
React Native JavaScript JSX
NativeScript JavaScript XML
Flutter Dart Dart

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.

Figure 3.1: An example of a root widget

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

For the full index of all widgets, please refer to

https://docs.utter.dev/reference/widgets

1.1 Categorization of Widgets


Flutter provides a vast library of pre-designed widgets that help developers construct user
interfaces quickly. These widgets can be categorized based on several factors.

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:

ˆ Accessibility: Make your app accessible.

ˆ Animation and Motion: Bring animations to your app.

ˆ Assets, Images, and Icons: Manage assets, display images, and show icons.

ˆ Async: Widgets supporting async patterns in your Flutter apps.

ˆ Basics: Widgets to know before building your rst Flutter app.

ˆ 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.

ˆ Scrolling: Scroll multiple widgets as children of the parent.

ˆ Styling: Manage the theme of your app, make your app responsive to screen sizes, or
add padding.
56 Building User Interfaces with Flutter

ˆ Text: Display and style text.

A third categorization based on common functions is listed by the author of


the book "Beginning App Development with Flutter" as follows:
ˆ Value widgets: Some widgets are designed to hold values, which may come from
local storage, an online service, or user input. These widgets are essential for showing
information to users and capturing input from them. For example, the Text widget
displays a small amount of text, while the Image widget shows pictures in formats such
as .jpg or .png.

ˆ 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.

ˆ Other widgets: a miscellaneous category

Table3.2 shows examples of the mentioned 4 types. these examples are copied from the
book "Beginning App Development with Flutter" for educational purposes.

Widget Type Example Widget


Value Widgets Checkbox, CircularProgressIndicator, Date & Time Pickers, DataTable,
DropdownButton, FlatButton, FloatingActionButton, FlutterLogo,
Form, FormField, Icon, IconButton, Image, LinearProgressIndicator,
PopupMenuButton, Radio, RaisedButton, RawImage, RefreshIndicator,
RichText, Slider, Switch, Text, TextField, Tooltip
Layout Widgets Align, AppBar, AspectRatio, Baseline, BottomSheet, ButtonBar, Card,
Column, ConstrainedBox, Container, CustomMultiChildLayout, Divider,
Expanded, ExpansionPanel, FittedBox, Flow, FractionallySizedBox,
GridView, IndexedStack, IntrinsicHeight, IntrinsicWidth, LayoutBuilder,
LimitedBox, ListBody, ListTile, ListView, MediaQuery, NestedScrollView,
OverowBox, Padding, PageView, Placeholder, Row, Scaold, Center
Scrollable, Scrollbar, SingleChildScrollView, SizedBox, SizedOverowBox,
SliverAppBar, SnackBar, Stack, Table, Wrap
Navigation Widgets AlertDialog, BottomNavigationBar, Drawer, MaterialApp, Navigator,
SimpleDialog, TabBar, TabBarView
Other Widgets GestureDetector, Dismissible, Cupertino Theme, Transitions, Transforms

Table 3.2: Types of Widgets and Examples


Building User Interfaces with Flutter 57

2 What are Widget Lifecycle Methods


The widget lifecycle is a sequence of events that occur when a widget is created, updated,
or destroyed. Understanding the widget lifecycle is important for writing ecient Flutter
applications.

ˆ 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.

class MyPage extends StatefulWidget {


@override
_MyPageState createState() => _MyPageState();
}

initState(): This method is called after the state object is created. It is used to initialise
the state of the widget.

late int _counter;


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

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"))
],
),
),
);
}

didChangeDependencies(): This method is called immediately after initState and when


the dependency of the State object changes via InheritedWidget.

@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 Introduction to Basic Flutter Widgets


Before building your rst Flutter app, it's essential to become familiar with some fundamental
widgets. Flutter is built around the concept of composable widgets, which allow developers
to create complex UIs by nesting and combining simple building blocks. Below are some of the
key widgets you should know. This section is copied from https://docs.utter.dev/ui/widgets/basics
for educational purpose.

3.1 AppBar

Figure 3.2: An example of a layout update

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

Behavior of the Container Widget


ˆ If the widget has no child, no height, no width, no constraints, and the parent provides
unbounded constraints, then the Container tries to size itself as small as possible.

ˆ 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 no child, no height, no width, no constraints, and no alignment,


but the parent provides bounded constraints, then the Container expands to t the
constraints provided by the parent.

ˆ 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.

Please refer to https://docs.utter.dev/ui/widgets/basics for further details


Building User Interfaces with Flutter 63

4 UI Styles: Declarative style vs Imperative style


The conceptual dierence between the declarative style used by Flutter and the imper-
ative style used by many other UI frameworks lies in how they approach UI updates and
state management.

4.1 Declarative Style (Flutter)


In a declarative UI framework, the developer focuses on what the UI should look like at
any given moment based on the current state. The UI is essentially a function of the state:
every time the state changes, the framework rebuilds the UI to reect those changes. This
approach emphasizes simplicity and clarity in describing the desired result.

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

4.2 Imperative Style (Traditional UI Frameworks)


In an imperative UI framework, the developer explicitly instructs the framework on how
to update the UI in response to specic actions or state changes. The focus is on managing
the sequence of operations that modify the UI, with the developer directly controlling each
UI update step.

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.

TextView textView = findViewById(R.id.text_view);


Button button = findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
counter++;
textView.setText("Counter: " + counter);
}
});

4.3 How to change UI in a declarative framework


Figure 3.4 shows an example of a layout update. This example is copied from
"https://docs.utter.dev/get-started/utter-for/declarative" for educational purposes.

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

Listing 3.1: Imperative Style


b . setColor ( red ) ;
b . clearChildren () ;
ViewC c3 = new ViewC (...) ;
b . add ( c3 ) ;

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:

Listing 3.2: Declarative Style


return ViewB (
color : red ,
child : const ViewC () ,
);

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.

As mentioned in Flutter documents at "https://docs.utter.dev/data-and-backend/state-


mgmt/declarative":

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 Comparison between Flutter's Scaold and Android's


Native Activities/Fragments
The Scaffold widget in Flutter and Android's native Activity and Fragment serve similar
purposes in managing the structure and layout of a screen. Below is a comparison to highlight
their similarities.

5.1 UI Structure Management


ˆ Flutter Scaold: The Scaffold widget provides the basic structure for implementing
Material Design layouts. It includes support for app bars, oating action buttons,
navigation drawers, and other common UI components, serving as the foundational
layout container for a screen.

ˆ Android Activity/Fragment: An Activity represents a single, focused screen in


a native Android app, while a Fragment is a reusable portion of the UI within an
Activity. Both manage the UI structure for a screen and handle common UI compo-
nents like toolbars, navigation drawers, and oating action buttons.

5.2 Handling Multiple Components


ˆ Flutter Scaold: A Scaffold manages various UI elements such as app bars, body
content, bottom navigation bars, and oating action buttons. It organizes how these
components t together on the screen.

ˆ Android Activity/Fragment: Activities and fragments manage multiple UI com-


ponents like toolbars and content views. Layouts are typically dened using XML
les. Fragments allow for modular UI components within activities, similar to how a
Scaffold can contain widgets.

5.3 Layout and Design


ˆ Flutter Scaold: Flutter uses a declarative layout system, where developers declare
the UI using widgets. The Scaffold integrates Material Design principles, making it
easier to create consistent UIs.

ˆ Android Activity/Fragment: Android's layout system is imperative and typically


dened using XML. Native Material Design components are supported, but developers
must manually add and congure them in their layouts.

5.4 State Management


ˆ Flutter Scaold: State in Flutter is managed using StatefulWidgets. The Scaffold
can trigger UI updates by calling setState(), which rebuilds the widget tree.
Building User Interfaces with Flutter 67

ˆ Android Activity/Fragment: State management in Android is handled through life-


cycle callbacks like onCreate() and onResume() in an Activity, and onViewCreated()
in a Fragment. These callbacks manage state transitions, view hierarchy, and updates.

5.5 Navigation
ˆ Flutter Scaold: Navigation in Flutter involves pushing and popping Scaffold wid-
gets using Navigator.push() and Navigator.pop().

ˆ Android Activity/Fragment: In Android, navigation involves starting a new Activity


or replacing Fragments. Android also uses a stack-based system for managing naviga-
tion with the back stack.

In summary, Flutter's Scaffold is analogous to Android's Activity or Fragment as they


provide the foundational layout structure for a screen, manage UI components, and handle
state and navigation. The key dierence lies in Flutter's declarative UI system compared to
Android's imperative, XML-based layout system.

6 Flutter Layout Widgets


In Flutter, layout widgets are fundamental building blocks for designing and organizing the
user interface of an application. They help in arranging and positioning widgets on the screen
in various ways, providing exibility and control over the layout. Flutter's layout system is
composed of both single-child and multi-child layout widgets, each serving dierent purposes
and oering unique capabilities for UI design.
Layout widgets can be broadly classied into two categories:

ˆ 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 Single-child Layout Widgets


Single-child layout widgets are designed to contain only one child widget. They oer various
ways to position and size the single child within their own bounds. Some common single-child
layout widgets include:
68 Building User Interfaces with Flutter

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 Multi-child Layout Widgets


Multi-child layout widgets manage and arrange multiple child widgets within their parent.
They provide various ways to control the layout and positioning of children. Some common
multi-child layout widgets include:
Building User Interfaces with Flutter 69

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.

10 Designing Your Layout in Flutter


This section follows the instructions in the Flutter ocial document at
https://docs.utter.dev/ui/layout/tutorial
When designing a user interface in Flutter, it's important to plan how you will position the
components on the screen. Your layout is essentially the end result of positioning various UI
elements, so putting thought into it early on can streamline your coding process. Visualizing
where components will go before writing any code is a smart move and can prevent errors
down the lineit's akin to the saying: Measure twice, cut once.
You can use any method that works for you to plan your layout, whether that's an interface
design tool or simply a pencil and paper. The key is to gure out the placement of elements
on your screen in advance.
To break down the layout into its core elements, ask yourself the following questions:

ˆ Can you identify the rows and columns in the design?

ˆ Does your layout involve a grid structure?

ˆ Are there any overlapping elements?

ˆ Does the UI require tabs?

ˆ What needs alignment, padding, or borders?

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.

11 CodeLab: Creating layouts in Flutter


https://codelabs.developers.google.com/codelabs/utter-codelab-rst

11.1 Tips and Tricks


ˆ For wrapping and unwrapping Flutter widgets in Android Studio, press alt+enter on
the widget and choose what you want!

ˆ SizedBox widget can be use in between two widget to add space.

12 Navigation and routing


Ref: https://docs.utter.dev/cookbook/navigation/navigation-basics
Ref: https://docs.utter.dev/ui/navigation
Most apps consist of multiple screens to present dierent types of information. For instance,
an app might feature a screen that shows a list of products, and when a user taps on a
product image, a new screen opens to display the product's details. In Flutter, screens or
pages are referred to as routes. For the rest of this section, we will use the term routes. In
Android, a route is comparable to an Activity, while in iOS, it is similar to a ViewController.
In Flutter, however, a route is simply a widget.
Dierence Between Using Navigation and Router for Navigating Between Screens in Flut-
ter
In Flutter, both navigation and router are used to move between dierent screens or
routes, but they have distinct purposes and provide dierent levels of control:

12.1 Navigation (Navigator)


ˆ Basic Use: The Navigator class is a simpler and traditional method for handling nav-
igation between screens. It's user-friendly and works well for apps with straightforward
navigation requirements.

ˆ 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

 Navigator.push(context, MaterialPageRoute(builder: (context) => Screen2()));


(to navigate to a new screen)
 Navigator.pop(context); (to return to the previous screen)

ˆ Good for: Basic navigation, such as transitioning between screens using a back button
or passing data between screens.

Pros:

ˆ Simple to use and implement for basic navigation.

ˆ Suitable for apps with limited navigation complexity.

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.

12.2 Router (Router API)


ˆ Advanced Use: The Router class provides a more exible and advanced way of
managing navigation. It follows a declarative approach, which ts well with Flutter's
declarative UI model.

ˆ Declarative Routing: Unlike Navigator, which uses imperative routing (explicit


push/pop), Router allows you to dene the navigation state declaratively, enabling
more complex navigation ows.

ˆ Good for: Apps that require advanced routing, such as deep linking, nested navigation,
URL-based navigation (web apps), or complex route management.

Pros:

ˆ Oers more power and exibility than Navigator.

ˆ Ideal for handling deep linking, nested routes, and URL parsing (useful for web appli-
cations).

ˆ Allows external management of the navigation state, improving separation of concerns.

Cons:

ˆ More complex setup and boilerplate code.

ˆ Requires a deeper understanding of navigation concepts.


Building User Interfaces with Flutter 73

12.3 Key Dierences


ˆ Ease of Use: Navigator is simpler and better suited for basic navigation, while Router
oers more control and is ideal for complex navigation scenarios.

ˆ 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.

13 Options to Pass Data Between Screens in Flutter


In Flutter, there are several ways to pass data between screens, depending on the complexity
of your app and how you want to manage the data. Below are the dierent methods:

13.1 Using Constructor Arguments


Description: This is the simplest method, where data is passed through the constructor of
the widget you're navigating to. Use Case: Suitable for small apps where only limited data
needs to be passed, like from a list to a detail screen.

class DetailScreen extends StatelessWidget {


final String data;
DetailScreen({required this.data});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Text(data),
);
}
}

// Navigating and passing data


Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(data: 'Hello World')),
);
74 Building User Interfaces with Flutter

13.2 Using Navigator.push and Navigator.pop


Description: Pass data back to the previous screen using Navigator.pop, and retrieve
it with Navigator.push. Use Case: Useful when selecting data on a second screen and
passing it back to the rst screen.

// Second screen returning data


class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Selected Data');
},
child: Text('Select Data'),
),
);
}
}

// Navigating and retrieving data


final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
print(result); // Outputs: Selected Data

13.3 Using Named Routes with Arguments


Description: Data can be passed between named routes using the arguments property.
Use Case: Ideal for structured navigation where you want to pass data via named routes.
// Navigating with arguments
Navigator.pushNamed(
context,
'/details',
arguments: 'Passed Data',
);

// Retrieving data in the new screen


class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String data = ModalRoute.of(context)!.settings.arguments as String;
Building User Interfaces with Flutter 75

return Scaffold(
appBar: AppBar(),
body: Text(data),
);
}
}

13.4 Using State Management Solutions


Description: Use state management tools like Provider, Riverpod, Bloc, or GetX to man-
age and share data across multiple screens. Use Case: Best for complex apps where data
needs to be shared across multiple components.

// ChangeNotifier to hold shared data


class Counter extends ChangeNotifier {
int _count = 0;
int get count => _count;

void increment() {
_count++;
notifyListeners();
}
}

// Providing the Counter globally


void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}

// Accessing shared data


class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Text('Count: ${counter.count}'),
ElevatedButton(onPressed: counter.increment, child: Text('Increment')),
76 Building User Interfaces with Flutter

],
),
);
}
}

13.5 Using Global Variables or Singletons


Description: Store data in a singleton class or global variable to share across screens. Use
Case: Suitable for small apps where a single instance of data needs to be accessed across
multiple screens.

// Singleton class to hold shared data


class DataStore {
static final DataStore _instance = DataStore._internal();
String sharedData = 'Initial Data';

factory DataStore() => _instance;


DataStore._internal();
}

// Accessing shared data


DataStore store = DataStore();
print(store.sharedData); // Outputs: Initial Data

13.6 Using InheritedWidget or InheritedModel


Description: InheritedWidget allows you to pass data down the widget tree and access it
from any descendant widget. Use Case: Useful for propagating data down the widget tree
without passing it through constructors.

// InheritedWidget to share data


class MyInheritedWidget extends InheritedWidget {
final String data;

MyInheritedWidget({required this.data, required Widget child})


: super(child: child);

static MyInheritedWidget? of(BuildContext context) {


return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}

@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
Building User Interfaces with Flutter 77

// Accessing inherited data


class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final inheritedData = MyInheritedWidget.of(context)?.data;
return Text(inheritedData ?? 'No Data');
}
}

13.7 Using Streams


Description: Streams allow real-time updates to be passed between components. Use
Case: Ideal for cases where you need live updates across multiple screens, such as in a chat
or notications feature.

// Stream of data
Stream<String> dataStream = Stream<String>.periodic(
Duration(seconds: 1),
(count) => 'Data $count',
);

// Displaying stream data


class StreamScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: StreamBuilder<String>(
stream: dataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
}
return CircularProgressIndicator();
},
),
);
}
}

Refhttps://docs.utter.dev/cookbook/navigation/passing-data
78 Building User Interfaces with Flutter

13.8 Navigate to the new screen using Navigator.push and Navi-


gator.pushNamed

Aspect Navigator.push Navigator.pushNamed


Route Denition Uses MaterialPageRoute to dy- Uses predened route names
namically dene the route. specied in the MaterialApp
routes table.
Flexibility Highly exible for dynamic rout- Simplies navigation with prede-
ing and custom route transitions ned routes; less exible for cus-
or animations. tom transitions.
Scalability Best for small apps or where Ideal for large apps with struc-
routes need to be dened dynam- tured and organized navigation
ically. paths.
Arguments Passing Data is passed directly through Data is passed via the arguments
the screen's constructor. property, retrievable using
ModalRoute.
Use Case Smaller, dynamic apps needing Larger apps with predened nav-
custom route handling. igation structure, deep linking, or
web-based navigation.

Table 3.3: Comparison between Navigator.push and Navigator.pushNamed in Flutter.

14 Chapter 3 Review Questions


1. Describe the conceptual dierence between the declarative style used by Flutter, and
the imperative style used by many other UI frameworks.

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

3. Explain how you can customize the style of a single ElevatedButton.

4. What happens to an ElevatedButton if both the onPressed and onLongPress call-


backs are set to null?

5. Describe the purpose of the static styleFrom method in the context of ElevatedButton.

6. Explain the behavior of the Container Widget in the following scenarios

(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

Figure 3.3: An example of a layout update


Building User Interfaces with Flutter 81

Figure 3.4: An example of a layout update

Figure 3.5: The MainAxisAlignment and CrossAxisAlignment enums oer a variety of


constants for controlling alignment. Ref:docs.utter.dev
82 Building User Interfaces with Flutter

Figure 3.6: A screenshot of the utter app after the update

You might also like