Application Development Using Flutter and Dart_ACE-INTL-đã gộp
Application Development Using Flutter and Dart_ACE-INTL-đã gộp
Learner’s Guide
© 2022 Aptech Limited
No part of this book may be reproduced or copied in any form or by any means – graphic, electronic
or mechanical, including photocopying, recording, taping, or storing in information retrieval system or
sent or transferred without the prior written permission of copyright owner Aptech Limited.
APTECH LIMITED
Contact E-mail: ov-support@onlinevarsity.com
This book is the result of a concentrated effort of the Design Team, which is continuously striving to
bring you the best and the latest in Information Technology. The process of design has been a part of
the ISO 9001 certification for Aptech-IT Division, Education Support Services. As part of Aptech’s
quality drive, this team does intensive research and curriculum enrichment to keep it in line with
industry trends.
Design Team
Table of Contents
Sessions
Learning Objectives
Session Goals:
The session introduces the Flutter framework and explain its features. Further,
the session lists the advantages of creating an application with Flutter. The
session finally concludes by elaborating the steps involved in installing Flutter
in Android studio.
What is Flutter?
A ‘tool’ that allows building native cross-platform (for Android or iOS devices)
applications with one programming language and codebase. Figure 1.1 shows
how Flutter can be utilized for developing applications across several
platforms.
Flutter uses Dart which is focused on frontend, that is, User Interface (UI)
development. Dart is developed by Google and is an Object-Oriented
programming language. The syntax for Dart is a mixture of JavaScript, Java, and
C#.
Easy Testing: App Testing is very easy in Flutter because of Flutter unit
testing functionality.
The Foundation library contains the required packages for writing full-fledged
Flutter applications. Since the Flutter engine being Portable, it can be utilized
for high-quality mobile apps. Flutter UI is composed of Widgets which are
classified as per design languages of Google and iOS. They are Material Design
and Cupertino Style. Figure 1.2 shows the architecture of Flutter in detail.
Before setting up Flutter, one must ensure some system requirements are in
place specific to Operating Systems.
Once the requirements are met, developer has to download the Flutter SDK
using following link:
Windows: https://docs.flutter.dev/get-started/install/windows
After downloading the SDK for Flutter, the next step is to set the SDK Path which
is automatically done while installing the SDK for Flutter.
Flutter apps can be built with any text editor that has command-line tools.
Following are the links to get started with each of these in Windows:
https://docs.flutter.dev/get-started/editor?tab=androidstudio
https://docs.flutter.dev/get-started/editor?tab=vscode
https://docs.flutter.dev/get-started/editor?tab=emacs
Android Studio is one of the widely used IDEs for developing Flutter
applications.
Step 2: Accept the license and click Download. Wait for the file to download.
Refer to Figure 1.4.
Step 4: Double-click the exe file. Wait for the launcher to show up and click ‘Yes’
to reveal the Android Studio Setup screen. Figure 1.6 shows the Android Studio
Setup screen.
Step 5: Click Next. Once done, the setup wizard will ask to choose components.
Select the default options that are pre-checked and click Next. Figure 1.7 shows
the screen used for selecting components
Figure 1.7: Android Studio Setup – Choosing Components
Step 6: The subsequent step is to choose the location where Android Studio is
to be installed. Figure 1.8 shows the path where Android Studio is going to be
installed.
Step 10: Once the installation process is complete, set up SDK Manager. Figure
1.12 shows how to navigate to Android Studio SDK Manager.
2. Is Flutter open-source?
a. True
b. False
1. UI SDK
2. True
3. Multi-Threading
4. All of these
5. Google
1.7 Try It Yourself
1) Install Android Studio, Flutter, and Dart in your computer and create a
new Flutter Project. Run the project which already has some predefined
program.
Learning Objectives
Session Goals:
Step 2: Create Flutter Project. To do this, click File → New → New Flutter
Project.
Step 3: Select Flutter Application. To do this, click Flutter Application in the left
pane and then, click Next.
Step 4: Configure the application with following details and then, click Next.
1. Project name: flutter_first
2. Flutter SDK Path: <path_to_flutter_sdk> (for example,
D:\fluttersdk\flutter)
3. Project Location: <path_to_project_folder> (for example,
D:\flutterexamples\flutterfirst)
4. Description: Flutter application
Step 6: Finish.
Whenever Flutter application is created, few files and folders will be generated
automatically. Refer to Figure 2.1.
Code Snippet 1:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
/*Center is a layout widget. Here single child is taken and
positioned in the center of the parent.*/
child: Column(
/*Column is also a layout widget. Here the children list is taken
and vertically arranged. By default, it alters itself to fit the
children horizontally, and tries to appear as tall as its parent.
Call on "debug painting" (press "p" in the console, pick the
"Toggle Debug Paint" action from the Flutter Inspector in Android
Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
wireframe can be seen for each widget.Column has properties to
control altering the size itself and how children are positioned.
Here mainAxisAlignment is used to bring the children to center
vertically; the main axis here is vertical axis as the Columns are
vertical (the cross axis might be horizontal).*/
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma is used to auto-format nicer for
build methods.
);
}
}
Click the Run button (Green Play button in the menu bar) in Android Studio.
The Console will appear and after a few minutes, the application will be loaded
in the device. Refer to Figures 2.2 and 2.3.
The main purpose is to create an application from widgets and to describe how
the application view should look with the current configuration and state.
When any change is made in the code, the widget remakes its description by
figuring out the difference between previous widget and the current widget to
settle the minimum changes for rendering in the application user interface.
As seen in Figure 2.4, Flutter widget groups can be grouped into two categories
based on the platform.
2.3.1 Platform-Specific
Under platform-specific, one majorly develops applications for Android and iOS
and each platform has their own guidelines. For Android, it is Material design
guideline and Android-specific widget are called Material Widgets in Flutter.
Some of the Material Widgets are as follows:
● TabBarView
● ListTile
● RaisedButton
● FloatingActionButton
● FlatButton
● IconButton
● DropdownButton
● PopupMenuButton
● ButtonBar
● TextField
For iOS (Apple), it is Human Interface Guidelines and iOS-specific widgets called
Cupertino widgets.
Stateless Widgets:
Stateless widgets are fixed, and its behavior does not change.
Example - icon, text.
Stateful Widgets:
Stateful widgets change with user interface and this widget has one variable
named state. In this variable, current state of the widget is stored.
Stateful widgets have one method called setState which is important to call
when one wants to change any widget. Refer to Code Snippet 2.
Code Snippet 2:
Figure 2.6 shows the widget tree for the code in Code Snippet 2.
⮚ It generates many files and folders when a new Flutter project is created.
⮚ The pubspec.yaml file contains all information about dependencies,
assets, fonts, and so on.
⮚ MyApp is the core widget of the Application.
⮚ In Flutter application, everything is a widget. It can be of any type based
on usage.
⮚ State management widgets are classified into two types - Stateful and
Stateless Widgets.
⮚ Layout widgets are classified into two types - Single-child Layout Widgets
and Multi-child Layout Widgets. It is classified on the basis of the child it
has.
2.6 Test Your Knowledge
1. Text
2. All of these
3. Container
4. README.md
5. pod file
2.7 Try It Yourself
1. Make following changes to Code Snippet 1 given in this session.
a. Add an image before the ‘You have pushed the button this
many times:’ Text. It can be any image and give it a height of 100.
b. Change the color of FloatingActionButton and AppBar.
c. Import Roboto font into the application and change all texts to
Roboto font.
2. Replicate the Profile Screen Design (Second image) from the given
reference link. https://dribbble.com/shots/19117061-Baims-App-
Design-Concept
Session 3
Building Flutter Application Using Platform
Specific Widgets
Learning Objectives
Session Goals:
In Flutter, widgets are classified into many different types. Out of them, one
classification is on the basis of platform. Some widgets in Flutter are platform
specific which means that based on platform used, they will produce a different
output. This session introduces widgets such as Scaffold, AppBar,
BottomNavigationBar, TabBar, and more. The session finally concludes
by explaining User Input Widgets and DatePickers and Dialogs.
3.1 Scaffold
3.2 AppBar
Figure 3.1 shows the definition of constructor of Scaffold in the Dart library and
the properties that the constructor accepts.
3.3 BottomNavigationBar
BottomNavigationBar resembles the AppBar, but is displayed at the
bottom section of the page. It is possible to use BottomNavigationBar() or
BottomAppBar() widget on Scaffold.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
Class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key? Key}) : super(key: key);
@override
_MyStatefulWidgetState createState() =>
_MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget>
{
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber,
appBar: AppBar(
title: Text(‘Scaffold Example’),
),
body: Center(
child: Text('Hi, happy learning Flutter!!!'),
),
bottomNavigationBar: BottomNavigationBar(
items: [
//items inside navigation bar
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: "Search",
),
BottomNavigationBarItem(
icon: Icon(Icons.camera),
label: "Take Photo",
),
//put more items here
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() {}),
child: Icon(Icons.add),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
drawer: Drawer(
elevation: 20.0,
child: Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("Flutter Learning"),
accountEmail:
Text("Flutterlearning@gmail.com "),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.blue,
child: Text("Ross"),
),
),
ListTile(
title: new Text("Inbox"),
leading: new Icon(Icons.mail),
),
Divider(
height: 0.1,
),
ListTile(
title: new Text("Primary "),
leading: new Icon(Icons.inbox),
),
ListTile(
title: new Text("Social"),
leading: new Icon(Icons.people),
),
ListTile(
title: new Text("Promotions"),
leading: new Icon(Icons.local_offer),
)
],
),
),
);
}
}
Figure 3.2 shows the output for the program in Code Snippet 1. The
components such as AppBar, BottomNavigationBar, and Floating Action
Bar are clearly mentioned in Figure 3.2.
Code Snippet 2 shows a sample program with TabBar implemented and the
corresponding output for the program is showcased in Figure 3.3.
Code Snippet 2:
MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: const Text('Travel Application - Demo'),
),
body: const TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
Figure 3.3: TabBar and TabBarView Sample
Figure 3.3 shows the TabBar at the top of the page and the contents that are
displayed using the TabBarView. Markers are used to point separate
components.
3.5 ListTile
Code Snippet 3:
ListTile(
title: Text('Demo ListTile'),
subtitle: Text('Usage of ListTile'),
trailing: Icon(Icons.favorite,
color: Colors.red),
)
Figure 3.4 shows the output of the program that implements ListTile. The
tile, title, and trailing icon are highlighted.
Figure 3.4: Output for ListTile Implementation
3.6 Buttons
When creating a simple app with a login page, once the user enters the
username and password, the app should take the authenticated user to the
welcome screen. Here, the logic for authentication happens when the button is
clicked after the user enters the username and the password.
Buttons are Material components in Flutter which are used to trigger actions
such as submitting forms, making selections, and so on. Buttons are one of the
relative user components while designing an application.
For example, a text button to display text and an icon button to display a
clickable icon. In addition to buttons, Flutter also provides a wide range of
features for styling and shaping buttons.
Note that the Raised button will be in a disabled state if the onPressed() and
onLongPress() callbacks are not available.
3.6.2 Floating Action Button
This is a circular button with an icon which hangs at the top of the content. It
supports the primary action in the app, which means that it is used to give an
extra focus that users can easily get attracted to. Floating action buttons are
most often used in the Scaffold.floatingActionButton field. It is a
parameter in the Scaffold widget using which you can add a floating button.
In Flutter, there are many input data widgets to help the developer get several
kinds of information from users.
3.7.1 TextField
It is used to accept alphanumeric input data (such as username, password,
product price, publication year, and so on) from the keyboard into the
application. It is important to note that, by default, the content from this widget
is decorated with underlines and it is possible to change the behavior of the
widget using extra decoration options.
3.7.2 RadioButton
This button is commonly used when one wants to choose a single option from
a list of options. Options such as Male/Female/Other, True/False,
Single/Married/Widow, and so on can be given and one value can be chosen
from this list using the RadioButton. It is important to note that the default
behavior of a RadioButton is to choose only one option. This behavior cannot
be changed.
3.7.3 CheckBox
This option helps to choose multiple options at one time. For example, when
developing a feature which involves selecting multiple inputs or parameters
from the user, checkboxes can be utilized. For example, to check what services
a vendor is offering, inputs from a checkbox can be taken. Figure 3.6 shows a
few User Input widgets in Flutter.
DatePickers and dialogs are simple widgets that allow users to select a date,
view alert dates, or confirm any action. For example - to confirm if the user
wants to log out or not or to confirm whether to make a payment or not.
This is a Material widget in a Flutter that allows the user to pick a date and time.
If there is no widget available to create a date and time picker, the
showDatePicker()and showTimePicker() functions can be utilized to
achieve the same. These two functions display the Material Design date and
time selection in a dialog by calling the built-in Flutter functions
showDatePicker() and showTimePicker() respectively. Additionally, it
is important to note that datePicker constructor have three major values.
Code Snippet 4:
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Text('Alert Dialog'),
content: Text('This is an Alert Dialog!'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel')),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Ok'))
],
);
});
2. What is BottomNavigationBar?
a. Component of Scaffold
b. Component of AppBar
c. None of these
d. All of these
1. Scaffold
2. Component of Scaffold
3. Raised Button
4. ListTile
5. Raised Button
3.11 Try It Yourself
2. Design a good looking form for collecting user details in Flutter. The Form
will contain following fields in order:
a.TextFields for First Name and Last Name in the same Row.
b. Radio Button with options for selecting Gender.
c. A Slider with value between 0 and 100 for choosing age.
d. A TextField with a SuffixIcon which on clicking should open
the DatePicker. This field is for entering Date of Birth.
e.A RaisedButton with text ‘Submit’. On clicking this button, every
field listed in the form should reset to empty values.
Session 4
Building Flutter Application Using Platform
Independent Widgets
Learning Objectives
Session Goals:
This session explains different widgets present in Flutter. It is possible to build
Flutter application using independent widgets. There are basic widgets that are
called independent widgets. These widgets do not have children within them.
The Text widget helps one create Text Elements and display them in the
application. The Text widget can be customized according to what the user
wants. For example, animation, colors, size, and so on can be customized.
Text("Flutter Development")
The Text widget can have a style parameter. This is an extra parameter that
can be added along with the syntax to add style-based additions. Text attributes
such as font weight, color, font size, and so on can all be changed using this
parameter. Code Snippet 1 shows an example of Text widget in Flutter.
Code Snippet 1:
Code Snippet 2:
String name, {
Key? key,
AssetBundle? bundle,
Widget Function(BuildContext, Widget, int?, bool)?
frameBuilder,
Widget Function(BuildContext, Object, StackTrace?)?
errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
double? scale,
double? width,
double? height,
Color? color,
Animation<double>? opacity,
BlendMode? colorBlendMode,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Rect? centerSlice,
bool matchTextDirection = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
String? package,
FilterQuality filterQuality = FilterQuality.low,
int? cacheWidth,
int? cacheHeight,
Utilizing the properties as in Code Snippet 2, users can adjust parameters such
as design, style, height, width, and so on.
Out of the properties, the BoxFit property is an important one for the Image
Class. This property can be utilized to set images according to the users’
requirements. If one wants to cover, fill, or contain the image, they can set the
respective parameter along with the BoxFit Parameter. Code Snippet 3 shows
the BoxFit property being utilized in a sample code and Figure 4.2 shows the
output for the code in Code Snippet 3.
Code Snippet 3:
Image.network(
"https://storage.googleapis.com/cms-storage-
bucket/70760bf1e88b184bb1bc.png",
fit: BoxFit.contain,
height: 100,
width: 100,
),
Basic Icon
Code Snippet 4 shows the basic Icon widget with default Values.
Code Snippet 4
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Icon(Icons.ac_unit_outlined)],
),
)
);
}
}
Resizing icon
Code Snippet 5
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.ac_unit_outlined,
size: 80,
)
],
),
));
}
Code Snippet 6
Icon(
Icons.ac_unit_outlined,
size: 80,
color: Colors.blue,
)
Figure 4.3 depicts the output.
Output:
Form validation and form submission are two things that the Form widget
helps to implement. For an application to be easy to use and safe, it is important
to check if the information provided by the user is valid. If the user happens to
fill out the form correctly, the information can be processed but if the user
submits incorrect information, the widget displays an error message which is
user friendly to let the users know what exactly happened.
4.4.1 Properties of Form
Code Snippet 7:
Form Form({
Key key,
Widget child,
bool autovalidate = false,
Future<bool> Function() onWillPop,
void Function() onChanged,
AutovalidateMode autovalidateMode,
})
There are two types of widgets. They are Form and FormField<T>. The Form
widget maintains FormField states and stores data such as current value and
validity.
Syntax:
Form({
key key,
@required Widget child,
bool autovalidate: false,
WillPopCallback onWillPop,
VoidCallback onChanged
})
When one wants to create a Form widget, it is mandatory to create a
GlobalKey. The GlobalKey allows one to link to the form in the future.
The GlobalKey also gives one the ability to invoke methods such as save in
order to save the value of each child FormField in the Form widget.
4.5 Summary
Learning Objectives
Session Goals:
Single Child Layout widgets are widgets in which users can define only one
widget at a time. Flutter has a widget tree that can show the parent as well as
the children widgets. A Single Child Layout widget can be made as a parent
widget and a Multi-Child Layout widget can be placed inside it. This way, users
can have multiple widgets in a view and can use the widget tree to view the
hierarchy. This session explains Single Child Layout widgets and Container
widget, Padding widget, Align widget, Center widget, and SizedBox
widget.
1. child: This property permits to store the child widget of the container.
2. color: This property helps in setting the color of the background for the
entire container.
3. height and width: This property is used for setting the height and
width of the container. By default, this takes the space depending on its
child widget.
Container (width: 200.0, height: 100.0, color: Colors.blue,
child: Text ("Hey There!"),);
6. padding: For setting the distance between the container’s border and
its child widget, this property of Flutter container is used.
10. constraints: For adding extra constraints for the child widget, the
constraint property is utilized. This property has different constructors
such as tight, loose, and so on.
Container(color:Colors.green,
constraints: BoxConstraints.tight(Size size) : minWidth =
size.width, maxWidth = size.width, minHeight = size.height,
maxHeight = size.height; child: Text("Hello! I am in the
container widget", style: TextStyle(fontSize: 25)), )
Example:
padding: EdgeInsets.all(15)
2. EdgeInsets.fromLTRB(): Provides padding to individual sides.
Here, LTRB refers to L – Left, T – Top, R – Right, and B – Bottom.
Example:
padding: EdgeInsets.only(left: 20, top:30)
Padding can be used whenever the user wants to add some extra space
around any widget. There are several types of padding available and it is
possible to choose which type to be implemented based on the user’s
requirement.
Code Snippet 2
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Padding Demo',
style: Theme.of(context).textTheme.headline4,
),
Padding(
padding: const EdgeInsets.all(10),
child: Container(
foregroundDecoration: BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.circular(100),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(50),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
height: 20,
)),
Padding(
padding: const EdgeInsets.fromLTRB(0, 1, 40,
0),
child: Container(
foregroundDecoration: BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.circular(100),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(50),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
height: 20,
)),
Padding(
padding: const EdgeInsets.only(left: 50),
child: Container(
foregroundDecoration: BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.circular(100),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(50),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20.0,
),
]),
height: 20,
)),
],
),
));
}
}
Align widget as the name suggests, is utilized for moving the widget to one
side or the other. Widgets can be aligned left, right, or in any other direction.
Align widget is utilized to align its child widget to the defined alignment. If a
particular widget must be aligned to the bottom of the screen, the align widget
can be utilized for that operation to be performed.
Center widget aligns its child widgets to the center of the screen. If any kind
of widget is inside the center widget, that widget will also be in the center of the
screen
Figure 5.6 shows the properties and the description for each property.
Figure 5.6: Properties of Center Widget
If child widgets must be aligned to the center, the center widget can be utilized
for that.
Syntax:
SizedBox({ Key key, this.width, this.height, Widget child })
Properties:
Sizedbox takes width and height as parameters.
1. this.width: Width will be applied to the child.
2. this.height: Height will be applied to the child.
4. Padding is ______________.
a. Single-child widget
b. Multi-child widget
c. Property of Container
d. Appending widget to container
1. Container
2. All of these
3. Assign border to widget
4. Single-child widget
5. Align according to width described
Try It Yourself
1. Create a Flutter application which has a Container of any color and two
Sliders.
a. First slider value will decide the size of the Container.
b. Second slider value will modify the margin of the Container.
Note: Make sure no overflow error occurs
2. Refer to the design in the given link and create the design in Flutter.
Make use of Container, Padding, SizedBox, and other widgets.
https://dribbble.com/shots/15865091-The-Brainbob-mobile-app
Session 6
Building a Flutter Application Using Multi Child
Layout Widgets
Learning Objectives
Session Goals:
This session covers in detail the widgets that can have multiple children.
Further, explains Row widget, Column widget, Stack widget, and other child
layout widgets. Multi-Child Layout widgets are those widgets inside which one
can define as many widgets as required at a time. In Flutter, for every
application, there can be a widget tree that shows parent and children widgets.
In the widget tree, the parent represents the widget that is on the top, whereas
the child represents the widget that is assigned as a child of any widget.
Refer to Figure 6.1 which shows the built-in definition of properties of Row
widget.
Code Snippet 1 shows the usage of a Row widget. In this code, three text widgets
will be used and space between them will be assigned evenly. Figure 6.2 shows
the output of Code Snippet 1.
Code Snippet 1
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Output:
Code Snippet 2 shows usage of Column widget. In this example, three child
widgets will be used to demonstrate columns. Figure 6.4 shows the output for
the Column widget.
Code Snippet 2
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Refer to Figure 6.5 which shows the built-in definition of properties of Stack
widget.
Code Snippet 3 demonstrates how to use the Stack widget. Children of Stack
will overlap as there would not be any gap applied. Row or Column widgets can
be used when widgets should be placed beside or below. If widgets in Stack are
to be positioned in a certain way, then Align or Positioned widgets can be
used.
Code Snippet 3
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Output:
Here, each of the children in the Stack are Text widgets. However, in practical
scenarios, one can use Containers, which in turn can have colors and/or text.
Figure 6.7 shows an example of a Stack widget using Container widgets of
different shapes and colors stacked on top of each other.
Refer to Code Snippet 4 for ListView example. Figure 6.8 shows the output
for Code Snippet 4.
Code Snippet 4:
ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.account_circle, size: 40,),
Output:
Refer to Code Snippet 5 for GridView example. Figure 6.9 is the output for
Code Snippet 5.
Code Snippet 5:
GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,
mainAxisSpacing: 10, crossAxisSpacing: 10),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Colors.blueGrey,
child: Center(child: Text('User ${index+1}', style:
TextStyle(
fontWeight: FontWeight.w700,
fontSize: 25,
color: Colors.white
),)),
);
}),
Output:
For example, when you are working with Row widget or Column widget and
using mainAxisAlignment.spaceBetween, it leaves a gap between
children. In that case, this widget can be used to fill the available space.
Flex and Child are the properties of Expanded widget. Using Flex expansion
the widgets can be controlled. The Child is used to hold another widget when
multiple widgets are expanded.
Code Snippet 6:
Table(
// textDirection: TextDirection.rtl,
// defaultVerticalAlignment:
TableCellVerticalAlignment.bottom,
// border:TableBorder.all(width: 2.0,color: Colors.red),
children: [
TableRow(
children: [
Text("Name",textScaleFactor: 1.5, style:
TextStyle(fontWeight: FontWeight.w600),),
Text("Subject",textScaleFactor: 1.5, style:
TextStyle(fontWeight: FontWeight.w600),),
Text("Marks",textScaleFactor: 1.5, style:
TextStyle(fontWeight: FontWeight.w600),),
]
),
TableRow(
children: [
Text("Alex",textScaleFactor: 1.5),
Text("Maths",textScaleFactor: 1.5),
Text("98",textScaleFactor: 1.5),
]
),
TableRow(
children: [
Text("Henry",textScaleFactor: 1.5),
Text("Chemistry",textScaleFactor: 1.5),
Text("35",textScaleFactor: 1.5),
]
),
TableRow(
children: [
Text("Luke",textScaleFactor: 1.5),
Text("Physics",textScaleFactor: 1.5),
Text("40",textScaleFactor: 1.5),
]
),
],
),
Output:
1. verticalDirection
2. defaultColumnWidth
3. GridView
4. Both
5. itemExtent
6.10 Try It Yourself
Learning Objectives
Session Goals:
This session explains state management and dependency injection in Flutter.
In Flutter, there are two types of widgets namely Stateful and Stateless widgets.
For a stateful widget, the data can be changed in runtime by managing the
states. In the stateless widget, the state of the widget once built cannot be
changed. Hence, there is no state management for the stateless widgets.
Following is an example to understand the concept of State Management:
Assume that an application for selling something is being built. On the item
page, it is possible to click the Add to cart button and use the ‘add' or 'subtract’
icon to increase or decrease the quantity of any item respectively. State
management helps to achieve this increase and decrease. Further, in the
example discussed, it is important to maintain cart data across several
purchases or browsing and this is also achieved with the help of state
management. The information that is used to add the list of items in cart is
stored in a separate class. This class is used to keep track of all the activities.
ScopedModelDescendant is used to
ScopedModelDescendant identify the correct ScopedModel from the
widget hierarchy.
import 'package:flutter/material.dart';
import 'InheritedWidget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return InheritedHomeWidget(
title: 'Dependency Injection Text',
key: key,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Demo'),
));
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key:
key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(InheritedHomeWidget.of(context).title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: const <Widget>[
Text(
'Dependency Injection Demo',
)
],
),
);
}
}
In Code Snippets 3 and 4, a text is added in the provider and subsequently, the
provider will be added in the main function. Another thing to observe will be
how one can access the text anytime inside the application.
void main() {
Provider.debugCheckInvalidValueType = null;
runApp(const MyApp());
}
class Api {
String get loggedIn => 'I am logged in';
}
1. ScopedModel
2. ScopedModelDescendant
3. Model
4. A designing pattern used to implement inversion of control
5. All of these
7.7 Try It Yourself
Learning Objectives
Session Goals:
Flutter supports many different pages within applications, similar to several
other technologies. For navigating from one page to another, Navigation and
Routing are utilized. This session introduces the navigation and explains the
concepts of navigation in Flutter. Further, the session details about routing
class. To control many different widgets during navigation, controllers come in
handy. The session finally concludes by explaining controllers and detailing the
working of controllers on Flutter.
Every page in Flutter has a route. In the Navigator, it is possible to mention any
route about the user requirements.
Code Snippet 1:
routes: {
'/': (context) => const MyHomePage(title: 'Flutter
Demo'),
'/second': (context) => const MyHomePage(title: 'Flutter
Demo'),
},
Route is an abstract class that is used to manage the entry to the Navigator class.
The Navigator class in Flutter is a widget which manages a set of child widgets
with a stack discipline. Navigator class is responsible for managing the routes
between these elements. An abstract class is a class that can have abstract
methods which does not have a body.
8.3.1 MaterialPageRoute
MaterialPageRoute is the modal route that replaces the entire screen with
platform-adaptive transition. In Android, the navigation to next screen takes
place by a fade animation whereas in iOS, the navigation takes place with a
sliding animation. In simple words, MaterialPageRoute takes care of the
transition between pages within the application.
Two methods are important to route from one page to another and they are as
follows:
In this code, routes are defined for MaterialApp and these routes help
navigate from page one to page two.
Code Snippet 2:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Output:
Figures 8.2 and 8.3 show outputs when the application is executed. Two
buttons exist in the home screen. Refer to Figure 8.2. When any of these buttons
are clicked, they take the user to the second page. Refer to Figure 8.3. Another
button exists in the second page to enable the user to navigate to previous
screen. This action of jumping from one screen to another and back is possible
because of the Navigation and Routes concept. The controller for the first or the
second page gets loaded when the user clicks the appropriate buttons and the
router aids in the navigation from one to another.
Figure 8.2: Demo Application Output Upon Code Execution
8.6.1 TextEditingController
8.6.2 ScrollController
Code Snippet 3 shows the MyHomePage class from Code Snippet 2 and
demonstrates how controllers work in Flutter.
Code Snippet 3:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key:
key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
Custom controllers are used in Flutter to control custom widgets that are also
used in combination with different controllers defined in Flutter.
To create custom controllers, users can use the changenotifier class. The
changenotifier is a class which can be used to notify to subclasses about
any changes that happens in the parent class using the notifyListeners
method.
Code Snippet 4:
class CustomController extends ChangeNotifier {
Color buttonColor = Colors.blue;
String buttonShape = 'Rectangle';
double buttonHeight = 10;
5. MaterialPageRoute is ____________.
a. To change whole page
b. To define route
c. To navigate between pages
d. To control the routes
Answers - Test Your Knowledge
Flutter offers many styling methods and the option to style the whole theme of
the application.
Here, text, icons, buttons, sliders, and check boxes are utilized without any
styling properties. By default, the primary color will be blue. This means that
all the widgets that are created without any styling will be in blue color.
Code Snippet 2:
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App Bar'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Heading 4',
style: Theme.of(context).textTheme.headline4,
),
Slider(value: 0, onChanged: (s) {}),
Icon(Icons.collections_sharp),
Checkbox(value: true, onChanged: (s) {}),
TextButton(onPressed: () {}, child: Text('Text
Button')),
ElevatedButton(onPressed: () {}, child:
Text('Elevated Button')),
],
),
);
}
}
a. Text:
Text can be styled using the TextStyle method which can modify color,
backgroundColor, fontSize, fontWeight, lineHeight, space
between words/letters, underlines, style, and so on.
Syntax for the TextStyle is given as follows:
TextStyle? style,
Code Snippet 3:
Text(
'New Text',
style: TextStyle(
//size
fontSize: 30,
//font weight
fontWeight: FontWeight.w600,
//color
color: Colors.teal,
//line height
height: 1.5,
//word spacing
wordSpacing: 2,
//letter spacing
letterSpacing: 2,
//background color of text
backgroundColor: Colors.yellow,
),
),
The Container has a decoration property that can be used to specify border,
borderRadius, shadow, gradients, images, and more.
Decoration? decoration,
Code Snippet 4:
Container(
height: 100,
width: 100,
decoration: BoxDecoration(
//color
color: Colors.orange,
//border radius
borderRadius: BorderRadius.circular(10),
//border with width 5
border: Border.all(color:
Colors.orange.shade900, width: 5),
//shadow for container
boxShadow: [
BoxShadow(
// offset where shadow should fall
offset: Offset(5, 10),
//shadow color
color: Colors.grey,
//blur radius
blurRadius: 5,
//spread radius
spreadRadius: 1,
),
],
),
),
c. RaisedButton:
ButtonStyle styleFrom({
Color? primary,
Color? onPrimary,
Color? onSurface,
Color? shadowColor,
Color? surfaceTintColor,
double? elevation,
TextStyle? textStyle,
EdgeInsetsGeometry? padding,
Size? minimumSize,
Size? fixedSize,
Size? maximumSize,
BorderSide? side,
OutlinedBorder? shape,
MouseCursor? enabledMouseCursor,
MouseCursor? disabledMouseCursor,
VisualDensity? visualDensity,
MaterialTapTargetSize? tapTargetSize,
Duration? animationDuration,
bool? enableFeedback,
AlignmentGeometry? alignment,
InteractiveInkFeatureFactory? splashFactory,
})
Code Snippet 5 and Figure 9.4 show an example and result for
ElevatedButton respectively.
Code Snippet 5:
ElevatedButton(
onPressed: () {},
child: Text('Button'),
style: ElevatedButton.styleFrom(
//button color
primary: Colors.amber.shade700,
//shape and border radius
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
//to specify fixed size for a button
fixedSize: Size(100, 100)),
),
Many applications today have the option to switch between light and dark
themes. Some apps even have the option to set custom themes. Users should
feel comfortable using the app as they wish to see. Themes in Flutter offer a way
by which they can set a custom color, font, size, and border for several widgets
that will be reflected all over the application.
Step 2: Now, call the theme inside MaterialApp. Code Snippet 7 shows the
theme being called inside the MaterialApp in the main.dart file.
Code Snippet 7:
MaterialApp(
//..
darkTheme: AppTheme.darkTheme,
//..
home: StylingExample(),
);
Code Snippet 8 shows the dark theme styling for the Flutter application.
Code Snippet 8:
static ThemeData darkTheme = ThemeData(
//scaffold bg
scaffoldBackgroundColor: Colors.black54,
//app bar theme
appBarTheme: AppBarTheme(color: Colors.purple),
//elevated button theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
primary: Colors.red,
),
),
//icon
iconTheme: IconThemeData(color: Colors.white),
//text theme - heading 4
textTheme: TextTheme(
headline4:
TextStyle(color: Colors.white70, fontWeight:
FontWeight.w500),
));
9.2.2 Creating a Light Theme in Flutter
Step 1: Follow the same steps that were followed when creating the dark theme
to create the light theme in the same class. Code Snippet 9 shows how to create
Light Theme.
Code Snippet 9:
static ThemeData lightTheme = ThemeData(
//scaffold bg
scaffoldBackgroundColor: Colors.white,
//app bar theme
appBarTheme: AppBarTheme(color: Colors.indigo),
//elevated button theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
primary: Colors.red,
),
),
//text theme - heading 4
textTheme: TextTheme(
headline4:
TextStyle(color: Colors.blueGrey, fontWeight:
FontWeight.w500),
));
Step 2: Inside the MaterialApp, apply the light theme as shown here.
theme: AppTheme.lightTheme,
Step 1: To toggle between themes, create a button that on click will toggle the
theme. There are two themes defined in the MaterialApp. Add another
property called themeMode and change the AppTheme class by extending a
ChangeNotifier. Refer to Code Snippet 10. It is a new file called
apptheme.dart
Step 2: Create a boolean variable to toggle dark and light themes followed by a
function to change themes and notify the listeners.
Code Snippet 10:
class AppTheme extends ChangeNotifier {
bool _isDarkTheme = true;
ThemeMode get currentTheme => _isDarkTheme ? ThemeMode.dark
: ThemeMode.light;
void toggleTheme() {
_isDarkTheme = !_isDarkTheme;
notifyListeners();
}
//..
}
Step 3: Create a global instance for the AppTheme in the main.dart file as
shown.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Styling Demo',
//theme
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
//theme mode
themeMode: appTheme.currentTheme,
home: StylingExample(),
);
}
}
Figures 9.5 A and B show the toggling between the light and the dark themes
respectively when the button is clicked.
Figure 9.5A: Output of ToggleTheme - Light Theme
Step 1: For the Flutter app to support all orientations, add the code given in
Code Snippet 13 inside the build of MyApp() from main.dart.
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
DeviceOrientation.portraitDown,
DeviceOrientation.portraitUp
]);
DeviceOrientation.landscapeLeft
DeviceOrientation.landscapeRight
Similarly, use the portrait options if the app has to support portrait.
MediaQuery in Flutter can be used to retrieve the size (width and height) and
orientation (portrait/landscape) of the device.
Note: MediaQuery can be used in the place where the total size of the screen
is required. Whereas, LayoutBuilder can be used in the place where
height/width of the particular widget is required.
Flutter offers some other widgets other than LayoutBuilder by which the
application can be made responsive.
9.4.1 Aspect Ratio
Aspect ratio can be used to assign a specific size to a child widget. This widget
first tries the largest width permitted by the layout constraints and then,
decides the height by applying a given aspect ratio to the width.
9.4.2 CustomSingleChildLayout
SingleChildLayout widgets are those which allow only one widget as their
child such as Container, Align, Center, SizedBox, Positioned, and so
on. A custom single-child widget can be created using the
CustomSingleChildLayout widget. It is important to note that the
developers have to specify the delegate and the child widget. The delegate has
two override methods performLayout and shouldRelayout which can
also be customized as required.
9.4.3 CustomMultiChildLayout
Syntax
CustomMultiChildLayout CustomMultiChildLayout({
Key? key,
required MultiChildLayoutDelegate delegate,
List<Widget> children = const <Widget>[],
})
It is important to note that the children in this widget should be inside a
layoutId widget with each child having a unique id.
9.4.4 FittedBox
FittedBox restricts its child from growing beyond a certain limit. It rescales
them according to the size available. Code Snippet 19 shows how this works.
9.4.6 OrientationBuilder
1. boxShadow
2. Size of a particular widget from a screen
3. All of these
4. SystemChrome.setPreferredOrientations([DeviceOrient
ation.portraitUp, DeviceOrientation.portraitDown]);
5. FittedBox rescales the size according to maximum space available
9.7 Try It Yourself
1) Design a login screen. The screen must have a linear gradient background
with any three colors, a large welcome text, two text fields for email and
password and a Login Button at the bottom. The password text fields
must be obscured.
https://blog.logicwind.com/content/images/size/w2000/2018/08/a1
12.png
Session 10
FutureBuilder and StreamBuilder in Flutter
Learning Objectives
Session Goals:
The session introduces concepts of FutureBuilder and StreamBuilder in
Flutter. These are essential widgets as they are used in most apps which include
fetching data from external servers or databases. By the end of this session,
students will be ready to fetch and display future and stream data in their
Flutter applications.
Some other examples are Google search, fetching data from a database,
uploading files from a device, and so on.
2. builder: This is where one builds the data to be displayed. Two arguments
Context and snapshot are passed here. Snapshot is a type available in
Flutter through which one can process the data.
An asynchronous function may not always return data. There are possibilities
that it may return an error, take more time to load, and so on. This is where a
snapshot helps us.
@override
State<FutureBuilderExample> createState() =>
_FutureBuilderExampleState();
}
Code Snippet 2:
Future<String> fetchTime() async {
await Future.delayed(const Duration(seconds: 2));
var date = DateTime.now();
return
'${date.hour}:${(date.minute).toString().padLeft(2,'0')}:${
(date.second).toString().padLeft(2,'0')}';
}
● The fetchTime() function used in the Code Snippet 2 will return the
current time in an asynchronous way.
● Since this is an asynchronous function, the keywords async and
Future<String> are used where String denotes the return type.
● Since the execution is to be delayed for two seconds, Future.delayed
method is used.
● The await keyword is used before Future.delayed to notify the
function to wait till the execution is completed. Hence, the function will
proceed only after the execution is completed.
● Next, DateTime.now() is used to get the current date and time and is
assigned to date variable.
● Finally, the date value is formatted in a presentable manner so the user
can understand. One can format it in whatever way they prefer.
Code Snippet 3:
body: Center(
child: FutureBuilder(
future: fetchTime(),
builder: (context, snapshot) {},
),),
In the code, fetchTime() returns a String and specifies that in the widget as
FutureBuilder<String>.
Code Snippet 4:
body: Center(
child:FutureBuilder<String>(
future: fetchTime(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Error',
style: TextStyle(fontWeight:
FontWeight.w500, fontSize: 16),
),
Text(
snapshot.error.toString(),
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 20),
),
],
);
}
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Time Now',
style: TextStyle(fontWeight:
FontWeight.w500, fontSize: 16),
),
Text(
snapshot.data.toString(),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 20),
),
],
);
}
return Text('state: ${snapshot.connectionState}');
},
),),
Streams are similar to Future. Streams are also asynchronous data, but in the
form of a sequence of events. A Future will terminate once it produces the
result. Whereas, a stream is an established connection and data will be received
as long as the stream is active.
A good example would be a chat application where both sender and receiver
will receive data as long as the participants are active. Some other examples
would be video streaming, playing music, and live location.
10.2.1 StreamBuilder Widget in Flutter
Syntax
StreamBuilder<Object?> StreamBuilder({
Key? key,
Object? initialData,
Stream<Object?>? stream,
required Widget Function(BuildContext,
AsyncSnapshot<Object?>) builder,
})
Stream<Object>?
builder
stream
This is where the data to be displayed is This is where the stream function that
built. Two arguments have to be passed returns a sequence of values of a type T
here. One can make use of the (int, String, anything) is
connectionStates seen in specified.
FutureBuilder.
ConnectionState.active: Denotes that
the stream is active and is being listened
to. ConnectionState.done: Denotes
that the listening to stream function has
ended.
async*: It will always return a stream and offer some syntax sugar to emit a
value through the yield keyword.
yield: It adds a value to the output stream of the surrounding async*
function. It is like a return function but does not terminate the function.
To understand with an example, consider Code Snippet 5 where the
fetchTimer() function is a Timer that will count from 10 to 0 and end.
Code Snippet 5:
int i = 10;
Stream<String> fetchTimer() async* {
while (i > 0) {
await Future.delayed(Duration(seconds: 1));
i--;
yield i.toString();
}
}
In this, a 10-second timer app is built, which begins after a second and ends
after 10 counts reducing to 0. After that, show a Time Up message.
Code Snippet 6 shows how to create a new widget called
StreamBuilderExample. Start by creating the StreamBuilder and
wrapping it inside the Center widget.
Code Snippet 6:
body: Center(
child: StreamBuilder<String>(
stream: fetchTimer(),
builder: (context, snapshot) {},
),
),
The fetchTimer() function is declared as the stream. Then, start building
the UI.
Code Snippet 7:
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Error',
style: TextStyle(fontWeight:
FontWeight.w500, fontSize: 16),
),
Text(
snapshot.error.toString(),
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 20),
),
],
);
}
if (snapshot.connectionState ==
ConnectionState.active) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Time Ends in:',
style: TextStyle(fontWeight:
FontWeight.w500, fontSize: 16),
),
Text(
snapshot.data.toString(),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 20),
),
],
);
}
if (snapshot.connectionState ==
ConnectionState.done) {
return Text(
'Time Up!\n${snapshot.connectionState}',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 20),
);
}
return Container();
}
While executing the code, first, check whether the data is loading. Use
ConnectionState.waiting to check loading and use
CircularLoadingIndicator() to notify the user.
FutureBuilder StreamBuilder
FutureBuilder is used for one- StreamBuilder is for fetching
time response data more than once. Like listening
to a data change
Once the FutureBuilder fetches StreamBuilder will rebuild the
the result, it is done. Widget will not UI whenever the data changes till
update if the date changes and the the connection is active
connection will end
Main keywords: Future, async Main keywords: Stream, async*,
yield
Use Case examples: HTTP request, Use Case examples: live location,
uploading a file from the device, streaming video/music, listening to
taking a picture from the camera, a WebSocket.
getting device info such as location
and battery.
Table 10.1: Difference between FutureBuilder and StreamBuilder
10.4 Summary
2) Create a Flutter application which will display the time remaining for
your next birthday. Make use of StreamBuilder and design an
attractive UI with confetti animations.
Session 11
Database Concepts with Sqflite and Firebase
Database
Learning Objectives
Session Goals:
Databases play a major role in any application. A database is necessary to store,
access, and modify data as required. This session describes how to use SQLite
database to store data locally on the device and also, the method to modify the
same. The session also explains about Firebase real-time databases to store
data in NoSQL format in a remote database. In addition to this, one will also
learn to authenticate users, sign up, and login using Firebase Authentication
feature.
Flutter has a sqflite plugin, through which developers can use this database
to store data locally in any Flutter applications.
Code Snippet 1:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
sqflite: ^2.0.3+1
11.2.2 Adding Path_Provider Dependency and Reasons to Add It
path_provider: ^2.0.11
UserModel({
required this.id,
required this.name,
required this.email,
required this.age,
});
The fromJson() and toMap() functions are used to convert the data model
to Map Object and vice versa.
class DatabaseService {
static final DatabaseService _databaseService =
DatabaseService._internal();
factory DatabaseService() => _databaseService;
DatabaseService._internal();
}
Now, the database must be initialized before creating any tables or performing
read/write operations. This is shown in Code Snippet 4. Inside the class, create
a variable of type Database. A Getter function has been used to fetch the
database as shown in Code Snippet 4.
In this function, first, the path is declared for creating/accessing the database
by making use of the path_provider package. The file is named as
users.db. Then, the openDatabase() function is called, which creates the
database using _onCreate() function. Refer to Code Snippet 6.
//create
db.execute(
‘CREATE TABLE Users(id TEXT PRIMARY KEY, name TEXT,
email TEXT, age INTEGER)’);
//rawInsert
db.rawInsert(
‘INSERT INTO Users(id, name, email, age )
VALUES(?,?,?,?)’,
[user.id, user.name, user.email, user.age]);
//rawUpdate
db.rawUpdate('UPDATE Users SET name=?,email=?,age=? WHERE
ID=?', [user.name, user.email, user.age, user.id]);
//rawQuery
db.rawQuery('SELECT * FROM Users');
Code Snippet 8:
db.insert('Users', user.toMap());
The table name and data object must be passed in the form of a Map<String,
dynamic> as arguments. The return type will be a Future<int> which will
be the inserted id from the table.
Code Snippet 9 shows the update helper.
Code Snippet 9:
db.update('Users', user.toMap(), where: 'id = ?', whereArgs:
[user.id]);
For this, developers have to pass the table name, data to be updated, and
conditions to be checked before inserting. If no conditions match, then the
update will not take place.
import 'package:flutter/material.dart';
import 'package:session11/sqflite/database_service.dart';
import 'package:session11/sqflite/user_model.dart';
import 'package:uuid/uuid.dart';
void addUser() {
showBottomSheet('Add User', () async {
var user = UserModel(
id: Uuid().v4(),
name: nameController.text,
email: emailController.text,
age: int.parse(ageController.text));
dbService.insertUser(user);
setState(() {});
nameController.clear();
emailController.clear();
ageController.clear();
Navigator.of(context).pop();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sqflite Example'),
),
body: FutureBuilder<List<UserModel>>(
future: dbService.getUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(child:
CircularProgressIndicator());
}
if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return const Center(
child: Text('No Users found'),
);
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => Card(
color: Colors.yellow[200],
margin: const EdgeInsets.all(15),
child: ListTile(
title: Text(snapshot.data![index].name +
' ' +
snapshot.data![index].age.toString()),
subtitle:
Text(snapshot.data![index].email),
trailing: SizedBox(
width: 100,
child: Row(
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () =>
editUser(snapshot.data![index]),
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () =>
deleteUser(snapshot.data![index].id),
),
],
),
)),
),
);
}
return const Center(
child: Text('No Users found'),
);
}),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => addUser(),
),
);
}
}
Explanation:
● List of users - name, age, and email will be displayed as ListTile
inside a ListView.
● The ListView has been wrapped inside a FutureBuilder since
the list of users will be fetched from the database. All the functions
involving communication with the database is a future function.
● The loading indicator and ‘no user found’ text are displayed to
enhance the UI.
● Floating action button is used, which on clicking opens a bottom area
containing three text fields. In this area, name, email, and age can be
entered.
● A package called uuid is used which will create a unique id for the
user.
● Individual functions have been written to perform CRUD operations
and communicate with the DatabaseService class.
● addUser - adds a user to the database, refreshes the app using
setState which will rebuild the UI and text fields will be cleared in
the same way as other functions.
Note: setState is used here for demonstration purposes only. Even though
this works, it is not a good practice to use setState. Proper state management
has to be used wherever possible, to avoid unnecessary UI rebuilds.
Hence, one can access, update, and sync data across multiple cross-platform
applications.
Provide a name for the project and click ‘Continue’. The next step would be to
enable/disable Google Analytics for this project. This step is optional and can
be skipped. After a few minutes, the project will be ready. Firebase offers many
features such as authentication and hosting. To begin with Firebase real-time
database, follow the steps as shown in Figure 11.3.
firebase_database: ^9.1.0
DatabaseReference databaseRef =
FirebaseDatabase.instance.ref(‘/users’);
The databaseRef variable has been initialized pointing to the ‘/users’
collection. Using this databaseRef, one can now communicate with the
Firebase real-time database.
To read from a database, use the get() method which will return
Future<DataSnapshot>. Since this is a NoSQL pattern, the data will be
nested and the children and child methods can be used to access the required
branch.
Under users, there is a list of users with user id as key and the value will be the
user model created.
Hence, the databaseRef created will point to the user’s list and can be
accessed using get() method.
databaseRef.get();
To access all the users from the list, use the getter children and convert them to
a list as shown in Code Snippet 16.
To write to a database, the set method is used. Data has to be provided in the
form of Map<String, dynamic>.
databaseRef.child('/${user.id}').set(user.toMap());
An addUser() function can be created that will retrieve user data from input
and add that to the Firebase database. Code Snippet 17 shows the code for this.
class FirebaseService {
static DatabaseReference databaseRef =
FirebaseDatabase.instance.ref('/users');
addUser(User user) {
databaseRef.child('/${user.id}').set(user.toMap());
}
}
runApp(const MyApp());
}
@override
State<FirebaseExampleScreen> createState() =>
_FirebaseExampleScreenState();
}
void addUser() {
showBottomSheet('Add User', () async {
var user = UserModel(
id: Uuid().v4(),
name: nameController.text,
email: emailController.text,
age: int.parse(ageController.text));
dbService.addUser(user);
setState(() {});
nameController.clear();
emailController.clear();
ageController.clear();
Navigator.of(context).pop();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase database Example'),
),
body: FutureBuilder<List<UserModel>>(
future: dbService.getUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(child:
CircularProgressIndicator());
}
if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return const Center(
child: Text('No Users found'),
);
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
var userData = snapshot.data![index];
return Card(
color: Colors.yellow[200],
margin: const EdgeInsets.all(15),
child: ListTile(
title: Text('${userData.name}
${userData.age}'),
subtitle: Text(userData.email),
),
);
});
}
return const Center(
child: Text('No Users found'),
);
}),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => addUser(),
),
);
}
}
So far, a function to get all users from Firebase database and a factory method
to parse the return type data to a UserModel are written.
Click the type of app required to be connected and follow the instructions as
given on the screen.
a. firebase_core
This package is used to initialize Flutter applications with Firebase. It is
responsible for establishing connections between the Firebase project and
Flutter app.
b. firebase_auth
The authentication feature from Firebase can be used by installing this package.
Add following dependencies in pubspec.yaml as shown in Code Snippet 22.
Here, in Code Snippet 24, the email and password sign-in method is being used.
To create a new registration, email, and password are used, as well as
displayName for the user.
}
Use the createUserWithEmailAndPassword function to register a new
user in Firebase. Then, to give a name to the user, update the username by the
updateDisplayName method. One can also provide phoneNumber,
photoUrl, and so on.
11.4.12 Create User Sign-in and Sign-Out
After registering, SignIn and SignOut functions must be used. Refer to Code
Snippet 25.
If the User object is null, then the user is not present and signIn has failed.
For SignOut, use the signOut() function. Refer to Code Snippet 26.
firebaseAuth.currentUser!.reload();
Design a simple login screen with email and password fields and a register
screen with name, email, and password fields. Refer to Figure 11.7.
Use of the Register and Login buttons functions are shown in Code Snippets 27
and 28.
Code Snippet 27: Register functionality in register_screen.dart
ElevatedButton(
onPressed: () async {
bool isRegistered = await
firebaseService.register(nameController.text,
emailController.text,
passController.text);
if (isRegistered) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FirebaseExampleScreen()));
} else {
log('registration failed');
}
},
child: Text('Register'),
),
The email and displayName can be fetched using Code Snippet 29.
Code Snippet 29
final fbService = FirebaseService();
fbService.firebaseAuth.currentUser!.email
fbService.firebaseAuth.currentUser!.displayName
FirebaseService().firebaseAuth.authStateChanges().listen((use
r) {
if (user == null) {
runApp(const MyApp(isLogin: false));
} else {
runApp(const MyApp(isLogin: true));
}
});
Code Snippet 31: main.dart
. . .
class MyApp extends StatelessWidget {
final bool isLogin;
const MyApp({Key? key, required this.isLogin}) : super(key:
key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Firebase Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: isLogin ? FirebaseExampleScreen() : LoginScreen(),
);
}
}
In this manner, developers can use Firebase to create and manipulate real-time
data in Flutter applications. Many practical real-world applications make use of
this.
11.5 Summary
➢ SQLite is a relational DBMS through which data can be stored in the form
of tables. SQLite in Flutter helps in easy access and performing complex
queries on the locally stored data.
➢ Flutter has sqflite plugin using which data can be stored locally on
user device as Relational Tables.
➢ All operations related to database are Future.
➢ Both raw queries and helper functions can be used to write SQL queries.
➢ Firebase offers a lot of features and is a good option for mobile backend.
➢ Real-time database offered by Flutter is a NoSQL database where data
will be stored in the form of JSON.
➢ Return type of Firebase Database will be DataSnapshot.
➢ Firebase Auth can be used to authenticate users of an application.
➢ Firebase auth has many sign-in methods such as email and phone
number, and GoogleSignIn.
➢ Firebase auth also has the option to retain the auth state of the user in
the application.
➢ Firebase offers multiple features, thus, it is fully capable of being used as
a backend for mobile and cross platform apps.
11.6 Test Your Knowledge
1) SQLite is a _____________.
a. Relational Database
b. NoSQL Database
c. Cloud Database
d. Object-Oriented Database
2) Which one of following is the correct code to insert data in a table using
sqflite?
a. db.rawUpdate('UPDATE Table(name, age)
VALUES(?,?)', [name, age]);
b. db.rawInsert('INSERT INTO Table(name, age)
VALUES(?,?)', [name, age]);
c. db.insert('INSERT INTO Table(name, age)
VALUES(?,?)', [name, age]);
d. db.rawQuery('INSERT INTO Table(name, age)
VALUES(?,?)', [name, age]);
1. Relational Database
2. db.rawInsert(‘INSERT INTO Table(name, age)
VALUES(?,?)’, [name, age]);
3. Internationalization
4. createUserWithEmailAndPassword(email: email,
password: password);
5. firebase_core, firebase_auth, firebase_database
11.7 Try It Yourself
Session Goals:
The session introduces concepts of REST Application Programming Interfaces
(APIs), describes their usage in Flutter programs, and explains core functions
of the HTTP component request, response, headers, body, and other essential
parameters of HTTP requests. In addition, examples of how to fetch data via
REST APIs inside a Flutter app, Registration, and Login employing REST APIs
are included.
APIs are the most popular form of data that can be integrated with applications.
Any API that adheres to the restrictions of a REST Architecture style and
permits communication among RESTful Web services is defined as a REST API
(often called RESTful API). In simple terms, APIs are the communication bridge
between the front and back end. Backend data will be shared as REST APIs,
which can be consumed in the front end.
Flutter offers some packages, such as http and Dio, through which one can
make API calls and fetch data. These data could be JSON (most common), XML,
or simple String, which could be modeled as per convenience.
HTTP module supplies top-level classes with http in making Web requests.
These functions take a URL and other content via Dart Map (post metadata,
extra headers, and more). Then, it sends a query to the host and waits for the
answer before sending it back.
Code Snippet 1:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
http: ^0.13.5
Postman is the best tool to test an API. Utilize this to see how APIs return the
information. Check the Postman official site at www.postman.com to discover
more regarding it.
12.3 Core Methods of http Package
http delivers better standard HTTP client classes. Clients are automatically
initialized whenever requests are created and are closed once requests are
complete.
Classes in the http package deliver functionality to serve all sorts of HTTP
requests.
12.3.1 Read
Utilize the READ function to make the necessary URL requests, then provide the
reply as a Future<String>.
12.3.2 Get
Use the GET function to send a query to the URL and then, return the reply as a
Future<Response>. Individual 'response' is a class containing the response
information.
The difference between READ and GET lies in the response. Read returns only
the body's response, whereas GET returns a response class that contains
headers, body, statusCode, contentLength, bodyBytes, and many
more.
12.3.3 Post
Call URL using POST by submitting supplied information and producing
responses as Future<Response>.
package:http/http.dart
…
Future<Response> post(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
})
Type: Future<Response> Function(Uri, {Object? body, Encoding?
encoding, Map<String, String>? headers})
Here, the post method transmits HTTP POST queries to a specified link,
including described headers and the content of the body. The body places the
body of the request. It could be String, Map<String, String>, or List.
When it is String, encoding is appended to it before being utilized as one
content of the request. The query's standard content style is text/plain.
Each query body is treated as a byte collection when the body would be List.
When the body would be Map, encoding transforms it into form fields.
application/x-www-form-urlencoded would be utilized as content of
the query types. Also, it cannot change. By design, encoding uses utf8. Utilize
Request or even StreamedRequest for extra granular hold well across
requests.
12.3.4 Put
Use a PUT function to make a query to the supplied link, and then, revert the
reply as Future<Response>.
12.3.5 Head
Use a HEAD function to make a query to the supplied URLs and then deliver the
result as Future<Response>.
12.3.6 Delete
Use a DELETE function to make a request for the provided Hyperlink and
produce replies as Future<Response>.
Let us implement a simple yet interesting API to fetch data from a Web server
using REST API.
Cat Fact API is a readymade API that contains a list of APIs which will return
some facts about cats.
The first API from this documentation is a GET method which will return a
random fact about cats each time it is called as shown here:
// GET METHOD
// https://catfact.ninja/fact
response:
{
"fact": "string",
"length": 0
}
The response is simple with a fact of type String and the length of the fact as
an integer. Figure 12.1 shows the output that will be generated by the
application.
class ApiCall {
Future<String?> getRandomFact() async {
var response = await
http.get(Uri.parse('https://catfact.ninja/fact'));
print(response);
String result = jsonDecode(response.body)['fact'];
print(result);
return result;
}
}
Step 8: Create a simple UI. The body must be a FutureBuilder, where one
will call the getRandomFact() function and display the fact in a Text Widget.
Refer to Code Snippet 3.
import 'fact_screen.dart';
...
home: const FactScreen(),
API will be successfully called using the HTTP function and the fact displayed
inside the UI.
Code Snippet 4:
...
Future<bool> registerAPI(String uname, String e-mail, String
password) async {
var response = await http.post(
Uri.parse('http://restapi.adequateshop.com/api/authaccount/regi
stration'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'name': uname, 'email': e-mail,
'password': password}),
);
var result = jsonDecode(response.body);
print(result);
if (result['message'] == 'success') {
return true;
}
return false;
}
Step 6: Create a simple Registration Screen UI, where username, e-mail, and
password are fetched using TextField and TextEditingControllers.
Step 7: Add following method to the register button. The register API function
is called Future and is based on the return bool type and navigation is
handled. Refer to Code Snippet 5.
Code Snippet 5:
...
ElevatedButton(
onPressed: () async {
bool isRegistered = await
ApiCall().registerAPI(
nameController.text,
emailController.text,
passController.text);
if (isRegistered) {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
FactScreen()));
} else {
log('registration failed');
}
},
child: Text('Register'),
),
Note that for the application to work successfully, a unique username and
password must be supplied during registration. If the username and password
are already taken, the application will not proceed.
Code Snippet 6:
/* POST Function
Url:
http://restapi.adequateshop.com/api/authaccount/registration*/
//Request body:
{
Additionally, the reply includes big data, which could be modeled and operated
throughout the app. Nevertheless, only registration success or none would be
considered.
Code Snippet 7:
//POST Function
//Url: http://restapi.adequateshop.com/api/authaccount/login
//Request body:
{
"email":"Input email",
"password":"Enter the password"
}
//Content-Type: application/json
//Example API Response
{
"$id": "01",
"code": 0,
"message": "success",
"data": {
"$id": "02",
"Id": 7075,
"Name": "App Developer",
"Email": "App_Developer1@gmail.com",
"Token": "02b869e4-ea45-4b5c-b764-642a39e95bb7"
}
}
//API Reply when email or password is incorrect
{
"$id": "01",
"code": 1,
"message": "Mail or perhaps Passwords are inexact.",
"data": null
}
Output of Code Snippets 4 and 5 for a simple Sign-up and Login Pages are shown
in Figure 12.2. Once authenticated, one might be redirected to a FactScreen.
Figure 12.2: Output of Sign-up and Login Screens
Code Snippet 8:
Future<bool> loginAPI(String e-mail, String password) async {
var response = await http.post(
Uri.parse('http://restapi.adequateshop.com/api/authaccount/login
'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': e-mail, 'password':
password}),
);
var result_1 = jsonDecode(response.body);
print(result_1);
if (result_1['message'] == 'success') {
return true;
}
return false;
}
Code Snippet 9:
ElevatedButton(
onPressed: () async {
bool isLogin = await
ApiCall().loginAPI(emailController.text, passController.text);
if (isLogin) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FactScreen()));
} else {
log('login failed'); }
},
child: Text('Login'),
),
Additionally, the auth state can be maintained using local storage similar
SharePreferences. Once logged in, store the login state in a boolean value
and keep it in SharedPreferences. So based on that value, one will be
logged in directly and it will no longer be necessary to pass the login page.
12.7 Summary
➢ Representational State Transfer - Application Programming Interface is
what REST API means.
➢ Flutter has packages comparable to http, dio, and chopper to
communicate with APIs.
➢ The HTTP package provides class functionalities to perform GET, POST,
PUT, PATCH, and DELETE methods.
➢ HTTP requests are asynchronous functions, and hence, they are Future
functions.
➢ FutureBuilder is recommended for displaying HTTP responses in the
UI.
➢ Dart model can simplify the reading and processing of HTTP responses.
➢ There are secure APIs that would verify the user token each time request
is made.
➢ For Authentication APIs, SharedPreferences can be used to store
user info and auth state of the particular user in a device.
12.8 Test Your Knowledge
3) What is the correct code to retrieve the length as Strings from the JSON
response beneath?
{
"fact": "string",
"length": 0
}
a. jsonDecode(response.body)["length"].toString();
b. response.body["length"];
c. response["length"];
d. jsonDecode(response)["length"].toString();
1. All of these
2. Default content-type for HTTP requests are application/json
3. jsonDecode(response.body)["length"].toString();
4. Option - b
5. They have a difference in response type
12.9 Try It Yourself
1) For the application given in the session, add a new Text Widget that
displays Login Failed when login is unsuccessful.
2) Go to https://openweathermap.org/api. Read the documentation
carefully and use the APIs in a new Flutter Weather Application.
a) Design an attractive UI with Splash Screen, Home Screen where
Weather Data including details such as Humidity, Temperature,
and Weather Status will be displayed.
b) UI should contain graphics as per the weather that is Rainy, Cloudy,
Hot, and so on.
c) Get Location Permission from User while opening the app and
display the weather data as per their location.
3) In the Same Weather app, add a search feature, where users can get
weather data for a particular city. OpenWeatherMap has an API for this
feature.