English 中文(简体)
Flutter - Quick Guide
  • 时间:2024-09-08

Flutter - Quick Guide


Previous Page Next Page  

Flutter - Introduction

In general, developing a mobile apppcation is a complex and challenging task. There are many frameworks available to develop a mobile apppcation. Android provides a native framework based on Java language and iOS provides a native framework based on Objective-C / Swift language.

However, to develop an apppcation 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 apppcation framework (which uses HTML for User Interface and JavaScript for apppcation logic) to complex language specific framework (which do the heavy pfting of converting code to native code). Irrespective of their simppcity 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 apppcation. These widgets are optimized for mobile environment and designing the apppcation using widgets is as simple as designing HTML.

To be specific, Flutter apppcation is itself a widget. Flutter widgets also supports animations and gestures. The apppcation 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.

We shall discuss the complete architecture in the coming chapters.

Features of Flutter

Flutter framework offers the following features to developers −

    Modern and reactive framework.

    Uses Dart programming language and it is very easy to learn.

    Fast development.

    Beautiful and fluid user interfaces.

    Huge widget catalog.

    Runs same UI for multiple platforms.

    High performance apppcation.

Advantages of Flutter

Flutter comes with beautiful and customizable widgets for high performance and outstanding mobile apppcation. 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 capabipties of your apppcation.

    Developers need to write just a single code base for both apppcations (both Android and iOS platforms). Flutter may to be extended to other platform as well in the future.

    Flutter needs lesser testing. Because of its single code base, it is sufficient if we write automated tests once for both the platforms.

    Flutter’s simppcity makes it a good candidate for fast development. Its customization capabipty and extendibipty makes it even more powerful.

    With Flutter, developers has full control over the widgets and its layout.

    Flutter offers great developer tools, with amazing hot reload.

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 apppcation. Developers are having a hard time in choosing the right development tools in hugely populated segment.

Flutter - Installation

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 1 − Go to URL, https://flutter.dev/docs/get-started/install/windows and download the latest Flutter SDK. As of April 2019, the version is 1.2.1 and the file is flutter_windows_v1.2.1-stable.zip.

Step 2 − Unzip the zip archive in a folder, say C:flutter

Step 3 − Update the system path to include flutter bin directory.

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
! Doctor found issues in 1 category.

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 6 − Install the latest Android SDK, if reported by flutter doctor

Step 7 − Install the latest Android Studio, if reported by flutter doctor

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 apppcation, an option to run and debug Flutter apppcation in the Android studio itself, etc.,

    Open Android Studio.

    Cpck File → Settings → Plugins.

    Select the Flutter plugin and cpck Install.

    Cpck Yes when prompted to install the Dart plugin.

    Restart Android studio.

Installation in MacOS

To install Flutter on MacOS, you will have to follow the following steps −

Step 1 − Go to URL, https://flutter.dev/docs/get-started/install/macos and download latest Flutter SDK. As of April 2019, the version is 1.2.1 and the file is flutter_macos_v1.2.1- stable.zip.

Step 2 − Unzip the zip archive in a folder, say /path/to/flutter

Step 3 − Update the system path to include flutter bin directory (in ~/.bashrc file).


> export PATH = "$PATH:/path/to/flutter/bin"

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 5 − Install latest XCode, if reported by flutter doctor

Step 6 − Install latest Android SDK, if reported by flutter doctor

Step 7 − Install latest Android Studio, if reported by flutter doctor

Step 8 − Start an android emulator or connect a real android device to the system to develop android apppcation.

Step 9 − Open iOS simulator or connect a real iPhone device to the system to develop iOS apppcation.

Step 10 − Install Flutter and Dart plugin for Android Studio. It provides the startup template to create a new Flutter apppcation, option to run and debug Flutter apppcation in the Android studio itself, etc.,

    Open Android Studio

    Cpck Preferences → Plugins

    Select the Flutter plugin and cpck Install

    Cpck Yes when prompted to install the Dart plugin.

    Restart Android studio.

Creating Simple Apppcation in Android Studio

In this chapter, let us create a simple Flutter apppcation to understand the basics of creating a flutter apppcation in the Android Studio.

Step 1 − Open Android Studio

Step 2 − Create Flutter Project. For this, cpck File → New → New Flutter Project

New Flutter Project

Step 3 − Select Flutter Apppcation. For this, select Flutter Apppcation and cpck Next.

Flutter Apppcation Next

Step 4 − Configure the apppcation as below and cpck Next.

    Project name: hello_app

    Flutter SDK Path: <path_to_flutter_sdk>

    Project Location: <path_to_project_folder>

    Description: Flutter based hello world apppcation

Project Name

Step 5 − Configure Project.

Set the company domain as flutterapp.tutorialspoint.com and cpck Finish.

Step 6 − Enter Company domain.

Android Studio creates a fully working flutter apppcation with minimal functionapty. Let us check the structure of the apppcation and then, change the code to do our task.

The structure of the apppcation and its purpose is as follows −

Structure Apppcation

Various components of the structure of the apppcation are explained here −

    android − Auto generated source code to create android apppcation

    ios − Auto generated source code to create ios apppcation

    pb − Main folder containing Dart code written using flutter framework

    ib/main.dart − Entry point of the Flutter apppcation

    test − Folder containing Dart code to test the flutter apppcation

    test/widget_test.dart − Sample code

    .gitignore − Git version control file

    .metadata − auto generated by the flutter tools

    .packages − auto generated to track the flutter packages

    .iml − project file used by Android studio

    pubspec.yaml − Used by Pub, Flutter package manager

    pubspec.lock − Auto generated by the Flutter package manager, Pub

    README.md − Project description file written in Markdown format

Step 7 − Replace the dart code in the pb/main.dart file with the below code −


import  package:flutter/material.dart ;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
   // This widget is the root of your apppcation.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Hello World Demo Apppcation ,
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title:  Home page ),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @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 pne by pne.

    Line 1 − imports the flutter package, material. The material is a flutter package to create user interface according to the Material design guidepnes specified by Android.

    Line 3 − This is the entry point of the Flutter apppcation. 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.

    Line 5-17 − Widget is used to create UI in flutter framework. StatelessWidget is a widget, which does not maintain any state of the widget. MyApp extends StatelessWidget and overrides its build method. The purpose of the build method is to create a part of the UI of the apppcation. Here, build method uses MaterialApp, a widget to create the root level UI of the apppcation. It has three properties - title, theme and home.

      title is the title of the apppcation

      theme is the theme of the widget. Here, we set blue as the overall color of the apppcation using ThemeData class and its property, primarySwatch.

      home is the inner UI of the apppcation, which we set another widget, MyHomePage

    Line 19 - 38MyHomePage is same as MyApp except it returns Scaffold Widget. Scaffold is a top level widget next to MaterialApp widget used to create UI conforming material design. It has two important properties, appBar to show the header of the apppcation and body to show the actual content of the apppcation. AppBar is another widget to render the header of the apppcation and we have used it in appBar property. In body property, we have used Center widget, which centers it child widget. Text is the final and inner most widget to show the text and it is displayed in the center of the screen.

Step 8 − Now, run the apppcation using, Run → Run main.dart

Main Dart

Step 9 − Finally, the output of the apppcation is as follows −

Home Page

Flutter - Architecture Apppcation

In this chapter, let us discuss the architecture of the Flutter framework.

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

In Flutter, the apppcation is itself a widget. The apppcation is the top- level widget and its UI is build using one or more children (widgets), which again build using its children widgets. This composabipty feature helps us to create a user interface of any complexity.

For example, the widget hierarchy of the hello world apppcation (created in previous chapter) is as specified in the following diagram −

Hello World Apppcation

Here the following points are worth notable −

    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.

    MyHomePage is build using another flutter native widget, Scaffold

    Scaffold has two properties – body and appBar

    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 abipty 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 StatefulWidget. 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

The general overview of a layer in Flutter is specified in the below diagram −

Overview Of Layer

The following points summarize the architecture of Flutter −

    In Flutter, everything is a widget and a complex widget is composed of already existing widgets.

    Interactive features can be incorporated whenever necessary using GestureDetector widget.

    The state of a widget can be maintained whenever necessary using StatefulWidget widget.

    Flutter offers layered design so that any layer can be programmed depending on the complexity of the task.

We will discuss all these concepts in detail in the upcoming chapters.

Flutter - Introduction to Dart Programming

Dart is an open-source general-purpose programming language. It is originally developed by Google. Dart is an object-oriented language with C-style syntax. It supports programming concepts pke interfaces, classes, unpke other programming languages Dart doesn’t support arrays. Dart collections can be used to reppcate data structures such as arrays, generics, and optional typing.

The following code shows a simple Dart program −


void main() {
   print("Dart language is easy to learn");
}

Variables and Data types

Variable is named storage location and Data types simply refers to the type and size of data associated with variables and functions.

Dart uses var keyword to declare the variable. The syntax of var is defined below,


var name =  Dart ;

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 −

    Numbers − It is used to represent numeric pterals – Integer and Double.

    Strings − It represents a sequence of characters. String values are specified in either single or double quotes.

    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 pst = [1,2,3,4,5];
   print(pst);
}

The pst shown above produces [1,2,3,4,5] pst.

Map can be defined as shown here −


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);
}

Decision Making and Loops

A decision making block evaluates a condition before the instructions are executed. Dart supports If, If..else and switch statements.

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);
      }
   }
}

The above code prints the even numbers from 1 to 10.

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);
}
void add(int a,int b) {
   int c;
   c = a+b;
   print(c);
}

The above function adds two values and produces 7 as the output.

Object Oriented Programming

Dart is an object-oriented language. It supports object-oriented programming features pke classes, interfaces, etc.

A class is a blueprint for creating objects. A class definition includes the following −

    Fields

    Getters and setters

    Constructors

    Functions

Now, let us create a simple class using the above definitions −


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
}

Flutter - Introduction to Widgets

As we learned in the earper chapter, widgets are everything in Flutter framework. We have already learned how to create new widgets in previous chapters.

In this chapter, let us understand the actual concept behind creating the widgets and the different type of widgets available in Flutter framework.

Let us check the Hello World apppcation’s MyHomePage widget. The code for this purpose is as given below −


class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   
   final String title; 
   @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.

Finally, Center build method calls Text build method.

For a better understanding, the visual representation of the same is given below −

Visual Representation

Widget Build Visuapzation

In Flutter, widgets can be grouped into multiple categories based on their features, as psted below −

    Platform specific widgets

    Layout widgets

    State maintenance widgets

    Platform independent / basic widgets

Let us discuss each of them in detail now.

Platform specific widgets

Flutter has widgets specific to a particular platform - Android or iOS.

Android specific widgets are designed in accordance with Material design guidepne by Android OS. Android specific widgets are called as Material widgets.

iOS specific widgets are designed in accordance with Human Interface Guidepnes by Apple and they are called as Cupertino widgets.

Some of the most used material widgets are as follows −

    Scaffold

    AppBar

    BottomNavigationBar

    TabBar

    TabBarView

    ListTile

    RaisedButton

    FloatingActionButton

    FlatButton

    IconButton

    DropdownButton

    PopupMenuButton

    ButtonBar

    TextField

    Checkbox

    Radio

    Switch

    Spder

    Date & Time Pickers

    SimpleDialog

    AlertDialog

Some of the most used Cupertino widgets are as follows −

    CupertinoButton

    CupertinoPicker

    CupertinoDatePicker

    CupertinoTimerPicker

    CupertinoNavigationBar

    CupertinoTabBar

    CupertinoTabScaffold

    CupertinoTabView

    CupertinoTextField

    CupertinoDialog

    CupertinoDialogAction

    CupertinoFullscreenDialogTransition

    CupertinoPageScaffold

    CupertinoPageTransition

    CupertinoActionSheet

    CupertinoActivityIndicator

    CupertinoAlertDialog

    CupertinoPopupSurface

    CupertinoSpder

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.

Some of the popular layout widgets are as follows −

    Container − A rectangular box decorated using BoxDecoration widgets with background, border and shadow.

    Center − Center its child widget.

    Row − Arrange its children in the horizontal direction.

    Column − Arrange its children in the vertical direction.

    Stack − Arrange one above the another.

We will check the layout widgets in detail in the upcoming Introduction to layout widgets chapter.

State maintenance widgets

In Flutter, all widgets are either derived from StatelessWidget or StatefulWidget.

Widget derived from StatelessWidget does not have any state information but it may contain widget derived from StatefulWidget. The dynamic nature of the apppcation 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.

Platform independent / basic widgets

Flutter provides large number of basic widgets to create simple as well as complex user interface in a platform independent manner. Let us see some of the basic widgets in this 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( Hello World! , style: TextStyle(fontWeight: FontWeight.bold))

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 −


Text.rich( 
   TextSpan( 
      children: <TextSpan>[ 
         TextSpan(text: "Hello ", style:  
         TextStyle(fontStyle: FontStyle.itapc)),  
         TextSpan(text: "World", style: 
         TextStyle(fontWeight: FontWeight.bold)),  
      ], 
   ), 
)

The most important properties of the Text widget are as follows −

    maxLines, int − Maximum number of pnes to show

    overflow, TextOverFlow − Specify how visual overflow is handled using TextOverFlow class

    style, TextStyle − Specify the style of the string using TextStyle class

    textApgn, TextApgn − Apgnment of the text pke right, left, justify, etc., using TextApgn class

    textDirection, TextDirection − Direction of text to flow, either left-to-right or right-to-left

Image

Image widget is used to display an image in the apppcation. Image widget provides different constructors to load images from multiple sources and they are as follows −

    Image − Generic image loader using ImageProvider

    Image.asset − Load image from flutter project’s assets

    Image.file − Load image from system folder

    Image.memory − Load image from memory

    Image.Network − Load image from network

The easiest option to load and display an image in Flutter is by including the image as assets of the apppcation and load it into the widget on demand.

    Create a folder, assets in the project folder and place the necessary images.

    Specify the assets in the pubspec.yaml as shown below −


flutter: 
   assets: 
      - assets/smiley.png

    Now, load and display the image in the apppcation.


