flutter tutorial
flutter tutorial
Flutter is an open source framework to create high quality, high performance mobile
applications across mobile operating systems - Android and iOS. It provides a simple,
powerful, efficient and easy to understand SDK to write mobile application in Google’s
own language, Dart.
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
1. FLUTTER – INTRODUCTION.....................................................................1
Features of Flutter........................................................................................................................................ 1
Advantages of Flutter.................................................................................................................................... 2
Disadvantages of Flutter............................................................................................................................... 2
2. FLUTTER – INSTALLATION.......................................................................3
Installation in Windows................................................................................................................................ 3
Installation in MacOS.................................................................................................................................... 4
Widgets...................................................................................................................................................... 12
Gestures..................................................................................................................................................... 13
Concept of State.......................................................................................................................................... 13
Layers......................................................................................................................................................... 13
Functions.................................................................................................................................................... 16
ii
Flutter
Widget Build Visualization........................................................................................................................... 19
Introduction................................................................................................................................................ 82
Working Application.................................................................................................................................... 84
iii
Flutter
15. FLUTTER – DATABASE CONCEPTS....................................................125
SQLite....................................................................................................................................................... 125
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.
To be specific, Flutter application is itself a widget. Flutter widgets also supports animations
and gestures. The application logic is based on reactive programming. Widget may
optionally have a state. By changing the state of the widget, Flutter will automatically
(reactive programming) compare the widget’s state (old and new) and render the widget
with only the necessary changes instead of re-rendering the whole widget.
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.
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
8
Flutter
Step 7: Replace the dart code in the lib/main.dart file with the below code:
import
'package:flutter/material.dart'; void
@override
Widget build(BuildContext context) {
return
Scaffold( appBar
: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)
),
Let us understand the dart code line by line.
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);
}
Dart language supports the following data types:
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
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',
)),
);
Here, we have created a new widget by extending StatelessWidget.
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)),
],
),
)
The most important properties of the Text widget are as follows:
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
Now, load and display the image in the application.
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")
),
);
The loaded image is as shown below:
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(
26
Flutter
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.
The modified code of the MyHomePage and MyApp widget is as below:
@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'; void
@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'; void
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( onT
ap: () {
_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( onTa
p: () {
_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.
Widget build(BuildContext
context) { double _size = 20;
print(_rating);
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( appBa
r: 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(),
],
)))
])));
}
Update the MyHomePage widget to include the ProductBox widget as
specified below:
@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
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"),
],
));
}
The complete code of the application is as follows:
import
'package:flutter/material.dart'; void
@override
Widget build(BuildContext context) {
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",
meeting", description: "Tablet is the most useful device ever for
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;
});
}
Widget build(BuildContext
context) { double _size = 20;
print(_rating);
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,) :
54
Flutter
Icon(Icons.star_border, size: _size,)),
55
Flutter
color: Colors.red[500],
onPressed:
_setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
56
Flutter
Finally, run the application and see the State management - Product list page
Results as shown here:
57
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.
58
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>( mo
del: 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);
59
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.
60
Flutter
Replace the default startup code (main.dart) with our startup code.
import
'package:flutter/material.dart'; void
@override
Widget build(BuildContext context) {
return
Scaffold( appBar:
AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
Import scoped_model package in the main.dart file
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model/scoped_model.dart';
61
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( "Pix
el",
"Pixel is the most feature-full phone
ever", 800,
"pixel.png", 0));
items.add(Product( "Lap
top",
"Laptop is most productive development
tool", 2000,
"laptop.png", 0));
items.add(Product( "Tab
let",
"Tablet is the most useful device ever for
meeting", 1500,
"tablet.png", 0));
items.add(Product( "Pen
drive",
"Pendrive is useful storage
medium", 100,
"pendrive.png", 0));
items.add(Product(
62
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';
Let us change our new widget, RatingBox to support scoped_model concept.
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,
)
63
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,
),
),
],
);
Here, we have extended the RatingBox from StatelessWidget instead of StatefulWidget.
Also, we have used Product model’s updateRating method to set the rating.
Let us modify our ProductBox widget to work with Product, ScopedModel and
ScopedModelDescendant class.
64
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>( bui
lder: (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]);
},
));
}
Here, we have used ListView.builder to dynamically build our product list.
Product.dart
import 'package:scoped_model/scoped_model.dart';
65
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( "Pendri
ve",
"Pendrive is useful storage
medium", 100,
"pendrive.png", 0));
items.add(Product( "F
loppy 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';
66
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(
67
Flutter
Icons.star,
size:
_size,
)
: Icon(
68
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,
),
),
],
);
}
}
69
Flutter
70
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>( bui
lder: (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
71
Flutter
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Widget()),
);
Navigation.pop
Navigation.pop is used to navigate to previous screen.
Navigator.push(context);
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'; void
72
Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body:
Center( c
hild:
Text(
'Hello World',
)),
);
}
}
Let us create a Product class to organize the product information.
class Product
{ final String
name;
final String
description; final int
price;
final String image;
items.add(Product( "Pi
xel",
"Pixel is the most feature-full phone
ever", 800,
"pixel.png"));
items.add(Product( "La
ptop",
"Laptop is most productive development
tool", 2000,
"laptop.png"));
items.add(Product( "Ta
blet",
"Tablet is the most useful device ever for
meeting", 1500,
"tablet.png"));
items.add(Product( "Pe
ndrive",
"Pendrive is useful storage medium",
73
Flutter
100,
"pendrive.png"));
items.add(Product( "Fl
oppy 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;
});
}
Widget build(BuildContext
context) { double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
74
Flutter
75
Flutter
Let us modify our ProductBox widget to work with our new Product class.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")), body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
76
Flutter
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push( cont
ext,
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)
77
Flutter
],
)))
]),
),
),
);
}
}
import
'package:flutter/material.dart'; void
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( "Ta
blet",
"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"));
78
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( cont
ext,
MaterialPageRoute(
builder: (context) => ProductPage(item:
items[index]),
),
);
},
);
},
));
}
}
@override
Widget build(BuildContext
context) { return Scaffold(
appBar: AppBar(
79
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.spaceEven
ly, 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;
});
}
80
Flutter
void _setRatingAsThree()
{ setState(() {
81
Flutter
_rating = 3;
});
}
Widget build(BuildContext
context) { double _size = 20;
print(_rating);
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(
82
Flutter
Icons.star,
size: _size,
)
83
Flutter
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed:
_setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
84
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:
85
Flutter
86
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:
87
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.
AnimationController controller =
AnimationController( duration: const
Duration(milliseconds: 1000), vsync: this);
Animation<int> customTween = IntTween(begin: 0, end:
Tween can also used along with CurvedAnimation as below:
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
88
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';
89
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,
90
Flutter
: super(key: key);
91
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",
92
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();
}
93
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
94
Flutter
95
Flutter
Compile and run the application to see the results. The initial and final version of
the application is as follows:
96
Flutter
97
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'; void
98
Flutter
);
}
}
@override
Widget build(BuildContext context) {
return
Scaffold( appBa
r: 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';
99
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;
Write a method, openBrowser to open a browser
activity.startActivity(intent);
result.success((Object) true);
}
new MethodChannel(getFlutterView(),
CHANNEL).setMethodCallHandler( new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result
if (call.method.equals("openBrowser")) {
10
0
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);
new MethodChannel(getFlutterView(),
CHANNEL).setMethodCallHandler( new MethodCallHandler()
{
@Override
public void onMethodCall(MethodCall call, Result result)
if
(call.method.equals("openBrowser"))
{ openBrowser(call, result, url);
} else {
10
1
Flutter
});
}
activity.startActivity(intent);
result.success((Object) true);
}
}
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import
'package:flutter/services.dart'; void
10
2
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:
10
3
Flutter
10
4
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];
}
105
Flutter
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewController;
methodChannelWithName:@"flutterapp.tutorialspoint.com/browser"
binaryMessenger:controller];
[weakSelf openBrowser:url];
} else {
result(FlutterMethodNotImplemented);
}
}];
The complete code is as follows:
#include "AppDelegate.h"
#include
"GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
methodChannelWithName:@"flutterapp.tutorialspoint.com/browser"
binaryMessenger:controller];
106
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.
107
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.
108
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
109
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.
110
Flutter
Enter the plugin name and other details in the window as shown here:
111
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)
112
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(
);
}
Write the platform specific openBrowser method to access browser
in MyBrowserPlugin class:
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');
113
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();
}
}
114
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';
StatelessWidget {
@override
Widget build(BuildContext context)
{ return MaterialApp(
title: 'Flutter
Demo', theme:
ThemeData(
primarySwatch: Colors.blue,
115
Flutter
}
}
@override
Widget build(BuildContext context) {
return
Scaffold( appBa
r: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: () =>
MyBrowser().openBrowser("https://flutter.dev"),
),
),
);
116
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:
117
Flutter
You can see a Browser app – Browser screen as shown in the screenshot shown
below:
118
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:
119
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"
120
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
121
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;
json) {
return Product(
json['name'],
json['description'],
json['price'],
json['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 {
122
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.
...
In MyHomePage class, add new member variable products of type
Future<Product> and include it in constructor. Also, remove items variable and its
relevant method, getProducts method call. Placing the products variable in
constructor. It will allow to fetch the products from Internet only once when the
application is first started.
home: MyHomePage(
title: 'Product Navigation demo home page',
products: products),
Create a new widget, ProductBoxList to build the product list in the home page.
123
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( cont
ext,
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>.
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.
124
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');
}
}
125
Flutter
126
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( con
text,
MaterialPageRoute
(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
);
}
}
127
Flutter
@override
Widget build(BuildContext
context) { return Scaffold(
128
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;
});
}
129
Flutter
void _setRatingAsThree()
{ setState(() {
130
Flutter
_rating = 3;
});
}
Widget build(BuildContext
context) { double _size = 20;
print(_rating);
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(
131
Flutter
Icons.star,
size: _size,
)
132
Flutter
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed:
_setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
133
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
134
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.
class Product
{ final int id;
final String
name;
final String
description; final int
price;
final String image;
this.image);
135
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';
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,
136
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"]);
});
}
137
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
138
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._();
139
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"]);
});
}
140
Flutter
return products;
}
return result;
}
return result;
}
141
Flutter
db.delete("Product", where: "id = ?", whereArgs: [id]);
142
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;
dynamic> json) {
return Product(
json['name'],
json['description']
, json['price'],
json['image'],
Copy the assets folder from product_rest_app to product_firebase_app and add
assets inside the pubspec.yaml file
flutter:
143
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
Here, use the latest version of the cloud_firestore package.
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.
144
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.
145
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>.
146
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.
147
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);
148
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
));
}
Here, load method returns CustomLocalizations. The returned
CustomLocalizations can be used to get values of title and message in both
English and Spanish.
@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));
149
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.
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
Here, the purpose of SynchronousFuture is to load the custom localizations
synchronously.
class CustomLocalizations {
CustomLocalizations(this.locale);
150
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;
}
Here, CustomLocalizations is created to support localization for title and message in
the application and CustomLocalizationsDelegate is used to load
CustomLocalizations.
localizationsDelegates: [
const
CustomLocalizationsDelegate(),
GlobalMaterialLocalizations.delegat
e,
GlobalWidgetsLocalizations.delegate
,
],
151
Flutter
@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( CustomLoc
alizations
.of(context)
.message,
),
],
),
Here, we have modified the MyHomePage class from StatefulWidget to
StatelessWidget for simplicity reason and used the CustomLocalizations to get title
and 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:
152
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
153
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();
});
}
String get
message{ return
Intl.message(
'Hello World',
name:
'message',
desc: 'Message for the Demo application',
);
}
}
154
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:
155
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.
156
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
157
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);
158
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.
159
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 Run menu.
Click tests in widget_test.dart option. This will run the test and report the result in
the result window.
160
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
You can see an output as shown below:
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.
161
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.
162
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
163
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
164
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,
165
Flutter
166
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.
167
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;
168
Flutter
Add new file, Database.dart to create SQLiteDbProvider class. The purpose of the
SQLiteDbProvider class is as follows:
return expenses;
}
169
Flutter
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;
}
170
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,
171
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;
}
172
Flutter
return result;
}
173
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();
});
}
byId - Used to get a particular expenses from _items variable.
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.
174
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';
Model { ExpenseListModel()
{ this.load(); } final
175
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();
}
176
Flutter
if(found) notifyListeners();
}
}
import 'package:flutter/material.dart';
import
'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';
Add main function and call runApp by passing ScopedModel<ExpenseListModel>
widget.
void main() {
final expenses = ExpenseListModel();
runApp(ScopedModel<ExpenseListModel>( m
odel: 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.
177
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>( b
uilder: (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( cont
ext,
178
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.
179
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);
}
final scaffoldKey =
GlobalKey<ScaffoldState>(); final formKey =
GlobalKey<FormState>();
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'),
180
Flutter
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child:
Column( chil
dren: [
TextFormField(
style: TextStyle(fontSize: 22),
decoration: const InputDecoration(
icon: const Icon(Icons.monetization_on),
18)), labelText: 'Amount', labelStyle: TextStyle(fontSize:
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)),
181
Flutter
onSaved: (val) => _category =
val, initialValue:
id == 0 ? '' : expenses.byId(id).category.toString(),
182
Flutter
),
RaisedButton( onPres
sed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
}
}
Here,
_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<ExpenseListMode
l>( model: expenses,
child: MyApp(),
));
}
183
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( on
Tap: () {
Navigator.push( cont
ext,
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),)));
}
},
184
Flutter
separatorBuilder: (context, index) {
185
Flutter
return Divider();
},
);
},
),
floatingActionButton:
ScopedModelDescendant<ExpenseListModel>( builder:
(context, child, expenses) {
return
FloatingActionButton( onPresse
d: () {
Navigator.push( cont
ext,
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);
}
final scaffoldKey =
GlobalKey<ScaffoldState>(); final formKey =
GlobalKey<FormState>();
186
Flutter
double _amount;
DateTime _date;
String
_category;
187
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( chil
dren: [
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:
188
Flutter
'Enter date',
labelText: 'Date', labelStyle: TextStyle(fontSize: 18),
),
validator: (val) {
189
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( onPres
sed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
Now, run the application.
Delete the existing expenses by swiping the expense entry in either direction.
190
Flutter
191
Flutter
192
Flutter
193
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.
194