Flutter Tutorial PDF
Flutter Tutorial PDF
Flutter Tutorial PDF
This tutorial walks through the basics of Flutter framework, installation of Flutter SDK,
setting up Android Studio to develop Flutter based application, architecture of Flutter
framework and developing all type of mobile applications using Flutter framework.
Audience
This tutorial is prepared for professionals who are aspiring to make a career in the field of
mobile applications. This tutorial is intended to make you comfortable in getting started
with Flutter framework and its various functionalities.
Prerequisites
This tutorial is written assuming that the readers are already aware about what a
Framework is and that the readers have a sound knowledge on Object Oriented
Programming and basic knowledge on Android framework and Dart programming.
If you are a beginner to any of these concepts, we suggest you to go through tutorials
related to these first, before you start with Flutter.
All the content and graphics published in this e-book are the property of Tutorials Point (I)
Pvt. Ltd. The user of this e-book is prohibited to reuse, retain, copy, distribute or republish
any contents or a part of contents of this e-book in any manner without written consent
of the publisher.
We strive to update the contents of our website and tutorials as timely and as precisely as
possible, however, the contents may contain inaccuracies or errors. Tutorials Point (I) Pvt.
Ltd. provides no guarantee regarding the accuracy, timeliness or completeness of our
website or its contents including this tutorial. If you discover any errors on our website or
in this tutorial, please notify us at [email protected]
i
Flutter
Table of Contents
About the Tutorial ............................................................................................................................................ i
Audience........................................................................................................................................................... i
Prerequisites..................................................................................................................................................... i
Table of Contents............................................................................................................................................. ii
2. FLUTTER – INSTALLATION..................................................................................................... 3
Widgets ......................................................................................................................................................... 12
Gestures ........................................................................................................................................................ 13
Layers ............................................................................................................................................................ 13
Functions ....................................................................................................................................................... 16
ii
Flutter
Introduction................................................................................................................................................... 82
iii
Flutter
iv
1. Flutter – Introduction
In general, developing a mobile application is a complex and challenging task. There are
many frameworks available to develop a mobile application. Android provides a native
framework based on Java language and iOS provides a native framework based on
Objective-C / Shift language.
However, to develop an application supporting both the OSs, we need to code in two
different languages using two different frameworks. To help overcome this complexity,
there exists mobile frameworks supporting both OS. These frameworks range from simple
HTML based hybrid mobile application framework (which uses HTML for User Interface and
JavaScript for application logic) to complex language specific framework (which do the
heavy lifting of converting code to native code). Irrespective of their simplicity or
complexity, these frameworks always have many disadvantages, one of the main
drawback being their slow performance.
In this scenario, Flutter – a simple and high performance framework based on Dart
language, provides high performance by rendering the UI directly in the operating system’s
canvas rather than through native framework.
Flutter also offers many ready to use widgets (UI) to create a modern application. These
widgets are optimized for mobile environment and designing the application using widgets
is as simple as designing HTML.
Features of Flutter
Flutter framework offers the following features to developers:
1
Flutter
Advantages of Flutter
Flutter comes with beautiful and customizable widgets for high performance and
outstanding mobile application. It fulfills all the custom needs and requirements. Besides
these, Flutter offers many more advantages as mentioned below:
Dart has a large repository of software packages which lets you to extend the
capabilities of your application.
Developers need to write just a single code base for both applications (both Android
and iOS platforms). Flutter may to be extended to other platform as well in the future.
Flutter needs lesser testing. Because of its single code base, it is sufficient if we write
automated tests once for both the platforms.
Flutter’s simplicity makes it a good candidate for fast development. Its customization
capability and extendibility makes it even more powerful.
With Flutter, developers has full control over the widgets and its layout.
Disadvantages of Flutter
Despite its many advantages, flutter has the following drawbacks in it:
Since it is coded in Dart language, a developer needs to learn new language (though
it is easy to learn).
Modern framework tries to separate logic and UI as much as possible but, in Flutter,
user interface and logic is intermixed. We can overcome this using smart coding and
using high level module to separate user interface and logic.
Flutter is yet another framework to create mobile application. Developers are having
a hard time in choosing the right development tools in hugely populated segment.
2
2. Flutter – Installation Flutter
This chapter will guide you through the installation of Flutter on your local computer in
detail.
Installation in Windows
In this section, let us see how to install Flutter SDK and its requirement in a windows
system.
Step 4: Flutter provides a tool, flutter doctor to check that all the requirement of flutter
development is met.
flutter doctor
Step 5: Running the above command will analyze the system and show its report as
shown below:
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, v1.2.1, on Microsoft Windows [Version
10.0.17134.706], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version
28.0.3)
[√] Android Studio (version 3.2)
[√] VS Code, 64-bit edition (version 1.29.1)
[!] Connected device
! No devices available
The report says that all development tools are available but the device is not connected.
We can fix this by connecting an android device through USB or starting an android
emulator.
Step 8: Start an android emulator or connect a real android device to the system.
Step 9: Install Flutter and Dart plugin for Android Studio. It provides startup template to
create new Flutter application, an option to run and debug Flutter application in the Android
studio itself, etc.,
3
Flutter
Installation in MacOS
To install Flutter on MacOS, you will have to follow the following steps:
Step 3: Update the system path to include flutter bin directory (in ~/.bashrc file).
Step 4: Enable the updated path in the current session using below command and then
verify it as well.
source ~/.bashrc
source $HOME/.bash_profile
echo $PATH
Flutter provides a tool, flutter doctor to check that all the requirement of flutter
development is met. It is similar to the Windows counterpart.
Step 8: Start an android emulator or connect a real android device to the system to
develop android application.
Step 9: Open iOS simulator or connect a real iPhone device to the system to develop iOS
application.
Step 10: Install Flutter and Dart plugin for Android Studio. It provides the startup
template to create a new Flutter application, option to run and debug Flutter application
in the Android studio itself, etc.,
4
3. Flutter – Creating Simple Application in Flutter
Android Studio
In this chapter, let us create a simple Flutter application to understand the basics of
creating a flutter application in the Android Studio.
Step 2: Create Flutter Project. For this, click File -> New -> New Flutter Project
5
Flutter
Step 3: Select Flutter Application. For this, select Flutter Application and click Next.
6
Flutter
Android Studio creates a fully working flutter application with minimal functionality.
Let us check the structure of the application and then, change the code to do our
task.
7
Flutter
lib - Main folder containing Dart code written using flutter framework
8
Flutter
Step 7: Replace the dart code in the lib/main.dart file with the below code:
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)
),
);
}
}
Line 1: imports the flutter package, material. The material is a flutter package
to create user interface according to the Material design guidelines specified
by Android.
Line 3: This is the entry point of the Flutter application. Calls runApp function
and pass it an object of MyApp class. The purpose of the runApp function is to
attach the given widget to the screen.
9
Flutter
method is to create a part of the UI of the application. Here, build method uses
MaterialApp, a widget to create the root level UI of the application. It has three
properties - title, theme and home.
o theme is the theme of the widget. Here, we set blue as the overall color
of the application using ThemeData class and its property,
primarySwatch.
Step 8: Now, run the application using, Run -> Run main.dart
10
Flutter
11
4. Flutter – Architecture of Flutter Application Flutter
Widgets
The core concept of the Flutter framework is In Flutter, Everything is a widget. Widgets
are basically user interface components used to create the user interface of the application.
In Flutter, the application is itself a widget. The application is the top- level widget and its
UI is build using one or more children (widgets), which again build using its children
widgets. This composability feature helps us to create a user interface of any complexity.
For example, the widget hierarchy of the hello world application (created in previous
chapter) is as specified in the following diagram:
12
Flutter
MyApp is the user created widget and it is build using the Flutter native widget,
MaterialApp.
MaterialApp has a home property to specify the user interface of the home page,
which is again a user created widget, MyHomePage.
body is used to specify its main user interface and appBar is used to specify its header
user interface.
Header UI is build using flutter native widget, AppBar and Body UI is build using
Center widget.
The Center widget has a property, Child, which refers the actual content and it is build
using Text widget.
Gestures
Flutter widgets support interaction through a special widget, GestureDetector.
GestureDetector is an invisible widget having the ability to capture user interactions such
as tapping, dragging, etc., of its child widget. Many native widgets of Flutter support
interaction through the use of GestureDetector. We can also incorporate interactive feature
into the existing widget by composing it with the GestureDetector widget. We will learn
the gestures separately in the upcoming chapters.
Concept of State
Flutter widgets support State maintenance by providing a special widget, StatefulWidget.
Widget needs to be derived from StatefulWidget widget to support state maintenance and
all other widget should be derived from StatelessWidget. Flutter widgets are reactive in
native. This is similar to reactjs and StatefulWidget will be auto re- rendered whenever its
internal state is changed. The re-rendering is optimized by finding the difference between
old and new widget UI and rendering only the necessary changes.
Layers
The most important concept of Flutter framework is that the framework is grouped into
multiple category in terms of complexity and clearly arranged in layers of decreasing
complexity. A layer is build using its immediate next level layer. The top most layer is
widget specific to Android and iOS. The next layer has all flutter native widgets. The next
layer is Rendering layer, which is low level renderer component and renders everything in
the flutter app. Layers goes down to core platform specific code.
13
Flutter
Flutter offers layered design so that any layer can be programmed depending on the
complexity of the task.
14
5. Flutter – Introduction to Dart Programming Flutter
void main()
{
print("Dart language is easy to learn");
}
Dart uses var keyword to declare the variable. The syntax of var is defined below,
The final and const keyword are used to declare constants. They are defined as below:
void main() {
final a = 12;
const pi = 3.14;
print(a);
print(pi);
}
Booleans: Dart uses the bool keyword to represent Boolean values – true and false.
Lists and Maps: It is used to represent a collection of objects. A simple List can be
defined as below:
void main() {
var list = [1,2,3,4,5];
print(list);
}
15
Flutter
void main() {
var mapping = {'id': 1,'name':'Dart'};
print(mapping);
}
Dynamic: If the variable type is not defined, then its default type is dynamic. The
following example illustrates the dynamic type variable:
void main() {
dynamic name = "Dart";
print(name);
}
Loops are used to repeat a block of code until a specific condition is met. Dart supports
for, for..in , while and do..while loops.
Let us understand a simple example about the usage of control statements and loops:
void main() {
for( var i = 1 ; i <= 10; i++ ) {
if(i%2==0)
{
print(i);
}
}
}
Functions
A function is a group of statements that together performs a specific task. Let us look into
a simple function in Dart as shown here:
void main() {
add(3,4);
}
16
Flutter
The above function adds two values and produces 7 as the output.
A class is a blueprint for creating objects. A class definition includes the following:
Fields
Constructors
Functions
class Employee {
String name;
//getter method
String get emp_name {
return name;
}
//setter method
void set emp_name(String name) {
this.name = name;
}
//function definition
void result()
{
print(name);
}
}
void main() {
//object creation
Employee emp = new Employee();
emp.name="employee1";
emp.result(); //function call
}
17
6. Flutter – Introduction to Widgets Flutter
As we learned in the earlier chapter, widgets are everything in Flutter framework. We have
already learned how to create new widgets in previous chapters.
In this chapter, let us understand the actual concept behind creating the widgets and the
different type of widgets available in Flutter framework.
Let us check the Hello World application’s MyHomePage widget. The code for this purpose
is as given below:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Text(
'Hello World',
)),
);
}
}
Note that the StatelessWidget only requires a single method build to be implemented in
its derived class. The build method gets the context environment necessary to build the
widgets through BuildContext parameter and returns the widget it builds.
In the code, we have used title as one of the constructor argument and also used Key as
another argument. The title is used to display the title and Key is used to identify the
widget in the build environment.
Here, the build method calls the build method of Scaffold, which in turn calls the build
method of AppBar and Center to build its user interface.
18
Flutter
For a better understanding, the visual representation of the same is given below:
Android specific widgets are designed in accordance with Material design guideline by
Android OS. Android specific widgets are called as Material widgets.
iOS specific widgets are designed in accordance with Human Interface Guidelines by Apple
and they are called as Cupertino widgets.
Scaffold
AppBar
19
Flutter
BottomNavigationBar
TabBar
TabBarView
ListTile
RaisedButton
FloatingActionButton
FlatButton
IconButton
DropdownButton
PopupMenuButton
ButtonBar
TextField
Checkbox
Radio
Switch
Slider
Date & Time Pickers
SimpleDialog
AlertDialog
CupertinoButton
CupertinoPicker
CupertinoDatePicker
CupertinoTimerPicker
CupertinoNavigationBar
CupertinoTabBar
CupertinoTabScaffold
CupertinoTabView
CupertinoTextField
CupertinoDialog
CupertinoDialogAction
CupertinoFullscreenDialogTransition
CupertinoPageScaffold
CupertinoPageTransition
CupertinoActionSheet
CupertinoActivityIndicator
CupertinoAlertDialog
CupertinoPopupSurface
20
Flutter
CupertinoSlider
Layout widgets
In Flutter, a widget can be created by composing one or more widgets. To compose
multiple widgets into a single widget, Flutter provides large number of widgets with layout
feature. For example, the child widget can be centered using Center widget.
We will check the layout widgets in detail in the upcoming Introduction to layout widgets
chapter.
Widget derived from StatelessWidget does not have any state information but it may
contain widget derived from StatefulWidget. The dynamic nature of the application is
through interactive behavior of the widgets and the state changes during interaction. For
example, tapping a counter button will increase / decrease the internal state of the counter
by one and reactive nature of the Flutter widget will auto re-render the widget using new
state information.
We will learn the concept of StatefulWidget widgets in detail in the upcoming State
management chapter.
Text
Text widget is used to display a piece of string. The style of the string can be set by using
style property and TextStyle class. The sample code for this purpose is as follows:
Text widget has a special constructor, Text.rich, which accepts the child of type TextSpan
to specify the string with different style. TextSpan widget is recursive in nature and it
accepts TextSpan as its children. The sample code for this purpose is as follows:
21
Flutter
Text.rich(
TextSpan(
children: <TextSpan>[
TextSpan(text: "Hello ", style: TextStyle(fontStyle:
FontStyle.italic)),
TextSpan(text: "World", style: TextStyle(fontWeight: FontWeight.bold)),
],
),
)
style, TextStyle: Specify the style of the string using TextStyle class
textAlign, TextAlign: Alignment of the text like right, left, justify, etc., using
TextAlign class
Image
Image widget is used to display an image in the application. Image widget provides
different constructors to load images from multiple sources and they are as follows:
The easiest option to load and display an image in Flutter is by including the image as
assets of the application and load it into the widget on demand.
Create a folder, assets in the project folder and place the necessary images.
flutter:
assets:
- assets/smiley.png
Image.asset('assets/smiley.png')
22
Flutter
The complete source code of MyHomePage widget of the hello world application and
the result is as shown below:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Image.asset("assets/smiley.png")
),
);
}
23
Flutter
Icon
Icon widget is used to display a glyph from a font described in IconData class. The code
to load a simple email icon is as follows:
Icon(Icons.email)
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Icon(Icons.email)
),
);
}
}
24
Flutter
25
7. Flutter – Introduction to Layouts Flutter
Since the core concept of Flutter is Everything is widget, Flutter incorporates a user
interface layout functionality into the widgets itself. Flutter provides quite a lot of specially
designed widgets like Container, Center, Align, etc., only for the purpose of laying out the
user interface. Widgets build by composing other widgets normally use layout widgets. Let
use learn the Flutter layout concept in this chapter.
Let us learn both type of widgets and its functionality in the upcoming sections.
For example, Center widget just centers it child widget with respect to its parent widget
and Container widget provides complete flexibility to place it child at any given place inside
it using different option like padding, decoration, etc.,
Single child widgets are great options to create high quality widget having single
functionality such as button, label, etc.,
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
left: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
right: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
bottom: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
),
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
26
Flutter
Here, we have used two widgets – a Container widget and a Text widget. The result of the
widget is as a custom button as shown below:
Let us check some of the most important single child layout widgets provided by Flutter:
Padding: Used to arrange its child widget by the given padding. Here, padding can
be provided by EdgeInsets class.
Align: Align its child widget within itself using the value of alignment property. The
value for alignment property can be provided by FractionalOffset class. The
FractionalOffset class specifies the offsets in terms of a distance from the top left.
Center(
child: Container(
height: 100.0,
width: 100.0,
color: Colors.yellow,
child: Align(
alignment: FractionalOffset(0.2, 0.6),
child: Container(
height: 40.0,
width: 40.0,
color: Colors.red,
),
),
),
)
FittedBox: It scales the child widget and then positions it according to the specified
fit.
AspectRatio: It attempts to size the child widget to the specified aspect ratio
27
Flutter
ConstrainedBox
Baseline
FractinallySizedBox
IntrinsicHeight
IntrinsicWidth
LiimitedBox
OffStage
OverflowBox
SizedBox
SizedOverflowBox
Transform
CustomSingleChildLayout
Our hello world application is using material based layout widgets to design the home
page. Let us modify our hello world application to build the home page using basic layout
widgets as specified below:
Container: Generic, single child, box based container widget with alignment,
padding, border and margin along with rich styling features.
Center: Simple, Single child container widget, which centers its child widget.
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
28
Flutter
color: Colors.white,
),
padding: EdgeInsets.all(25),
child: Center(child:
Text(
'Hello World',
style: TextStyle(
color: Colors.black,
letterSpacing: 0.5,
fontSize: 20,
),
textDirection: TextDirection.ltr,
),
));
}
}
Here,
Container widget is the top level or root widget. Container is configured using
decoration and padding property to layout its content.
BoxDecoration has many properties like color, border, etc., to decorate the Container
widget and here, color is used to set the color of the container.
padding of the Container widget is set by using dgeInsets class, which provides the
option to specify the padding value.
Center is the child widget of the Container widget. Again, Text is the child of the
Center widget. Text is used to show message and Center is used to center the text
message with respect to the parent widget, Container.
29
Flutter
The final result of the code given above is a layout sample as shown below:
For example, Row widget allows the laying out of its children in horizontal direction,
whereas Column widget allows laying out of its children in vertical direction. By composing
Row and Column, widget with any level of complexity can be built.
Expanded - Used to make the children of Row and Column widget to occupy the
maximum possible area.
30
Flutter
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}
31
Flutter
Here,
Now, create a new widget, ProductBox according to the specified design as shown
below:
32
Flutter
o Container
o Expanded
o Row
o Column
o Card
o Text
o Image
33
Flutter
Now, place some dummy image (see below) for product information in the assets
folder of the application and configure the assets folder in the pubspec.yaml file as
shown below:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
iPhone.png
34
Flutter
Pixel.png
Laptop.png
Tablet.png
Pendrive.png
Floppy.png
Finally, Use the ProductBox widget in the MyHomePage widget as specified below:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
35
Flutter
import 'package:flutter/material.dart';
36
Flutter
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"),
],
));
}
}
37
Flutter
38
Flutter
39
8. Flutter – Introduction to Gestures Flutter
Gestures are primarily a way for a user to interact with a mobile (or any touch based
device) application. Gestures are generally defined as any physical action / movement of
a user in the intention of activating a specific control of the mobile device. Gestures are
as simple as tapping the screen of the mobile device to more complex actions used in
gaming applications.
Tap: Touching the surface of the device with fingertip for a short period and then
releasing the fingertip.
Drag: Touching the surface of the device with fingertip and then moving the
fingertip in a steady manner and then finally releasing the fingertip.
Panning: Touching the surface of the device with fingertip and moving it in any
direction without releasing the fingertip.
Flutter provides an excellent support for all type of gestures through its exclusive widget,
GestureDetector. GestureDetector is a non-visual widget primarily used for detecting the
user’s gesture. To identify a gesture targeted on a widget, the widget can be placed inside
GestureDetector widget. GestureDetector will capture the gesture and dispatch multiple
events based on the gesture.
Some of the gestures and the corresponding events are given below:
Tap
o onTapDown
o onTapUp
o onTap
o onTapCancel
Double tap
o onDoubleTap
Long press
o onLongPress
40
Flutter
Vertical drag
o onVerticalDragStart
o onVerticalDragUpdate
o onVerticalDragEnd
Horizontal drag
o onHorizontalDragStart
o onHorizontalDragUpdate
o onHorizontalDragEnd
Pan
o onPanStart
o onPanUpdate
o onPanEnd
Now, let us modify the hello world application to include gesture detection feature and try
to understand the concept.
body: Center(
child: GestureDetector(
onTap: () {
_showDialog(context);
},
child: Text(
'Hello World',
)
)
),
Observe that here we have placed the GestureDetector widget above the Text
widget in the widget hierarchy, captured the onTap event and then finally shown a
dialog window.
Implement the *_showDialog* function to present a dialog when user tabs the hello
world message. It uses the generic showDialog and AlertDialog widget to create a
new dialog widget. The code is shown below:
41
Flutter
return AlertDialog(
title: new Text("Message"),
content: new Text("Hello World"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
The application will reload in the device using Hot Reload feature. Now, simply click
the message, Hello World and it will show the dialog as below:
Now, close the dialog by clicking the close option in the dialog.
42
Flutter
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
43
Flutter
body: Center(
child: GestureDetector(
onTap: () {
_showDialog(context);
},
child: Text(
'Hello World',
))),
);
}
}
Finally, Flutter also provides a low-level gesture detection mechanism through Listener
widget. It will detect all user interactions and then dispatches the following events:
PointerDownEvent
PointerMoveEvent
PointerUpEvent
PointerCancelEvent
Flutter also provides a small set of widgets to do specific as well as advanced gestures.
The widgets are listed below:
IgnorePointer: Hides the widget and its children from the gesture detection
process.
AbsorbPointer: Stops the gesture detection process itself and so any overlapping
widget also can not able to participate in the gesture detection process and hence,
no event is raised.
44
9. Flutter – State Management Flutter
Managing state in an application is one of the most important and necessary process in
the life cycle of an application.
Once user is logged in, the application should persist the logged in user detail in all
the screen.
Again, when the user selects a product and saved into a cart, the cart information
should persist between the pages until the user checked out the cart.
User and their cart information at any instance is called the state of the application
at that instance.
A state management can be divided into two categories based on the duration the
particular state lasts in an application.
Ephemeral - Last for a few seconds like the current state of an animation or a single
page like current rating of a product. Flutter supports its through StatefulWidget.
app state - Last for entire application like logged in user details, cart information,
etc., Flutter supports its through scoped_model.
Let us create a widget, RatingBox with state maintenance. The purpose of the widget is to
show the current rating of a specific product. The step by step process for creating a
RatingBox widget with state maintenance is as follows:
45
Flutter
Create the user interface of the RatingBox widget in build method of _RatingBoxState.
Usually, the user interface will be done in the build method of RatingBox widget itself. But,
when state maintenance is needed, we need to build the user interface in _RatingBoxState
widget. This ensures the re-rendering of user interface whenever the state of the widget
is changed.
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
iconSize: _size,
),
),
],
);
}
46
Flutter
Here, we have used three star, created using IconButton widget and arranged it using Row
widget in a single row. The idea is to show the rating through the sequence of red stars.
For example, if the rating is two star, then first two star will be red and the last one is in
white.
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
Here, each method sets the current rating of the widget through setState
Wire the user gesture (tapping the star) to the proper state changing method.
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
47
Flutter
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
Here, the onPressed event calls the relevant function to change the state and subsequently
change the user interface. For example, if a user clicks the third star, then
_setRatingAsThree will be called and it will change the _rating to 3. Since the state is
changed, the build method will be called again and the user interface will be build and
rendered again.
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
48
Flutter
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Let us create a new application and use our newly created RatingBox widget to show the
rating of the product.
49
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}
Here,
Create a ProductBox widget to list the product along with rating as specified below:
50
Flutter
padding: EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.description),
Text("Price: " + this.price.toString()),
RatingBox(),
],
)))
])));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most feature phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
51
Flutter
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage
medium",
price: 20,
image: "floppy.png"),
],
));
}
}
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
52
Flutter
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "iPhone is the stylist phone ever",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "iPhone is the stylist phone ever",
price: 20,
image: "floppy.png"),
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
],
));
}
}
53
Flutter
int _rating = 0;
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
54
Flutter
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
55
Flutter
Finally, run the application and see the State management - Product list page
Results as shown here:
56
Flutter
Clicking the rating star will update the rating of the product. For example, setting 2-star
rating for iPhone will display the rating as below:
Model
Model encapsulates the state of an application. We can use as many Model (by inheriting
Model class) as needed to maintain the application state. It has a single method,
notifyListeners, which needs to be called whenever the Model state changes.
notifyListeners will do necessary things to update the UI.
57
Flutter
notifyListeners();
}
}
ScopedModel
ScopedModel is a widget, which holds the given model and then passes it to all the
descendant widget if requested. If more than one model is needed, then we need to nest
the ScopedModel.
Single model
ScopedModel<Product>(
model: item,
child: AnyWidget()
)
Multiple model
ScopedModel<Product>(
model: item1,
child: ScopedModel<Product>(
model: item2,
child: AnyWidget(),
),
)
ScopedModel.of is a method used to get the model underlying the ScopedModel. It can be
used when no UI changes are necessary even though the model is going to change. The
following will not change the UI (rating) of the product.
ScopedModel.of<Product>(context).updateRating(2);
58
Flutter
ScopedModelDescendant
ScopedModelDescendant is a widget, which gets the model from the upper level widget,
ScopedModel and build its user interface whenever the model changes.
ScopedModelDescendant has a two properties – builder and child. child is the UI part
which does not change and will be passed to builder. builder accepts a function with
three arguments:
return ScopedModelDescendant<ProductModel>(
builder: (context, child, cart) => { ... Actual UI ... },
child: PartOfTheUI(),
);
Let us change our previous sample to use the scoped_model instead of StatefulWidget
Replace the default startup code (main.dart) with our product_state_app code.
Copy the assets folder from product_nav_app to product_rest_app and add assets
inside the pubspec.yaml file
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
dependencies:
scoped_model: ^1.0.1
Here, you should use the latest version of the http package
Click Get dependencies option. Android studio will get the package from Internet
and properly configure it for the application.
59
Flutter
Replace the default startup code (main.dart) with our startup code.
import 'package:flutter/material.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model/scoped_model.dart';
60
Flutter
notifyListeners();
}
}
Here, we have used notifyListeners to change the UI whenever the rating is changed.
Let us write a method getProducts in the Product class to generate our dummy
product records.
items.add(Product(
"Pixel",
"Pixel is the most feature-full phone ever",
800,
"pixel.png", 0));
items.add(Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.png", 0));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png", 0));
items.add(Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png", 0));
items.add(Product(
61
Flutter
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png", 0));
return items;
}
import product.dart in main.dart
import 'Product.dart';
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
62
Flutter
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
Let us modify our ProductBox widget to work with Product, ScopedModel and
ScopedModelDescendant class.
63
Flutter
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:
TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
})
],
))))
]),
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
));
}
}
Product.dart
import 'package:scoped_model/scoped_model.dart';
64
Flutter
void cn
"Laptop is most productive development tool",
2000,
"laptop.png", 0));
items.add(Product(
"Tablet"cnvn,
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png", 0));
items.add(Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png", 0));
items.add(Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png", 0));
return items;
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'Product.dart';
65
Flutter
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
));
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
66
Flutter
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
67
Flutter
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:
TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
})
],
))))
]),
));
}
}
Finally, compile and run the application to see its result. It will work similar to previous
example except the application uses the scoped_model concept.
MaterialPageRoute
MaterialPageRoute is a widget used to render its UI by replacing the entire screen with a
platform specific animation.
Here, builder will accepts a function to build its content by suppling the current context of
the application.
Navigation.push
68
Flutter
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Widget()),
);
Navigation.pop
Navigation.pop is used to navigate to previous screen.
Navigator.push(context);
Copy the assets folder from product_nav_app to product_state_app and add assets
inside the pubspec.yaml file
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
Replace the default startup code (main.dart) with our startup code.
import 'package:flutter/material.dart';
69
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}
class Product {
final String name;
final String description;
final int price;
final String image;
Let us write a method getProducts in the Product class to generate our dummy
product records.
items.add(Product(
"Pixel",
"Pixel is the most feature-full phone ever",
800,
"pixel.png"));
items.add(Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.png"));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"));
items.add(Product(
"Pendrive",
"Pendrive is useful storage medium",
70
Flutter
100,
"pendrive.png"));
items.add(Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png"));
return items;
}
import product.dart in main.dart
import 'Product.dart';
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
71
Flutter
72
Flutter
Let us modify our ProductBox widget to work with our new Product class.
Let us rewrite our MyHomePage widget to work with Product model and to list all
products using ListView.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
73
Flutter
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item:
items[index]),
),
);
},
);
},
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" +
this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
74
Flutter
],
)))
]),
),
),
);
}
}
import 'package:flutter/material.dart';
class Product {
final String name;
final String description;
final int price;
final String image;
items.add(Product(
"Pixel", "Pixel is the most featureful phone ever", 800,
"pixel.png"));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"));
items.add(Product(
"Pendrive", "iPhone is the stylist phone ever", 100,
"pendrive.png"));
items.add(Product(
"Floppy Drive", "iPhone is the stylist phone ever", 20,
"floppy.png"));
items.add(Product(
"iPhone", "iPhone is the stylist phone ever", 1000,
"iphone.png"));
return items;
}
}
75
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item:
items[index]),
),
);
},
);
},
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
76
Flutter
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)))
]),
),
),
);
}
}
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
77
Flutter
_rating = 3;
});
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3
? Icon(
Icons.star,
size: _size,
)
78
Flutter
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
79
Flutter
Run the application and click any one of the product item. It will show the relevant
details page. We can move to home page by clicking back button. The product list
page and product details page of the application are shown as follows:
80
Flutter
81
10. Flutter – Animation Flutter
Introduction
Animation is a process of showing a series of images / picture in a particular order within
a specific duration to give an illusion of movement. The most important aspects of the
animation are as follows:
Animation have two distinct values: Start value and End value. The animation starts
from Start value and goes through a series of intermediate values and finally ends at
End values. For example, to animate a widget to fade away, the initial value will be
the full opacity and the final value will be the zero opacity.
The intermediate values may be linear or non-linear (curve) in nature and it can be
configured. Understand that the animation works as it is configured. Each
configuration provides a different feel to the animation. For example, fading a widget
will be linear in nature whereas bouncing of a ball will be non-linear in nature.
The duration of the animation process affects the speed (slowness or fastness) of the
animation.
The ability to control the animation process like starting the animation, stopping the
animation, repeating the animation to set number of times, reversing the process of
animation, etc.,
In Flutter, animation system does not do any real animation. Instead, it provides only
the values required at every frame to render the images.
Animation
Generates interpolated values between two numbers over a certain duration. The most
common Animation classes are:
82
Flutter
Here, controller controls the animation and duration option controls the duration of
the animation process. vsync is a special option used to optimize the resource used
in the animation.
CurvedAnimation
Similar to AnimationController but supports non-linear animation. CurvedAnimation can
be used along with Animation object as below:
Tween<T>
Derived from Animatable<T> and used to generate numbers between any two numbers
other than 0 and 1. It can be used along with Animation object by using animate method
and passing actual Animation object.
Here, controller is the actual animation controller. curve provides the type of non-linearity
and the customTween provides custom range from 0 to 255.
Define and start the animation controller in the initState of the StatefulWidget.
Add animation based listener, addListener to change the state of the widget
83
Flutter
child: Container(
height: animation.value,
width: animation.value,
child: <Widget>,
)
Working Application
Let us write a simple animation based application to understand the concept of animation
in Flutter framework.
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
import 'package:flutter/material.dart';
84
Flutter
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(seconds: 10),
vsync: this);
animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
controller.forward();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
Here,
85
Flutter
: super(key: key);
86
Flutter
value, animation.value to set the opacity of the child widget. In effect, the widget
will animate the child widget using opacity concept.
Finally, create the MyHomePage widget and use the animation object to animate
any of its content.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(child: ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
opacity: animation),
MyAnimatedWidget(child: ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
animation: animation),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage
medium",
price: 20,
image: "floppy.png"),
],
87
Flutter
));
}
}
Here, we have used FadeAnimation and MyAnimationWidget to animate the first two
items in the list. FadeAnimation is a build-in animation class, which we used to
animate its child using opacity concept.
import 'package:flutter/material.dart';
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(seconds:
10), vsync: this);
animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
controller.forward();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
88
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(child: ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
opacity: animation),
MyAnimatedWidget(child: ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
animation: animation),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage
medium",
price: 20,
image: "floppy.png"),
],
));
}
}
89
Flutter
90
Flutter
Compile and run the application to see the results. The initial and final version of
the application is as follows:
91
Flutter
92
11. Flutter – Writing Android Specific Code Flutter
Flutter provides a general framework to access platform specific feature. This enables the
developer to extend the functionality of the Flutter framework using platform specific code.
Platform specific functionality like camera, battery level, browser, etc., can be accessed
easily through the framework.
The general idea of accessing the platform specific code is through simple messaging
protocol. Flutter code, Client and the platform code and Host binds to a common Message
Channel. Client sends message to the Host through the Message Channel. Host listens on
the Message Channel, receives the message and does the necessary functionality and
finally, returns the result to the Client through Message Channel.
The platform specific code architecture is shown in the block diagram given below:
Let us write a simple application to open a browser using Android SDK and understand
how to invoke SDK from flutter application.
import 'package:flutter/material.dart';
93
Flutter
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: null,
),
),
);
}
}
Here, we have created a new button to open the browser and set its onPressed
method as null.
import 'dart:async';
import 'package:flutter/services.dart';
94
Flutter
onPressed: _openBrowser,
Open MainActivity.java (inside the android folder) and import the required library:
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;
activity.startActivity(intent);
result.success((Object) true);
}
Write android specific code to set message handling in the onCreate method.
if (call.method.equals("openBrowser")) {
95
Flutter
}
});
Here, we have created a message channel using MethodChannel class and used
MethodCallHandler class to handle the message. onMethodCall is the actual method
responsible for calling the correct platform specific code by the checking the message.
onMethodCall method extracts the url from message and then invokes the openBrowser
only when the method call is openBrowser. Otherwise, it returns notImplemented method.
main.dart
MainActivity.java
package com.tutorialspoint.flutterapp.flutter_browser_app;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
if (call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}
96
Flutter
});
}
activity.startActivity(intent);
result.success((Object) true);
}
}
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
97
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: _openBrowser,
),
),
);
}
}
Run the application and click the Open Browser button and you can see that the browser
is launched. The Browser app - Home page is as shown in the screenshot here:
98
Flutter
99
12. Flutter – Writing iOS Specific Code Flutter
Accessing iOS specific code is similar to that on Android platform except that it uses iOS
specific languages - Objective-C or Swift and iOS SDK. Otherwise, the concept is same as
that of the Android platform.
Let us write the same application as in the previous chapter for iOS platform as well.
Choose the xcode project under ios directory of our flutter project.
Open AppDelegate.m under Runner -> Runner path. It contains the following
code:
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
@end
- (void)openBrowser:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
UIApplication *application = [UIApplication sharedApplication];
[application openURL:url];
}
100
Flutter
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewController;
methodChannelWithName:@"flutterapp.tutorialspoint.com/browser"
binaryMessenger:controller];
[weakSelf openBrowser:url];
} else {
result(FlutterMethodNotImplemented);
}
}];
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
methodChannelWithName:@"flutterapp.tutorialspoint.com/browser"
binaryMessenger:controller];
101
Flutter
[weakSelf openBrowser:url];
} else {
result(FlutterMethodNotImplemented);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
- (void)openBrowser:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
UIApplication *application = [UIApplication sharedApplication];
[application openURL:url];
}
@end
Now, run the application. It works similar to Android version but the Safari browser
will be opened instead of chrome.
102
13. Flutter – Introduction to Package Flutter
Dart’s way of organizing and sharing a set of functionality is through Package. Dart
Package is simply sharable libraries or modules. In general, the Dart Package is same as
that of Dart Application except Dart Package does not have application entry point, main.
import 'package:my_demo_package/my_demo_package.dart'
Other private code file may be exported into the main code file
(my_demo_package.dart), if necessary as shown below:
export src/my_private_code.dart
lib/* : Any number of Dart code files arranged in any custom folder structure. The
code can be accessed as,
import 'package:my_demo_package/custom_folder/custom_file.dart'
All Dart code files in the Package are simply Dart classes and it does not have any special
requirement for a Dart code to include it in a Package.
Types of Packages
Since Dart Packages are basically a small collection of similar functionality, it can be
categorized based on its functionality.
Dart Package
Generic Dart code, which can be used in both web and mobile environment. For example,
english_words is one such package which contains around 5000 words and has basic utility
functions like nouns (list nouns in the English), syllables (specify number of syllables in a
word.
Flutter Package
Generic Dart code, which depends on Flutter framework and can be used only in mobile
environment. For example, fluro is a custom router for flutter. It depends on the Flutter
framework.
103
Flutter
Flutter Plugin
Generic Dart code, which depends on Flutter framework as well as the underlying platform
code (Android SDK or iOS SDK). For example, camera is a plugin to interact with device
camera. It depends on the Flutter framework as well as the underlying framework to get
access to camera.
Include the package name and the version needed into the pubspec.yaml as shown
below:
dependencies:
english_words: ^3.1.5
The latest version number can be found by checking the online server.
Install the package into the application by using the following command:
While developing in the Android studio, Android Studio detects any change in the
pubspec.yaml and displays an Android studio package alert to the developer as
shown below:
Dart Packages can be installed or updated in Android Studio using the menu
options.
Import the necessary file using the command shown below and start working:
import 'package:english_words/english_words.dart';
nouns.take(50).forEach(print);
Here, we have used nouns function to get and print the top 50 words.
As we have already learned how to access platform code in the previous chapters, let us
develop a simple plugin, my_browser to understand the plugin development process. The
104
Flutter
functionality of the my_browser plugin is to allow the application to open the given website
in the platform specific browser.
Click File -> New Flutter Project and select Flutter Plugin option.
105
Flutter
Enter the plugin name and other details in the window as shown here:
106
Flutter
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
Here, we have to import library required for opening a browser from Android.
@Override
public void onMethodCall(MethodCall call, Result result) {
107
Flutter
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else if (call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}
}
activity.startActivity(intent);
result.success((Object) true);
}
my_browser.dart
import 'dart:async';
import 'package:flutter/services.dart';
class MyBrowser {
static const MethodChannel _channel =
const MethodChannel('my_browser');
108
Flutter
}
}
MyBrowserPlugin.java
package com.tutorialspoint.flutterplugins.my_browser;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
/** MyBrowserPlugin */
public class MyBrowserPlugin implements MethodCallHandler {
private final Registrar mRegistrar;
@Override
public void onMethodCall(MethodCall call, Result result) {
String url = call.argument("url");
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else if (call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}
}
109
Flutter
activity.startActivity(intent);
result.success((Object) true);
}
}
dependencies:
flutter:
sdk: flutter
my_browser:
path: ../my_browser
Android studio will alert that the pubspec.yaml is updated as shown in the Android
studio package alert given below:
Click Get dependencies option. Android studio will get the package from Internet
and properly configure it for the application.
import 'package:my_browser/my_browser.dart';
import 'package:flutter/material.dart';
import 'package:my_browser/my_browser.dart';
110
Flutter
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: () => MyBrowser().openBrowser("https://flutter.dev"),
),
),
);
}
}
111
Flutter
Run the application and click the Open Browser button and see that the browser is
launched. You can see a Browser app - Home page as shown in the screenshot
shown below:
112
Flutter
You can see a Browser app – Browser screen as shown in the screenshot shown
below:
113
14. Flutter – Accessing REST API Flutter
Flutter provides http package to consume HTTP resources. http is a Future-based library
and uses await and async features. It provides many high level methods and simplifies the
development of REST based mobile applications.
Basic Concepts
http package provides a high level class and http to do web requests.
http methods accept a url, and additional information through Dart Map (post data,
additional headers, etc.,). It requests the server and collects the response back in
async/await pattern. For example, the below code reads the data from the specified
url and print it in the console.
print(await http.read('https://flutter.dev/'));
read - Request the specified url through GET method and return back the response
as Future<String>
get - Request the specified url through GET method and return back the response
as Future<Response>. Response is a class holding the response information.
post - Request the specified url through POST method by posting the supplied data
and return back the response as Future<Response>
put - Request the specified url through PUT method and return back the response
as Future<Response>
head - Request the specified url through HEAD method and return back the
response as Future<Response>
delete - Request the specified url through DELETE method and return back the
response as Future<Response>
http also provides a more standard HTTP client class, client. client supports persistent
connection. It will be useful when a lot of request to be made to a particular server. It
needs to be closed properly using close method. Otherwise, it is similar to http class. The
sample code is as follows:
114
Flutter
Replace the default startup code (main.dart) with our product_nav_app code.
Copy the assets folder from product_nav_app to product_rest_app and add assets
inside the pubspec.yaml file
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
dependencies:
http: ^0.12.0+2
Here, we will use the latest version of the http package. Android studio will send a
package alert that the pubspec.yaml is updated.
Click Get dependencies option. Android studio will get the package from Internet and
properly configure it for the application.
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Create a new JSON file, products.json with product information as shown below:
[
{
"name": "iPhone",
"description": "iPhone is the stylist phone ever",
"price": 1000,
"image": "iphone.png"
},
{
"name": "Pixel",
"description": "Pixel is the most feature phone ever",
"price": 800,
"image": "pixel.png"
115
Flutter
},
{
"name": "Laptop",
"description": "Laptop is most productive development tool",
"price": 2000,
"image": "laptop.png"
},
{
"name": "Tablet",
"description": "Tablet is the most useful device ever for meeting",
"price": 1500,
"image": "tablet.png"
},
{
"name": "Pendrive",
"description": "Pendrive is useful storage medium",
"price": 100,
"image": "pendrive.png"
},
{
"name": "Floppy Drive",
"description": "Floppy drive is useful rescue storage medium",
"price": 20,
"image": "floppy.png"
}
]
Create a new folder, JSONWebServer and place the JSON file, products.json.
Run any web server with JSONWebServer as its root directory and get its web path.
For example, http://192.168.184.1:8000/products.json. We can use any web server
like apache, nginx etc.,
The easiest way is to install node based http-server application. Follow the steps given
below to install and run http- server application.
cd /path/to/JSONWebServer
http-server . -p 8000
116
Flutter
http://127.0.0.1:8000
Hit CTRL-C to stop the server
Create a new file, Product.dart in the lib folder and move the Product class into it.
class Product {
final String name;
final String description;
final int price;
final String image;
Write two methods - parseProducts and fetchProducts - in the main class to fetch
and load the product information from web server into the List<Product> object.
if (response.statusCode == 200) {
return parseProducts(response.body);
} else {
throw Exception('Unable to fetch products from the REST API');
117
Flutter
}
}
Future is used to lazy load the product information. Lazy loading is a concept to
defer the execution of the code until it is necessary.
json.decode is used to decode the JSON data into the Dart Map object. Once JSON
data is decoded, it will be converted into List<Product> using fromMap of the
Product class.
In MyApp class, add new member variable, products of type Future<Product> and
include it in constructor.
...
Change the home option (MyHomePage) in the build method of MyApp widget to
accommodate above changes:
home: MyHomePage(
title: 'Product Navigation demo home page', products:
products),
Create a new widget, ProductBoxList to build the product list in the home page.
118
Flutter
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
);
}
}
Note that we used the same concept used in Navigation application to list the product
except it is designed as a separate widget by passing products (object) of type
List<Product>.
Finally, modify the MyHomePage widget’s build method to get the product
information using Future option instead of normal method call.
return snapshot.hasData
? ProductBoxList(
items: snapshot.data) // return the ListView widget
: Center(child: CircularProgressIndicator());
},
),
));
}
Here note that we used FutureBuilder widget to render the widget. FutureBuilder
will try to fetch the data from it’s future property (of type Future<List<Product>>).
If future property returns data, it will render the widget using ProductBoxList,
otherwise throws an error.
119
Flutter
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'Product.dart';
if (response.statusCode == 200) {
return parseProducts(response.body);
} else {
throw Exception('Unable to fetch products from the REST API');
}
}
120
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: Center(
child: FutureBuilder<List<Product>>(
future: products,
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ProductBoxList(
items: snapshot.data) // return the ListView widget
: Center(child: CircularProgressIndicator());
},
),
));
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
121
Flutter
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)))
]),
),
),
);
}
}
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
122
Flutter
_rating = 3;
});
}
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3
? Icon(
Icons.star,
size: _size,
)
123
Flutter
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Finally run the application to see the result. It will be same as our Navigation example
except the data is from Internet instead of local, static data entered while coding the
application.
124
15. Flutter – Database Concepts Flutter
Flutter provides many advanced packages to work with databases. The most important
packages are:
SQLite
SQLite database is the de-facto and standard SQL based embedded database engine. It is
small and time-tested database engine. sqflite package provides a lot of functionality to
work efficiently with SQLite database. It provides standard methods to manipulate SQLite
database engine. The core functionality provided by sqflite package is as follows:
Advanced query methods (query method) to reduce to code required to query and
get information from SQLite database.
Let us create a product application to store and fetch product information from a standard
SQLite database engine using sqflite package and understand the concept behind the
SQLite database and sqflite package.
Replace the default startup code (main.dart) with our product_rest_app code.
Copy the assets folder from product_nav_app to product_rest_app and add assets
inside the *pubspec.yaml` file
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
dependencies:
sqflite: any
125
Flutter
dependencies:
path_provider: any
Here, path_provider package is used to get temporary folder path of the system and
path of the application. Use the latest version number of sqflite in place of any.
Click Get dependencies option. Android studio will get the package from Internet and
properly configure it for the application.
In database, we need primary key, id as additional field along with Product properties
like name, price, etc., So, add id property in the Product class. Also, add a new
method, toMap to convert product object into Map object. fromMap and toMap are
used to serialize and de- serialize the Product object and it is used in database
manipulation methods.
class Product {
final int id;
final String name;
final String description;
final int price;
final String image;
Create a new file, Database.dart in the lib folder to write SQLite related functionality.
126
Flutter
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart';
path is used to access dart core utility function related to file paths.
class SQLiteDbProvider {
SQLiteDbProvider._();
SQLiteDBProvoider object and its method can be accessed through the static db
variable.
SQLiteDBProvoider.db.<emthod>
initDB() async {
Directory documentsDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "ProductDB.db");
return await openDatabase(
path,
version: 1,
127
Flutter
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[1, "iPhone", "iPhone is the stylist phone ever", 1000,
"iphone.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[2, "Pixel", "Pixel is the most feature phone ever", 800,
"pixel.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[3, "Laptop", "Laptop is most productive development tool",
2000, "laptop.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[4, "Tablet", "Laptop is most productive development tool",
1500, "tablet.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[5, "Pendrive", "Pendrive is useful storage medium", 100,
"pendrive.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[6, "Floppy Drive", "Floppy drive is useful rescue storage
medium", 20, "floppy.png"]);
});
}
128
Flutter
return products;
}
Used query method to fetch all the product information. query provides shortcut
to query a table information without writing the entire query. query method will
generate the proper query itself by using our input like columns, orderBy, etc.,
Used Product’s fromMap method to get product details by looping the results
object, which holds all the rows in the table.
Create three methods - insert, update and delete method to insert, update and
delete product from the database
129
Flutter
var id = maxIdResult.first["last_inserted_id"];
return result;
}
return result;
}
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart';
class SQLiteDbProvider {
SQLiteDbProvider._();
initDB() async {
130
Flutter
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[1, "iPhone", "iPhone is the stylist phone ever", 1000,
"iphone.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[2, "Pixel", "Pixel is the most feature phone ever", 800,
"pixel.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[3, "Laptop", "Laptop is most productive development tool",
2000, "laptop.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[4, "Tablet", "Laptop is most productive development tool",
1500, "tablet.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[5, "Pendrive", "Pendrive is useful storage medium", 100,
"pendrive.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[6, "Floppy Drive", "Floppy drive is useful rescue storage
medium", 20, "floppy.png"]);
});
}
131
Flutter
return products;
}
return result;
}
return result;
}
132
Flutter
}
}
void main() {
runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts()));
}
Here, we have used the getAllProducts method to fetch all products from the
database.
Run the application and see the results. It will be similar to previous example,
Accessing Product service API, except the product information is stored and fetched
from the local SQLite database.
Cloud Firestore
Firebase is a BaaS app development platform. It provides many feature to speed up the
mobile application development like authentication service, cloud storage, etc., One of the
main feature of Firebase is Cloud Firestore, a cloud based real time NoSQL database.
Flutter provides a special package, cloud_firestore to program with Cloud Firestore. Let us
create an online product store in the Cloud Firestore and create a application to access the
product store.
Replace the default startup code (main.dart) with our product_rest_app code.
class Product {
final String name;
final String description;
final int price;
final String image;
flutter:
133
Flutter
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
dependencies:
cloud_firestore: ^0.9.13+1
Android studio will alert that the pubspec.yaml is updated as shown here:
Click Get dependencies option. Android studio will get the package from Internet and
properly configure it for the application.
Once Firebase account is created, it will redirect to the project overview page. It
list all the Firebase based project and provides an option to create a new project.
Enter products app db as project name and click Create project option.
Go to *Firebase console.
Click android icon. It will open project setting specific to Android development.
134
Flutter
buildscript {
repositories {
// ...
}
dependencies {
// ...
classpath 'com.google.gms:google-services:3.2.1' // new
}
}
Here, the plugin and class path are used for the purpose of reading
google_service.json file.
android {
defaultConfig {
...
multiDexEnabled true
}
...
}
dependencies {
...
compile 'com.android.support: multidex:1.0.3'
}
Follow the remaining steps in the Firebase Console or just skip it.
Create a product store in the newly created project using the following steps:
Go to Firebase console.
Click Add collection. Enter product as collection name and then click Next.
135
Flutter
Open main.dart file and import Cloud Firestore plugin file and remove http package.
import 'package:cloud_firestore/cloud_firestore.dart';
Stream<QuerySnapshot> fetchProducts() {
return Firestore.instance.collection('product').snapshots();
}
Cloud Firestore provides the collection through Dart Stream concept and so modify
the products type in MyApp and MyHomePage widget from Future<list<Product>> to
Stream<QuerySnapshot>.
136
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: Center(
child: StreamBuilder<QuerySnapshot>(
stream: products,
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
if(snapshot.hasData) {
List<DocumentSnapshot> documents = snapshot.data.documents;
List<Product> items = List<Product>();
items.add(Product.fromMap(document.data));
}
Finally, run the application and see the result. Since, we have used the same product
information as that of SQLite application and changed the storage medium only, the
resulting application looks identical to SQLite application application.
137
16. Flutter – Internationalization Flutter
Nowadays, mobile applications are used by customers from different countries and as a
result, applications are required to display the content in different languages. Enabling an
application to work in multiple languages is called Internationalizing the application.
For an application to work in different languages, it should first find the current locale of
the system in which the application is running and then need to show it’s content in that
particular locale, and this process is called Localization.
Flutter framework provides three base classes for localization and extensive utility classes
derived from base classes to localize an application.
Locale - Locale is a class used to identify the user’s language. For example, en-us
identifies the American English and it can be created as:
Here, the first argument is language code and the second argument is country code.
Another example of creating Argentina Spanish (es-ar) locale is as follows:
Localizations - Localizations is a generic widget used to set the Locale and the
localized resources of its child.
class CustomLocalizations {
CustomLocalizations(this.locale);
138
Flutter
@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
load - Accepts a locale and start loading the resources for the specified
locale.
@override
Future<CustomLocalizations> load(Locale locale) {
return
SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
}
@override
bool shouldReload(CustomLocalizationsDelegate old) => false;
@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
@override
Future<CustomLocalizations> load(Locale locale) {
return
SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
139
Flutter
@override
bool shouldReload(CustomLocalizationsDelegate old) => false;
}
In general, Flutter applications are based on two root level widgets, MaterialApp or
WidgetsApp. Flutter provides ready made localization for both widgets and they are
MaterialLocalizations and WidgetsLocaliations. Further, Flutter also provides delegates to
load MaterialLocalizations and WidgetsLocaliations and they are
GlobalMaterialLocalizations.delegate and GlobalWidgetsLocalizations.delegate
respectively.
Let us create a simple internationalization enabled application to test and understand the
concept.
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
Android studio will display the following alert that the pubspec.yaml is updated.
Click Get dependencies option. Android studio will get the package from Internet
and properly configure it for the application.
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart' show SynchronousFuture;
class CustomLocalizations {
CustomLocalizations(this.locale);
140
Flutter
@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
@override
Future<CustomLocalizations> load(Locale locale) {
return
SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
}
@override
bool shouldReload(CustomLocalizationsDelegate old) => false;
}
localizationsDelegates: [
const CustomLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
141
Flutter
Use CustomLocalizations method, of to get the localized value of title and message
and use it in appropriate place as specified below:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(CustomLocalizations
.of(context)
.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
CustomLocalizations
.of(context)
.message,
),
],
),
),
);
}
Compile and run the application. The application will show its content in English.
Close the application. Go to Settings -> System -> Languages and Input ->
Languages*
Click Add a language option and select Spanish. This will install Spanish language and
then list it as one of the option.
Select Spanish and move it above English. This will set as Spanish as first language
and everything will be changed to Spanish text.
Now relaunch the internationalization application and you will see the title and
message in Spanish language.
We can revert the language to English by move the English option above Spanish
option in the setting.
The result of the application (in Spanish) is shown in the screenshot given below:
142
Flutter
Let us create a new localized application by using intl package and understand the concept.
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.15.7
intl_translation: ^0.17.3
143
Flutter
Android studio will display the alert as shown below informing that the pubspec.yaml
is updated.
Click Get dependencies option. Android studio will get the package from Internet
and properly configure it for the application.
import 'package:intl/intl.dart';
class CustomLocalizations {
static Future<CustomLocalizations> load(Locale locale) {
final String name = locale.countryCode.isEmpty ? locale.languageCode :
locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
return CustomLocalizations();
});
}
@override
144
Flutter
@override
Future<CustomLocalizations> load(Locale locale) {
return CustomLocalizations.load(locale);
}
@override
bool shouldReload(CustomLocalizationsDelegate old) => false;
}
Here, we have used three methods from the intl package instead of custom methods.
Otherwise, the concepts are same.
import 'l10n/messages_all.dart';
{
"@@last_modified": "2019-04-19T02:04:09.627551",
"title": "Demo",
"@title": {
"description": "Title for the Demo application",
"type": "text",
"placeholders": {}
},
"message": "Hello World",
"@message": {
"description": "Message for the Demo application",
"type": "text",
"placeholders": {}
}
}
Copy intl_message.arb and create new file, intl_es.arb and change the content to
Spanish language as shown below:
145
Flutter
{
"@@last_modified": "2019-04-19T02:04:09.627551",
"title": "Manifestación",
"@title": {
"description": "Title for the Demo application",
"type": "text",
"placeholders": {}
},
"message": "Hola Mundo",
"@message": {
"description": "Message for the Demo application",
"type": "text",
"placeholders": {}
}
}
Now, run the following command to create final message file, messages_all.dart
Compile and run the application. It will work similar to above application,
flutter_localization_app.
146
17. Flutter – Testing Flutter
Testing is very important phase in the development life cycle of an application. It ensures
that the application is of high quality. Testing requires careful planning and execution. It
is also the most time consuming phase of the development.
Dart language and Flutter framework provides extensive support for the automated testing
of an application.
Types of Testing
Generally, three types of testing processes are available to completely test an application.
They are as follows:
Unit Testing
Unit testing is the easiest method to test an application. It is based on ensuring the
correctness of a piece of code (a function, in general) o a method of a class. But, it does
not reflect the real environment and subsequently, is the least option to find the bugs.
Widget Testing
Widget testing is based on ensuring the correctness of the widget creation, rendering and
interaction with other widgets as expected. It goes one step further and provides near
real-time environment to find more bugs.
Integration Testing
Integration testing involves both unit testing and widget testing along with external
component of the application like database, web service, etc., It simulates or mocks the
real environment to find nearly all bugs, but it is the most complicated process.
Flutter provides support for all types of testing. It provides extensive and exclusive support
for Widget testing. In this chapter, we will discuss widget testing in detail.
Widget Testing
Flutter testing framework provides testWidgets method to test widgets. It accepts two
arguments:
Test description
Test code
147
Flutter
Steps Involved
Widget Testing involves three distinct steps:
WidgetTester is the class provided by Flutter testing framework to build and renders
the widget. pumpWidget method of the WidgetTester class accepts any widget and
renders it in the testing environment.
Flutter framework provides many options to find the widgets rendered in the testing
environment and they are generally called Finders. The most frequently used
finders are find.text, find.byKey and find.byWidget
find.text('Hello')
find.byKey('home')
find.byWidget(homeWidget)
Flutter framework provides many options to match the widget with the expected
widget and they are normally called Matchers. We can use the expect method
provided by the testing framework to match the widget, which we found in the second
step with our our expected widget by choosing any of the matchers. Some of the
important matchers are as follows:
expect(find.text('Hello'), findsOneWidget);
148
Flutter
expect(find.text('Save'), findsWidgets);
expect(find.text('Save'), findsNWidgets(2));
expect(find.text('Hello'), findsOneWidget);
});
Here, we rendered a MaterialApp widget with text Hello using Text widget in its body.
Then, we used find.text to find the widget and then matched it using findsOneWidget.
Working Example
Let us create a simple flutter application and write a widget test to understand better the
steps involved and the concept.
Open widget_test.dart in test folder. It has a sample testing code as given below:
Ensures that the counter is initially zero using findsOneWidget and findsNothing
matchers.
149
Flutter
Let us again tap the counter increment button and then check whether the counter
is increased to two.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('2'), findsOneWidget);
Click tests in widget_test.dart option. This will run the test and report the result in
the result window.
150
18. Flutter – Deployment Flutter
This chapter explains how to deploy Flutter application in both Android and iOS platforms.
Android Application
Change the application name using android:label entry in android manifest file.
Android app manifest file, AndroidManifest.xml is located in <app
dir>/android/app/src/main. It contains entire details about an android application.
We can set the application name using android:label entry.
cd /path/to/my/application
flutter build apk
flutter install
Publish the application into Google Playstore by creating an appbundle and push it
into playstore using standard methods.
iOS Application
Register the iOS application in App Store Connect using standard method. Save the
=Bundle ID used while registering the application.
Update Display name in the XCode project setting to set the application name.
151
Flutter
Update Bundle Identifier in the XCode project setting to set the bundle id, which we
used in step 1.
Test the application by pushing the application, IPA file into TestFlight using
standard method.
Finally, push the application into App Store using standard method.
152
19. Flutter – Development Tools Flutter
This chapter explains about Flutter development tools in detail. The first stable release of
the cross-platform development toolkit was released on December 4th, 2018, Flutter 1.0.
Well, Google is continuously working on the improvements and strengthening the Flutter
framework with different development tools.
Widget Sets
Google updated for Material and Cupertino widget sets to provide pixel-perfect quality in
the components design. The upcoming version of flutter 1.2 will be designed to support
desktop keyboard events and mouse hover support.
Code assist - When you want to check for options, you can use Ctrl+Space to get a
list of code completion options.
Quick fix - Ctrl+. is quick fix tool to help in fixing the code.
Debugging shortcuts.
Hot restarts
Dart DevTools
We can use Android Studio or Visual Studio Code, or any other IDE to write our code and
install plugins. Google’s development team has been working on yet another development
tool called Dart DevTools It is a web-based programming suite. It supports both Android
and iOS platforms. It is based on time line view so developers can easily analyze their
applications.
Install DevTools
To install DevTools run the following command in your console:
Resolving dependencies...
+ args 1.5.1
+ async 2.2.0
153
Flutter
+ charcode 1.1.2
+ codemirror 0.5.3+5.44.0
+ collection 1.14.11
+ convert 2.1.1
+ devtools 0.0.16
+ devtools_server 0.0.2
+ http 0.12.0+2
+ http_parser 3.1.3
+ intl 0.15.8
+ js 0.6.1+1
+ meta 1.1.7
+ mime 0.9.6+2
..................
..................
Run Server
You can run the DevTools server using the following command:
http://localhost:9100/?port=9200
154
Flutter
Flutter SDK
To update Flutter SDK, use the following command:
flutter upgrade
Flutter Inspector
It is used to explore flutter widget trees. To achieve this, run the below command in your
console,
155
Flutter
To hot reload changes while running, press "r". To hot restart (and rebuild
state), press "R".
An Observatory debugger and profiler on iPhone X is available at:
http://127.0.0.1:50399/
For a more detailed help message, press "h". To detach, press "d"; to quit,
press "q".
Now go to the url, http://127.0.0.1:50399/ you could see the following result:
156
20. Flutter – Writing Advanced Applications Flutter
In this chapter, we are going to learn how to write a full fledged mobile application,
expense_calculator. The purpose of the expense_calculator is to store our expense
information. The complete feature of the application is as follows:
Expense list
Form to enter new expenses
Option to edit / delete the existing expenses
Total expenses at any instance.
dependencies:
flutter:
sdk: flutter
sqflite: ^1.1.0
path_provider: ^0.5.0+1
scoped_model: ^1.0.1
intl: any
Android studio will display the following alert that the pubspec.yaml is updated.
Click Get dependencies option. Android studio will get the package from Internet and
properly configure it for the application.
157
Flutter
Add new file, Expense.dart to create Expense class. Expense class will have the below
properties and methods.
toMap - Used to convert the expense object to Dart Map, which can be further
used in database programming
Enter and save the following code into the Expense.dart file.
import 'package:intl/intl.dart';
class Expense {
final int id;
final double amount;
final DateTime date;
final String category;
158
Flutter
Add new file, Database.dart to create SQLiteDbProvider class. The purpose of the
SQLiteDbProvider class is as follows:
return expenses;
}
159
Flutter
Get the total expenses of the user using getTotalExpense method. It will be
used to show the current total expense to the user.
Add new expense information into the database using insert method. It will
be used to add new expense entry into the application by the user.
return result;
}
160
Flutter
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Expense.dart';
class SQLiteDbProvider {
SQLiteDbProvider._();
initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "ExpenseDB2.db");
return await openDatabase(
path,
version: 1,
onOpen: (db) {},
onCreate: (Database db, int version) async {
await db.execute(
"INSERT INTO Expense ('id', 'amount', 'date', 'category') values
(?, ?, ?, ?)",
[1, 1000, '2019-04-01 10:00:00', "Food"]);
/*await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[2, "Pixel", "Pixel is the most feature phone ever", 800,
161
Flutter
"pixel.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[3, "Laptop", "Laptop is most productive development tool", 2000,
"laptop.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[4, "Tablet", "Laptop is most productive development tool", 1500,
"tablet.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[5, "Pendrive", "iPhone is the stylist phone ever", 100,
"pendrive.png"]);
await db.execute(
"INSERT INTO Product ('id', 'name', 'description', 'price',
'image') values (?, ?, ?, ?, ?)",
[6, "Floppy Drive", "iPhone is the stylist phone ever", 20,
"floppy.png"]);
*/
});
}
return expenses;
}
162
Flutter
return result;
}
Here,
163
Flutter
return amount;
}
load - Used to load the complete expenses from database and into the _items
variable. It also calls notifyListeners to update the UI.
void load() {
Future<List<Expense>> list = SQLiteDbProvider.db.getAllExpenses();
list.then( (dbItems) {
for(var i = 0; i < dbItems.length; i++) {
_items.add(dbItems[i]);
}
notifyListeners();
});
}
return null;
}
add - Used to add a new expense item into the _items variable as well as
into the database. It also calls notifyListeners to update the UI.
notifyListeners();
});
}
add - Used to add a new expense item into the _items variable as well as into
the database. It also calls notifyListeners to update the UI.
164
Flutter
if(found) notifyListeners();
}
delete - Used to remove an existing expense item in the _items variable as well as
from the database. It also calls notifyListeners to update the UI.
if(found) notifyListeners();
}
import 'dart:collection';
import 'package:scoped_model/scoped_model.dart';
import 'Expense.dart';
import 'Database.dart';
ExpenseListModel() { this.load(); }
165
Flutter
return amount;
}
void load() {
Future<List<Expense>> list =
SQLiteDbProvider.db.getAllExpenses();
list.then( (dbItems) {
for(var i = 0; i < dbItems.length; i++) {
_items.add(dbItems[i]);
}
notifyListeners();
});
}
return null;
}
notifyListeners();
});
}
if(found) notifyListeners();
}
166
Flutter
if(found) notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';
void main() {
final expenses = ExpenseListModel();
runApp(ScopedModel<ExpenseListModel>(
model: expenses,
child: MyApp(),
));
}
Here,
expenses object loads all the user expenses information from the database.
Also, when the application is opened for the first time, it will create the
required database with proper tables.
ScopedModel provides the expense information during the whole life cycle of
the application and ensures the maintenance of state of the application at
any instance. It enables us to use StatelessWidget instead of StatefulWidget.
167
Flutter
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Expense calculator'),
);
}
}
Create MyHomePage widget to display all the user’s expense information along with
total expenses at the top. Floating button at the bottom right corner will be used to
add new expenses.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return ListView.separated(
itemCount: expenses.items == null ? 1 :
expenses.items.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return ListTile(
title: Text("Total expenses: " +
expenses.totalExpense.toString(), style: TextStyle(fontSize:
24,fontWeight: FontWeight.bold),)
);
} else {
index = index - 1;
return Dismissible(
key: Key(expenses.items[index].id.toString()),
onDismissed: (direction) {
expenses.delete(expenses.items[index]);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("Item with id, " +
expenses.items[index].id.toString()
+
" is dismissed")));
},
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FormPage(
id:
expenses.items[index].id,
168
Flutter
expenses: expenses,
)));
},
leading: Icon(Icons.monetization_on),
trailing:
Icon(Icons.keyboard_arrow_right),
title: Text(expenses.items[index].category
+
": " +
expenses.items[index].amount.toString() +
" \nspent on " +
expenses.items[index].formattedDate,
style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),)));
}
},
separatorBuilder: (context, index) {
return Divider();
},
);
},
),
floatingActionButton:
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FormPage(
id: 0,
expenses: expenses,
);
})));
// expenses.add(new Expense(
// 2, 1000, DateTime.parse('2019-04-01 11:00:00'),
'Food'));
// print(expenses.items.length);
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}));
}
}
Here,
ScopedModelDescendant is used to pass the expense model into the ListView and
FloatingActionButton widget.
169
Flutter
Dismissible widget is used to delete the expense entry using swipe gesture.
Create a FormPage widget. The purpose of the FormPage widget is to add or update
an expense entry. It handles expense entry validation as well.
@override
_FormPageState createState() => _FormPageState(id: id, expenses:
expenses);
}
double _amount;
DateTime _date;
String _category;
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
if (this.id == 0)
expenses.add(Expense(0, _amount, _date, _category));
else
expenses.update(Expense(this.id, _amount, _date, _category));
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Enter expense details'),
170
Flutter
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.monetization_on),
labelText: 'Amount', labelStyle: TextStyle(fontSize:
18)),
validator: (val) {
Pattern pattern = r'^[1-9]\d*(\.\d+)?$';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(val))
return 'Enter a valid number';
else
return null;
},
initialValue:
id == 0 ? '' : expenses.byId(id).amount.toString(),
onSaved: (val) => _amount = double.parse(val),
),
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.calendar_today),
hintText: 'Enter date',
labelText: 'Date', labelStyle: TextStyle(fontSize: 18),
),
validator: (val) {
Pattern pattern =
r'^((?:19|20)\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-
9]|[12][0-9]|3[01])$';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(val))
return 'Enter a valid date';
else
return null;
},
onSaved: (val) => _date = DateTime.parse(val),
initialValue: id == 0 ? '' :
expenses.byId(id).formattedDate,
keyboardType: TextInputType.datetime,
),
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.category), labelText:
'Category', labelStyle: TextStyle(fontSize: 18)),
onSaved: (val) => _category = val,
initialValue:
id == 0 ? '' : expenses.byId(id).category.toString(),
171
Flutter
),
RaisedButton(
onPressed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
}
}
Here,
validator property of TextFormField is used to validate the form element along with
RegEx patterns.
_submit function is used along with expenses object to add or update the expenses
into the database.
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';
void main() {
final expenses = ExpenseListModel();
runApp(ScopedModel<ExpenseListModel>(
model: expenses,
child: MyApp(),
));
}
172
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return ListView.separated(
itemCount: expenses.items == null ? 1 :
expenses.items.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return ListTile(
title: Text("Total expenses: " +
expenses.totalExpense.toString(), style: TextStyle(fontSize:
24,fontWeight: FontWeight.bold),)
);
} else {
index = index - 1;
return Dismissible(
key: Key(expenses.items[index].id.toString()),
onDismissed: (direction) {
expenses.delete(expenses.items[index]);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("Item with id, " +
expenses.items[index].id.toString() +
" is dismissed")));
},
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FormPage(
id: expenses.items[index].id,
expenses: expenses,
)));
},
leading: Icon(Icons.monetization_on),
trailing: Icon(Icons.keyboard_arrow_right),
title: Text(expenses.items[index].category +
": " +
expenses.items[index].amount.toString() +
" \nspent on " +
expenses.items[index].formattedDate, style:
TextStyle(fontSize: 18, fontStyle: FontStyle.italic),)));
}
},
separatorBuilder: (context, index) {
173
Flutter
return Divider();
},
);
},
),
floatingActionButton: ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FormPage(
id: 0,
expenses: expenses,
);
})));
// expenses.add(new Expense(
// 2, 1000, DateTime.parse('2019-04-01 11:00:00'),
'Food'));
// print(expenses.items.length);
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}));
}
}
@override
_FormPageState createState() => _FormPageState(id: id, expenses:
expenses);
}
double _amount;
DateTime _date;
String _category;
174
Flutter
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
if (this.id == 0)
expenses.add(Expense(0, _amount, _date, _category));
else
expenses.update(Expense(this.id, _amount, _date, _category));
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Enter expense details'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.monetization_on),
labelText: 'Amount', labelStyle: TextStyle(fontSize:
18)),
validator: (val) {
Pattern pattern = r'^[1-9]\d*(\.\d+)?$';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(val))
return 'Enter a valid number';
else
return null;
},
initialValue:
id == 0 ? '' : expenses.byId(id).amount.toString(),
onSaved: (val) => _amount = double.parse(val),
),
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.calendar_today),
hintText: 'Enter date',
labelText: 'Date', labelStyle: TextStyle(fontSize: 18),
),
validator: (val) {
175
Flutter
Pattern pattern =
r'^((?:19|20)\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-
9]|[12][0-9]|3[01])$';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(val))
return 'Enter a valid date';
else
return null;
},
onSaved: (val) => _date = DateTime.parse(val),
initialValue: id == 0 ? '' :
expenses.byId(id).formattedDate,
keyboardType: TextInputType.datetime,
),
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.category), labelText:
'Category', labelStyle: TextStyle(fontSize: 18)),
onSaved: (val) => _category = val,
initialValue:
id == 0 ? '' : expenses.byId(id).category.toString(),
),
RaisedButton(
onPressed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
}
}
Delete the existing expenses by swiping the expense entry in either direction.
176
Flutter
177
Flutter
178
Flutter
179
21. Flutter – Conclusion Flutter
Flutter framework does a great job by providing an excellent framework to build mobile
applications in a truly platform independent way. By providing simplicity in the
development process, high performance in the resulting mobile application, rich and
relevant user interface for both Android and iOS platform, Flutter framework will surely
enable a lot of new developers to develop high performance and feature-full mobile
application in the near future.
180