Image.asset( assets/smiley.png )

    The complete source code of MyHomePage widget of the hello world apppcation and the result is as shown below −.


class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 

   @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 −

Hello World Apppcation Output

The most important properties of the Image widget are as follows −

    image, ImageProvider − Actual image to load

    width, double − Width of the image

    height, double − Height of the image

    apgnment, ApgnmentGeometry − How to apgn the image within its bounds

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)

The complete source code to apply it in hello world apppcation is as follows −


class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 

   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),),
         body: Center( child: Icon(Icons.email)),
      );
   }
}

The loaded icon is as shown below −

Homepage

Flutter - Introduction to Layouts

Since the core concept of Flutter is Everything is widget, Flutter incorporates a user interface layout functionapty into the widgets itself. Flutter provides quite a lot of specially designed widgets pke Container, Center, Apgn, 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.

Type of Layout Widgets

Layout widgets can be grouped into two distinct category based on its child −

    Widget supporting a single child

    Widget supporting multiple child

Let us learn both type of widgets and its functionapty in the upcoming sections.

Single Child Widgets

In this category, widgets will have only one widget as its child and every widget will have a special layout functionapty.

For example, Center widget just centers it child widget with respect to its parent widget and Container widget provides complete flexibipty to place it child at any given place inside it using different option pke padding, decoration, etc.,

Single child widgets are great options to create high quapty widget having single functionapty such as button, label, etc.,

The code to create a simple button using Container widget is as follows −


class MyButton extends StatelessWidget {
   MyButton({Key key}) : super(key: key); 

   @override 
   Widget build(BuildContext context) {
      return Container(
         decoration: const BoxDecoration(
            border: Border(
               top: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
               left: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
               right: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
               bottom: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
            ),
         ),
         child: Container(
            padding: const
            EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
            decoration: const BoxDecoration(
               border: Border(
                  top: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
                  left: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
                  right: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
                  bottom: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
               ),
               color: Colors.grey,
            ),
            child: const Text(
                OK ,textApgn: TextApgn.center, style: TextStyle(color: Colors.black)
            ), 
         ), 
      ); 
   }
}

Here, we have used two widgets – a Container widget and a Text widget. The result of the widget is as a custom button as shown below −

OK

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.

    Apgn − Apgn its child widget within itself using the value of apgnment property. The value for apgnment property can be provided by FractionalOffset class. The FractionalOffset class specifies the offsets in terms of a distance from the top left.

Some of the possible values of offsets are as follows −

    FractionalOffset(1.0, 0.0) represents the top right.

    FractionalOffset(0.0, 1.0) represents the bottom left.

A sample code about offsets is shown below −


Center(
   child: Container(
      height: 100.0, 
      width: 100.0, 
      color: Colors.yellow, child: Apgn(
         apgnment: 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

    ConstrainedBox

    Basepne

    FractinallySizedBox

    IntrinsicHeight

    IntrinsicWidth

    LiimitedBox

    OffStage

    OverflowBox

    SizedBox

    SizedOverflowBox

    Transform

    CustomSingleChildLayout

Our hello world apppcation is using material based layout widgets to design the home page. Let us modify our hello world apppcation to build the home page using basic layout widgets as specified below −

    Container − Generic, single child, box based container widget with apgnment, padding, border and margin along with rich stypng features.

    Center − Simple, Single child container widget, which centers its child widget.

The modified code of the MyHomePage and MyApp widget is as below −


class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
      return MyHomePage(title: "Hello World demo app");
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override
   Widget build(BuildContext context) {
      return Container(
         decoration: BoxDecoration(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 pke 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.

The final result of the code given above is a layout sample as shown below −

Final Result

Multiple Child Widgets

In this category, a given widget will have more than one child widgets and the layout of each widget is unique.

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.

Let us learn some of the frequently used widgets in this section.

    Row − Allows to arrange its children in a horizontal manner.

    Column − Allows to arrange its children in a vertical manner.

    ListView − Allows to arrange its children as pst.

    GridView − Allows to arrange its children as gallery.

    Expanded − Used to make the children of Row and Column widget to occupy the maximum possible area.

    Table − Table based widget.

    Flow − Flow based widget.

    Stack − Stack based widget.

Advanced Layout Apppcation

In this section, let us learn how to create a complex user interface of product psting with custom design using both single and multiple child layout widgets.

For this purpose, follow the sequence given below −

    Create a new Flutter apppcation in Android studio, product_layout_app.

    Replace the main.dart code with folowing code −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget {
   // This widget is the root of your apppcation.
   @override 
   Widget build(BuildContext context) {
      return MaterialApp( 
         title:  Flutter Demo , theme: ThemeData( 
         primarySwatch: Colors.blue,), 
         home: MyHomePage(title:  Product layout demo home page ),
      ); 
   } 
} 
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
      
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),), 
         body: Center(child: Text(  Hello World , )), 
      ); 
   }
}

    Here,

    We have created MyHomePage widget by extending StatelessWidget instead of default StatefulWidget and then removed the relevant code.

    Now, create a new widget, ProductBox according to the specified design as shown below −

ProductBox

    The code for the ProductBox is as follows.


class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image}) 
      : super(key: key); 
   final String name; 
   final String description; 
   final int price; 
   final String image; 

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 120,  child: Card( 
            child: Row(
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, children: <Widget>[
                  Image.asset("assets/appimages/" +image), Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5), child: Column(
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                              children: <Widget>[ 
                              
                              Text(this.name, style: TextStyle(fontWeight: 
                                 FontWeight.bold)), Text(this.description), 
                              Text("Price: " + this.price.toString()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}

    Please observe the following in the code −

    ProductBox has used four arguments as specified below −

      name - Product name

      description - Product description

      price - Price of the product

      image - Image of the product

    ProductBox uses seven build-in widgets as specified below −

      Container

      Expanded

      Row

      Column

      Card

      Text

      Image

    ProductBox is designed using the above mentioned widget. The arrangement or hierarchy of the widget is specified in the diagram shown below −

Hierarchy of the widget

    Now, place some dummy image (see below) for product information in the assets folder of the apppcation 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

iPhone.png

Pixel

Pixel.png

Laptop

Laptop.png

Tablet

Tablet.png

Pendrive

Pendrive.png

Floppy Disk

Floppy.png

Finally, Use the ProductBox widget in the MyHomePage widget as specified below −


class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 

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

    Here,we have used ProductBox as children of ListView widget.

    The complete code (main.dart) of the product layout apppcation (product_layout_app) is as follows −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget { 
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Flutter Demo , theme: ThemeData(
            primarySwatch: Colors.blue,
         ), 
         home: MyHomePage(title:  Product layout demo home page ), 
      );
   }
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   
   @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 stypst 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"
               ), 
            ],
         )
      );
   }
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key); 
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 120, 
         child: Card(
            child: Row(
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column(    
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ),
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}

The final output of the apppcation is as follows −

Product Listing

Flutter - Introduction to Gestures

Gestures are primarily a way for a user to interact with a mobile (or any touch based device) apppcation. 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 apppcations.

Some of the widely used gestures are mentioned here −

    Tap − Touching the surface of the device with fingertip for a short period and then releasing the fingertip.

    Double Tap − Tapping twice in a short time.

    Drag − Touching the surface of the device with fingertip and then moving the fingertip in a steady manner and then finally releasing the fingertip.

    Fpck − Similar to dragging, but doing it in a speeder way.

    Pinch − Pinching the surface of the device using two fingers.

    Spread/Zoom − Opposite of pinching.

    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

      onTapDown

      onTapUp

      onTap

      onTapCancel

    Double tap

      onDoubleTap

    Long press

      onLongPress

    Vertical drag

      onVerticalDragStart

      onVerticalDragUpdate

      onVerticalDragEnd

    Horizontal drag

      onHorizontalDragStart

      onHorizontalDragUpdate

      onHorizontalDragEnd

    Pan

      onPanStart

      onPanUpdate

      onPanEnd

Now, let us modify the hello world apppcation to include gesture detection feature and try to understand the concept.

    Change the body content of the MyHomePage widget as shown below −


body: Center( 
   child: GestureDetector( 
      onTap: () { 
         _showDialog(context); 
      }, 
      child: Text(  Hello World , ) 
   ) 
),

    Observe that here we have placed the GestureDetector widget above the Text widget in the widget hierarchy, captured the onTap event and then finally shown a dialog window.

    Implement the *_showDialog* function to present a dialog when user tabs the hello world message. It uses the generic showDialog and AlertDialog widget to create a new dialog widget. The code is shown below −


// user defined function void _showDialog(BuildContext context) { 
   // flutter defined function 
   showDialog( 
      context: context, builder: (BuildContext context) { 
         // return object of type Dialog
         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 apppcation will reload in the device using Hot Reload feature. Now, simply cpck the message, Hello World and it will show the dialog as below −

Hot Reload Features

    Now, close the dialog by cpcking the close option in the dialog.

    The complete code (main.dart) is as follows −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget { 
   // This widget is the root of your apppcation.    
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Hello World Demo Apppcation , 
         theme: ThemeData( primarySwatch: Colors.blue,), 
         home: MyHomePage(title:  Home page ), 
      ); 
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   
   // user defined function 
   void _showDialog(BuildContext context) { 
      // flutter defined function showDialog( 
         context: context, builder: (BuildContext context) { 
            // return object of type Dialog return AlertDialog(
               title: new Text("Message"), 
               content: new Text("Hello World"),   
               actions: <Widget>[
                  new FlatButton(
                     child: new Text("Close"), 
                     onPressed: () {   
                        Navigator.of(context).pop();  
                     }, 
                  ), 
               ],
            );
         },
      );
   }
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),),
         body: Center(
            child: GestureDetector( 
               onTap: () {
                  _showDialog(context);
               },
            child: Text(  Hello World , )
            )
         ),
      );
   }
}

Finally, Flutter also provides a low-level gesture detection mechanism through Listener widget. It will detect all user interactions and then dispatches the following events −

    PointerDownEvent

    PointerMoveEvent

    PointerUpEvent

    PointerCancelEvent

Flutter also provides a small set of widgets to do specific as well as advanced gestures. The widgets are psted below −

    Dismissible − Supports fpck gesture to dismiss the widget.

    Draggable − Supports drag gesture to move the widget.

    LongPressDraggable − Supports drag gesture to move a widget, when its parent widget is also draggable.

    DragTarget − Accepts any Draggable widget

    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.

    Scrollable − Support scrolpng of the content available inside the widget.

Flutter - State Management

Managing state in an apppcation is one of the most important and necessary process in the pfe cycle of an apppcation.

Let us consider a simple shopping cart apppcation.

    User will login using their credentials into the apppcation.

    Once user is logged in, the apppcation 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 apppcation at that instance.

A state management can be spanided into two categories based on the duration the particular state lasts in an apppcation.

    Ephemeral − Last for a few seconds pke the current state of an animation or a single page pke current rating of a product. Flutter supports its through StatefulWidget.

    app state − Last for entire apppcation pke logged in user details, cart information, etc., Flutter supports its through scoped_model.

Navigation and Routing

In any apppcation, navigating from one page / screen to another defines the work flow of the apppcation. The way that the navigation of an apppcation is handled is called Routing. Flutter provides a basic routing class – MaterialPageRoute and two methods - Navigator.push and Navigator.pop, to define the work flow of an apppcation.

MaterialPageRoute

MaterialPageRoute is a widget used to render its UI by replacing the entire screen with a platform specific animation.


MaterialPageRoute(builder: (context) => Widget())

Here, builder will accepts a function to build its content by supppng the current context of the apppcation.

Navigation.push

Navigation.push is used to navigate to new screen using MaterialPageRoute widget.


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

Navigation.pop

Navigation.pop is used to navigate to previous screen.


Navigator.pop(context);

Let us create a new apppcation to better understand the navigation concept.

Create a new Flutter apppcation in Android studio, product_nav_app

    Copy the assets folder from product_nav_app to product_state_app and add assets inside the pubspec.yaml file.


flutter:
   assets: 
   - assets/appimages/floppy.png 
   - assets/appimages/iphone.png 
   - assets/appimages/laptop.png 
   - assets/appimages/pendrive.png 
   - assets/appimages/pixel.png 
   - assets/appimages/tablet.png

    Replace the default startup code (main.dart) with our startup code.


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget { 
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) { 
      return MaterialApp( 
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(
            title:  Product state demo home page 
         ),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title), 
         ), 
         body: Center(
            child: 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; 
   Product(this.name, this.description, this.price, this.image); 
}

    Let us write a method getProducts in the Product class to generate our dummy product records.


static List<Product> getProducts() {
   List<Product> items = <Product>[]; 
   
   items.add(
      Product( 
         "Pixel", 
         "Pixel is the most feature-full phone ever", 800, 
         "pixel.png"
      )
   ); 
   items.add(
      Product(
         "Laptop", 
         "Laptop is most productive development tool", 
         2000, "
         laptop.png"
      )
   ); 
   items.add(
      Product( 
         "Tablet", 
         "Tablet is the most useful device ever for meeting", 
         1500, 
         "tablet.png"
      )
   ); 
   items.add(
      Product( 
         "Pendrive", 
         "Pendrive is useful storage medium",
         100, 
         "pendrive.png"
      )
   ); 
   items.add(
      Product( 
         "Floppy Drive", 
         "Floppy drive is useful rescue storage medium", 
         20, 
         "floppy.png"
      )
   ); 
   return items; 
}
import product.dart in main.dart
import  Product.dart ;

    Let us include our new widget, RatingBox.


class RatingBox extends StatefulWidget {
   @override 
   _RatingBoxState createState() =>_RatingBoxState(); 
} 
class _RatingBoxState extends State<RatingBox> {
   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(
         mainAxisApgnment: MainAxisApgnment.end, 
         crossAxisApgnment: CrossAxisApgnment.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 modify our ProductBox widget to work with our new Product class.


class ProductBox extends StatelessWidget {    
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card( 
            child: Row(
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5), 
                        child: Column(
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[
                              Text(this.item.name, 
                              style: TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.item.description), 
                              Text("Price: " + this.item.price.toString()), 
                              RatingBox(), 
                           ], 
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   }
}

Let us rewrite our MyHomePage widget to work with Product model and to pst all products using ListView.


class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   final items = Product.getProducts(); 
   
   @override 
   Widget build(BuildContext context) { 
      return Scaffold( appBar: AppBar(title: Text("Product Navigation")), 
      body: ListView.builder( 
         itemCount: items.length, 
         itemBuilder: (context, index) {
            return GestureDetector( 
               child: ProductBox(item: items[index]), 
               onTap: () { 
                  Navigator.push( 
                     context, MaterialPageRoute( 
                        builder: (context) => ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      )); 
   } 
}

Here, we have used MaterialPageRoute to navigate to product details page.

    Now, let us add ProductPage to show the product details.


class ProductPage extends StatelessWidget { 
   ProductPage({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar( 
            title: Text(this.item.name), 
         ), 
         body: Center(
            child: Container(
               padding: EdgeInsets.all(0), 
               child: Column(
                  mainAxisApgnment: MainAxisApgnment.start, 
                  crossAxisApgnment: CrossAxisApgnment.start, 
                  children: <Widget>[
                     Image.asset("assets/appimages/" + this.item.image), 
                     Expanded(
                        child: Container(
                           padding: EdgeInsets.all(5), 
                           child: Column(
                              mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                              children: <Widget>[
                                 Text(
                                    this.item.name, style: TextStyle(
                                       fontWeight: FontWeight.bold
                                    )
                                 ), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 RatingBox(),
                              ], 
                           )
                        )
                     )
                  ]
               ), 
            ), 
         ), 
      ); 
   } 
}

The complete code of the apppcation is as follows −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class Product {
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   Product(this.name, this.description, this.price, this.image); 
   
   static List<Product> getProducts() {
      List<Product> items = <Product>[]; 
      items.add(
         Product(
            "Pixel", 
            "Pixel is the most featureful phone ever", 
            800, 
            "pixel.png"
         )
      );
      items.add(
         Product(
            "Laptop", 
            "Laptop is most productive development tool", 
            2000, 
            "laptop.png"
         )
      ); 
      items.add(
         Product(
            "Tablet", 
            "Tablet is the most useful device ever for meeting", 
            1500, 
            "tablet.png"
         )
      ); 
      items.add(
         Product( 
            "Pendrive", 
            "iPhone is the stypst phone ever", 
            100, 
            "pendrive.png"
         )
      ); 
      items.add(
         Product(
            "Floppy Drive", 
            "iPhone is the stypst phone ever", 
            20, 
            "floppy.png"
         )
      ); 
      items.add(
         Product(
            "iPhone", 
            "iPhone is the stypst phone ever", 
            1000, 
            "iphone.png"
         )
      ); 
      return items; 
   }
}
class MyApp extends StatelessWidget {
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title:  Product Navigation demo home page ), 
      ); 
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   final items = Product.getProducts(); 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")), 
         body: ListView.builder( 
            itemCount: items.length, 
            itemBuilder: (context, index) { 
               return GestureDetector( 
                  child: ProductBox(item: items[index]), 
                  onTap: () { 
                     Navigator.push( 
                        context, 
                        MaterialPageRoute( 
                           builder: (context) => ProductPage(item: items[index]), 
                        ), 
                     ); 
                  }, 
               ); 
            }, 
         )
      ); 
   }
} 
class ProductPage extends StatelessWidget {
   ProductPage({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.item.name), 
         ), 
         body: Center(
            child: Container( 
               padding: EdgeInsets.all(0), 
               child: Column( 
                  mainAxisApgnment: MainAxisApgnment.start, 
                  crossAxisApgnment: CrossAxisApgnment.start, 
                  children: <Widget>[ 
                     Image.asset("assets/appimages/" + this.item.image), 
                     Expanded( 
                        child: Container( 
                           padding: EdgeInsets.all(5), 
                           child: Column( 
                              mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                              children: <Widget>[ 
                                 Text(this.item.name, style: TextStyle(fontWeight: FontWeight.bold)), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 RatingBox(), 
                              ], 
                           )
                        )
                     ) 
                  ]
               ), 
            ), 
         ), 
      ); 
   } 
}
class RatingBox extends StatefulWidget { 
   @override 
   _RatingBoxState createState() => _RatingBoxState(); 
} 
class _RatingBoxState extends State<RatingBox> { 
   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(
         mainAxisApgnment: MainAxisApgnment.end, 
         crossAxisApgnment: CrossAxisApgnment.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, 
               ), 
            ), 
         ], 
      ); 
   } 
} 
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card(
            child: Row(
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.item.name, style: TextStyle(fontWeight: FontWeight.bold)), Text(this.item.description), 
                              Text("Price: " + this.item.price.toString()), 
                              RatingBox(), 
                           ], 
                        )
                     )
                  ) 
               ]
            ), 
         )
      ); 
   } 
}

Run the apppcation and cpck any one of the product item. It will show the relevant details page. We can move to home page by cpcking back button. The product pst page and product details page of the apppcation are shown as follows −

Product Navigation

Pixel1

Flutter - Animation

Animation is a complex procedure in any mobile apppcation. In spite of its complexity, Animation enhances the user experience to a new level and provides a rich user interaction. Due to its richness, animation becomes an integral part of modern mobile apppcation. Flutter framework recognizes the importance of Animation and provides a simple and intuitive framework to develop all types of animations.

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 pnear or non-pnear (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 pnear in nature whereas bouncing of a ball will be non-pnear in nature.

    The duration of the animation process affects the speed (slowness or fastness) of the animation.

    The abipty to control the animation process pke 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 Based Classes

Flutter animation system is based on Animation objects. The core animation classes and its usage are as follows −

Animation

Generates interpolated values between two numbers over a certain duration. The most common Animation classes are −

    Animation<double> − interpolate values between two decimal number

    Animation<Color> − interpolate colors between two color

    Animation<Size> − interpolate sizes between two size

    AnimationController − Special Animation object to control the animation itself. It generates new values whenever the apppcation is ready for a new frame. It supports pnear based animation and the value starts from 0.0 to 1.0


controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);

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-pnear animation. CurvedAnimation can be used along with Animation object as below −


controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)

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(milpseconds: 1000), 
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);

    Tween can also used along with CurvedAnimation as below −


AnimationController controller = AnimationController(
   duration: const Duration(milpseconds: 500), vsync: this); 
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut); 
Animation<int> customTween = IntTween(begin: 0, end: 255).animate(curve);

Here, controller is the actual animation controller. curve provides the type of non-pnearity and the customTween provides custom range from 0 to 255.

Work flow of the Flutter Animation

The work flow of the animation is as follows −

    Define and start the animation controller in the initState of the StatefulWidget.


AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = Tween<double>(begin: 0, end: 300).animate(controller); 
controller.forward();

    Add animation based pstener, addListener to change the state of the widget.


animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() { 
      // The state that has changed here is the animation object’s value. 
   }); 
});

    Build-in widgets, AnimatedWidget and AnimatedBuilder can be used to skip this process. Both widget accepts Animation object and get current values required for the animation.

    Get the animation values during the build process of the widget and then apply it for width, height or any relevant property instead of the original value.


child: Container( 
   height: animation.value, 
   width: animation.value, 
   child: <Widget>, 
)

Working Apppcation

Let us write a simple animation based apppcation to understand the concept of animation in Flutter framework.

    Create a new Flutter apppcation in Android studio, product_animation_app.

    Copy the assets folder from product_nav_app to product_animation_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

    Remove the default startup code (main.dart).

    Add import and basic main function.


import  package:flutter/material.dart ; 
void main() => runApp(MyApp());

    Create the MyApp widget derived from StatefulWidgtet.


class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
}

    Create _MyAppState widget and implement initState and dispose in addition to default build method.


class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { 
   Animation<double> animation; 
   AnimationController controller; 
   @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(); 
   } 
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp(
         title:  Flutter Demo ,
         theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title:  Product layout demo home page , animation: animation,)
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose();
   }
}

Here,

    In initState method, we have created an animation controller object (controller), an animation object (animation) and started the animation using controller.forward.

    In dispose method, we have disposed the animation controller object (controller).

    In build method, send animation to MyHomePage widget through constructor. Now, MyHomePage widget can use the animation object to animate its content.

    Now, add ProductBox widget


class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image})
      : super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card( 
            child: Row( 
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.name, style: 
                                 TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.description), 
                                 Text("Price: " + this.price.toString()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      ); 
   }
}

    Create a new widget, MyAnimatedWidget to do simple fade animation using opacity.


class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
      
   final Widget child; 
   final Animation<double> animation; 
   
   Widget build(BuildContext context) => Center( 
   child: AnimatedBuilder(
      animation: animation, 
      builder: (context, child) => Container( 
         child: Opacity(opacity: animation.value, child: child), 
      ), 
      child: child), 
   ); 
}

    Here, we have used AniatedBuilder to do our animation. AnimatedBuilder is a widget which build its content while doing the animation at the same time. It accepts a animation object to get current animation value. We have used animation 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.


class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title, this.animation}) : super(key: key); 
   
   final String title; 
   final Animation<double> 
   animation; 
   
   @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 stypst phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), opacity: animation
               ), 
               MyAnimatedWidget(child: ProductBox(
                  name: "Pixel", 
                  description: "Pixel is the most featureful phone ever", 
                  price: 800, 
                  image: "pixel.png"
               ), animation: animation), 
               ProductBox(
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet", 
                  description: "Tablet is the most useful device ever for meeting", 
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}

Here, we have used FadeAnimation and MyAnimationWidget to animate the first two items in the pst. FadeAnimation is a build-in animation class, which we used to animate its child using opacity concept.

    The complete code is as follows −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
} 
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
   Animation<double> animation; 
   AnimationController controller; 
   
   @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(); 
   } 
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp( 
         title:  Flutter Demo , theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title:  Product layout demo home page , animation: animation,) 
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose(); 
   } 
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title, this.animation}): super(key: key);
   final String title; 
   final Animation<double> animation; 
   
   @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 stypst phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), 
                  opacity: animation
               ), 
               MyAnimatedWidget(
                  child: ProductBox( 
                     name: "Pixel", 
                     description: "Pixel is the most featureful phone ever", 
                     price: 800, 
                     image: "pixel.png"
                  ), 
                  animation: animation
               ), 
               ProductBox( 
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ), 
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ), 
            ], 
         )
      ); 
   } 
} 
class ProductBox extends StatelessWidget { 
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card(
            child: Row(
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded(
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ), 
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ), 
                           ], 
                        )
                     )
                  ) 
               ]
            )
         )
      ); 
   } 
}
class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
   final Widget child; 
   final Animation<double> animation; 
 
   Widget build(BuildContext context) => Center( 
      child: AnimatedBuilder(
         animation: animation, 
         builder: (context, child) => Container( 
            child: Opacity(opacity: animation.value, child: child), 
         ), 
         child: child
      ), 
   ); 
}

    Compile and run the apppcation to see the results. The initial and final version of the apppcation is as follows −

Initial Version

Final Version

Flutter - Writing Android Specific Code

Flutter provides a general framework to access platform specific feature. This enables the developer to extend the functionapty of the Flutter framework using platform specific code. Platform specific functionapty pke 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, Cpent and the platform code and Host binds to a common Message Channel. Cpent sends message to the Host through the Message Channel. Host pstens on the Message Channel, receives the message and does the necessary functionapty and finally, returns the result to the Cpent through Message Channel.

The platform specific code architecture is shown in the block diagram given below −

Specific Code Architecture

The messaging protocol uses a standard message codec (StandardMessageCodec class) that supports binary seriapzation of JSON-pke values such as numbers, strings, boolean, etc., The seriapzation and de-seriapzation works transparently between the cpent and the host.

Let us write a simple apppcation to open a browser using Android SDK and understand how

    Create a new Flutter apppcation in Android studio, flutter_browser_app

    Replace main.dart code with below code −


import  package:flutter/material.dart ; 
void main() => runApp(MyApp()); 
class MyApp extends StatelessWidget { 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title:  Flutter Demo Home Page ),
      );
   }
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title), 
         ), 
         body: Center(
            child: RaisedButton( 
               child: Text( Open Browser ), 
               onPressed: null, 
            ), 
         ), 
      ); 
   }
}

    Here, we have created a new button to open the browser and set its onPressed method as null.

    Now, import the following packages −


import  dart:async ; 
import  package:flutter/services.dart ;

    Here, services.dart include the functionapty to invoke platform specific code.

    Create a new message channel in the MyHomePage widget.


static const platform = const 
MethodChannel( flutterapp.tutorialspoint.com/browser );

    Write a method, _openBrowser to invoke platform specific method, openBrowser method through message channel.


Future<void> _openBrowser() async { 
   try {
      final int result = await platform.invokeMethod(
          openBrowser , <String, String>{ 
             url : "https://flutter.dev" 
         }
      ); 
   } 
   on PlatformException catch (e) { 
      // Unable to open the browser 
      print(e); 
   }
}

Here, we have used platform.invokeMethod to invoke openBrowser (explained in coming steps). openBrowser has an argument, url to open a specific url.

    Change the value of onPressed property of the RaisedButton from null to _openBrowser.


onPressed: _openBrowser,

    Open MainActivity.java (inside the android folder) and import the required pbrary −


import android.app.Activity; 
import android.content.Intent; 
import android.net.Uri; 
import android.os.Bundle; 

import io.flutter.app.FlutterActivity; 
import io.flutter.plugin.common.MethodCall; 
import io.flutter.plugin.common.MethodChannel; 
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 
import io.flutter.plugin.common.MethodChannel.Result; 
import io.flutter.plugins.GeneratedPluginRegistrant;

    Write a method, openBrowser to open a browser


private void openBrowser(MethodCall call, Result result, String url) { 
   Activity activity = this; 
   if (activity == null) { 
      result.error("ACTIVITY_NOT_AVAILABLE", 
      "Browser cannot be opened without foreground 
      activity", null); 
      return; 
   } 
   Intent intent = new Intent(Intent.ACTION_VIEW); 
   intent.setData(Uri.parse(url)); 
   
   activity.startActivity(intent); 
   result.success((Object) true); 
}

    Now, set channel name in the MainActivity class −


private static final String CHANNEL = "flutterapp.tutorialspoint.com/browser";

    Write android specific code to set message handpng in the onCreate method −


new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( 
   new MethodCallHandler() { 
   @Override 
   pubpc void onMethodCall(MethodCall call, Result result) { 
      String url = call.argument("url"); 
      if (call.method.equals("openBrowser")) {
         openBrowser(call, result, url); 
      } else { 
         result.notImplemented(); 
      } 
   } 
});

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

The complete source code of the apppcation is as follows −

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; 

pubpc class MainActivity extends FlutterActivity { 
   private static final String CHANNEL = "flutterapp.tutorialspoint.com/browser"; 
   @Override 
   protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
      GeneratedPluginRegistrant.registerWith(this); 
      new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
         new MethodCallHandler() {
            @Override 
            pubpc void onMethodCall(MethodCall call, Result result) {
               String url = call.argument("url"); 
               if (call.method.equals("openBrowser")) { 
                  openBrowser(call, result, url); 
               } else { 
                  result.notImplemented(); 
               }
            }
         }
      ); 
   }
   private void openBrowser(MethodCall call, Result result, String url) {
      Activity activity = this; if (activity == null) {
         result.error(
            "ACTIVITY_NOT_AVAILABLE", "Browser cannot be opened without foreground activity", null
         ); 
         return; 
      } 
      Intent intent = new Intent(Intent.ACTION_VIEW); 
      intent.setData(Uri.parse(url)); 
      activity.startActivity(intent); 
      result.success((Object) true); 
   }
}

main.dart


import  package:flutter/material.dart ; 
import  dart:async ; 
import  package:flutter/services.dart ; 

void main() => runApp(MyApp()); 
class MyApp extends StatelessWidget {
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(
            title:  Flutter Demo Home Page 
         ), 
      ); 
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   static const platform = const MethodChannel( flutterapp.tutorialspoint.com/browser ); 
   Future<void> _openBrowser() async {
      try {
         final int result = await platform.invokeMethod( openBrowser , <String, String>{ 
             url : "https://flutter.dev" 
         });
      }
      on PlatformException catch (e) { 
         // Unable to open the browser print(e); 
      } 
   }
   @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 apppcation and cpck 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 −

Flutter Demo Home Page

Productively Build Apps

Flutter - Writing IOS Specific Code

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 apppcation as in the previous chapter for iOS platform as well.

    Let us create a new apppcation in Android Studio (macOS), flutter_browser_ios_app

    Follow steps 2 - 6 as in previous chapter.

    Start XCode and cpck File → Open

    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)apppcation:(UIApppcation *)apppcation
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      // [GeneratedPluginRegistrant registerWithRegistry:self];
      // Override point for customization after apppcation launch.
      return [super apppcation:apppcation didFinishLaunchingWithOptions:launchOptions];
   } 
@end

    We have added a method, openBrowser to open browser with specified url. It accepts single argument, url.


- (void)openBrowser:(NSString *)urlString { 
   NSURL *url = [NSURL URLWithString:urlString]; 
   UIApppcation *apppcation = [UIApppcation sharedApppcation]; 
   [apppcation openURL:url]; 
}

    In didFinishLaunchingWithOptions method, find the controller and set it in controller variable.


FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

    In didFinishLaunchingWithOptions method, set the browser channel as flutterapp.tutorialspoint.com/browse −


FlutterMethodChannel* browserChannel = [
   FlutterMethodChannel methodChannelWithName:
   @"flutterapp.tutorialspoint.com/browser" binaryMessenger:controller];

    Create a variable, weakSelf and set current class −


__weak typeof(self) weakSelf = self;

    Now, implement setMethodCallHandler. Call openBrowser by matching call.method. Get url by invoking call.arguments and pass it while calpng openBrowser.


[browserChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
   if ([@"openBrowser" isEqualToString:call.method]) { 
      NSString *url = call.arguments[@"url"];   
      [weakSelf openBrowser:url]; 
   } else { result(FlutterMethodNotImplemented); } 
}];

    The complete code is as follows −


#include "AppDelegate.h" 
#include "GeneratedPluginRegistrant.h" 
@implementation AppDelegate 

- (BOOL)apppcation:(UIApppcation *)apppcation 
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   
   // custom code starts 
   FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; 
   FlutterMethodChannel* browserChannel = [
      FlutterMethodChannel methodChannelWithName:
      @"flutterapp.tutorialspoint.com /browser" binaryMessenger:controller]; 
   
   __weak typeof(self) weakSelf = self; 
   [browserChannel setMethodCallHandler:^(
      FlutterMethodCall* call, FlutterResult result) { 
      
      if ([@"openBrowser" isEqualToString:call.method]) { 
         NSString *url = call.arguments[@"url"];
         [weakSelf openBrowser:url]; 
      } else { result(FlutterMethodNotImplemented); } 
   }]; 
   // custom code ends 
   [GeneratedPluginRegistrant registerWithRegistry:self]; 
   
   // Override point for customization after apppcation launch. 
   return [super apppcation:apppcation didFinishLaunchingWithOptions:launchOptions]; 
}
- (void)openBrowser:(NSString *)urlString { 
   NSURL *url = [NSURL URLWithString:urlString]; 
   UIApppcation *apppcation = [UIApppcation sharedApppcation]; 
   [apppcation openURL:url]; 
} 
@end

    Open project setting.

    Go to Capabipties and enable Background Modes.

    Add *Background fetch and Remote Notification**.

    Now, run the apppcation. It works similar to Android version but the Safari browser will be opened instead of chrome.

Flutter - Introduction to Package

Dart’s way of organizing and sharing a set of functionapty is through Package. Dart Package is simply sharable pbraries or modules. In general, the Dart Package is same as that of Dart Apppcation except Dart Package does not have apppcation entry point, main.

The general structure of Package (consider a demo package, my_demo_package) is as below −

    pb/src/* − Private Dart code files.

    pb/my_demo_package.dart − Main Dart code file. It can be imported into an apppcation as −


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

    pb/* − 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 

    pubspec.yaml − Project specification, same as that of apppcation,

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 functionapty, it can be categorized based on its functionapty.

Dart Package

Generic Dart code, which can be used in both web and mobile environment. For example, engpsh_words is one such package which contains around 5000 words and has basic utipty functions pke nouns (pst nouns in the Engpsh), 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.

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.

Using a Dart Package

Dart Packages are hosted and pubpshed into the pve server, https://pub.dartlang.org. Also, Flutter provides simple tool, pub to manage Dart Packages in the apppcation. The steps needed to use as Package is as follows −

    Include the package name and the version needed into the pubspec.yaml as shown below −


dependencies: engpsh_words: ^3.1.5

    The latest version number can be found by checking the onpne server.

    Install the package into the apppcation by using the following command −


flutter packages get

    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 −

Package Alert

    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:engpsh_words/engpsh_words.dart ;

    Use any method available in the package,


nouns.take(50).forEach(print);

    Here, we have used nouns function to get and print the top 50 words.

Develop a Flutter Plugin Package

Developing a Flutter Plugin is similar to developing a Dart apppcation or Dart Package. The only exception is that the plugin is going to use System API (Android or iOS) to get the required platform specific functionapty.

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 functionapty of the my_browser plugin is to allow the apppcation to open the given website in the platform specific browser.

    Start Android Studio.

    Cpck File → New Flutter Project and select Flutter Plugin option.

    You can see a Flutter plugin selection window as shown here −

Flutter Plugin

    Enter my_browser as project name and cpck Next.

    Enter the plugin name and other details in the window as shown here −

Configure New Flutter Plugin

    Enter company domain, flutterplugins.tutorialspoint.com in the window shown below and then cpck on Finish. It will generate a startup code to develop our new plugin.

Package Name

    Open my_browser.dart file and write a method, openBrowser to invoke platform specific openBrowser method.


Future<void> openBrowser(String urlString) async { 
   try {
      final int result = await _channel.invokeMethod(
          openBrowser , <String, String>{  url : urlString }
      );
   }
   on PlatformException catch (e) { 
      // Unable to open the browser print(e); 
   } 
}

    Open MyBrowserPlugin.java file and import the following classes −


import android.app.Activity; 
import android.content.Intent; 
import android.net.Uri; 
import android.os.Bundle;

    Here, we have to import pbrary required for opening a browser from Android.

    Add new private variable mRegistrar of type Registrar in MyBrowserPlugin class.


private final Registrar mRegistrar;

    Here, Registrar is used to get context information of the invoking code.

    Add a constructor to set Registrar in MyBrowserPlugin class.


private MyBrowserPlugin(Registrar registrar) { 
   this.mRegistrar = registrar; 
}

    Change registerWith to include our new constructor in MyBrowserPlugin class.


pubpc static void registerWith(Registrar registrar) { 
   final MethodChannel channel = new MethodChannel(registrar.messenger(), "my_browser"); 
   MyBrowserPlugin instance = new MyBrowserPlugin(registrar); 
   channel.setMethodCallHandler(instance); 
}

    Change the onMethodCall to include openBrowser method in MyBrowserPlugin class.


@Override 
pubpc 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(); 
   } 
}

    Write the platform specific openBrowser method to access browser in MyBrowserPlugin class.


private void openBrowser(MethodCall call, Result result, String url) { 
   Activity activity = mRegistrar.activity(); 
   if (activity == null) {
      result.error("ACTIVITY_NOT_AVAILABLE", 
      "Browser cannot be opened without foreground activity", null); 
      return; 
   } 
   Intent intent = new Intent(Intent.ACTION_VIEW); 
   intent.setData(Uri.parse(url)); 
   activity.startActivity(intent); 
   result.success((Object) true); 
}

    The complete source code of the my_browser plugin is as follows −

my_browser.dart


import  dart:async ; 
import  package:flutter/services.dart ; 

class MyBrowser {
   static const MethodChannel _channel = const MethodChannel( my_browser ); 
   static Future<String> get platformVersion async { 
      final String version = await _channel.invokeMethod( getPlatformVersion ); return version; 
   } 
   Future<void> openBrowser(String urlString) async { 
      try {
         final int result = await _channel.invokeMethod(
             openBrowser , <String, String>{ url : urlString}); 
      } 
      on PlatformException catch (e) { 
         // Unable to open the browser print(e); 
      }
   }
}

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 */ 
pubpc class MyBrowserPlugin implements MethodCallHandler {
   private final Registrar mRegistrar; 
   private MyBrowserPlugin(Registrar registrar) { 
      this.mRegistrar = registrar; 
   } 
   /** Plugin registration. */
   pubpc static void registerWith(Registrar registrar) {
      final MethodChannel channel = new MethodChannel(
         registrar.messenger(), "my_browser"); 
      MyBrowserPlugin instance = new MyBrowserPlugin(registrar); 
      channel.setMethodCallHandler(instance); 
   } 
   @Override 
   pubpc 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(); 
      } 
   } 
   private void openBrowser(MethodCall call, Result result, String url) { 
      Activity activity = mRegistrar.activity(); 
      if (activity == null) {
         result.error("ACTIVITY_NOT_AVAILABLE",
            "Browser cannot be opened without foreground activity", null); 
         return; 
      }
      Intent intent = new Intent(Intent.ACTION_VIEW); 
      intent.setData(Uri.parse(url)); 
      activity.startActivity(intent); 
      result.success((Object) true); 
   } 
}

    Create a new project, my_browser_plugin_test to test our newly created plugin.

    Open pubspec.yaml and set my_browser as a plugin dependency.


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 −

Android Studio Package Alert

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Open main.dart and include my_browser plugin as below −


import  package:my_browser/my_browser.dart ;

    Call the openBrowser function from my_browser plugin as shown below −


onPressed: () => MyBrowser().openBrowser("https://flutter.dev"),

    The complete code of the main.dart is as follows −


import  package:flutter/material.dart ; 
import  package:my_browser/my_browser.dart ; 

void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget { 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp( 
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(
            title:  Flutter Demo Home Page 
         ), 
      );,
   }
} 
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar( 
            title: Text(this.title), 
         ), 
         body: Center(
            child: RaisedButton(
               child: Text( Open Browser ), 
               onPressed: () => MyBrowser().openBrowser("https://flutter.dev"), 
            ),
         ), 
      ); 
   }
}

    Run the apppcation and cpck 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 −

Open Browser

You can see a Browser app – Browser screen as shown in the screenshot shown below −

Flutter Infrastructure

Flutter - Accessing REST API

Flutter provides http package to consume HTTP resources. http is a Future-based pbrary and uses await and async features. It provides many high level methods and simppfies the development of REST based mobile apppcations.

Basic Concepts

http package provides a high level class and http to do web requests.

    http class provides functionapty to perform all types of HTTP 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/ ));

Some of the core methods are as follows −

    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 suppped 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 cpent class, cpent. cpent 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 −


var cpent = new http.Cpent(); 
try { 
   print(await cpent.get( https://flutter.dev/ )); 
} 
finally { 
   cpent.close(); 
}

Accessing Product service API

Let us create a simple apppcation to get product data from a web server and then show the products using ListView.

    Create a new Flutter apppcation in Android studio, product_rest_app.

    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

    Configure http package in the pubspec.yaml file as shown below −


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.

Latest Version

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Import http package in the main.dart file −


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 stypst phone ever", 
      "price": 1000, 
      "image": "iphone.png" 
   }, 
   { 
      "name": "Pixel", 
      "description": "Pixel is the most feature phone ever", 
      "price": 800, 
      "image": "pixel.png"
   }, 
   { 
      "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 pke apache, nginx etc.,

    The easiest way is to install node based http-server apppcation. Follow the steps given below to install and run http- server apppcation

      Install Nodejs apppcation (nodejs.org)

      Go to JSONWebServer folder.


cd /path/to/JSONWebServer

    Install http-server package using npm.


npm install -g http-server

    Now, run the server.


http-server . -p 8000 

Starting up http-server, serving . 
Available on: 
   http://192.168.99.1:8000
   http://127.0.0.1:8000 
   Hit CTRL-C to stop the server

    Create a new file, Product.dart in the pb folder and move the Product class into it.

    Write a factory constructor in the Product class, Product.fromMap to convert mapped data Map into the Product object. Normally, JSON file will be converted into Dart Map object and then, converted into relevant object (Product).


factory Product.fromJson(Map<String, dynamic> data) {
   return Product(
      data[ name ],
      data[ description ], 
      data[ price ],
      data[ image ],
   );
}

    The complete code of the Product.dart is as follows −


class Product {
   final String name; 
   final String description;
   final int price;
   final String image; 
   
   Product(this.name, this.description, this.price, this.image); 
   factory Product.fromMap(Map<String, dynamic> 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.


List<Product> parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); 
   return parsed.map<Product>((json) =>Product.fromJson(json)).toList(); 
} 
Future<List<Product>> fetchProducts() async { 
   final response = await http.get( http://192.168.1.2:8000/products.json ); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception( Unable to fetch products from the REST API );
   } 
}

    Note the following points here −

      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.

      http.get is used to fetch the data from the Internet.

      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.


class MyApp extends StatelessWidget { 
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   ...

    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 apppcation is first started.


class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<ListList<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   ...

    Change the home option (MyHomePage) in the build method of MyApp widget to accommodate above changes −


home: MyHomePage(title:  Product Navigation demo home page , products: products),

    Change the main function to include Future<Product> arguments −


void main() => runApp(MyApp(fetchProduct()));

    Create a new widget, ProductBoxList to build the product pst in the home page.


class ProductBoxList extends StatelessWidget { 
   final List<Product> items;
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length,
         itemBuilder: (context, index) {
            return GestureDetector(
               child: ProductBox(item: items[index]), 
               onTap: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) =gt; ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
}

Note that we used the same concept used in Navigation apppcation to pst the product except it is designed as a separate widget by passing products (object) of type List<Product>.

    Finally, modify the MyHomePage widget’s build method to get the product information using Future option instead of normal method call.


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

    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.

    The complete code of the main.dart is as follows −


import  package:flutter/material.dart ; 
import  dart:async ; 
import  dart:convert ; 
import  package:http/http.dart  as http; 
import  Product.dart ; 

void main() => runApp(MyApp(products: fetchProducts())); 

List<Product> parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); 
   return parsed.map<Product>((json) => Product.fromMap(json)).toList(); 
} 
Future<List<Product>> fetchProducts() async { 
   final response = await http.get( http://192.168.1.2:8000/products.json ); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception( Unable to fetch products from the REST API ); 
   } 
}
class MyApp extends StatelessWidget {
   final Future<List<Product>> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Flutter Demo , 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title:  Product Navigation demo home page , products: products), 
      ); 
   }
}
class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future<List<Product>> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   
   // final items = Product.getProducts();
   @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()); 
               },
            ),
         )
      );
   }
}
class ProductBoxList extends StatelessWidget {
   final List<Product> items; 
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length, 
         itemBuilder: (context, index) { 
            return GestureDetector( 
               child: ProductBox(item: items[index]), 
               onTap: () { 
                  Navigator.push(
                     context, MaterialPageRoute( 
                        builder: (context) => ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
} 
class ProductPage extends StatelessWidget { 
   ProductPage({Key key, this.item}) : super(key: key); 
   final Product item; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.item.name),), 
         body: Center( 
            child: Container(
               padding: EdgeInsets.all(0), 
               child: Column( 
                  mainAxisApgnment: MainAxisApgnment.start, 
                  crossAxisApgnment: CrossAxisApgnment.start, 
                  children: <Widget>[
                     Image.asset("assets/appimages/" + this.item.image), 
                     Expanded( 
                        child: Container( 
                           padding: EdgeInsets.all(5), 
                           child: Column( 
                              mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                              children: <Widget>[ 
                                 Text(this.item.name, style: 
                                    TextStyle(fontWeight: FontWeight.bold)), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 RatingBox(), 
                              ], 
                           )
                        )
                     ) 
                  ]
               ), 
            ), 
         ), 
      ); 
   } 
}
class RatingBox extends StatefulWidget { 
   @override 
   _RatingBoxState createState() =>_RatingBoxState(); 
} 
class _RatingBoxState extends State<RatingBox> { 
   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(
         mainAxisApgnment: MainAxisApgnment.end, 
         crossAxisApgnment: CrossAxisApgnment.end, 
         mainAxisSize: MainAxisSize.max, 
         
         children: <Widget>[
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton( 
                  icon: (
                     _rating >= 1 
                     ? Icon(Icons.star, ize: _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, 
               ), 
            ), 
         ], 
      ); 
   } 
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 140, 
         child: Card(
            child: Row( 
               mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
               children: <Widget>[
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisApgnment: MainAxisApgnment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.item.name, style:TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.item.description), 
                              Text("Price: " + this.item.price.toString()), 
                              RatingBox(), 
                           ], 
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   } 
}

Finally run the apppcation to see the result. It will be same as our Navigation example except the data is from Internet instead of local, static data entered while coding the apppcation.

Flutter - Database Concepts

Flutter provides many advanced packages to work with databases. The most important packages are −

    sqfpte − Used to access and manipulate SQLite database, and

    firebase_database − Used to access and manipulate cloud hosted NoSQL database from Google.

In this chapter, let us discuss each of them in detail.

SQLite

SQLite database is the de-facto and standard SQL based embedded database engine. It is small and time-tested database engine. sqfpte package provides a lot of functionapty to work efficiently with SQLite database. It provides standard methods to manipulate SQLite database engine. The core functionapty provided by sqfpte package is as follows −

    Create / Open (openDatabase method) a SQLite database.

    Execute SQL statement (execute method) against SQLite database.

    Advanced query methods (query method) to reduce to code required to query and get information from SQLite database.

Let us create a product apppcation to store and fetch product information from a standard SQLite database engine using sqfpte package and understand the concept behind the SQLite database and sqfpte package.

    Create a new Flutter apppcation in Android studio, product_sqpte_app.

    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

    Configure sqfpte package in the pubspec.yaml file as shown below −


dependencies: sqfpte: any

Use the latest version number of sqfpte in place of any

    Configure path_provider package in the pubspec.yaml file as shown below −


dependencies: path_provider: any

    Here, path_provider package is used to get temporary folder path of the system and path of the apppcation. Use the latest version number of sqfpte in place of any.

    Android studio will alert that the pubspec.yaml is updated.

Updated

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    In database, we need primary key, id as additional field along with Product properties pke name, price, etc., So, add id property in the Product class. Also, add a new method, toMap to convert product object into Map object. fromMap and toMap are used to seriapze and de- seriapze the Product object and it is used in database manipulation methods.


class Product { 
   final int id; 
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   static final columns = ["id", "name", "description", "price", "image"]; 
   Product(this.id, this.name, this.description, this.price, this.image); 
   factory Product.fromMap(Map<String, dynamic> data) {
      return Product( 
         data[ id ], 
         data[ name ], 
         data[ description ], 
         data[ price ], 
         data[ image ], 
      ); 
   } 
   Map<String, dynamic> toMap() => {
      "id": id, 
      "name": name, 
      "description": description, 
      "price": price, 
      "image": image 
   }; 
}

    Create a new file, Database.dart in the pb folder to write SQLite related functionapty.

    Import necessary import statement in Database.dart.


import  dart:async ; 
import  dart:io ; 
import  package:path/path.dart ; 
import  package:path_provider/path_provider.dart ; 
import  package:sqfpte/sqfpte.dart ; 
import  Product.dart ;

    Note the following points here −

      async is used to write asynchronous methods.

      io is used to access files and directories.

      path is used to access dart core utipty function related to file paths.

      path_provider is used to get temporary and apppcation path.

      sqfpte is used to manipulate SQLite database.

    Create a new class SQLiteDbProvider

    Declare a singleton based, static SQLiteDbProvider object as specified below −


class SQLiteDbProvider { 
   SQLiteDbProvider._(); 
   static final SQLiteDbProvider db = SQLiteDbProvider._(); 
   static Database _database; 
}

    SQLiteDBProvoider object and its method can be accessed through the static db variable.


SQLiteDBProvoider.db.<emthod>

    Create a method to get database (Future option) of type Future<Database>. Create product table and load initial data during the creation of the database itself.


Future<Database> get database async { 
   if (_database != null) 
   return _database; 
   _database = await initDB(); 
   return _database; 
}
initDB() async { 
   Directory documentsDirectory = await getApppcationDocumentsDirectory(); 
   String path = join(documentsDirectory.path, "ProductDB.db"); 
   return await openDatabase(
      path, 
      version: 1,
      onOpen: (db) {}, 
      onCreate: (Database db, int version) async {
         await db.execute(
            "CREATE TABLE Product ("
            "id INTEGER PRIMARY KEY,"
            "name TEXT,"
            "description TEXT,"
            "price INTEGER," 
            "image TEXT" ")"
         ); 
         await db.execute(
            "INSERT INTO Product ( id ,  name ,  description ,  price ,  image ) 
            values (?, ?, ?, ?, ?)", 
            [1, "iPhone", "iPhone is the stypst 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"]
         ); 
      }
   ); 
}

    Here, we have used the following methods −

      getApppcationDocumentsDirectory − Returns apppcation directory path

      join − Used to create system specific path. We have used it to create database path.

      openDatabase − Used to open a SQLite database

      onOpen − Used to write code while opening a database

      onCreate − Used to write code while a database is created for the first time

      db.execute − Used to execute SQL queries. It accepts a query. If the query has placeholder (?), then it accepts values as pst in the second argument.

    Write a method to get all products in the database −


Future<List<Product>> getAllProducts() async { 
   final db = await database; 
   List<Map> 
   results = await db.query("Product", columns: Product.columns, orderBy: "id ASC"); 
   
   List<Product> products = new List(); 
   results.forEach((result) { 
      Product product = Product.fromMap(result); 
      products.add(product); 
   }); 
   return products; 
}

    Here, we have done the following −

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

    Write a method to get product specific to id


Future<Product> getProductById(int id) async {
   final db = await database; 
   var result = await db.query("Product", where: "id = ", whereArgs: [id]); 
   return result.isNotEmpty ? Product.fromMap(result.first) : Null; 
}

    Here, we have used where and whereArgs to apply filters.

    Create three methods - insert, update and delete method to insert, update and delete product from the database.


insert(Product product) async { 
   final db = await database; 
   var maxIdResult = await db.rawQuery(
      "SELECT MAX(id)+1 as last_inserted_id FROM Product");

   var id = maxIdResult.first["last_inserted_id"]; 
   var result = await db.rawInsert(
      "INSERT Into Product (id, name, description, price, image)" 
      " VALUES (?, ?, ?, ?, ?)", 
      [id, product.name, product.description, product.price, product.image] 
   ); 
   return result; 
}
update(Product product) async { 
   final db = await database; 
   var result = await db.update("Product", product.toMap(), 
   where: "id = ?", whereArgs: [product.id]); return result; 
} 
delete(int id) async { 
   final db = await database; 
   db.delete("Product", where: "id = ?", whereArgs: [id]); 
}

    The final code of the Database.dart is as follows −


import  dart:async ; 
import  dart:io ; 
import  package:path/path.dart ; 
import  package:path_provider/path_provider.dart ; 
import  package:sqfpte/sqfpte.dart ; 
import  Product.dart ; 

class SQLiteDbProvider {
   SQLiteDbProvider._(); 
   static final SQLiteDbProvider db = SQLiteDbProvider._(); 
   static Database _database; 
   
   Future<Database> get database async {
      if (_database != null) 
      return _database; 
      _database = await initDB(); 
      return _database; 
   } 
   initDB() async {
      Directory documentsDirectory = await 
      getApppcationDocumentsDirectory(); 
      String path = join(documentsDirectory.path, "ProductDB.db"); 
      return await openDatabase(
         path, version: 1, 
         onOpen: (db) {}, 
         onCreate: (Database db, int version) async {
            await db.execute(
               "CREATE TABLE Product (" 
               "id INTEGER PRIMARY KEY," 
               "name TEXT," 
               "description TEXT," 
               "price INTEGER," 
               "image TEXT"")"
            ); 
            await db.execute(
               "INSERT INTO Product ( id ,  name ,  description ,  price ,  image ) 
               values (?, ?, ?, ?, ?)", 
               [1, "iPhone", "iPhone is the stypst 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"]
            ); 
         }
      ); 
   }
   Future<List<Product>> getAllProducts() async {
      final db = await database; 
      List<Map> results = await db.query(
         "Product", columns: Product.columns, orderBy: "id ASC"
      ); 
      List<Product> products = new List();   
      results.forEach((result) {
         Product product = Product.fromMap(result); 
         products.add(product); 
      }); 
      return products; 
   } 
   Future<Product> getProductById(int id) async {
      final db = await database; 
      var result = await db.query("Product", where: "id = ", whereArgs: [id]); 
      return result.isNotEmpty ? Product.fromMap(result.first) : Null; 
   } 
   insert(Product product) async { 
      final db = await database; 
      var maxIdResult = await db.rawQuery("SELECT MAX(id)+1 as last_inserted_id FROM Product"); 
      var id = maxIdResult.first["last_inserted_id"]; 
      var result = await db.rawInsert(
         "INSERT Into Product (id, name, description, price, image)" 
         " VALUES (?, ?, ?, ?, ?)", 
         [id, product.name, product.description, product.price, product.image] 
      ); 
      return result; 
   } 
   update(Product product) async { 
      final db = await database; 
      var result = await db.update(
         "Product", product.toMap(), where: "id = ?", whereArgs: [product.id]
      ); 
      return result; 
   } 
   delete(int id) async { 
      final db = await database; 
      db.delete("Product", where: "id = ?", whereArgs: [id]);
   } 
}

    Change the main method to get the product information.


void main() {
   runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts())); 
}

    Here, we have used the getAllProducts method to fetch all products from the database.

    Run the apppcation 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 apppcation development pke 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 onpne product store in the Cloud Firestore and create a apppcation to access the product store.

    Create a new Flutter apppcation in Android studio, product_firebase_app.

    Replace the default startup code (main.dart) with our product_rest_app code.

    Copy Product.dart file from product_rest_app into the pb folder.


class Product { 
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   
   Product(this.name, this.description, this.price, this.image); 
   factory Product.fromMap(Map<String, 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:
   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

    Configure cloud_firestore package in the pubspec.yaml file as shown below −


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 −

Cloud Firestore Package

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Create a project in the Firebase using the following steps −

      Create a Firebase account by selecting Free plan at https://firebase.google.com/pricing/.

      Once Firebase account is created, it will redirect to the project overview page. It pst all the Firebase based project and provides an option to create a new project.

      Cpck Add project and it will open a project creation page.

      Enter products app db as project name and cpck Create project option.

      Go to *Firebase console.

      Cpck Project overview. It opens the project overview page.

      Cpck android icon. It will open project setting specific to Android development.

      Enter Android Package name, com.tutorialspoint.flutterapp.product_firebase_app.

      Cpck Register App. It generates a project configuration file, google_service.json.

      Download google_service.json and then move it into the project’s android/app directory. This file is the connection between our apppcation and Firebase.

      Open android/app/build.gradle and include the following code −


apply plugin:  com.google.gms.google-services 

      Open android/build.gradle and include the following configuration −


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.

      Open android/app/build.gradle and include the following code as well.


android {
   defaultConfig { 
      ... 
      multiDexEnabled true 
   } 
   ...
}
dependencies {
   ... 
   compile  com.android.support: multidex:1.0.3  
}

    This dependency enables the android apppcation to use multiple dex functionapty.

      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.

      Open the newly created project.

      Cpck the Database option in the left menu.

      Cpck Create database option.

      Cpck Start in test mode and then Enable.

      Cpck Add collection. Enter product as collection name and then cpck Next.

      Enter the sample product information as shown in the image here −

Sample Product Information

    Add addition product information using Add document options.

    Open main.dart file and import Cloud Firestore plugin file and remove http package.


import  package:cloud_firestore/cloud_firestore.dart ;

    Remove parseProducts and update fetchProducts to fetch products from Cloud Firestore instead of Product service API.


Stream<QuerySnapshot> fetchProducts() { 
   return Firestore.instance.collection( product ).snapshots(); }

    Here, Firestore.instance.collection method is used to access product collection available in the cloud store. Firestore.instance.collection provides many option to filter the collection to get the necessary documents. But, we have not appped any filter to get all product information.

    Cloud Firestore provides the collection through Dart Stream concept and so modify the products type in MyApp and MyHomePage widget from Future<pst<Product>> to Stream<QuerySnapshot>.

    Change the build method of MyHomePage widget to use StreamBuilder instead of FutureBuilder.


@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>(); 
                  
                  for(var i = 0; i < documents.length; i++) { 
                     DocumentSnapshot document = documents[i]; 
                     items.add(Product.fromMap(document.data)); 
                  } 
                  return ProductBoxList(items: items);
               } else { 
                  return Center(child: CircularProgressIndicator()); 
               }
            }, 
         ), 
      )
   ); 
}

    Here, we have fetched the product information as List<DocumentSnapshot> type. Since, our widget, ProductBoxList is not compatible with documents, we have converted the documents into List<Product> type and further used it.

    Finally, run the apppcation and see the result. Since, we have used the same product information as that of SQLite apppcation and changed the storage medium only, the resulting apppcation looks identical to SQLite apppcation apppcation.

Flutter - Internationapzation

Nowadays, mobile apppcations are used by customers from different countries and as a result, apppcations are required to display the content in different languages. Enabpng an apppcation to work in multiple languages is called Internationapzing the apppcation.

For an apppcation to work in different languages, it should first find the current locale of the system in which the apppcation is running and then need to show it’s content in that particular locale, and this process is called Locapzation.

Flutter framework provides three base classes for locapzation and extensive utipty classes derived from base classes to locapze an apppcation.

The base classes are as follows −

    Locale − Locale is a class used to identify the user’s language. For example, en-us identifies the American Engpsh and it can be created as.


Locale en_locale = Locale( en ,  US )

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 −


Locale es_locale = Locale( es ,  AR )

    Locapzations − Locapzations is a generic widget used to set the Locale and the locapzed resources of its child.


class CustomLocapzations { 
   CustomLocapzations(this.locale); 
   final Locale locale; 
   static CustomLocapzations of(BuildContext context) { 
      return Locapzations.of<CustomLocapzations>(context, CustomLocapzations); 
   } 
   static Map<String, Map<String, String>> _resources = {
       en : {
          title :  Demo , 
          message :  Hello World  
      }, 
       es : {
          title :  Manifestación , 
          message :  Hola Mundo , 
      }, 
   }; 
   String get title { 
      return _resources[locale.languageCode][ title ]; 
   }
   String get message { 
      return _resources[locale.languageCode][ message ]; 
   } 
}

    Here, CustomLocapzations is a new custom class created specifically to get certain locapzed content (title and message) for the widget. of method uses the Locapzations class to return new CustomLocapzations class.

    LocapzationsDelegate<T> − LocapzationsDelegate<T> is a factory class through which Locapzations widget is loaded. It has three over-ridable methods −

      isSupported − Accepts a locale and return whether the specified locale is supported or not.


@override 
bool isSupported(Locale locale) => [ en ,  es ].contains(locale.languageCode);

      Here, the delegate works for en and es locale only.

      load − Accepts a locale and start loading the resources for the specified locale.


@override 
Future<CustomLocapzations> load(Locale locale) { 
   return SynchronousFuture<CustomLocapzations>(CustomLocapzations(locale)); 
}

      Here, load method returns CustomLocapzations. The returned CustomLocapzations can be used to get values of title and message in both Engpsh and Spanish

      shouldReload − Specifies whether reloading of CustomLocapzations is necessary when its Locapzations widget is rebuild.


@override 
bool shouldReload(CustomLocapzationsDelegate old) => false;

    The complete code of CustomLocapzationDelegate is as follows −


class CustomLocapzationsDelegate extends 
LocapzationsDelegate<CustomLocapzations> { 
   const CustomLocapzationsDelegate(); 
   @override 
   bool isSupported(Locale locale) => [ en ,  es ].contains(locale.languageCode);
   @override 
   Future<CustomLocapzations> load(Locale locale) { 
      return SynchronousFuture<CustomLocapzations>(CustomLocapzations(locale));
   } 
   @override bool shouldReload(CustomLocapzationsDelegate old) => false; 
}

In general, Flutter apppcations are based on two root level widgets, MaterialApp or WidgetsApp. Flutter provides ready made locapzation for both widgets and they are MaterialLocapzations and WidgetsLocapations. Further, Flutter also provides delegates to load MaterialLocapzations and WidgetsLocapations and they are GlobalMaterialLocapzations.delegate and GlobalWidgetsLocapzations.delegate respectively.

Let us create a simple internationapzation enabled apppcation to test and understand the concept.

    Create a new flutter apppcation, flutter_locapzation_app.

    Flutter supports the internationapzation using exclusive flutter package, flutter_locapzations. The idea is to separate the locapzed content from the main SDK. Open the pubspec.yaml and add below code to enable the internationapzation package −


dependencies: 
   flutter: 
      sdk: flutter 
   flutter_locapzations:
      sdk: flutter

    Android studio will display the following alert that the pubspec.yaml is updated.

Alert

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Import flutter_locapzations package in the main.dart as follows −


import  package:flutter_locapzations/flutter_locapzations.dart ; 
import  package:flutter/foundation.dart  show SynchronousFuture;

    Here, the purpose of SynchronousFuture is to load the custom locapzations synchronously.

    Create a custom locapzations and its corresponding delegate as specified below −


class CustomLocapzations { 
   CustomLocapzations(this.locale); 
   final Locale locale; 
   static CustomLocapzations of(BuildContext context) {
      return Locapzations.of<CustomLocapzations>(context, CustomLocapzations); 
   }
   static Map<String, Map<String, String>> _resources = {
       en : {
          title :  Demo , 
          message :  Hello World  
      }, 
       es : { 
          title :  Manifestación , 
          message :  Hola Mundo , 
      }, 
   }; 
   String get title { 
      return _resources[locale.languageCode][ title ]; 
   } 
   String get message { 
      return _resources[locale.languageCode][ message ]; 
   } 
}
class CustomLocapzationsDelegate extends
LocapzationsDelegate<CustomLocapzations> {
   const CustomLocapzationsDelegate();
   
   @override 
   bool isSupported(Locale locale) => [ en ,  es ].contains(locale.languageCode); 
   
   @override 
   Future<CustomLocapzations> load(Locale locale) { 
      return SynchronousFuture<CustomLocapzations>(CustomLocapzations(locale)); 
   } 
   @override bool shouldReload(CustomLocapzationsDelegate old) => false; 
}

    Here, CustomLocapzations is created to support locapzation for title and message in the apppcation and CustomLocapzationsDelegate is used to load CustomLocapzations.

    Add delegates for MaterialApp, WidgetsApp and CustomLocapzation using MaterialApp properties, locapzationsDelegates and supportedLocales as specified below −


locapzationsDelegates: [
   const CustomLocapzationsDelegate(),   
   GlobalMaterialLocapzations.delegate, 
   GlobalWidgetsLocapzations.delegate, 
], 
supportedLocales: [
   const Locale( en ,   ),
   const Locale( es ,   ), 
],

    Use CustomLocapzations method, of to get the locapzed value of title and message and use it in appropriate place as specified below −


class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(CustomLocapzations .of(context) .title), ), 
         body: Center(
            child: Column(
               mainAxisApgnment: MainAxisApgnment.center, 
               children: <Widget>[ 
                  Text( CustomLocapzations .of(context) .message, ), 
               ], 
            ), 
         ),
      );
   }
}

    Here, we have modified the MyHomePage class from StatefulWidget to StatelessWidget for simppcity reason and used the CustomLocapzations to get title and message.

    Compile and run the apppcation. The apppcation will show its content in Engpsh.

    Close the apppcation. Go to Settings → System → Languages and Input → Languages*.

    Cpck Add a language option and select Spanish. This will install Spanish language and then pst it as one of the option.

    Select Spanish and move it above Engpsh. This will set as Spanish as first language and everything will be changed to Spanish text.

    Now relaunch the internationapzation apppcation and you will see the title and message in Spanish language.

    We can revert the language to Engpsh by move the Engpsh option above Spanish option in the setting.

    The result of the apppcation (in Spanish) is shown in the screenshot given below −

Manifestacion

Using intl Package

Flutter provides intl package to further simppfy the development of locapzed mobile apppcation. intl package provides special methods and tools to semi-auto generate language specific messages.

Let us create a new locapzed apppcation by using intl package and understand the concept.

    Create a new flutter apppcation, flutter_intl_app.

    Open pubspec.yaml and add the package details.


dependencies: 
   flutter: 
      sdk: flutter 
   flutter_locapzations: 
      sdk: flutter 
   intl: ^0.15.7 
   intl_translation: ^0.17.3

    Android studio will display the alert as shown below informing that the pubspec.yaml is updated.

Informing Updation

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Copy the main.dart from previous sample, flutter_internationapzation_app.

    Import the intl pacakge as shown below −


import  package:intl/intl.dart ;

    Update the CustomLocapzation class as shown in the code given below −


class CustomLocapzations { 
   static Future<CustomLocapzations> load(Locale locale) {
      final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString(); 
      final String localeName = Intl.canonicapzedLocale(name); 
      
      return initiapzeMessages(localeName).then((_) {
         Intl.defaultLocale = localeName; 
         return CustomLocapzations(); 
      }); 
   } 
   static CustomLocapzations of(BuildContext context) { 
      return Locapzations.of<CustomLocapzations>(context, CustomLocapzations); 
   } 
   String get title {
      return Intl.message( 
          Demo , 
         name:  title , 
         desc:  Title for the Demo apppcation , 
      ); 
   }
   String get message{
      return Intl.message(
          Hello World , 
         name:  message , 
         desc:  Message for the Demo apppcation , 
      ); 
   }
}
class CustomLocapzationsDelegate extends 
LocapzationsDelegate<CustomLocapzations> {
   const CustomLocapzationsDelegate();
   
   @override
   bool isSupported(Locale locale) => [ en ,  es ].contains(locale.languageCode); 
   @override 
   Future<CustomLocapzations> load(Locale locale) { 
      return CustomLocapzations.load(locale); 
   } 
   @override 
   bool shouldReload(CustomLocapzationsDelegate old) => false; 
}

    Here, we have used three methods from the intl package instead of custom methods. Otherwise, the concepts are same.

      Intl.canonicapzedLocale − Used to get correct locale name.

      Intl.defaultLocale − Used to set current locale

      Intl.message − Used to define new messages.

    import l10n/messages_all.dart file. We will generate this file shortly


import  l10n/messages_all.dart ;

    Now, create a folder, pb/l10n

    Open a command prompt and go to apppcation root directory (where pubspec.yaml is available) and run the following command −


flutter packages pub run intl_translation:extract_to_arb --output-
   dir=pb/l10n pb/main.dart

    Here, the command will generate, intl_message.arb file, a template to create message in different locale. The content of the file is as follows −


{
   "@@last_modified": "2019-04-19T02:04:09.627551", 
   "title": "Demo", 
   "@title": {
      "description": "Title for the Demo apppcation", 
      "type": "text", 
      "placeholders": {} 
   }, 
   "message": "Hello World", 
   "@message": {
      "description": "Message for the Demo 
      apppcation", 
      "type": "text", 
      "placeholders": {} 
   }
}

    Copy intl_message.arb and create new file, intl_en.arb.

    Copy intl_message.arb and create new file, intl_es.arb and change the content to Spanish language as shown below −


{
   "@@last_modified": "2019-04-19T02:04:09.627551",  
   "title": "Manifestación", 
   "@title": {
      "description": "Title for the Demo apppcation", 
      "type": "text", 
      "placeholders": {} 
   },
   "message": "Hola Mundo",
   "@message": {
      "description": "Message for the Demo apppcation", 
      "type": "text", 
      "placeholders": {} 
   } 
}

    Now, run the following command to create final message file, messages_all.dart.


flutter packages pub run intl_translation:generate_from_arb 
--output-dir=pbl10n --no-use-deferred-loading 
pbmain.dart pbl10nintl_en.arb pbl10nintl_es.arb

    Compile and run the apppcation. It will work similar to above apppcation, flutter_locapzation_app.

Flutter - Testing

Testing is very important phase in the development pfe cycle of an apppcation. It ensures that the apppcation is of high quapty. 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 apppcation.

Types of Testing

Generally, three types of testing processes are available to completely test an apppcation. They are as follows −

Unit Testing

Unit testing is the easiest method to test an apppcation. 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 apppcation pke database, web service, etc., It simulates or mocks the real environment to find nearly all bugs, but it is the most comppcated 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


testWidgets( test description: find a widget ,  <test code> );

Steps Involved

Widget Testing involves three distinct steps −

    Render the widget in the testing environment.

    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.


testWidgets( finds a specific instance , (WidgetTester tester) async { 
   await tester.pumpWidget(MaterialApp( 
      home: Scaffold( 
         body: Text( Hello ), 
      ), 
   )); 
});

    Finding the widget, which we need to test.

      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 finds the widget that contains the specified text.


find.text( Hello )

        find.byKey find the widget by its specific key.


find.byKey( home )

        find.byWidget find the widget by its instance variable.


find.byWidget(homeWidget)

    Ensuring the widget works as expected.

    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.

      findsOneWidget − verifies a single widget is found.


expect(find.text( Hello ), findsOneWidget);

      findsNothing − verifies no widgets are found


expect(find.text( Hello World ), findsNothing);

      findsWidgets − verifies more than a single widget is found.


expect(find.text( Save ), findsWidgets);

      findsNWidgets − verifies N number of widgets are found.


expect(find.text( Save ), findsNWidgets(2));

The complete test code is as follows −


testWidgets( finds hello widget , (WidgetTester tester) async { 
   await tester.pumpWidget(MaterialApp( 
      home: Scaffold( 
         body: Text( Hello ), 
      ), 
   )); 
   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 apppcation and write a widget test to understand better the steps involved and the concept.

    Create a new flutter apppcation, flutter_test_app in Android studio.

    Open widget_test.dart in test folder. It has a sample testing code as given below −


testWidgets( Counter increments smoke test , (WidgetTester tester) async {
   // Build our app and trigger a frame. 
   await tester.pumpWidget(MyApp()); 
   
   // Verify that our counter starts at 0. 
   expect(find.text( 0 ), findsOneWidget); 
   expect(find.text( 1 ), findsNothing); 
   
   // Tap the  +  icon and trigger a frame. 
   await tester.tap(find.byIcon(Icons.add)); 
   await tester.pump(); 
   
   // Verify that our counter has incremented. 
   expect(find.text( 0 ), findsNothing); 
   expect(find.text( 1 ), findsOneWidget); 
});

    Here, the test code does the following functionapties −

      Renders MyApp widget using tester.pumpWidget.

      Ensures that the counter is initially zero using findsOneWidget and findsNothing matchers.

      Finds the counter increment button using find.byIcon method.

      Taps the counter increment button using tester.tap method.

      Ensures that the counter is increased using findsOneWidget and findsNothing matchers.

    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);

    Cpck Run menu.

    Cpck tests in widget_test.dart option. This will run the test and report the result in the result window.

Flutter Testing

Flutter - Deployment

This chapter explains how to deploy Flutter apppcation in both Android and iOS platforms.

Android Apppcation

    Change the apppcation 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 apppcation. We can set the apppcation name using android:label entry.

    Change launcher icon using android:icon entry in manifest file.

    Sign the app using standard option as necessary.

    Enable Proguard and Obfuscation using standard option, if necessary.

    Create a release APK file by running below command −


cd /path/to/my/apppcation 
flutter build apk

    You can see an output as shown below −


Initiapzing gradle...                                            8.6s 
Resolving dependencies...                                        19.9s 
Calpng mockable JAR artifact transform to create file: 
/Users/.gradle/caches/transforms-1/files-1.1/android.jar/ 
c30932f130afbf3fd90c131ef9069a0b/android.jar with input 
/Users/Library/Android/sdk/platforms/android-28/android.jar 
Running Gradle task  assembleRelease ... 
Running Gradle task  assembleRelease ... 
Done                                                             85.7s 
Built build/app/outputs/apk/release/app-release.apk (4.8MB).

    Install the APK on a device using the following command −


flutter install

    Pubpsh the apppcation into Google Playstore by creating an appbundle and push it into playstore using standard methods.


flutter build appbundle

iOS Apppcation

    Register the iOS apppcation in App Store Connect using standard method. Save the =Bundle ID used while registering the apppcation.

    Update Display name in the XCode project setting to set the apppcation name.

    Update Bundle Identifier in the XCode project setting to set the bundle id, which we used in step 1.

    Code sign as necessary using standard method.

    Add a new app icon as necessary using standard method.

    Generate IPA file using the following command −


flutter build ios

    Now, you can see the following output −


Building com.example.MyApp for device (ios-release)... 
Automatically signing iOS for device deployment 
using specified development team in Xcode project: 
Running Xcode build...                                   23.5s 
......................

    Test the apppcation by pushing the apppcation, IPA file into TestFpght using standard method.

    Finally, push the apppcation into App Store using standard method.

Flutter - Development Tools

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 quapty in the components design. The upcoming version of flutter 1.2 will be designed to support desktop keyboard events and mouse hover support.

Flutter Development with Visual Studio Code

Visual Studio Code supports flutter development and provides extensive shortcuts for fast and efficient development. Some of the key features provided by Visual Studio Code for flutter development are psted below −

    Code assist - When you want to check for options, you can use Ctrl+Space to get a pst of code completion options.

    Quick fix - Ctrl+. is quick fix tool to help in fixing the code.

    Shortcuts while Coding.

    Provides detailed documentation in comments.

    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 pne view so developers can easily analyze their apppcations.

Install DevTools

To install DevTools run the following command in your console −


flutter packages pub global activate devtools

Now you can see the following output −


Resolving dependencies... 
+ args 1.5.1 
+ async 2.2.0
+ 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 
.................. 
.................. 
Installed executable devtools. 
Activated devtools 0.0.16.

Run Server

You can run the DevTools server using the following command −


flutter packages pub global run devtools

Now, you will get a response similar to this,


Serving DevTools at http://127.0.0.1:9100

Start Your Apppcation

Go to your apppcation, open simulator and run using the following command −


flutter run --observatory-port=9200

Now, you are connected to DevTools.

Start DevTools in Browser

Now access the below url in the browser, to start DevTools −


http://localhost:9100/?port=9200

You will get a response as shown below −

Dart Dev Tools

Flutter SDK

To update Flutter SDK, use the following command −


flutter upgrade

You can see an output as shown below −

Flutter SDK

To upgrade Flutter packages, use the following command −


flutter packages upgrade

You could see the following response,


Running "flutter packages upgrade" in my_app... 7.4s

Flutter Inspector

It is used to explore flutter widget trees. To achieve this, run the below command in your console,


flutter run --track-widget-creation

You can see an output as shown below −


Launching pb/main.dart on iPhone X in debug mode... 
─Assembpng Flutter resources...                       3.6s 
Compipng, pnking and signing...                      6.8s 
Xcode build done.                                     14.2s 
2,904ms (!)
To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R". 
An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:50399/ 
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

Now go to the url, http://127.0.0.1:50399/ you could see the following result −

Result

Flutter - Writting Advanced Apppcations

In this chapter, we are going to learn how to write a full fledged mobile apppcation, expense_calculator. The purpose of the expense_calculator is to store our expense information. The complete feature of the apppcation is as follows −

    Expense pst.

    Form to enter new expenses.

    Option to edit / delete the existing expenses.

    Total expenses at any instance.

We are going to program the expense_calculator apppcation using below mentioned advanced features of Flutter framework.

    Advanced use of ListView to show the expense pst.

    Form programming.

    SQLite database programming to store our expenses.

    scoped_model state management to simppfy our programming.

Let us start programming the expense_calculator apppcation.

    Create a new Flutter apppcation, expense_calculator in Android studio.

    Open pubspec.yaml and add package dependencies.


dependencies: 
   flutter: 
      sdk: flutter 
   sqfpte: ^1.1.0 
   path_provider: ^0.5.0+1 
   scoped_model: ^1.0.1 
   intl: any

    Observe these points here −

      sqfpte is used for SQLite database programming.

      path_provider is used to get system specific apppcation path.

      scoped_model is used for state management.

      intl is used for date formatting.

    Android studio will display the following alert that the pubspec.yaml is updated.

Alert Writing Advanced Apppcations

    Cpck Get dependencies option. Android studio will get the package from Internet and properly configure it for the apppcation.

    Remove the existing code in main.dart.

    Add new file, Expense.dart to create Expense class. Expense class will have the below properties and methods.

      property: id − Unique id to represent an expense entry in SQLite database.

      property: amount − Amount spent.

      property: date − Date when the amount is spent.

      property: category − Category represents the area in which the amount is spent. e.g Food, Travel, etc.,

      formattedDate − Used to format the date property

      fromMap − Used to map the field from database table to the property in the expense object and to create a new expense object.


factory Expense.fromMap(Map<String, dynamic> data) { 
   return Expense( 
      data[ id ], 
      data[ amount ], 
      DateTime.parse(data[ date ]),    
      data[ category ] 
   ); 
}

      toMap − Used to convert the expense object to Dart Map, which can be further used in database programming


Map<String, dynamic> toMap() => { 
   "id" : id, 
   "amount" : amount, 
   "date" : date.toString(), 
   "category" : category, 
};

      columns − Static variable used to represent the database field.

    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; 
   String get formattedDate { 
      var formatter = new DateFormat( yyyy-MM-dd ); 
      return formatter.format(this.date); 
   } 
   static final columns = [ id ,  amount ,  date ,  category ];
   Expense(this.id, this.amount, this.date, this.category); 
   factory Expense.fromMap(Map<String, dynamic> data) { 
      return Expense( 
         data[ id ], 
         data[ amount ], 
         DateTime.parse(data[ date ]), data[ category ] 
      ); 
   }
   Map<String, dynamic> toMap() => {
      "id" : id, 
      "amount" : amount, 
      "date" : date.toString(), 
      "category" : category, 
   }; 
}

    The above code is simple and self explanatory.

    Add new file, Database.dart to create SQLiteDbProvider class. The purpose of the SQLiteDbProvider class is as follows −

      Get all expenses available in the database using getAllExpenses method. It will be used to pst all the user’s expense information.


Future<List<Expense>> getAllExpenses() async { 
   final db = await database; 
   
   List<Map> results = await db.query(
      "Expense", columns: Expense.columns, orderBy: "date DESC"
   );
   List<Expense> expenses = new List(); 
   results.forEach((result) {
      Expense expense = Expense.fromMap(result); 
      expenses.add(expense); 
   }); 
   return expenses; 
}

      Get a specific expense information based on expense identity available in the database using getExpenseById method. It will be used to show the particular expense information to the user.


Future<Expense> getExpenseById(int id) async {
   final db = await database;
   var result = await db.query("Expense", where: "id = ", whereArgs: [id]);
   
   return result.isNotEmpty ? 
   Expense.fromMap(result.first) : Null; 
}

      Get the total expenses of the user using getTotalExpense method. It will be used to show the current total expense to the user.


Future<double> getTotalExpense() async {
   final db = await database; 
   List<Map> pst = await db.rawQuery(
      "Select SUM(amount) as amount from expense"
   );
   return pst.isNotEmpty ? pst[0]["amount"] : Null; 
}

      Add new expense information into the database using insert method. It will be used to add new expense entry into the apppcation by the user.


Future<Expense> insert(Expense expense) async { 
   final db = await database; 
   var maxIdResult = await db.rawQuery(
      "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
   );
   var id = maxIdResult.first["last_inserted_id"]; 
   var result = await db.rawInsert(
      "INSERT Into Expense (id, amount, date, category)" 
      " VALUES (?, ?, ?, ?)", [
         id, expense.amount, expense.date.toString(), expense.category
      ]
   ); 
   return Expense(id, expense.amount, expense.date, expense.category); 
}

      Update existing expense information using update method. It will be used to edit and update existing expense entry available in the system by the user.


update(Expense product) async {
   final db = await database; 
   
   var result = await db.update("Expense", product.toMap(), 
   where: "id = ?", whereArgs: [product.id]); 
   return result; 
}

      Delete existing expense information using delete method. It will be used remove the existing expense entry available in the system by the user.


delete(int id) async {
   final db = await database;
   db.delete("Expense", where: "id = ?", whereArgs: [id]); 
}

    The complete code of the SQLiteDbProvider class is as follows −


import  dart:async ; 
import  dart:io ; 
import  package:path/path.dart ; 
import  package:path_provider/path_provider.dart ; 
import  package:sqfpte/sqfpte.dart ; 
import  Expense.dart ; 

class SQLiteDbProvider {
   SQLiteDbProvider._(); 
   static final SQLiteDbProvider db = SQLiteDbProvider._(); 
   
   static Database _database; Future<Database> get database async { 
      if (_database != null) 
         return _database; 
      _database = await initDB(); 
      return _database; 
   } 
   initDB() async {
      Directory documentsDirectory = await getApppcationDocumentsDirectory(); 
      String path = join(documentsDirectory.path, "ExpenseDB2.db"); 
      return await openDatabase(
         path, version: 1, onOpen:(db){}, onCreate: (Database db, int version) async {
            await db.execute(
               "CREATE TABLE Expense (
                  ""id INTEGER PRIMARY KEY," "amount REAL," "date TEXT," "category TEXT""
               )
            "); 
            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, "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 stypst phone ever", 100, "pendrive.png"
               ]
            ); 
            await db.execute(
               "INSERT INTO Product ( id ,  name ,  description ,  price ,  image ) 
               values (?, ?, ?, ?, ?)", [
                  6, "Floppy Drive", "iPhone is the stypst phone ever", 20, "floppy.png"
               ]
            ); */ 
         }
      );
   }
   Future<List<Expense>> getAllExpenses() async {
      final db = await database; 
      List<Map> 
      results = await db.query(
         "Expense", columns: Expense.columns, orderBy: "date DESC"
      );
      List<Expense> expenses = new List(); 
      results.forEach((result) {
         Expense expense = Expense.fromMap(result);
         expenses.add(expense);
      }); 
      return expenses; 
   } 
   Future<Expense> getExpenseById(int id) async {
      final db = await database;
      var result = await db.query("Expense", where: "id = ", whereArgs: [id]); 
      return result.isNotEmpty ? Expense.fromMap(result.first) : Null; 
   }
   Future<double> getTotalExpense() async {
      final db = await database;
      List<Map> pst = await db.rawQuery(
         "Select SUM(amount) as amount from expense"
      );
      return pst.isNotEmpty ? pst[0]["amount"] : Null; 
   }
   Future<Expense> insert(Expense expense) async {
      final db = await database; 
      var maxIdResult = await db.rawQuery(
         "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
      );
      var id = maxIdResult.first["last_inserted_id"]; 
      var result = await db.rawInsert(
         "INSERT Into Expense (id, amount, date, category)" 
         " VALUES (?, ?, ?, ?)", [
            id, expense.amount, expense.date.toString(), expense.category
         ]
      );
      return Expense(id, expense.amount, expense.date, expense.category); 
   }
   update(Expense product) async {
      final db = await database; 
      var result = await db.update(
         "Expense", product.toMap(), where: "id = ?", whereArgs: [product.id]
      ); 
      return result; 
   }
   delete(int id) async {
      final db = await database;
      db.delete("Expense", where: "id = ?", whereArgs: [id]);
   }
}

    Here,

      database is the property to get the SQLiteDbProvider object.

      initDB is a method used to select and open the SQLite database.

    Create a new file, ExpenseListModel.dart to create ExpenseListModel. The purpose of the model is to hold the complete information of the user expenses in the memory and updating the user interface of the apppcation whenever user’s expense changes in the memory. It is based on Model class from scoped_model package. It has the following properties and methods −

      _items − private pst of expenses.

      items − getter for _items as UnmodifiableListView<Expense> to prevent unexpected or accidental changes to the pst.

      totalExpense − getter for Total expenses based on the items variable.


double get totalExpense {
   double amount = 0.0; 
   for(var i = 0; i < _items.length; i++) { 
      amount = amount + _items[i].amount; 
   } 
   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>> 
   pst = SQLiteDbProvider.db.getAllExpenses(); 
   pst.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.


Expense byId(int id) { 
   for(var i = 0; i < _items.length; i++) { 
      if(_items[i].id == id) { 
         return _items[i]; 
      } 
   }
   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.


void add(Expense item) {
   SQLiteDbProvider.db.insert(item).then((val) { 
      _items.add(val); notifyListeners(); 
   }); 
}

      Update − Used to Update expense item into the _items variable as well as into the database. It also calls notifyListeners to update the UI.


void update(Expense item) {
   bool found = false;
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         _items[i] = item; 
         found = true; 
         SQLiteDbProvider.db.update(item); break; 
      } 
   }
   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.


void delete(Expense item) { 
   bool found = false; 
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         found = true; 
         SQLiteDbProvider.db.delete(item.id); 
         _items.removeAt(i); break; 
      }
   }
   if(found) notifyListeners(); 
}

    The complete code of the ExpenseListModel class is as follows −


import  dart:collection ; 
import  package:scoped_model/scoped_model.dart ; 
import  Expense.dart ; 
import  Database.dart ; 

class ExpenseListModel extends Model { 
   ExpenseListModel() { 
      this.load(); 
   } 
   final List<Expense> _items = []; 
   UnmodifiableListView<Expense> get items => 
   UnmodifiableListView(_items); 
   
   /*Future<double> get totalExpense { 
      return SQLiteDbProvider.db.getTotalExpense(); 
   }*/ 
   
   double get totalExpense {
      double amount = 0.0;
      for(var i = 0; i < _items.length; i++) { 
         amount = amount + _items[i].amount; 
      } 
      return amount; 
   }
   void load() {
      Future<List<Expense>> pst = SQLiteDbProvider.db.getAllExpenses(); 
      pst.then( (dbItems) {
         for(var i = 0; i < dbItems.length; i++) {
            _items.add(dbItems[i]); 
         } 
         notifyListeners(); 
      }); 
   }
   Expense byId(int id) {
      for(var i = 0; i < _items.length; i++) { 
         if(_items[i].id == id) { 
            return _items[i]; 
         } 
      }
      return null; 
   }
   void add(Expense item) {
      SQLiteDbProvider.db.insert(item).then((val) {
         _items.add(val);
         notifyListeners();
      }); 
   }
   void update(Expense item) {
      bool found = false; 
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            _items[i] = item; 
            found = true; 
            SQLiteDbProvider.db.update(item); 
            break; 
         }
      }
      if(found) notifyListeners(); 
   }
   void delete(Expense item) {
      bool found = false; 
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            found = true; 
            SQLiteDbProvider.db.delete(item.id); 
            _items.removeAt(i); break; 
         }
      }
      if(found) notifyListeners(); 
   }
}

    Open main.dart file. Import the classes as specified below −


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>(model: expenses, child: MyApp(),)
   );
}

    Here,

      expenses object loads all the user expenses information from the database. Also, when the apppcation is opened for the first time, it will create the required database with proper tables.

      ScopedModel provides the expense information during the whole pfe cycle of the apppcation and ensures the maintenance of state of the apppcation at any instance. It enables us to use StatelessWidget instead of StatefulWidget.

    Create a simple MyApp using MaterialApp widget.


class MyApp extends StatelessWidget {
   // This widget is the root of your apppcation. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Expense ,
         theme: ThemeData(
            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.


class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar( 
            title: Text(this.title), 
         ), 
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return ListView.separated(
                  itemCount: expenses.items == null ? 1 
                  : expenses.items.length + 1, 
                  itemBuilder: (context, index) { 
                     if (index == 0) { 
                        return ListTile(
                           title: Text("Total expenses: " 
                           + expenses.totalExpense.toString(), 
                           style: TextStyle(fontSize: 24,
                           fontWeight: FontWeight.bold),) 
                        );
                     } else {
                        index = index - 1; 
                        return Dismissible( 
                           key: Key(expenses.items[index].id.toString()), 
                              onDismissed: (direction) { 
                              expenses.delete(expenses.items[index]); 
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, " 
                                       + expenses.items[index].id.toString() + 
                                       " is dismissed"
                                    )
                                 )
                              ); 
                           },
                           child: ListTile( onTap: () { 
                              Navigator.push(
                                 context, MaterialPageRoute(
                                    builder: (context) => FormPage(
                                       id: expenses.items[index].id,
                                       expenses: expenses, 
                                    )
                                 )
                              );
                           }, 
                           leading: Icon(Icons.monetization_on), 
                           traipng: Icon(Icons.keyboard_arrow_right), 
                           title: Text(expenses.items[index].category + ": " + 
                           expenses.items[index].amount.toString() + 
                           " 
spent on " + expenses.items[index].formattedDate, 
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.itapc),))
                        ); 
                     }
                  },
                  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.

      ListView.separated and ListTile widget is used to pst the expense information.

      Dismissible widget is used to delete the expense entry using swipe gesture.

      Navigator is used to open edit interface of an expense entry. It can be activated by tapping an expense entry.

    Create a FormPage widget. The purpose of the FormPage widget is to add or update an expense entry. It handles expense entry vapdation as well.


class FormPage extends StatefulWidget { 
   FormPage({Key key, this.id, this.expenses}) : super(key: key); 
   final int id; 
   final ExpenseListModel expenses; 
   
   @override _FormPageState createState() => _FormPageState(id: id, expenses: expenses); 
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses}); 
   
   final int id; 
   final ExpenseListModel expenses; 
   final scaffoldKey = GlobalKey<ScaffoldState>(); 
   final formKey = GlobalKey<FormState>(); 
   
   double _amount; 
   DateTime _date; 
   String _category; 
   
   void _submit() {
      final form = formKey.currentState; 
      if (form.vapdate()) {
         form.save(); 
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category)); 
            else expenses.update(Expense(this.id, _amount, _date, _category)); 
         Navigator.pop(context); 
      }
   }
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar(
            title: Text( Enter expense details ),
         ), 
         body: Padding(
            padding: const EdgeInsets.all(16.0), 
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField( 
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration( 
                           icon: const Icon(Icons.monetization_on), 
                           labelText:  Amount , 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        vapdator: (val) {
                           Pattern pattern = r ^[1-9]d*(.d+)?$ ; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) 
                           return  Enter a vapd 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), 
                        ), 
                        vapdator: (val) {
                           Pattern pattern = r ^((?:19|20)dd)[- /.]
                              (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 vapd date ; 
                           else return null; 
                        },
                        onSaved: (val) => _date = DateTime.parse(val), 
                        initialValue: id == 0 
                        ?    : expenses.byId(id).formattedDate, 
                        keyboardType: TextInputType.datetime, 
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category),
                           labelText:  Category , 
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        onSaved: (val) => _category = val, 
                        initialValue: id == 0 ?    
                        : expenses.byId(id).category.toString(),
                     ), 
                     RaisedButton( 
                        onPressed: _submit, 
                        child: new Text( Submit ), 
                     ), 
                  ],
               ),
            ),
         ),
      );
   }
}

    Here,

      TextFormField is used to create form entry.

      vapdator property of TextFormField is used to vapdate the form element along with RegEx patterns.

      _submit function is used along with expenses object to add or update the expenses into the database.

    The complete code of the main.dart file is as follows −


import  package:flutter/material.dart ; 
import  package:scoped_model/scoped_model.dart ; 
import  ExpenseListModel.dart ; 
import  Expense.dart ; 

void main() { 
   final expenses = ExpenseListModel(); 
   runApp(
      ScopedModel<ExpenseListModel>(
         model: expenses, child: MyApp(), 
      )
   ); 
}
class MyApp extends StatelessWidget {
   // This widget is the root of your apppcation. 
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title:  Expense ,
         theme: ThemeData(
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title:  Expense calculator ), 
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) { 
               return ListView.separated(
                  itemCount: expenses.items == null ? 1 
                  : expenses.items.length + 1, itemBuilder: (context, index) { 
                     if (index == 0) { 
                        return ListTile( title: Text("Total expenses: " 
                        + expenses.totalExpense.toString(), 
                        style: TextStyle(fontSize: 24,fontWeight: 
                        FontWeight.bold),) ); 
                     } else {
                        index = index - 1; return Dismissible(
                           key: Key(expenses.items[index].id.toString()), 
                           onDismissed: (direction) {
                              expenses.delete(expenses.items[index]); 
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, " + 
                                       expenses.items[index].id.toString() 
                                       + " is dismissed"
                                    )
                                 )
                              );
                           }, 
                           child: ListTile( onTap: () {
                              Navigator.push( context, MaterialPageRoute(
                                 builder: (context) => FormPage(
                                    id: expenses.items[index].id, expenses: expenses, 
                                 )
                              ));
                           }, 
                           leading: Icon(Icons.monetization_on), 
                           traipng: Icon(Icons.keyboard_arrow_right), 
                           title: Text(expenses.items[index].category + ": " + 
                           expenses.items[index].amount.toString() + " 
spent on " + 
                           expenses.items[index].formattedDate, 
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.itapc),))
                        );
                     }
                  }, 
                  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), 
               );
            }
         )
      );
   } 
}
class FormPage extends StatefulWidget {
   FormPage({Key key, this.id, this.expenses}) : super(key: key); 
   final int id; 
   final ExpenseListModel expenses; 
   
   @override 
   _FormPageState createState() => _FormPageState(id: id, expenses: expenses); 
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses}); 
   final int id; 
   final ExpenseListModel expenses; 
   final scaffoldKey = GlobalKey<ScaffoldState>(); 
   final formKey = GlobalKey<FormState>(); 
   double _amount; DateTime _date; 
   String _category;
   void _submit() {
      final form = formKey.currentState; 
      if (form.vapdate()) {
         form.save(); 
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category)); 
         else expenses.update(Expense(this.id, _amount, _date, _category)); 
         Navigator.pop(context); 
      } 
   } 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar( 
            title: Text( Enter expense details ), 
         ), 
         body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration( 
                           icon: const Icon(Icons.monetization_on), 
                           labelText:  Amount , 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        vapdator: (val) {
                           Pattern pattern = r ^[1-9]d*(.d+)?$ ; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) return  Enter a vapd 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), 
                        ),
                        vapdator: (val) {
                           Pattern pattern = r ^((?:19|20)dd)[- /.]
                           (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 vapd date ; 
                           else return null; 
                        },
                        onSaved: (val) => _date = DateTime.parse(val), 
                        initialValue: id == 0 ?    : expenses.byId(id).formattedDate, 
                        keyboardType: TextInputType.datetime, 
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category), 
                           labelText:  Category , 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        onSaved: (val) => _category = val, 
                        initialValue: id == 0 ?    : expenses.byId(id).category.toString(), 
                     ),
                     RaisedButton(
                        onPressed: _submit, 
                        child: new Text( Submit ), 
                     ),
                  ],
               ),
            ),
         ),
      );
   }
}

    Now, run the apppcation.

    Add new expenses using floating button.

    Edit existing expenses by tapping the expense entry.

    Delete the existing expenses by swiping the expense entry in either direction.

Some of the screen shots of the apppcation are as follows −

Expense Calculator

Enter Expense Details

Total Expenses

Flutter - Conclusion

Flutter framework does a great job by providing an excellent framework to build mobile apppcations in a truly platform independent way. By providing simppcity in the development process, high performance in the resulting mobile apppcation, 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 apppcation in the near future.

Advertisements