0% found this document useful (0 votes)
34 views134 pages

Mobile App Using Flutter - Handout

The document outlines the curriculum for a mobile application development module using Flutter, focusing on Dart programming language fundamentals, UI design implementation, backend integration, and application publishing. It details performance criteria and elements of competence, including Dart's features, frameworks, and use cases in various domains such as mobile, web, and server-side development. Additionally, it provides an overview of key data types in Dart and their implementations.

Uploaded by

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

Mobile App Using Flutter - Handout

The document outlines the curriculum for a mobile application development module using Flutter, focusing on Dart programming language fundamentals, UI design implementation, backend integration, and application publishing. It details performance criteria and elements of competence, including Dart's features, frameworks, and use cases in various domains such as mobile, web, and server-side development. Additionally, it provides an overview of key data types in Dart and their implementations.

Uploaded by

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

L5 SOFTWARE DEVELOPMENT

MOBILE APPLICATION DEVELOPMENT


Module Code: SWDMA501
Module Competence: Develop Mobile Application using Flutter

Prepared by: NIYONZIMA LOUIS


MIPC
L5 SOFTWARE DEVELOPMENT
Elements of Competence and Performance Criteria

Elements of
competence Performance criteria
1.1 Development environment is correctly prepared according to the operating
system.
1.2 Dart concepts are appropriately applied according to the dart standards.
1. Apply Basics of
Dart 1.3 Object-oriented principles are properly implemented in accordance with the
dart syntax.
1.4 Dart Libraries and packages are appropriately used based the application
requirements
2.1 Flutter environment is properly prepared based on system requirements.
2.2 Flutter's widget system is correctly utilized based on responsiveness
principles for mobile application
2. Implement UI
designs 2.3 State management is efficiently implemented based on app functionality.
2.4 Flutter's rich set of pre-designed widgets are effectively used based on
industry standards and design guidelines.
3.1 External services with flutter packages are effectively integrated based on
industry standards and best practices.
3.2 Storage management is correctly implemented based on data integrity and
security standards.
3. Integrate 3.3 Modular microapps are properly implemented based on application
backend requirements.
functionality 3.4 Error handling mechanisms are effectively performed in accordance with
industry standards.
3.5 Tests are efficiently performed based on the reliability of the mobile
application requirements.
3.6 Codebase issues are correctly debugged based on relevant techniques.
4.1 Installable files for mobile platforms are correctly generated based on the
manufacturer's guide.
4. Publish 4.2 Submissions to relevant app stores of the app generated builds are correctly
Application published following their respective submission guidelines.
4.3 Post-deployment issues are correctly addressed based on quality standards
and user expectations.

1
Learning outcome 1: Apply Basics of Dart
1.1. Preparation of development environment
1.1.1. Introduction to Dart
Dart is the programming language used by Flutter to build mobile, web, and desktop applications.
It is a flexible and powerful language optimized for building user interfaces quickly and efficiently.
Dart is often associated with Flutter, Google’s UI toolkit for building cross-platform mobile
applications, but it can also be used independently for backend development, web development,
and command-line applications.
Dart features and characteristics
1. Object-Oriented:
Dart is fully object-oriented, meaning everything in Dart is an object, even primitive types
like integers and booleans. It supports class-based inheritance, encapsulation, and
polymorphism, making it easy to design reusable, modular code.
2. Compiled Language:
Dart supports two types of compilation:
 Ahead-of-Time (AOT) Compilation: Dart can be compiled to native machine
code, providing fast execution for mobile and desktop applications.
 Just-in-Time (JIT) Compilation: Dart can also be compiled to JavaScript,
enabling it to run in browsers for web applications. The JIT compilation also allows
for hot reload during development, which lets developers see changes instantly
without restarting the app.
3. Null Safety:
Dart’s null safety feature prevents null reference errors, one of the most common runtime
errors in many programming languages. Variables in Dart are non-nullable by default,
meaning they can’t contain null unless explicitly marked as nullable (String?).

4. Easy Syntax:
Dart’s syntax is designed to be easy to learn and use. It is similar to popular languages
like Java, JavaScript, and C++, which helps developers transition smoothly to Dart. It
also supports modern features like arrow functions and optional named parameters.

2
5. Cross-Platform Development:
Dart is the language behind Flutter, a powerful UI toolkit that allows developers to build
cross-platform apps from a single codebase. Using Dart with Flutter, developers can create
applications for iOS, Android, web, desktop, and more with one language.
6. Asynchronous Programming:
Dart provides excellent support for asynchronous programming using async and await
keywords along with Futures and Streams. This makes it easy to handle non-blocking
operations, such as network requests or file I/O, without freezing the main thread.
7. Hot Reload:
When using Dart with Flutter, developers can leverage the hot reload feature, allowing
them to instantly see the results of changes in code without restarting the entire app. This
speeds up development, especially when working on UI and visual elements.

8. Strongly Typed with Type Inference:


Dart is a statically typed language, meaning the type of a variable is known at compile-
time, leading to fewer runtime errors. It also features type inference, so you don’t always
have to explicitly declare types Dart will infer them for you.

Example:
var message = 'Hello'; // Type inferred as String
int count = 10; // Explicitly typed as int
9. Rich Standard Library:
Dart comes with a comprehensive standard library, which includes utilities for collections
(lists, sets, maps), I/O operations, asynchronous programming, and much more. The
library is well-structured and provides numerous built-in functions for day-to-day tasks.

10. Memory Management (Garbage Collection):


Dart handles memory management automatically through garbage collection. It cleans
up unused objects and resources, which helps avoid memory leaks and ensures efficient
memory usage in applications.

3
11. Functional Programming Features:
Dart supports functional programming with features like first-class functions, closures,
and higher-order functions. This allows developers to pass functions as parameters,
return them from other functions, and capture variables from the surrounding context.
Example:
Function add(int a) {
return (int b) => a + b;
}
var addFive = add(5);
print(addFive(10)); // Output: 15
12. Platform Interoperability:
Dart can interact with native platform code, allowing developers to write native iOS,
Android, or desktop code and communicate with Dart through platform channels in
Flutter. Dart’s FFI (Foreign Function Interface) also allows calling C libraries for
performance-critical tasks.
13. Modular and Scalable Code:
Dart promotes writing modular code with its support for libraries, packages, and
namespaces. The Dart package ecosystem is well-developed, allowing developers to
easily use and share packages via pub.dev.
14. Security:
Dart’s null safety and strong static typing features significantly reduce the chances of
common bugs like null pointer exceptions and type mismatches. This results in more
reliable and secure applications.
15. Extensive Tooling:
Dart has a range of tools that support debugging, profiling, and analyzing code:
o Dart Analyzer: Catches potential issues during development.
o Dart DevTools: A suite of tools for debugging, profiling, and performance
monitoring.
o Dart VM: Executes Dart code in various environments, including servers and
command-line apps.

4
16. Wide Ecosystem:
Dart has a growing package ecosystem that includes libraries for networking, databases,
authentication, state management, animations, and more. Many of these packages are
freely available through pub.dev.
Dart frameworks
Dart has several frameworks that help streamline the development of various types of applications.
The most notable Dart frameworks are:
1. Flutter: Flutter is a popular open-source UI toolkit created by Google for building natively
compiled applications for mobile, web, and desktop from a single codebase.
 Key Features:
Widgets: Flutter provides a rich set of highly customizable widgets that make it easy
to create beautiful and responsive UIs.
Hot Reload: Allows developers to see the impact of their changes instantly without
restarting the app.
Cross-Platform: Build applications for iOS, Android, web, and desktop (Windows,
macOS, Linux) with a single codebase.
High Performance: Uses Dart’s AOT compilation to produce highly performant
native code.
2. AngularDart: AngularDart is a web application framework based on Angular (the TypeScript
framework) but built specifically for Dart. It provides a robust framework for developing scalable
and maintainable web applications.
3. Angel: Angel is a full-featured web framework for Dart that focuses on building server-side
applications with a modular and extensible architecture.
4. Shelf: Shelf is a lightweight middleware framework for handling HTTP requests in Dart. It’s
designed for creating web servers and APIs.
5. Flame: Flame is a lightweight game engine for Dart that simplifies game development by
providing a set of tools and abstractions.
6. StageXL: StageXL is a 2D game engine and graphical library for Dart, providing tools for
creating interactive graphics and games.
7. dart_frog: dart_frog is a framework designed for building fast, scalable web applications and
APIs in Dart.

5
Use cases.
Dart's versatility and features make it suitable for various use cases across different domains. the
following are the prominent use cases where Dart excels:
1. Mobile Application Development
Dart is primarily known for its role in mobile application development through the Flutter
framework. Flutter allows developers to create natively compiled applications for both iOS and
Android from a single codebase.
Use Cases:
Business Apps: Enterprise applications that require a polished UI and high performance.
Consumer Apps: Mobile apps for e-commerce, social media, and entertainment.
Cross-Platform Apps: Apps that need to run on multiple platforms without rewriting code.
Example: Apps like Google Ads, Alibaba's Xianyu, and Reflectly are built using Flutter and Dart.
2. Web Application Development
Dart is used with AngularDart for building scalable and maintainable web applications.
AngularDart leverages Dart’s type safety and asynchronous features for robust web app
development.
Use Cases:
Single Page Applications (SPAs): Dynamic and interactive web applications with rich
user interfaces.
Progressive Web Apps (PWAs): Web apps that provide a native app-like experience with
offline capabilities.
Example: The Dart website and the AngularDart showcase apps are built using AngularDart.
3. Server-Side Development
Dart frameworks like Aqueduct and Angel are used for building server-side applications, RESTful
APIs, and microservices.
Use Cases:
Web Servers: High-performance servers handling HTTP requests and responses.
RESTful APIs: APIs providing data and functionality to web and mobile clients.
Microservices: Small, independent services that communicate over a network.

6
Example: While not as widely used as other server-side technologies, Dart-based server
frameworks are suitable for specific server-side applications.
4. Desktop Application Development
With Flutter’s growing support for desktop platforms, Dart can be used to build cross-platform
desktop applications for Windows, macOS, and Linux.
Use Cases:
Productivity Apps: Desktop tools and utilities for various business needs.
Creative Software: Applications for design, media, and content creation.
Example: Flutter’s desktop support is still evolving, but tools like Flutter Desktop Embeddings
demonstrate Dart’s potential for desktop apps.
5. Game Development
Dart is used for developing 2D games with engines like Flame and StageXL.
Use Cases:
Mobile Games: Games for iOS and Android platforms that require efficient graphics and
performance.
Web Games: Interactive games that run in the browser, utilizing Dart’s capabilities for
web applications.
Example: Games built with Flame include casual and indie games designed for mobile and web
platforms.
6. IoT (Internet of Things)
Dart can be used in conjunction with Flutter for building UIs for IoT devices, although this is a
more niche use case compared to other languages more traditionally used in embedded systems.
Use Cases:
Smart Home Devices: User interfaces for smart appliances and home automation systems.
Wearables: Applications for wearable devices, providing a smooth user experience.
Example: While not mainstream, Dart's potential for IoT is explored through custom integrations
and experimental projects.
7. Command-Line Tools
Dart can be used to build command-line applications for various tasks such as scripting,
automation, and data processing.

7
Use Cases:
Utilities and Tools: Command-line tools for developers and system administrators.
Automation Scripts: Scripts for automating repetitive tasks and workflows.
Example: Dart can be used for building tools like development utilities, build scripts, and other
CLI applications.
1.1.2. Description of key terms
Data types
Data types define the kind of data that can be stored and manipulated within a program.
In Dart, The data type classification is as given below:

Data Type Keyword Description

int, double, num, Numbers in Dart are used to represent


Number
BigInt numeric literals

Strings String Strings represent a sequence of characters

Booleans Bool It represents Boolean values true and false

Lists List It is an ordered group of objects

It represents a set of values as key-value


Maps Map
pairs

1. Number
The number in Dart Programming is the data type that is used to hold the numeric value. Dart
numbers can be classified as:
 int: data type is used to represent whole numbers (64-bit Max).
 double: data type is used to represent 64-bit precise floating-point numbers.
 num: type is an inherited data type of the int and double types.

8
Below is the implementation of Numbers in Dart:
// Dart Program to demonstrate
// Number Data Type

void main() {

// declare an integer
int num1 = 2;

// declare a double value


double num2 = 1.5;

// print the values


print(num1);
print(num2);
var a1 = num.parse("1");
var b1 = num.parse("2.34");

var c1 = a1+b1;
print("Product = ${c1}");
}
Output:
2
1.5
Product = 3.34
2. String
It used to represent a sequence of characters. It is a sequence of UTF-16 code units. The keyword
string is used to represent string literals. String values are embedded in either single or double-
quotes.
Syntax: String str_name;
Below is the implementation of String data type in Dart:
// Dart Program to demonstrate
// String Data Type

// Driver Class
void main() {
// Declaration of String type
String string = 'Geeks' 'for' 'Geeks';
String str = 'Coding is ';
String str1 = 'Fun';

print (string);
print (str + str1);

9
}
Output:
GeeksforGeeks
Coding is Fun
3. Boolean
It represents Boolean values true and false. The keyword bool is used to represent a Boolean literal
in DART.
Syntax: bool var_name;
Below is the implementation of Boolean in Dart:
// Dart Program to demonstrate
// Boolean Data Type

void main() {
String str = 'Coding is ';
String str1 = 'Fun';

bool val = (str==str1);


print (val);
}
Output:
false
4. List
List data type is similar to arrays in other programming languages. A list is used to represent a
collection of objects. It is an ordered group of objects.
 Declaration of List
There are multiple methods to declare List in Dart as mentioned below:
a. Variable Size List
// Correct way to declare a variable-sized list
List<int> var_name1 = [];

// Alternative declaration
List<int> var_name2;
b. Fixed Size List
Fixed Size doesn’t mean that we can’t change the size of List, but we have predefined that the
List has this much elements while declaring.

10
List<int> var_name1 = List<int>.filled(size, 0);
List<int> var_name2 = List<int>.generate(size, (index) => index * 2);
Below is the implementation of List in Dart:

void main() {
// Creating a fixed-size list using List.filled
List<String> gfg = List<String>.filled(3, "default");

// Modifying the elements of the list


gfg[0] = 'Geeks';
gfg[1] = 'For';
gfg[2] = 'Geeks';

// Printing the entire list


print(gfg); // Output: [Geeks, For, Geeks]

// Printing a specific element


print(gfg[0]); // Output: Geeks
}
Output:
[Geeks, For, Geeks]
Geeks
5. Map
The Map object is a key and value pair. Keys and values on a map may be of any type. It is a
dynamic collection.
 Declare Map in Dart
While Declaring Map there can be only two cases one where declared Map is empty and another
where declared Map contains elements in it. Both Cases are mentioned below:
a. Declaring Empty Map
// Method 1
Map? map_name;

// Method 2
Map<key_datatype , value_datatype>? map_name;

// Method 3
var map_name = new Map();
b. Declaring Map with Elements inside it.
// Method 1
Map x={
key1 : value1;

11
key2 : value2;
};

// Method 2
Map<key_datatype , value_datatype> map_name{
key1 : value1;
key2 : value2;
};

// Method 3
var map_name{
key1 : value1;
key2 : value2;
};
Below is the implementation of Map in Dart:
Example1:
void main() {
Map gfg = new Map();
gfg['First'] = 'Geeks';
gfg['Second'] = 'For';
gfg['Third'] = 'Geeks';
print(gfg);
}
Output:
{First: Geeks, Second: For, Third: Geeks}

Example2:
void main() {
// Method 1: Declaring an empty map
Map? emptyMap1;

// Method 2: Declaring a map with specified key and value types


Map<String, int>? emptyMap2;

// Method 3: Declaring an empty map with inferred types


var emptyMap3 = <String, int>{};

// Method 1: Declaring a map with elements


Map<String, int> mapWithElements1 = {
'key1': 1,
'key2': 2,
};

// Method 2: Declaring a map with specified key and value types and elements
Map<String, int> mapWithElements2 = {
'key1': 1,

12
'key2': 2,
};

// Method 3: Declaring a map with inferred types and elements


var mapWithElements3 = {
'key1': 1,
'key2': 2,
};

// Printing the maps


print('Empty Map 1: $emptyMap1');
print('Empty Map 2: $emptyMap2');
print('Empty Map 3: $emptyMap3');
print('Map with Elements 1: $mapWithElements1');
print('Map with Elements 2: $mapWithElements2');
print('Map with Elements 3: $mapWithElements3');
}
Output
Empty Map 1: null
Empty Map 2: null
Empty Map 3: {}
Map with Elements 1: {key1: 1, key2: 2}
Map with Elements 2: {key1: 1, key2: 2}
Map with Elements 3: {key1: 1, key2: 2}

Note: If the type of a variable is not specified, the variable’s type is dynamic. The dynamic
keyword is used as a type annotation explicitly.

Variables
A variable name is the name assigned to the memory location where the user stores the data and
that data can be fetched when required with the help of the variable by calling its variable name.
There are various types of variable that are used to store the data. The type which will be used to
store data depends upon the type of data to be stored.
Syntax: To declare a variable:
type variable_name;
Syntax: To declare multiple variables of the same type:
type variable1_name, variable2_name, variable3_name, ....variableN_name;
 Types of Variables
Type of the variable can be among:
1. Static Variable

13
2. Dynamic Variable
3. Final or const
 Conditions to Write Variable Name
Conditions to write variable names or identifiers are as follows:
1. Variable names or identifiers can’t be the keyword.
2. Variable names or identifiers can contain alphabets and numbers.
3. Variable names or identifiers can’t contain spaces and special characters, except
the underscore(_) and the dollar($) sign.
4. Variable names or identifiers can’t begin with a number.
Note: Dart supports type-checking, it means that it checks whether the data type and the data that
variable holds are specific to that data or not.
 Example of Dart Variable
Example 1:
Printing default and assigned values in Dart of variables of different data types.
void main()
{
// Declaring and initialising a variable
int gfg1 = 10;

// Declaring another variable


double gfg2 = 0.2; // must declare double a value or it
// will throw error
bool gfg3 = false; // must declare boolean a value or it
// will throw error

// Declaring multiple variable


String gfg4 = "0", gfg5 = "Geeks for Geeks";

// Printing values of all the variables


print(gfg1); // Print 10
print(gfg2); // Print 0.2
print(gfg3); // Print default string value
print(gfg4); // Print default bool value
print(gfg5); // Print Geeks for Geeks
}
Output:
10
0.2
false

14
0
Geeks for Geeks
 Keywords in Dart
Keywords are the set of reserved words which can’t be used as a variable name or identifier
because they are standard identifiers whose function are predefined in Dart.

 Dynamic Type Variable in Dart


This is a special variable initialised with keyword dynamic. The variable declared with this data
type can store implicitly any value during running the program. It is quite similar to var datatype
in Dart, but the difference between them is the moment you assign the data to variable with var
keyword it is replaced with the appropriate data type.
Syntax: dynamic variable_name;
Example 2:
Showing how datatype are dynamically change using dynamic keyword.
void main()
{

15
// Assigning value to geek variable
dynamic geek = "Geeks For Geeks";

// Printing variable geek


print(geek);

// Reassigning the data to variable and printing it


geek = 3.14157;
print(geek);
}
Output:
Geeks For Geeks
3.14157
Note: If we use var instead of dynamic in the above code then it will show error.
Error compiling to JavaScript:
main.dart:9:10:
Error: A value of type 'double' can't be assigned to a variable of type 'String'.
geek = 3.14157;
^
Error: Compilation failed.
 Final And Const Keyword in Dart
These keywords are used to define constant variable in Dart i.e. once a variable is defined using
these keyword then its value can’t be changed in the entire code. These keyword can be used with
or without data type name.
a. Final
A final variable can only be set once and it is initialized when accessed.
Syntax for Final:
// Without datatype
final variable_name

// With datatype
final data_type variable_name
Example of final keywords in a Dart
Below is the implementation of final keywords in Dart Program:
void main() {
// Assigning value to geek1 variable without datatype
final geek1 = "Geeks For Geeks";
// Printing variable geek1
print(geek1);

// Assigning value to geek2 variable with datatype

16
final String geek2 = "Geeks For Geeks Again!!";
// Printing variable geek2
print(geek2);
}
Output:
Geeks For Geeks
Geeks For Geeks Again!!
Now, if we try to reassign the geek1 variable in the above program, then:
Output:
Error compiling to JavaScript:
main.dart:8:3:
Error: Can't assign to the final variable 'geek1'.
geek1 = "Geeks For Geeks Again!!";
^^^^^
Error: Compilation failed.
b. Const
A constant variable is a compile-time constant and its value must be known before the program
runs.
Syntax for Const:
// Without datatype
const variable_name;

// With datatype
const data_type variable_name;
Example const Keywords in a Dart Program
Below is the implementation of const Keyword in Dart Program:
void main() {
// Assigning value to geek1 variable without datatype
const geek1 = "Geeks For Geeks";
// Printing variable geek1
print(geek1);

// Assigning value to geek2 variable with datatype


const geek2 = "Geeks For Geeks Again!!";
// Printing variable geek2
print(geek2);
}
Output:
Geeks For Geeks
Geeks For Geeks Again!!
Now, if we try to reassign the geek1 variable in the above program, then output is as follows:

17
Error compiling to JavaScript:
main.dart:8:2:
Error: Can't assign to the const variable 'geek1'.
geek1 = "Geeks For Geeks Again!!";
^^^^^
Error: Compilation failed.
 Null Safety in Dart
In Dart, by default a variable can’t be assigned Null value till it is defined that the variable can
store Null value in it. This to avoid cases where user assign null value in Dart.
Example:
void main() {
int a=10;
a=NULL;
print(a);
}
The above Program will return a error.
How to assign null value to variable in Dart?
To declare a variable as nullable, you append a ‘?' to the type of the variable. The declared variable
will by default store null as value and even after assigning it values of your choice you can declare
it as null afterwards.
Below is the implementation to assign null value to variables:
void main() {
int? a;
a=null;
print(a);
}
Output:
null

Control flow structures.


Control flow structures determine the order in which statements are executed in a program.
Control Flow in Programming is refers to the order in which set of instructions or statements that
are executed or evaluated. It provides flexibility in Decision making and makes code more user
friendly.
Control statements are classified into:
 Conditions/Decision-making statements
 Loop/Iteration control statements

18
a. Conditions statements
Conditions or Decision-making statements are those statements that allow the programmers to
decide which statement should run in different conditions.
There are four ways to achieve this:
 if Statement
 if-else Statement
 else-if Ladder
 Nested if Statement
1. if Statement
This type of statement simply checks the condition and if it is true the statements within it are
executed but if it is not then the statements are simply ignored in the code.
Syntax:
if ( condition ){
// body of if
}
Illustration with Image:

Example:
void main()
{
int gfg = 10;

// Condition is true
if (gfg > 3) {
// This will be printed
print("Condition is true");
}
}
Output:

19
Condition is true
2. if…else Statement
This type of statement simply checks the condition and if it is true, the statements within are
executed but if not then else statements are executed.
Syntax:
if ( condition ){
// body of if
}
else {
// body of else
}
Illustration with Image:

Example:
void main()
{
int gfg = 10;

// Condition is false
if (gfg > 30) {
// This will not be printed
print("Condition is true");
}
else {
// This will be printed
print("Condition id false");
}
}
Output:
Condition is false

20
3. else…if Ladder
This type of statement simply checks the condition and if it is true the statements within it are
executed but if it is not then other if conditions are checked, if they are true then they are executed,
and if not then the other if conditions are checked. This process is continued until the ladder is
completed.
Syntax:
if ( condition1 ){
// body of if
}
else if ( condition2 ){
// body of if
}
.
.
.
else {
// statement
}
Illustration with Image:

Example:
void main()
{
int gfg = 10;
if (gfg < 9) {
print("Condition 1 is true");

21
gfg++;
}
else if (gfg < 10) {
print("Condition 2 is true");
}
else if (gfg >= 10) {
print("Condition 3 is true");
}
else if (++gfg > 11) {
print("Condition 4 is true");
}
else {
print("All the conditions are false");
}
}
Output:
Condition 3 is true
4. Nested if Statement
This type of statement checks the condition and if it is true then the if statement inside it checks
its condition and if it is true then the statements are executed otherwise else statement is executed.
Syntax:
if ( condition1 ){
if ( condition2 ){
// Body of if
}
else {
// Body of else
}
}
Illustration with Image:

22
Example:
void main()
{
int gfg = 10;
if (gfg > 9) {
gfg++;
if (gfg < 10) {
print("Condition 2 is true");
}
else {
print("All the conditions are false");
}
}
}
Output:
All the conditions are false
5. Try code of if-else with Operator:
void main() {

int x = 5;
int y = 7;

if((++x > y--) && (++x < ++y)){


print("Condition true");
}
else {
print("Condition false");
}

23
print(x);
print(y);
}
Output:
Condition false
6
6

6. Switch Case statement


In Dart, switch-case statements are a simplified version of the nested if-else statements. Its
approach is the same as that in Java.
Syntax:
switch ( expression ) {
case value1: {
// Body of value1
} break;
case value2: {
//Body of value2
} break;
.
.
.
default: {
//Body of default case
} break;
}
The default case is the case whose body is executed if none of the above cases matches the
condition.
Rules to follow in switch case:
1. There can be any number of cases. But values should not be repeated.
2. The case statements can include only constants. It should not be a variable or an expression.
3. There should be a flow control i.e break within cases. If it is omitted than it will show error.
4. The default case is optional.
5. Nested switch is also there thus you can have switch inside switch.

24
Example 1: Normal switch-case statement
void main() {
int number = 1;

switch (number) {
case 1:
print('GeeksforGeeks number 1');
break;
case 2:
print('GeeksforGeeks number 2');
break;
default:
print('Number not found');
}
}
Output:
GeeksforGeeks number 1
Example 2: Nested switch-case statement
void main() {
String greeting = 'Welcome';
String site = 'GeeksforGeeks';

25
switch (greeting) {
case 'Welcome':
switch (site) {
case 'GeeksforGeeks':
print('Welcome to GeeksforGeeks');
break;
default:
print('Welcome to an unknown site');
}
break;
default:
print('Hello, world');
}
}
Output:
Welcome to GeeksforGeeks
b. Loops
A looping statement in Dart or any other programming language is used to repeat a particular set
of commands until certain conditions are not completed. There are different ways to do so. They
are:
 for loop
 for… in loop
 for each loop
 while loop
 do-while loop
1. for loop
The for loop is a control flow statement that allows code to be executed repeatedly based on a
condition.
Syntax:
for(initialization; condition; text expression){
// Body of the loop
}
Control flow:
Control flow goes as:
1. initialization
2. Condition

26
3. Body of loop
4. Test expression
The first is executed only once i.e in the beginning while the other three are executed until the
condition turns out to be false.
Example:

// Printing GeeksForGeeks 5 times


void main()
{
for (int i = 0; i < 5; i++) {
print('GeeksForGeeks');
}
}

Output:
GeeksForGeeks
GeeksForGeeks
GeeksForGeeks
GeeksForGeeks
GeeksForGeeks

2. for…in loop
The for…in loop is used to iterate over each element in a collection such as a list, set, or map. It
simplifies the process of accessing each item in the collection without needing an index variable.
This loop is ideal for operations where you need to process each element individually.
A for…in loop iterates over elements of a collection, executing a block of code for each item in
the collection.
Syntax:
for (var in expression) {
// Body of loop
}

Example:

void main()
{
var GeeksForGeeks = [ 1, 2, 3, 4, 5 ];
for (int i in GeeksForGeeks) {
print(i);

27
}
}

Output:
1
2
3
4
5
3. for each … loop
The for-each loop iterates over all elements in some container/collectible and passes the elements
to some specific function.
Syntax:
collection.foreach(void f(value))
Parameters:
 f( value): It is used to make a call to the f function for each element in the collection.
Example:

void main() {
var GeeksForGeeks = [1,2,3,4,5];
GeeksForGeeks.forEach((var num)=> print(num));
}

Output:
1
2
3
4
5
4. while loop
The body of the loop will run until and unless the condition is true.

Syntax:
while(condition){
text expression;
// Body of loop
}
Example:

void main()

28
{
var GeeksForGeeks = 4;
int i = 1;
while (i <= GeeksForGeeks) {
print('Hello Geek');
i++;
}
}

Output:
Hello Geek
Hello Geek
Hello Geek
Hello Geek

5. do..while loop
The body of the loop will be executed first and then the condition is tested.
Syntax:
do{
text expression;
// Body of loop
}while(condition);
Example:

void main()
{
var GeeksForGeeks = 4;
int i = 1;
do {
print('Hello Geek');
i++;
} while (i <= GeeksForGeeks);
}

Output:
Hello Geek
Hello Geek
Hello Geek
Hello Geek

6. Break Statement

29
This statement is used to break the flow of control of the loop i.e if it is used within a loop then it
will terminate the loop whenever encountered. It will bring the flow of control out of the nearest
loop.
Syntax: break;
Example 1: Using break inside while loop

void main()
{
int count = 1;

while (count <= 10) {


print("Geek, you are inside loop $count");
count++;

if (count == 4) {
break;
}
}
print("Geek, you are out of while loop");
}

Output:
Geek, you are inside loop 1
Geek, you are inside loop 2
Geek, you are inside loop 3
Geek, you are out of while loop
Explanation:
Initially count value is 1, as it goes inside loop the condition is checked, 1 <= 10 and as it is true the
statement is printed variable is increased and then condition is checked, 2 == 4, which is false.
Then the loop is followed again till the condition 4 == 4 is encountered and the flow comes out of
the loop and then last print statement is executed.
Example 2: Using break inside do..while loop

void main()
{
int count = 1;

do {
print("Geek, you are inside loop $count");
count++;

30
if (count == 5) {
break;
}
} while (count <= 10);
print("Geek, you are out of do..while loop");
}

Output:
Geek, you are inside loop 1
Geek, you are inside loop 2
Geek, you are inside loop 3
Geek, you are inside loop 4
Geek, you are out of do..while loop
Example 3: Using break inside for loop

void main()
{
for (int i = 1; i <= 10; ++i) {
if (i == 2)
break;

print("Geek, you are inside loop $i");


}

print("Geek, you are out of loop");


}

Output:
Geek, you are inside loop 1
Geek, you are out of loop
7. Continue Statement
While the break is used to end the flow of control, continue on the other hand is used to continue
the flow of control. When a continue statement is encountered in a loop it doesn’t terminate the
loop but rather jump the flow to next iteration.
Syntax: continue;
Example 1: Using continue inside while loop

void main()
{
int count = 0;

31
while (count <= 10) {
count++;

if (count == 4) {
print("Number 4 is skipped");
continue;
}

print("Geek, you are inside loop $count");


}

print("Geek, you are out of while loop");


}

Output:
Geek, you are inside loop 1
Geek, you are inside loop 2
Geek, you are inside loop 3
Number 4 is skipped
Geek, you are inside loop 5
Geek, you are inside loop 6
Geek, you are inside loop 7
Geek, you are inside loop 8
Geek, you are inside loop 9
Geek, you are inside loop 10
Geek, you are inside loop 11
Geek, you are out of while loop
Explanation:
Here control flow of the loop will go smooth but when count value becomes 4 the if condition
becomes true and the below statement is skipped because of continue and next iteration skipping
number 4.
Example 2: Using continue inside do..while loop

void main()
{
int count = 0;

do {
count++;

if (count == 4) {
print("Number 4 is skipped");

32
continue;
}

print("Geek, you are inside loop $count");


} while (count <= 10);
print("Geek, you are out of while loop");
}

Output:
Geek, you are inside loop 1
Geek, you are inside loop 2
Geek, you are inside loop 3
Number 4 is skipped
Geek, you are inside loop 5
Geek, you are inside loop 6
Geek, you are inside loop 7
Geek, you are inside loop 8
Geek, you are inside loop 9
Geek, you are inside loop 10
Geek, you are inside loop 11
Geek, you are out of while loop
Example 3: Using continue inside for loop

void main()
{
for (int i = 1; i <= 10; ++i) {

if (i == 2) {
print("Geek, you are inside loop $i");
continue;
}
}

print("Geek, you are out of loop");


}

Output:
Geek, you are inside loop 2
Geek, you are out of loop

c. Labels in Dart
Most of the people, who have programmed in C programming language, are aware
of goto and label statements which are used to jump from one point to other but unlike Java,
Dart also doesn’t have any goto statements but indeed it has labels which can be used

33
with continue and break statements and help them to take a bigger leap in the code.
It must be noted that line-breaks are not allowed between ‘label-name’ and loop control
statements.
Example #1: Using label with the break statement

void main() {

// Defining the label


Geek1:for(int i=0; i<3; i++)
{
if(i < 2)
{
print("You are inside the loop Geek");

// breaking with label


break Geek1;
}
print("You are still inside the loop");
}
}

Output:
You are inside the loop Geek

The above code results into only one-time printing of statement because once the loop is broken
it doesn’t go back into it.
Example #2: Using label with the continue statement

void main() {

// Defining the label


Geek1:for(int i=0; i<3; i++)
{
if(i < 2)
{
print("You are inside the loop Geek");

// Continue with label


continue Geek1;
}
print("You are still inside the loop");
}

34
}

Output:

You are inside the loop Geek


You are inside the loop Geek
You are still inside the loop
The above code results in printing of the statement twice because of the condition it didn’t break
out of the loop and thus printing it twice.

Functions

The function is a set of statements that take inputs, do some specific computation and produces
output. Functions are created when certain statements are repeatedly occurring in the program and
a function is created to replace them. Functions make it easy to divide the complex program into
smaller sub-groups and increase the code reusability of the program.
Defining the Function in Dart
Dart provides us with the facility of using functions in its program.

In the above syntax:


 function_name: defines the name of the function.
 return_type: defines the datatype in which output is going to come.
 return value: defines the value to be returned from the function.
How to Call Functions in Dart?

35
In the above syntax:
 function_name: defines the name of the function.
 argument list: is the list of the parameters that the function requires.
Example 1: Complete function in Dart
int add(int a, int b){
// Creating function
int result = a + b;
// returning value result
return result;
}

void main(){
// Calling the function
var output = add(10, 20);

// Printing output
print(output);
}
Output:
30
Note: You must note that two functions can’t have the same function name although they differ in
parameters.
Example 2: Function without parameter and return value.
void GFG(){
// Creating function
print("Welcome to GeeksForGeeks");
}

void main()
{
// Calling the function
GFG();
}
Output:
Welcome to GeeksForGeeks

36
Note: You must note that you can also directly return string or integer or any output of expression
through the return statement.
 main() Function
The main() function is a predefined method in Dart. It is the most important and mandatory part
of any Dart Program. Any Dart script requires the main() method for its execution. This method
acts as the entry point for any Dart application. It is responsible for executing all library functions,
user-defined statements, and user-defined functions.
Syntax of main() function:
void main()
{
//main() function body
}
The main function can be further structured to variable declaration, function declaration, and
executable statements. The main function returns void. Also, optional
parameters List<String> may be used as arguments to the function. These arguments may be used
in case we need to control our program from outside.
Example of Dart main() function
Below are the Examples of Dart main() function
Example 1:
The following example is a basic example of how the main function is the entry point of a Dart
program.
main(){
print("Main is the entry point!");
}
Output:

Example 2:
The following example shows how we can pass arguments inside the main() function.
main(List<String> arguments){
//printing the arguments along with length
print(arguments.length);
print(arguments);
}
We run the app using the following code(if main.dart is the name of the saved file):
dart main.dart Argument1 Argument2
Output:

37
 Functions with Optional Parameter
There are also optional parameter system in Dart which allows user to give some optional
parameters inside the function.

No. Parameter Description

To specify it use square (‘[ ]’)


1. Optional Positional Parameter
brackets

When we pass this parameter it is


mandatory to pass it while passing
2. Optional Named parameter
values. It is specify by curly(‘{ }’)
brackets.

Here parameters are assign with


3. Optional parameter with default values
default values.

Example:
void gfg1(int g1, [ var g2 ])
{
// Creating function 1
print("g1 is $g1");
print("g2 is $g2");
}

void gfg2(int g1, { var g2, var g3 })


{
// Creating function 1
print("g1 is $g1");
print("g2 is $g2");
print("g3 is $g3");
}

void gfg3(int g1, { int g2 : 12 })


{
// Creating function 1
print("g1 is $g1");

38
print("g2 is $g2");
}

void main()
{
// Calling the function with optional parameter
print("Calling the function with optional parameter:");
gfg1(01);

// Calling the function with Optional Named parameter


print("Calling the function with Optional Named parameter:");
gfg2(01, g3 : 12);

// Calling function with default valued parameter


print("Calling function with default valued parameter");
gfg3(01);
}
Output:
Calling the function with optional parameter:
g1 is 1
g2 is null
Calling the function with Optional Named parameter:
g1 is 1
g2 is null
g3 is 12
Calling function with default valued parameter
g1 is 1
g2 is 12
 Recursive Function in Dart
The recursive function is those functions in which function calls itself. It is a good way to avoid
repeatedly calling the same function to get the output.
Example: Recursive function for fibonacci series.
/// Computes the nth Fibonacci number.
int fibonacci(int n)
{
// This is recursive function as it calls itself
return n < 2 ? n : (fibonacci(n - 1) + fibonacci(n - 2));
}

void main()
{
// input
var i = 20;

39
print('fibonacci($i) = ${fibonacci(i)}');
}
Output:
For input as 20
fibonacci(20) = 6765

 Lambda Function in Dart


They are the short way of representing a function in Dart. They are also called arrow function.
But you should note that with lambda function you can return value for only one expression.
Example: Lambda function in dart.
// Lambda function in Dart
void gfg() => print("Welcome to GeeksforGeeks");

void main()
{
// Calling Lambda function
gfg();
}
Output:
Welcome to GeeksforGeeks

Anonymous Functions
An anonymous function in Dart is like a named function but they do not have names associated
with it. An anonymous function can have zero or more parameters with optional type annotations.
An anonymous function consists of self-contained blocks of code and that can be passed around
in our code as a function parameter.
 In Dart most of the functions are named functions we can also create nameless function
knows as an anonymous function, lambda, or closure.
 In Dart we can assign an anonymous function to constants or variables, later we can access
or retrieve the value of closure based on our requirements:
Syntax:
(parameter_list)
{

statement(s)
}
Example:

40
// Dartprogram to illustrate
// Anonymous functions in Dart
void main()
{
var list = ["Shubham","Nick","Adil","Puthal"];
print("GeeksforGeeks - Anonymous function in Dart");
list.forEach((item) {
print('${list.indexOf(item)} : $item');
});
}

Output:

This example defines an anonymous function with an untyped parameter, item. The function,
invoked for each item in the list, prints a string that includes the value at the specified index.

Different Types of Functions in Dart


The function is a set of statements that take inputs, do some specific computation, and produce
output. Functions are created when certain statements are repeatedly occurring in the program and
a function is created to replace them. Functions make it easy to divide the complex program into
smaller sub-groups and increase the code reusability of the program.
Basically, there are four types of functions in Dart.
These are as follows:
1. No arguments and no return type
2. With arguments and no return type
3. No arguments and return type
4. With arguments and with return type
1. Function with no argument and no return type:
Basically in this function, we do not give any argument and expect no return type. It can be better
understood by an example.

41
void myName(){
print("GeeksForGeeks");
}

void main(){
print("This is the best website for developers:");
myName();
}

Output:
This is the best website for the developers : GeeksForGeeks
So myName is the function that is void means it is not returning anything and the empty pair of
parentheses suggest that there is no argument that is passed to the function.
2. Function with no arguments but return type:
Basically in this function, we are giving an argument and expect no return type.

int myPrice(){
int price = 0;
return price;
}

// Driver
void main(){
int Price = myPrice();
print("GeeksforGeeks is the best website for developers which costs : ${Price}/-");
}

Output:
GeeksforGeeks is the best website for developers which costs : 0/-

So myPrice is the function that is int means it is returning int type and the empty pair of parentheses
suggests that there is no argument which is passed to the function.
3. Function with arguments but no return type:
Basically in this function, we do not give any argument but expect a return type.

myPrice(int price){
print(price);
}

// Driver
void main() {

42
print("GeeksforGeeks is the best website for developers which costs : ");
myPrice(0);
}

Output:
GeeksforGeeks is the best website for developers which costs : 0

So myPrice is the function that is void means it is not returning anything and the pair of parentheses
is not empty this time which suggests that it accept an argument.
4. Function with arguments and with return type:
Basically in this function, we are giving an argument and expect return type. It can be better
understood by an example.

int mySum(int firstNumber, int secondNumber) {


return (firstNumber + secondNumber);
}

// Driver
void main(){

int additionOfTwoNumber = mySum(100, 500);


print(additionOfTwoNumber);
}

Output:
600
So mySum is the function that is int means it is returning int type and the pair of parentheses is
having two arguments that are used further in this function and then in the main function we are
printing the addition of two numbers.
 Common Collection Methods
List, Set, and Map share common functionality found in many collections. Some of this common
functionality is defined by the Iterable class, which List and Set implement.
1. isEmpty() or isNotEmpty:
Use isEmpty or isNotEmpty to check whether a list, set, or map has items:
Example:

void main(){

43
var coffees = [];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
print(coffees.isEmpty);
print(teas.isNotEmpty);
}

Output:
true
true

2. forEach():
To apply a function to each item in a list, set, or map, you can use forEach():
Example:

void main(){

var teas = ['green', 'black', 'chamomile', 'earl grey'];

var loudTeas = teas.map((tea) => tea.toUpperCase());


loudTeas.forEach(print);
}

Output:
GREEN
BLACK
CHAMOMILE
EARL GREY
3.where():
Use Iterable’s where() method to get all the items that match a condition. Use Iterable’s any() and
every() methods to check whether some or all items match a condition.
Example:

void main(){

var teas = ['green', 'black', 'chamomile', 'earl grey'];

// Chamomile is not caffeinated.


bool isDecaffeinated(String teaName) =>
teaName == 'chamomile';

// Use where() to find only the items that return true

44
// from the provided function.

// Use any() to check whether at least one item in the


// collection satisfies a condition.
print(teas.any(isDecaffeinated));

// Use every() to check whether all the items in a


// collection satisfy a condition.
print(!teas.every(isDecaffeinated));
}

Output:
true
true
 How to Exit a Dart Application Unconditionally?
The exit() method exits the current program by terminating running Dart VM. This method takes
a status code. A non-zero value of status code is generally used to indicate abnormal termination.
This method doesn’t wait for any asynchronous operations to terminate.
Syntax: exit(exit_code);
To use this method we have to import ‘dart:io’ package. The handling of exit codes is platform-
specific.
 On Linux and OS, an exit code for normal termination will always be in the range of 0 to
255. If an exit code outside this range is set the actual exit code will be the lower 8 bits
masked off and treated as an unsigned value. E.g. using an exit code of -1 will result in an
actual exit code of 255 being reported.
 On Windows, the exit code can be set to any 32-bit value. However, some of these values
are reserved for reporting system errors like crashes. Besides this, the Dart executable itself
uses an exit code of 254 for reporting compile-time errors and an exit code of 255 for
reporting runtime error (unhandled exception). Due to these facts, it is recommended to
only use exit codes in the range 0 to 127 for communicating the result of running a Dart
program to the surrounding environment. This will avoid any cross-platform issues.
Note: The exit(0) Generally used to indicate successful termination while rest generally indicates
unsuccessful termination.
Implementation of the exit() method is as:
void exit(int code) {

45
ArgumentError.checkNotNull(code, "code");
if (!_EmbedderConfig._mayExit) {
throw new UnsupportedError(
"This embedder disallows calling dart:io's exit()");
}
_ProcessUtils._exit(code);
}
Example: Using the exit() method to exit the program abruptly.

// Importing the packages


import 'dart:io';

// Main Function
void main() {

// This will be printed


print("Hello GeeksForGeeks");

// Standard out code


exit(0);

// This will not be printed


print("Good Bye GeeksForGeeks");
}

Output:
Hello GeeksForGeeks

 Getters and Setters


Getters and Setters, also called accessors and mutators, allow the program to initialize and
retrieve the values of class fields respectively.
 Getters or accessors are defined using the get keyword.
 Setters or mutators are defined using the set keyword.
A default getter/setter is associated with every class. However, the default ones can be overridden
by explicitly defining a setter/ getter. A getter has no parameters and returns a value, and the setter
has one parameter and does not return a value.
Syntax: Defining a getter

46
Return_type get identifier
{
// statements
}
Syntax: Defining a setter

set identifier
{
// statements
}

Example 1: The following example shows how you can use getters and setters in a Dart class:

// Dart Program in Dart to illustrate


// getters and setters #GFG
class Student {
String name;
int age;

String get stud_name {


return name;
}

void set stud_name(String name) {


this.name = name;
}

void set stud_age(int age) {


if(age<= 0) {
print("Age should be greater than 5");
} else {
this.age = age;
}
}

int get stud_age {


return age;
}
}
void main() {
Student s1 = new Student();
s1.stud_name = 'Nitin';
s1.stud_age = 0;
print(s1.stud_name);

47
print(s1.stud_age);
}

Output:
Age should be greater than 5
Nitin
Null
Example 2:

// Dart program in Dart to illustrate


// getters and setters #GFG
void main() {
var cat = new Cat();

// Is cat hungry? true


print("Is cat hungry? ${cat.isHungry}");

// Is cat cuddly? false


print("Is cat cuddly? ${cat.isCuddly}");

print("Feed cat.");
cat.isHungry = false;

// Is cat hungry? false


print("Is cat hungry? ${cat.isHungry}");

// Is cat cuddly? true


print("Is cat cuddly? ${cat.isCuddly}");
}

class Cat {
bool _isHungry = true;

bool get isCuddly => !_isHungry;

bool get isHungry => _isHungry;


bool set isHungry(bool hungry) => this._isHungry = hungry;
}

Output:

48
Native Apps

Native apps are software applications built specifically for a particular operating system (OS), such
as Android, iOS, or Windows. These apps are written in programming languages that are natively
supported by the operating system (e.g., Swift for iOS, Kotlin/Java for Android). Native apps can
take full advantage of the hardware and software features available on the target device, including
the camera, GPS, accelerometer, and other system functions.
Native apps offer better performance because they are compiled directly into the machine code of
the device’s operating system. This results in faster load times, smooth animations, and
responsiveness.
Native apps can interact with all the features and APIs provided by the operating system, including
the camera, sensors, GPS, contacts, and more. This allows for richer functionality.
Native apps can work without an internet connection once they are installed on the device, allowing
users to access some or all of the app's features offline.
Native apps are distributed through official app stores like the Apple App Store and Google Play
Store, making it easier for users to find and install them.
Examples of Native Apps:
 iOS: Instagram, WhatsApp, Spotify (built using Swift/Objective-C).
 Android: Google Maps, Facebook, TikTok (built using Kotlin/Java).

Dart for Native Apps


Dart, in combination with the Flutter framework, can be used to build native apps for mobile
platforms such as Android and iOS. While Dart itself is a general-purpose programming language,
it becomes particularly powerful when used with Flutter to develop native apps that provide the

49
same performance, look, and feel as apps built with platform-specific languages (e.g., Swift for
iOS, Kotlin for Android).
Examples of Native Apps Built with Dart (using Flutter):
1. Google Ads: The official app for managing Google Ads campaigns.
2. Alibaba: The mobile commerce app uses Flutter for parts of its app to achieve a native-
like experience.
3. Reflectly: A journaling app built with Flutter to provide a cross-platform native experience.
4. BMW App: BMW's companion app uses Flutter for its cross-platform development.

Cross Platform

Cross-platform refers to the development approach where applications or software can run on
multiple operating systems or platforms with a single codebase. This is in contrast to native
development, where separate codebases are needed for each platform (e.g., Android, iOS,
Windows). Cross-platform frameworks allow developers to write code once and deploy it across
different platforms without having to rewrite it for each one.

The application works across multiple operating systems such as Android, iOS, Windows, macOS,
and Linux. It provides a similar user experience on all platforms with minimal tweaks.

Cross-platform tools provide pre-built UI components that look consistent across platforms.
However, customization is often available to ensure the UI follows platform-specific design
guidelines.

Popular cross-platform development frameworks include Flutter (using Dart), React Native
(using JavaScript), Xamarin (using C#), and Ionic (using web technologies). These frameworks
provide the tools to develop apps that run on various platforms.

Examples of Cross-Platform Apps:

 Google Ads (built with Flutter)

 Skype (built with Xamarin)

50
 Instagram (parts built with React Native)

 Uber Eats (built with React Native)

1.1.3. Installation of key tools (Windows and Apple)


To develop applications using Dart, you need to install essential tools, configure them correctly,
and test the Dart environment. This section provides guidance on installing the Dart SDK,
integrating Dart with your code editor (such as Visual Studio Code), selecting the appropriate IDE
for your operating system, and testing the Dart setup.
1. Dart SDK Installation
The Dart SDK (Software Development Kit) contains all the tools needed to compile, run, and
manage Dart programs. Here's how to install it on both Windows and macOS:
a. Windows Installation:
1. Download Dart SDK:
o Visit the Dart SDK download page: https://dart.dev/get-dart.
o Choose the Windows option and download the Dart SDK (Stable release).
2. Extract the SDK:
o Once downloaded, extract the Dart SDK files to a preferred location on your system
(e.g., C:\dart).
3. Set Environment Variables:
o Right-click on This PC → Properties → Advanced system settings →
Environment Variables.
o Under System Variables, select Path → Edit → New, and add the path to the Dart
SDK’s bin folder (e.g., C:\dart\bin).
4. Verify Installation:
o Open Command Prompt and run dart --version to check if Dart has been installed
successfully.
b. macOS Installation:
1. Install Homebrew (if not installed):
o Open Terminal and run:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

51
2. Install Dart SDK:
o In Terminal, use Homebrew to install Dart by running:
brew tap dart-lang/dart
brew install dart
3. Verify Installation:
o After installation, verify the installation by running dart --version in the Terminal.
2. Integrating Dart with the Code Editor (Visual Studio Code)
Visual Studio Code (VS Code) is a lightweight code editor with extensive support for Dart and
Flutter. Here's how to set it up:
a. Install Visual Studio Code:
 Windows: Download and install from VS Code website.
 macOS: Download the macOS version from the same website.
b. Install Dart Plugin:
1. Open VS Code.
2. Go to the Extensions tab (on the left sidebar).
3. Search for Dart and install the Dart extension. This provides Dart language support and
tools inside VS Code.
c. Configure VS Code for Dart:
 Once the Dart extension is installed, VS Code should automatically detect Dart SDK if the
bin path is set correctly (in Windows) or installed via Homebrew (in macOS).
d. Enable Format on Save (optional):
 Go to File → Preferences → Settings.
 Search for Format on Save, and check the box to auto-format Dart code on saving.
3. IDE for Specific Operating Systems
Though Visual Studio Code is popular and widely used for Dart development, other Integrated
Development Environments (IDEs) might be preferred based on the OS:
a. Windows:
 Visual Studio Code: Lightweight, supports Dart with extensions.
 IntelliJ IDEA: A more robust IDE that also supports Dart with plugins.
b. macOS:
 Visual Studio Code: Cross-platform and popular.

52
 Android Studio: Good if you're working with both Dart and Flutter.
 IntelliJ IDEA: Advanced, with strong Dart/Flutter support.
4. Testing Dart Environment
After setting up the Dart SDK and integrating with an IDE or code editor, it's essential to ensure
that the environment is working correctly.
a. Create a Simple Dart Program:
1. Open VS Code or your preferred IDE.
2. Create a new file named hello.dart.
3. Write the following code:
void main() {
print('Hello, Dart!');
}
4. Save the file.
b. Run the Dart Program:
 Open the integrated terminal in VS Code (or your IDE).
 Navigate to the folder where hello.dart is saved.
 Run the command:
dart hello.dart
c. Expected Output:
If everything is set up correctly, the output should be:
Hello, Dart!

53
Learning outcome 2: Implement UI designs
2.1. Preparation of Flutter Environment
Flutter is a powerful open-source UI software development framework created by Google.
Preparing the Flutter environment involves setting up the tools and resources required to build,
test, and deploy cross-platform mobile, web, and desktop applications.
2.1.1 Introduction to Flutter Framework
Definition
Flutter is a UI toolkit designed for building natively compiled applications for mobile (iOS and
Android), web, and desktop from a single codebase. It uses the Dart programming language and
provides a rich set of pre-built widgets that ensure high performance and a visually appealing user
interface.
Purpose
The purpose of Flutter is to:
 Provide a unified framework for building cross-platform applications.
 Enable faster development with a single codebase.
 Offer high performance and native-like experiences for apps across different platforms.
 Empower developers with customizable widgets and tools to create visually stunning
designs.
Features
Key features of the Flutter framework include:
1. Cross-platform Development: Build apps for Android, iOS, web, and desktop from a
single codebase.
2. Fast Development (Hot Reload): With Hot Reload, developers can see changes in the
app's code instantly, improving productivity.
3. Rich Widget Library: Flutter provides a large collection of customizable widgets for
creating expressive and flexible UI designs.
4. High Performance: Flutter apps are compiled to native ARM code, ensuring smooth
performance on devices.
5. Single Codebase: Write one codebase to deploy apps on multiple platforms, reducing
development time and costs.

54
6. Dart Programming Language: Flutter uses Dart, which is simple to learn, supports
object-oriented programming, and is optimized for UI development.
7. Open Source: Flutter is free to use, supported by Google, and maintained by an active
community of developers worldwide.
8. Customizable UI: Flutter widgets are fully customizable, allowing developers to create
unique, branded designs.
9. Wide Range of Plugins: Flutter supports a vast library of plugins for additional
functionality, such as accessing device features like cameras, sensors, and location
services.
10. Support for Web and Desktop: Flutter has expanded to support web and desktop apps,
making it a versatile framework for multiple platforms.
2.1.2. Widgets
Widgets are the core building blocks of a Flutter application, representing everything in the user
interface (UI), such as buttons, text, layouts, and animations. They are declarative, meaning the UI
is defined in terms of the widget tree structure.
Definition
A widget is a Flutter UI component that describes how a part of the app's interface should appear,
interact, and behave. Widgets can be stateless or stateful, depending on whether they change over
time or not.
Types of Widgets
Widgets in Flutter are broadly categorized into two main types:
1. Stateless Widgets: Widgets that do not have any mutable state. They render the UI once and
do not change unless triggered by an external factor like user input.
Examples:
 Text: Displays static text.
 Container: A box model widget used for layout and styling.
 Icon: Displays a static icon.
Stateless Widgets is Suitable for static content like labels, buttons, or non-interactive UI elements.

55
2. Stateful Widgets: Widgets that maintain mutable state and can rebuild themselves
dynamically when their state changes.
Examples:
 Checkbox: A clickable box that toggles between checked and unchecked
states.
 Slider: Allows the user to select a value from a range.
 TextField: Accepts user input.
Stateful Widgets is Suitable for dynamic content like forms, animations, or interactive UI
elements.
Widget Lifecycle
The lifecycle of a widget in Flutter describes the different stages a widget goes through from its
creation to its destruction. This applies primarily to stateful widgets, as stateless widgets do not
maintain state and have a simpler lifecycle.
1. Initialization Phase -> createState()
 Invoked when the widget is created.
 Returns a State object that holds the widget's mutable state.
2. Mounting Phase -> initState()
 Called when the State object is created and the widget is added to the widget tree.
 Used for one-time initialization, such as setting up controllers or listeners.
3. Update Phase:
 didChangeDependencies()
 Called when the widget's dependencies (e.g., inherited widgets) change.
 build()
 Called whenever the widget needs to rebuild its UI.
 Returns the widget tree for the current state.
4. State Changes -> setState()
 Called when the state of the widget changes, triggering a rebuild of the widget tree.

56
5. Unmounting Phase
 deactivate()
 Invoked when the widget is removed from the widget tree temporarily (e.g., during
navigation).
 dispose()
 Called when the widget is permanently removed from the widget tree.
 Used for cleanup, such as disposing of controllers, streams, or other resources.

Summary Table: Widget Lifecycle Methods


Stage Method Purpose
Widget Creation createState() Creates the mutable state object.
Initialization initState() Initializes resources for the widget.
Dependency didChangeDependencies() Responds to changes in inherited
Change widgets.
Build UI build() Constructs and returns the widget tree.
Update UI setState() Updates the state and triggers a
rebuild.
Temporary deactivate() Handles temporary removal from the
Removal widget tree.
Cleanup dispose() Cleans up resources when the widget
is destroyed.

2.1.3. State Management


State management is a critical concept in Flutter, as it determines how the state (data) of an
application is handled and synchronized with the user interface (UI). It ensures that changes in
data are reflected in the UI and vice versa.
Definition
State management in Flutter refers to the approach of managing the state of widgets and sharing
it across different parts of the app. A state is the information that can change over time, such as
user input, app data, or visual changes in the UI.
For example:
 A toggle button’s on/off state.
 A list of items displayed in a shopping cart.
 The authentication status of a user.

57
Packages for State Management
Flutter provides various packages to simplify state management. Here are some popular ones:
1. Provider: A recommended package by Google for state management. It uses the
InheritedWidget under the hood and provides a simple and efficient way to share and
update state across widgets.
Use Case: Lightweight applications or when you need to share state across the widget tree.
Installation: Add provider: ^X.X.X to your pubspec.yaml.

2. Riverpod: A modern and robust alternative to Provider with improved developer


experience and performance. It avoids common pitfalls like unintentional rebuilds.
Use Case: Applications that need scalability and strong type safety.
Installation: Add flutter_riverpod: ^X.X.X to your pubspec.yaml.

3. Bloc (Business Logic Component): A highly structured package for managing state using
events and streams. It enforces a clean separation between UI and business logic.
Use Case: Complex applications requiring predictable state changes.
Installation: Add flutter_bloc: ^X.X.X to your pubspec.yaml.

4. Redux: A state container inspired by Redux in JavaScript. It uses a single store for
application-wide state, making it easier to manage and debug.
Use Case: Large-scale applications with highly predictable state transitions.
Installation: Add redux: ^X.X.X to your pubspec.yaml.

5. GetX: A lightweight and fast state management solution with built-in navigation,
dependency injection, and more.
Use Case: Simple to moderately complex applications that need a quick setup.
Installation: Add get: ^X.X.X to your pubspec.yaml.
Libraries for State Management
Beyond packages, Flutter developers often rely on libraries or frameworks that enhance state
management capabilities. Here are some commonly used libraries:

58
1. InheritedWidget (Built-in Library): The core mechanism used for passing data down the
widget tree. Custom implementations can be built upon it.
Use Case: When a lightweight, custom solution is sufficient.

2. Cubit: A simplified version of Bloc that manages state using direct methods without the
complexity of events.
Use Case: Applications requiring a lighter alternative to Bloc.

3. MobX: A library based on observables and reactions for state management. It makes it
easier to create reactive and dynamic UIs.
Use Case: Applications needing a reactive programming style.

4. ScopedModel: An older state management library used for sharing state between widgets.
It is now largely replaced by Provider.
Use Case: Small applications where simplicity is a priority.

5. Riverpod (Library Alternative): A more powerful, compile-safe alternative to Provider


that eliminates limitations and improves the developer experience.
Use Case: Modern Flutter applications requiring performance and flexibility.
Comparison of Popular State Management Options
Option Complexity Scalability Learning Recommended For
Curve
Provider Low Medium Easy Small to medium-sized apps.
Riverpod Low High Moderate Apps requiring scalability and safety.
Bloc High High Steep Large, complex apps with predictable
state.
GetX Low Medium Easy Quick and simple apps.
Redux High High Steep Apps needing a single state store.

2.1.4. Environment Set Up


Setting up the Flutter development environment involves installing the Flutter SDK, an Integrated
Development Environment (IDE), and configuring necessary tools to build and test Flutter
applications.
Installation of Flutter SDK
The Flutter Software Development Kit (SDK) is the foundation for developing Flutter applications.

59
1. Download Flutter SDK
Visit the Flutter website and download the SDK for your operating system (Windows, macOS, or
Linux).
2. Extract the SDK
Extract the downloaded .zip file (Windows/macOS) or .tar.xz file (Linux) to a suitable
location on your system.
3. Add Flutter to PATH
Windows:
 Go to System Properties → Environment Variables.
 Add the bin directory inside the Flutter folder to the PATH variable (e.g.,
C:\flutter\bin).
macOS/Linux:
 Open a terminal and add the Flutter path to .bashrc, .zshrc, or
.bash_profile:
export PATH="$PATH:/path-to-flutter-sdk/bin"
4. Verify Installation
 Open a terminal or command prompt and run:
flutter doctor
 This command checks for required dependencies and tools and provides instructions to
resolve any issues.
Installation of IDE
Flutter supports various IDEs like Android Studio, Visual Studio Code, and Xcode (macOS
only).
1. Android Studio
 Download and Install:
 Download Android Studio from the official website.
 Install Android Studio and launch it.
 Install Flutter and Dart Plugins:
 Go to File → Settings → Plugins.
 Search for "Flutter" and click Install (this automatically installs the Dart plugin as
well).

60
 Set Up Android SDK:
 Open Android Studio and navigate to SDK Manager under File → Settings →
Appearance & Behavior → System Settings → Android SDK.
 Install the necessary Android SDK versions and tools.
2. Visual Studio Code (Optional)
 Download and Install:
 Download VS Code from the official website.
 Install and launch the application.
 Install Flutter Extension:
 Open the Extensions view (Ctrl + Shift + X), search for "Flutter," and
install the Flutter extension (this also installs Dart).
3. Xcode (macOS Only)
 Download and Install:
 Download Xcode from the [Mac App Store] or Apple Developer Website.
 Command-Line Tools:
 Install Xcode command-line tools by running:
xcode-select --install
 Set Up iOS Simulator:
 Launch Xcode and open the Preferences menu.
 Go to the Components tab and download the required iOS simulators.
Configuration of Development Environment
1. Set Up Device Emulators
 Android Emulator: Open Android Studio, go to AVD Manager, and create a new
Android Virtual Device (AVD).
 iOS Simulator (macOS): Launch the iOS Simulator from Xcode or by running:
open -a Simulator
2. Run “flutter doctor”: This command verifies that all required tools, plugins, and
dependencies are installed. Follow the output instructions to fix any missing components.
3. Connect a Physical Device
Android:
 Enable USB Debugging in Developer Options on the device.

61
 Connect the device via USB, and run:
flutter devices
iOS:
 Connect the iPhone via USB.
 Set up signing in Xcode for the Flutter project.
4. Test the Setup
 Create a new Flutter project:
flutter create my_app
cd my_app
flutter run
 This runs the sample Flutter app on the connected emulator or physical device.

2.1.5. Creating a New Flutter Project


Creating a new Flutter project is a straightforward process that involves using the Flutter
command-line tool or an IDE like Android Studio or Visual Studio Code. Here's how you can set
up your first Flutter project:

Step 1: Using the Command Line


You can create a Flutter project directly from the terminal or command prompt.
1. Open Terminal/Command Prompt
 Ensure Flutter is installed and added to your system’s PATH.
2. Run the Create Command
 Use the following command to create a new project:
flutter create my_project
Replace my_project with the desired project name.

3. Navigate to the Project Directory


cd my_project
4. Run the Project
 To test the project, use:
flutter run
 This command runs the default Flutter app on a connected device or emulator.

62
Step 2: Using Android Studio
1. Open Android Studio
 Launch Android Studio and ensure the Flutter and Dart plugins are installed.
2. Create a New Project
 Go to File → New → New Flutter Project.
 Select Flutter Application and click Next.
3. Configure the Project
 Enter the project name, location, and a description.
 Specify the Flutter SDK path (if not already configured).
 Click Finish to create the project.
4. Run the Project
 Click the green Run button or press Shift + F10 to run the default Flutter app.
Step 3: Using Visual Studio Code
1. Open VS Code
 Launch VS Code and ensure the Flutter extension is installed.
2. Create a New Project
 Open the Command Palette (Ctrl + Shift + P or Cmd + Shift + P on
macOS).
 Search for and select Flutter: New Project.
 Enter the project name and select the folder to save the project.
3. Open the Project
 The project opens automatically after creation.
4. Run the Project
 Press F5 or select Run → Start Debugging to run the app.
Default Project Structure
When a new Flutter project is created, the following structure is generated:
 android/: Native Android code for the app.
 ios/: Native iOS code for the app.
 lib/: Contains Dart code, including the main.dart file where the app logic resides.
 test/: For writing unit tests.

63
 pubspec.yaml: Configuration file for dependencies and assets.
Testing the Project
1. Start an Emulator
 Launch an Android Emulator or iOS Simulator.
 Alternatively, connect a physical device.
2. Run the App
 Use flutter run or the IDE’s Run/Debug feature.
 The default app shows a counter incrementing as you press the + button.
2.2 Applying Flutter’s Widget System
Flutter’s widget system is the foundation of how Flutter apps are built. Everything in Flutter is a
widget whether it’s a button, a piece of text, or a layout. Widgets are used to define the app's UI
and behavior.
2.2.1 Stateful and Stateless Widgets
Widgets in Flutter are classified into two types:
 Stateless Widget: A widget that doesn’t change its state after being created. It is static and
only updates when rebuilt.
 Stateful Widget: A widget that can change its state over time (e.g., based on user
interactions). It dynamically updates the UI when its state changes.
Example:
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget { // Stateless Widget


@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Stateless & Stateful
Example")),
body: Center(child: CounterWidget()), // Using
Stateful Widget
),
);

64
}
}

class CounterWidget extends StatefulWidget { // Stateful


Widget
@override
_CounterWidgetState createState() =>
_CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {


int count = 0;

@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Count: $count", style: TextStyle(fontSize:
24)),
ElevatedButton(
onPressed: () {
setState(() {
count++; // Update the state
});
},
child: Text("Increment"),
),
],
);
}
}

2.2.2 Widget Tree and Hierarchy


The widget tree represents how widgets are nested and organized hierarchically in a Flutter app.
Every widget is a node in this tree, and widgets are either parents or children of other widgets.
Parent-Child Relationships in Flutter
Widgets are organized in a parent-child relationship. The parent widget determines the properties,
layout, and constraints of its child widgets.
Example:
Scaffold(
appBar: AppBar(title: Text("Parent-Child Example")), // Parent

65
body: Center( // Parent
child: Text("I am a child widget"), // Child
),
);

Widget Composition
Flutter emphasizes composition over inheritance, meaning complex UIs are built by combining
smaller widgets.
Example: A Scaffold widget might contain:
 An AppBar at the top.
 A Body in the center with a Column of widgets.
 A FloatingActionButton at the bottom.
Scaffold(
appBar: AppBar(title: Text("Widget Composition Example")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Hello"),
ElevatedButton(onPressed: () {}, child: Text("Click Me")),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {},
child: Icon(Icons.add)),
);

Row and Column


These widgets are used to arrange other widgets in a horizontal (Row) or vertical (Column)
direction.
Example: Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
],
);
Example: Column

66
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
],
);
Container
A Container widget is used for adding padding, margins, borders, or background colors. It is
like a box that can wrap around another widget.
Example:
Container(
padding: EdgeInsets.all(16),
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
child: Text("This is inside a container", style:
TextStyle(color: Colors.white)),
);

Expanded
The Expanded widget is used within a Row or Column to divide available space among child
widgets.
Example:
Row(
children: [
Expanded(
child: Container(color: Colors.red, height: 50),
),
Expanded(
child: Container(color: Colors.green, height: 50),
),
Expanded(
child: Container(color: Colors.blue, height: 50),
),
],
);
In this example, the three containers share equal space within the row.

67
Stack
The Stack widget allows widgets to be placed on top of each other, like layers.
Example:
Stack(
children: [
Container(
width: 200,
height: 200,
color: Colors.blue, // Bottom layer
),
Positioned(
top: 50,
left: 50,
child: Container(
width: 100,
height: 100,
color: Colors.red, // Top layer
),
),
],
);

Summary
Flutter’s widget system provides tools to build UIs efficiently:
 Stateless and Stateful widgets handle static and dynamic parts of the app.
 The widget tree shows the hierarchy of widgets, with parent-child relationships.
 Layout widgets like Row, Column, Container, Expanded, and Stack help create structured
and flexible UIs.
Each widget plays a specific role in making your app interactive, visually appealing, and easy to
maintain.
2.2.3 Core Widgets
Flutter provides a wide range of core widgets to create and manage the user interface. These
widgets are essential for building both basic and complex Flutter apps. Below is a detailed
explanation of the commonly used core widgets, along with examples.
Text and Styling

68
The Text widget is used to display text in Flutter apps. You can style text using the TextStyle
class to customize properties like font size, color, weight, and more.
Example: Basic Text
Text("Hello, Flutter!");
Example: Styled Text
Text(
"Styled Text Example",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
fontStyle: FontStyle.italic,
),
);

Image and Asset


The Image widget is used to display images in Flutter apps. Images can be loaded from:
 Assets (local images bundled with your app)
 Network (images from the internet)
 File (images stored on the device)
Example: Asset Image
To display an image from assets:
1. Add the image to the assets folder.
2. Declare it in the pubspec.yaml file:
flutter:
assets:
- assets/my_image.png
3. Use the Image.asset widget:
Image.asset('assets/my_image.png');
Example: Network Image
Image.network('https://flutter.dev/images/flutter-logo-
sharing.png');

Interactive Widgets (Buttons, Gestures)

69
Flutter provides interactive widgets like buttons and gesture detectors to handle user input.
Buttons
1. ElevatedButton: A button with elevation (raised).
ElevatedButton(
onPressed: () {
print("Elevated Button Clicked");
},
child: Text("Click Me"),
);
2. TextButton: A simple button with no background.
TextButton(
onPressed: () {
print("Text Button Clicked");
},
child: Text("Click Me"),
);
3. IconButton: A button with an icon.
IconButton(
onPressed: () {
print("Icon Button Clicked");
},
icon: Icon(Icons.add),
);

4. GestureDetector
The GestureDetector widget allows you to detect gestures like taps, swipes, and long presses.
Example: Gesture Detector
GestureDetector(
onTap: () {
print("Widget Tapped");
},
child: Container(
color: Colors.blue,
width: 100,
height: 50,
child: Center(child: Text("Tap Me")),
),
);

Layout Widgets

70
Layout widgets are used to arrange and structure other widgets on the screen. Common layout
widgets include:
1. Center: Aligns its child widget to the center of the screen.
Center(
child: Text("Centered Text"),
);

2. Padding: Adds space around its child widget.


Padding(
padding: EdgeInsets.all(16),
child: Text("Padded Text"),
);
3. Row: Arranges widgets horizontally.
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
],
);
4. Column: Arranges widgets vertically.
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
],
);
5. Stack: Places widgets on top of each other.
Stack(
children: [
Container(
color: Colors.green,
height: 100,
width: 100,
),
Positioned(
top: 20,
left: 20,

71
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
],
);
6. Container: A versatile widget used for layouts, styling, and wrapping content.
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text("This is inside a container"),
);

Summary
Core widgets in Flutter provide a robust set of tools for building responsive and interactive UIs:
 Text: Display and style text content.
 Image: Render images from assets or the web.
 Interactive widgets: Handle user input with buttons and gestures.
 Layout widgets: Structure the app's UI with tools like Row, Column, and Container.
2.3. Implementation of State Management
State management is essential in Flutter to manage and share the app's state (data and logic) across
widgets efficiently. Below, we’ll explore the implementation of state management using two
popular packages: GetX and Provider.
2.3.1 Using Packages
Flutter provides various state management packages to simplify state handling. Two widely used
packages are GetX and Provider. Both have different approaches and are suited for different
scenarios.
GetX Package
GetX is a powerful and lightweight state management solution that doesn’t require context to
update the UI. It simplifies state management, dependency injection, and route management.
Key Features of GetX:

72
 Easy state management with reactive variables.
 No need to write boilerplate code.
 Built-in dependency injection.
 Built-in navigation and routing.
Steps to Use GetX:
1. Add the GetX dependency to your pubspec.yaml:
dependencies:
get: ^4.6.5
2. Create a Controller to manage the state:
import 'package:get/get.dart';

class CounterController extends GetxController {


var count = 0.obs; // Observable variable

void increment() {
count++;
}
}
3. Bind the Controller to the UI:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'counter_controller.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: CounterPage(),
);
}
}

class CounterPage extends StatelessWidget {


final CounterController controller =
Get.put(CounterController());

@override
Widget build(BuildContext context) {

73
return Scaffold(
appBar: AppBar(title: Text("GetX Example")),
body: Center(
child: Obx(() => Text("Count: ${controller.count}",
style: TextStyle(fontSize: 24))),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
child: Icon(Icons.add),
),
);
}
}

Provider Package
Provider is a state management solution that integrates seamlessly with Flutter's widget tree. It
uses the InheritedWidget internally to efficiently pass data to child widgets.
Key Features of Provider:
 Well-suited for large apps with complex state sharing.
 Uses context for updating widgets.
 Recommended by Flutter’s documentation.
Steps to Use Provider:
1. Add the Provider dependency to your pubspec.yaml:
dependencies:
provider: ^6.0.5
2. Create a ChangeNotifier class to manage the state:
import 'package:flutter/foundation.dart';

class CounterProvider extends ChangeNotifier {


int count = 0;

void increment() {
count++;
notifyListeners(); // Notify listeners to rebuild the UI
}
}
3. Wrap your app with a ChangeNotifierProvider to make the state accessible:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

74
import 'counter_provider.dart';

void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) =>
CounterProvider()),
],
child: MyApp(),
),
);
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}
4. Access the state in your UI using Provider:
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider Example")),
body: Center(
child: Consumer<CounterProvider>(
builder: (context, provider, child) {
return Text("Count: ${provider.count}",
style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () =>
context.read<CounterProvider>().increment(),
child: Icon(Icons.add),
),
);
}
}

75
Comparison of GetX and Provider
Feature GetX Provider
Ease of Use Minimal boilerplate, easy API More boilerplate required
Performance Efficient, uses reactive vars Efficient with proper setup
Learning Curve Easier for beginners Steeper for beginners
Context Dependency No context needed Requires BuildContext

Example Scenarios
1. GetX: Best for simple, reactive apps where quick state updates are needed (e.g., a counter
app, form validation).
2. Provider: Ideal for apps with complex state requirements, such as e-commerce apps, where
multiple widgets share interdependent data.
2.3.2 Using Pattern
State management in Flutter can also be implemented using patterns such as Redux and BLoC
(Business Logic Component). These patterns are ideal for managing complex state and logic in
large-scale applications. Below is a detailed explanation of these patterns with examples.
Redux Pattern
Redux is a predictable state container based on the principles of a unidirectional data flow. It is
popular in React and Flutter applications.
Key Concepts of Redux:
 Store: Holds the application’s state.
 Actions: Events describing what you want to do (e.g., increment a counter).
 Reducers: Functions that specify how the state changes in response to an action.
 Middleware: Logic that intercepts actions for additional processing.
Steps to Use Redux in Flutter:
1. Add the Redux dependency to your pubspec.yaml:
dependencies:
flutter_redux: ^0.10.0
redux: ^5.0.0
2. Create an Action:
class IncrementAction {}

76
3. Create a Reducer:
int counterReducer(int state, dynamic action) {
if (action is IncrementAction) {
return state + 1;
}
return state;
}
4. Create a Store:
import 'package:redux/redux.dart';

final store = Store<int>(


counterReducer,
initialState: 0,
);

5. Use StoreProvider and StoreConnector in your app:


import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return StoreProvider<int>(
store: store,
child: MaterialApp(
home: CounterPage(),
),
);
}
}

class CounterPage extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Redux Example")),
body: Center(
child: StoreConnector<int, int>(
converter: (store) => store.state,
builder: (context, count) {

77
return Text("Count: $count", style:
TextStyle(fontSize: 24));
},
),
),
floatingActionButton: StoreConnector<int,
VoidCallback>(
converter: (store) {
return () => store.dispatch(IncrementAction());
},
builder: (context, callback) {
return FloatingActionButton(
onPressed: callback,
child: Icon(Icons.add),
);
},
),
);
}
}

Business Logic Component (BLoC) Pattern


BLoC (Business Logic Component) separates the business logic from the UI, making the app more
testable and reusable. It uses Streams to manage state and events, which ensures a reactive
approach to state management.
Key Concepts of BLoC:
 Event: Represents user actions (e.g., button click).
 State: Represents the app's UI state.
 Bloc: Handles events and emits new states using streams.
 StreamBuilder: Updates the UI based on the state emitted by the Bloc.
Steps to Use BLoC:
1. Add the BLoC dependency to your pubspec.yaml:
dependencies:
flutter_bloc: ^8.1.2
bloc: ^8.1.2
2. Create an Event:
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

78
3. Create a State:
class CounterState {
final int counter;

CounterState(this.counter);
}
4. Create the Bloc:
import 'package:bloc/bloc.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState>


{
CounterBloc() : super(CounterState(0)) {
on<IncrementEvent>((event, emit) {
emit(CounterState(state.counter + 1));
});
}
}
5. Use the Bloc in the app:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => CounterBloc(),
child: CounterPage(),
),
);
}
}

class CounterPage extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("BLoC Example")),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(

79
builder: (context, state) {
return Text("Count: ${state.counter}", style:
TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {

context.read<CounterBloc>().add(IncrementEvent());
},
child: Icon(Icons.add),
),
);
}
}

Comparison of Redux and BLoC


Feature Redux BLoC
Learning Curve Moderate to High High
Complexity More boilerplate code Uses Streams, more logic separation
Use Case Large, scalable apps Reactive apps with heavy logic
State Updates Unidirectional flow Stream-based reactive updates

Example Scenarios
1. Redux: Suitable for apps requiring a centralized state, such as e-commerce apps with
multiple shared states (e.g., user profile, cart, etc.).
2. BLoC: Best for apps requiring reactive behavior, such as chat applications, live data feeds,
or dashboards.
2.3.3. Using setState() Method
The setState() method is the simplest way to manage state in Flutter. It is ideal for small-
scale applications or when the state only needs to be managed within a single widget.
How setState() Works:
1. Stateful Widgets are used to manage mutable state.
2. When setState() is called, the framework re-renders the widget with updated data.
Example: Counter App Using setState()

80
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}

class CounterPage extends StatefulWidget {


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

class _CounterPageState extends State<CounterPage> {


int counter = 0;

void incrementCounter() {
setState(() {
counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("setState Example")),
body: Center(
child: Text(
"Count: $counter",
style: TextStyle(fontSize: 24),
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
child: Icon(Icons.add),
),
);
}
}

81
2.3.4. Using the Riverpod Solution
Riverpod is a modern and efficient state management solution in Flutter. It provides a simpler and
more flexible approach compared to other state management libraries.
Key Features of Riverpod:
 Compile-time safety (no need for BuildContext to access state).
 Lazy initialization of providers.
 Enhanced testability.
Steps to Use Riverpod:
1. Add Riverpod to pubspec.yaml:
dependencies:
flutter_riverpod: ^2.0.0
2. Create a Provider:
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateProvider<int>((ref) => 0);


3. Use the provider in the app:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}

class CounterPage extends ConsumerWidget {


@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);

return Scaffold(
appBar: AppBar(title: Text("Riverpod Example")),
body: Center(

82
child: Text("Count: $counter", style:
TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: () =>
ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}

2.3.5. Using Navigation and Routing


Navigation and routing in Flutter allow users to move between screens. Below are common ways
to implement navigation.
Navigator
The Navigator widget manages a stack of routes (pages). You can use methods like push and
pop to navigate between screens.
Example: Navigator
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Home")),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
DetailsPage()),
);
},
child: Text("Go to Details"),
),
),
);
}
}

class DetailsPage extends StatelessWidget {


@override

83
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Details")),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Go Back"),
),
),
);
}
}

Route
Routes define named navigation paths that make it easier to manage navigation across multiple
screens.
Example: Named Routes
void main() {
runApp(MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/details': (context) => DetailsPage(),
},
));
}
Navigate using:
Navigator.pushNamed(context, '/details');

BottomNavigationBar
A BottomNavigationBar is used for navigation between different tabs at the bottom of the
screen.
Example: BottomNavigationBar
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

84
class _MyAppState extends State<MyApp> {
int _currentIndex = 0;
final List<Widget> _pages = [HomePage(), ProfilePage(),
SettingsPage()];

@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label:
"Home"),
BottomNavigationBarItem(icon: Icon(Icons.person),
label: "Profile"),
BottomNavigationBarItem(icon: Icon(Icons.settings),
label: "Settings"),
],
),
);
}
}

TabBar and TabBarView


The TabBar and TabBarView widgets are used to create tabs within a single screen.
Example: TabBar and TabBarView
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("TabBar Example"),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.home), text: "Home"),

85
Tab(icon: Icon(Icons.person), text: "Profile"),
Tab(icon: Icon(Icons.settings), text:
"Settings"),
],
),
),
body: TabBarView(
children: [
Center(child: Text("Home Tab")),
Center(child: Text("Profile Tab")),
Center(child: Text("Settings Tab")),
],
),
),
),
);
}
}

2.4. Using Pre-Designed Widgets


Flutter provides a wide range of pre-designed widgets to create beautiful, responsive, and platform-
specific user interfaces. These widgets save time and effort by providing reusable components.
2.4.1. Material Design Widgets
Material Design is Google's design language for creating visually appealing and interactive UIs.
Flutter includes built-in Material Design widgets to build apps with consistent design across
Android and iOS.
Key Examples of Material Design Widgets:
 Scaffold: Provides a basic layout structure with AppBar, Drawer, BottomNavigationBar,
etc.
 AppBar: Displays the app's title and actions.
 FloatingActionButton: A circular button for primary actions.
 Card: A widget for displaying content inside a card.
 SnackBar: Used to show temporary messages at the bottom of the screen.
Example: Material Design App
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(home: MaterialExample()));

86
}

class MaterialExample extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Material Design Example')),
body: Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('This is a Material Card!'),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
);
}
}

2.4.2. Cupertino Widgets


Cupertino widgets are styled according to iOS design guidelines and are used to create apps that
feel native on iOS devices.
Key Examples of Cupertino Widgets:
 CupertinoApp: The equivalent of MaterialApp for iOS styling.
 CupertinoNavigationBar: A navigation bar for iOS apps.
 CupertinoButton: A button styled for iOS.
 CupertinoPicker: A picker widget styled like iOS.
 CupertinoActivityIndicator: A spinning indicator for iOS.
Example: Cupertino App
import 'package:flutter/cupertino.dart';

void main() {
runApp(CupertinoApp(home: CupertinoExample()));
}

class CupertinoExample extends StatelessWidget {

87
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Cupertino Design Example'),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: Text('Click Me'),
onPressed: () {},
),
),
);
}
}

2.4.3. Flutter Icons


Icons are a vital part of user interfaces, and Flutter provides a wide range of built-in icons through
its Icons class. Additionally, custom icon sets can be used.
How to Use Icons in Flutter:
 Use the Icon widget with predefined icons from the Icons class.
 Customize icons with colors, sizes, and other properties.
Example: Using Icons
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(home: IconExample()));
}

class IconExample extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Icons Example')),
body: Center(
child: Icon(
Icons.favorite,
color: Colors.red,
size: 50.0,
),
),

88
);
}
}

Custom Icon Sets:


You can use third-party icon libraries like Font Awesome or Material Icons Extended by
including them in your pubspec.yaml file.
2.4.4. Third-Party Packages
Flutter has an extensive ecosystem of third-party packages that provide additional pre-designed
widgets. These packages help developers extend their app's functionality without starting from
scratch.
Popular Third-Party Widget Packages:
1. Fluttertoast: Displays toast notifications.
2. CarouselSlider: Creates image sliders.
3. GoogleFonts: Adds custom fonts.
4. FlutterSpinkit: Provides loading animations.
Example: Using Fluttertoast
1. Add the dependency to pubspec.yaml:
dependencies:
fluttertoast: ^8.2.0
2. Use the package in your app:
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';

void main() {
runApp(MaterialApp(home: ToastExample()));
}

class ToastExample extends StatelessWidget {


void showToast() {
Fluttertoast.showToast(
msg: "This is a toast message!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
}

@override

89
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Toast Example')),
body: Center(
child: ElevatedButton(
onPressed: showToast,
child: Text('Show Toast'),
),
),
);
}
}
Finding Third-Party Packages:
 Visit pub.dev to explore a wide range of Flutter packages for widgets and utilities.

90
Learning outcome 3: Integrate Backend Functionality.
3.1. Integration of External Services
Modern applications often interact with external services, such as APIs, to fetch and store data.
Flutter provides several ways to make HTTP requests, with the http package being one of the
most commonly used.
3.1.1. Description of HTTP Requests
HTTP Requests in Flutter
HTTP (Hypertext Transfer Protocol) is used for communication between a client (Flutter app) and
a server (backend). The most common HTTP methods include:
1. GET – Fetch data from a server.
2. POST – Send new data to the server.
3. PUT – Update an entire resource on the server.
4. DELETE – Remove a resource from the server.
5. PATCH – Partially update a resource.
6. UPDATE – Typically handled using PUT or PATCH.
1. GET Request (Fetching Data)
Example Codes: Fetching a list of users from an API.
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> fetchUsers() async {


final response = await
http.get(Uri.parse('https://jsonplaceholder.typicode.com/users')
);

if (response.statusCode == 200) {
List users = jsonDecode(response.body);
print(users);
} else {
print('Failed to load users');
}
}

void main() {
fetchUsers();
}

91
Explanation of the codes above:
This Dart code makes an HTTP GET request to fetch user data from an API
(https://jsonplaceholder.typicode.com/users). It then decodes the response
and prints the user data.
i. Importing Required Packages
import 'package:http/http.dart' as http;
import 'dart:convert';
 package:http/http.dart: This is the HTTP package that allows us to make API

calls in Dart and Flutter.


 dart:convert: Provides functions to convert JSON data into Dart objects
(jsonDecode() is used later in the code).
ii. Defining the fetchUsers() Function
Future<void> fetchUsers() async {
 Future<void>: Since HTTP requests take time, we use Future to handle
asynchronous operations.
 async: This tells Dart that the function contains asynchronous operations (like an API
request).
iii. Making an HTTP Request
final response = await
http.get(Uri.parse('https://jsonplaceholder.typicode.com/users')
);
 http.get(...): Sends an HTTP GET request to the API.

 Uri.parse(...): Converts the URL into a valid URI format. This is required because
Dart’s http package expects a Uri instead of a plain string.
 await: Waits for the API response before moving to the next line. Since
http.get(...) is an asynchronous operation, await tells the program to wait until
the request is completed before moving to the next line.
 https://jsonplaceholder.typicode.com/users: This is a free, fake API
endpoint provided by JSONPlaceholder, which is a public testing API. It allows
developers to simulate making API requests without setting up a backend.

92
iv. Handling the Response
if (response.statusCode == 200) {
 response.statusCode: Checks the HTTP status code.

 200: Success (API returned data correctly).


v. Decoding and Printing the Data
List users = jsonDecode(response.body);
print(users);
 jsonDecode(response.body): Converts the JSON response into a Dart List of

users.
 print(users): Displays the list of users in the console.
Example Output:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "[email protected]"
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "[email protected]"
}
]
6. Handling Errors
} else {
print('Failed to load users');
}
 If the API fails (e.g., network error, wrong URL), it prints 'Failed to load users'.

7. Calling fetchUsers() in main()


void main() {
fetchUsers();
}
 main() is the entry point of the Dart program.

 It calls fetchUsers() to fetch and display user data.

93
2. POST Request (Sending Data to the Server)
Example Codes: Creating a new user.
Future<void> createUser() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"name": "John Doe", "email":
"[email protected]"}),
);

if (response.statusCode == 201) {
print("User created successfully!");
} else {
print("Failed to create user");
}
}

void main() {
createUser();
}
 Sends a POST request to create a user.

 Includes headers and body with JSON data.


 If successful (201 status code), prints success; otherwise, an error.

3. PUT Request (Updating an Entire Resource)


Example Codes: Updating a user's information completely.
Future<void> updateUser() async {
final response = await http.put(
Uri.parse('https://jsonplaceholder.typicode.com/users/1'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"name": "Updated Name", "email":
"[email protected]"}),
);

if (response.statusCode == 200) {
print("User updated successfully!");
} else {
print("Failed to update user");
}
}

94
void main() {
updateUser();
}
 PUT replaces the entire resource (user) with new data.

4. DELETE Request (Removing Data)


Example Codes: Deleting a user.
Future<void> deleteUser() async {
final response = await http.delete(
Uri.parse('https://jsonplaceholder.typicode.com/users/1'),
);

if (response.statusCode == 200) {
print("User deleted successfully!");
} else {
print("Failed to delete user");
}
}

void main() {
deleteUser();
}
 Sends a DELETE request to remove a user.

5. PATCH Request (Partially Updating Data)


Example Codes: Changing only the user's email.
Future<void> updateUserEmail() async {
final response = await http.patch(
Uri.parse('https://jsonplaceholder.typicode.com/users/1'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"email": "[email protected]"}),
);

if (response.statusCode == 200) {
print("User email updated successfully!");
} else {
print("Failed to update email");
}
}

void main() {
updateUserEmail();
}
 PATCH updates only specific fields rather than replacing the entire resource.

95
6. UPDATE Request
Flutter does not have a specific UPDATE method, but generally, PUT or PATCH is used.

 Use PUT when replacing the entire object.


 Use PATCH when modifying only some fields.
3.1.2. Adding Dependencies
Dependencies are external packages or libraries that add extra functionality to a Flutter project.
Example: Adding the http package for API calls
1. Open pubspec.yaml
2. Add the dependency:
dependencies:
http: ^0.13.4
3. Run flutter pub get to install it.
📌 Example Usage: Importing the package
import 'package:http/http.dart' as http;
3.1.3. Adding Calls
API calls allow Flutter apps to fetch, send, update, or delete data from a backend server.
Example: Making an HTTP GET request
Future<void> fetchData() async {
final response = await
http.get(Uri.parse('https://jsonplaceholder.typicode.com/users')
);

if (response.statusCode == 200) {
print(response.body);
} else {
print('Failed to fetch data');
}
}

void main() {
fetchData();
}

96
3.1.4. Handling Responses
After making an API call, we need to handle different response cases (success, error, etc.).
Example: Handling success and error
Future<void> fetchData() async {
final response = await
http.get(Uri.parse('https://jsonplaceholder.typicode.com/users')
);

if (response.statusCode == 200) {
print('Success: ${response.body}');
} else if (response.statusCode == 404) {
print('Error: Resource not found');
} else {
print('Error: Something went wrong');
}
}
3.1.5. Parsing JSON Data
JSON data from APIs must be converted into Dart objects to use them efficiently.
Example: Parsing JSON into a List of Users
import 'dart:convert';

Future<void> fetchUsers() async {


final response = await
http.get(Uri.parse('https://jsonplaceholder.typicode.com/users')
);

if (response.statusCode == 200) {
List users = jsonDecode(response.body); // Convert JSON to
List
users.forEach((user) => print(user['name']));
}
}
📌 If there is a complex structure, use a model class:
class User {
final int id;
final String name;
final String email;

User({required this.id, required this.name, required


this.email});

factory User.fromJson(Map<String, dynamic> json) {

97
return User(id: json['id'], name: json['name'], email:
json['email']);
}
}

3.1.6. Performing Authentication and Authorization


Authentication verifies who the user is, while authorization checks what they can access.
Example: Login with an API
Future<void> loginUser(String email, String password) async {
final response = await http.post(
Uri.parse('https://example.com/api/login'),
body: {'email': email, 'password': password},
);

if (response.statusCode == 200) {
var data = jsonDecode(response.body);
print('Login successful, Token: ${data['token']}');
} else {
print('Login failed');
}
}
🔹 Authorization: Use the token to access protected routes
Future<void> fetchProfile(String token) async {
final response = await http.get(
Uri.parse('https://example.com/api/profile'),
headers: {'Authorization': 'Bearer $token'},
);

if (response.statusCode == 200) {
print('Profile Data: ${response.body}');
} else {
print('Unauthorized access');
}
}

3.1.7. Push Notifications (Firebase Cloud Messaging - FCM)


Push notifications allow apps to receive real-time updates even when the app is not running.
Steps to Set Up Firebase in Flutter:
1. Create a Firebase project at Firebase Console.
2. Add Firebase to your app (register package name).

98
3. Add dependencies in pubspec.yaml:
dependencies:
firebase_core: latest_version
firebase_messaging: latest_version
4. Initialize Firebase in main.dart:
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
5. Receive and handle push notifications:
import
'package:firebase_messaging/firebase_messaging.dart';

FirebaseMessaging messaging = FirebaseMessaging.instance;

Future<void> setupFCM() async {


// Get permission for notifications
NotificationSettings settings = await
messaging.requestPermission();

// Get the device FCM token


String? token = await messaging.getToken();
print("FCM Token: $token");

// Handle incoming messages


FirebaseMessaging.onMessage.listen((RemoteMessage
message) {
print("Message received:
${message.notification?.title}");
});
}

Firebase Authentication in Flutter (Login Example)


This example demonstrates Firebase Authentication in Flutter, allowing users to log in using
email and password.
1. Set Up Firebase in Flutter
i. Go to Firebase Console.
 Open your browser and go to Firebase Console.
 Sign in with your Google account.

99
ii. Create a Firebase project.
 Click on "Create a project".
 Enter a project name (e.g., my_flutter_app).
 Click Continue.
 Disable Google Analytics (optional) and click Create project.
 Wait for Firebase to set up your project, then click Continue.
iii. Add your Flutter app (package name required).
 In the Firebase dashboard, click on the Android icon (for Android setup) or iOS icon
(for iOS setup).
 Enter your package name (found in android/app/build.gradle under
applicationId).
 (Optional) Add a nickname for your app.
 Click Register app.
iv. Enable Email/Password Authentication in Firebase:
 Go to Authentication > Sign-in method.
 Enable Email/Password.
v. Download the google-services.json file (for Android) and place it in
android/app/. in the Flutter project.
vi. Configure Firebase in Your Flutter Project
 Open android/build.gradle and add Google services classpath:
dependencies {
classpath 'com.google.gms:google-
services:latest_version'
}
 Open android/app/build.gradle and apply the Google services plugin:
apply plugin: 'com.google.gms.google-services'
2. Add Dependencies
In pubspec.yaml, add:
dependencies:
firebase_core: latest_version
firebase_auth: latest_version
Run:

100
flutter pub get
3. Initialize Firebase
Modify main.dart:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

void main() async {


WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // Initialize Firebase
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginScreen(),
);
}
}
4. Create Authentication Service
Create a file auth_service.dart:
import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;

// Sign In Method
Future<User?> signIn(String email, String password) async {
try {
UserCredential userCredential = await
_auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return userCredential.user;
} catch (e) {
print("Error: $e");
return null;
}
}

// Sign Up Method

101
Future<User?> signUp(String email, String password) async {
try {
UserCredential userCredential = await
_auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return userCredential.user;
} catch (e) {
print("Error: $e");
return null;
}
}

// Sign Out Method


Future<void> signOut() async {
await _auth.signOut();
}
}

5. Create Login Screen


Create login_screen.dart:
import 'package:flutter/material.dart';
import 'auth_service.dart';

class LoginScreen extends StatefulWidget {


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

class _LoginScreenState extends State<LoginScreen> {


final TextEditingController emailController =
TextEditingController();
final TextEditingController passwordController =
TextEditingController();
final AuthService authService = AuthService();

void login() async {


final email = emailController.text;
final password = passwordController.text;

final user = await authService.signIn(email, password);


if (user != null) {
print("Login Successful: ${user.email}");
Navigator.push(

102
context,
MaterialPageRoute(builder: (context) =>
HomeScreen(userEmail: user.email!)),
);
} else {
print("Login Failed");
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Login")),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: emailController,
decoration: InputDecoration(labelText: "Email"),
),
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(labelText:
"Password"),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: login,
child: Text("Login"),
),
],
),
),
);
}
}

6. Create a Home Screen


Create home_screen.dart:
import 'package:flutter/material.dart';
import 'auth_service.dart';

class HomeScreen extends StatelessWidget {

103
final String userEmail;
final AuthService authService = AuthService();

HomeScreen({required this.userEmail});

void logout(BuildContext context) async {


await authService.signOut();
Navigator.pop(context);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Welcome")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Logged in as: $userEmail"),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => logout(context),
child: Text("Logout"),
),
],
),
),
);
}
}
7. Register a New User (Sign Up Feature - Optional)
To allow user registration, modify login_screen.dart to add a Sign Up button:
ElevatedButton(
onPressed: () async {
final email = emailController.text;
final password = passwordController.text;

final user = await authService.signUp(email, password);


if (user != null) {
print("User Registered: ${user.email}");
} else {
print("Registration Failed");
}
},
child: Text("Sign Up"),

104
),

2.1.8. Implement Security Measures in Flutter


Security is crucial in any Flutter application to protect sensitive data, ensure safe communication,
and prevent common vulnerabilities like data breaches and attacks.
1. Secure Data Storage
This involves protecting sensitive information (e.g., API keys, user credentials, tokens) stored
locally on a device.
Best Practices:
 Use Flutter Secure Storage: Encrypt and store sensitive data.
 Avoid storing sensitive data in SharedPreferences: It's not encrypted.
 Use Encrypted Databases: SQLite with SQLCipher or Hive with encryption.
Example using flutter_secure_storage:
import
'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();

// Store data securely


await storage.write(key: "token", value: "your_secret_token");

// Retrieve stored data


String? token = await storage.read(key: "token");

2. Secure Network Communication


This ensures that data transmitted between the app and backend is encrypted and protected against
interception.
Best Practices:
 Use HTTPS instead of HTTP.
 Implement SSL Pinning: Prevents man-in-the-middle attacks.
 Encrypt Requests & Responses: Use libraries like encrypt.
Example using http with HTTPS:
import 'package:http/http.dart' as http;

Future<void> fetchData() async {

105
final response = await
http.get(Uri.parse('https://secureapi.com/data'));

if (response.statusCode == 200) {
print('Data: ${response.body}');
} else {
print('Failed to load data');
}
}
Example using SSL Pinning (flutter_ssl_pinning_plugin):
import
'package:flutter_ssl_pinning_plugin/flutter_ssl_pinning_plugin.d
art';

void secureRequest() async {


await SslPinningPlugin.check(
serverURL: "secureapi.com",
allowedSHAFingerprints: ["your_ssl_certificate_fingerprint"],
timeout: 60,
);
}
3. Input Validation and Output Encoding
This prevents security risks like SQL Injection, Cross-Site Scripting (XSS), and Code
Injection.
Best Practices:
 Validate user inputs before processing.

 Sanitize inputs to remove harmful code.


 Encode outputs before displaying them.
Example of Input Validation:
String sanitizeInput(String input) {
return input.replaceAll(RegExp(r'[<>]'), ''); // Remove
harmful characters
}

void validateForm(String email) {


final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-
]+\.[a-zA-Z]{2,}$');

if (!emailRegex.hasMatch(email)) {
print('Invalid Email Address');
} else {
print('Valid Email');

106
}
}
3.2. Implement Storage Management in Flutter
Storage management in Flutter ensures that app data is efficiently stored, retrieved, and secured.
This includes maintaining data integrity, following security standards, and using different local
storage options like Shared Preferences, SQLite, and File Storage.
3.2.1. Data Integrity
Data integrity ensures that stored data remains accurate, consistent, and reliable throughout its
lifecycle.
Best Practices for Data Integrity:
 Validation before storing data (e.g., checking required fields).
 Error handling to prevent data corruption.
 Backup strategies (e.g., saving user preferences in a cloud database).
 Transactions in databases to maintain consistency.
Example: Checking Data Before Storing in SQLite
if (userInput.isNotEmpty) {
saveToDatabase(userInput); // Store only valid data
} else {
print("Invalid input, cannot save!");
}
3.2.2. Security Standards
Security is crucial when handling sensitive user data. Following best security practices protects
stored data from leaks and unauthorized access.
Security Best Practices:
 Use encryption for sensitive data.
 Follow secure authentication & authorization for access control.
 Prevent unauthorized access using role-based permissions.
 Use HTTPS for secure data transmission.
Example: Encrypting Stored Data with flutter_secure_storage
import
'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();

107
await storage.write(key: "user_password", value:
"encrypted_password_here");

3.2.3. Use Local Data Storage


Flutter offers multiple local storage options based on app needs.
1. Working with Shared Preferences
Used for storing small key-value pairs (e.g., user settings, login states).
Example: Storing & Retrieving User Preferences
import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveUserTheme(bool isDark) async {


final prefs = await SharedPreferences.getInstance();
await prefs.setBool('darkMode', isDark);
}

Future<bool> getUserTheme() async {


final prefs = await SharedPreferences.getInstance();
return prefs.getBool('darkMode') ?? false; // Default to false
}
✅ Best for: Storing small data (e.g., user preferences, login state).
❌ Not for: Large structured data (use SQLite instead).
2. Working with SQLite
SQLite is a lightweight relational database that allows structured data storage.
Example: Creating & Retrieving Data in SQLite
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

Future<Database> initDatabase() async {


return openDatabase(
join(await getDatabasesPath(), 'users.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT,
age INTEGER)");
},
version: 1,
);
}

Future<void> insertUser(String name, int age) async {


final db = await initDatabase();

108
await db.insert("users", {'name': name, 'age': age});
}
✅ Best for: Storing structured data like user profiles, orders, messages.
❌ Not for: Unstructured data like images or logs (use File Storage).
3. Working with File Storage
File Storage allows saving large unstructured data like images, PDFs, logs.
Example: Storing & Retrieving a File in Local Storage
import 'dart:io';
import 'package:path_provider/path_provider.dart';

Future<void> writeToFile(String fileName, String content) async


{
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/$fileName.txt');
await file.writeAsString(content);
}

Future<String> readFromFile(String fileName) async {


final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/$fileName.txt');
return await file.readAsString();
}
✅ Best for: Storing images, PDFs, logs, and unstructured data.
❌ Not for: Querying data efficiently (use SQLite instead).
3.3. Implementation of Microapps in Flutter
Microapps in Flutter involve breaking a large application into smaller, independent modules
that can be developed, tested, and deployed separately while still working together as a complete
system.
3.3.1. Description of Modular Microapp
a. Definition
A modular microapp is an independent Flutter module that can function on its own or as part of
a larger application. Each module can have its own UI, business logic, dependencies, and
configurations while still interacting with other modules.
b. Structure of a Modular Microapp
A modular microapp project is structured into separate modules (packages), with each module
containing:
 UI components specific to that module.

109
 Services & APIs handling business logic.
 Dependencies required only for that module.
 Shared components (if needed).
Example Structure:
/main_app
├── /lib
│ ├── /modules
│ │ ├── /auth_module
│ │ │ ├── lib/
│ │ │ ├── pubspec.yaml
│ │ ├── /profile_module
│ │ │ ├── lib/
│ │ │ ├── pubspec.yaml
├── pubspec.yaml
├── android/
├── ios/
3.3.2. Applying Modular Microapps Concept to a Project
1. Structure of a Modular Project
A modular project consists of:
 Core Module: Handles shared services like authentication, networking, and database.
 Feature Modules: Each feature (e.g., Auth, Dashboard, Profile) is a separate module.
 App Module: The main app that integrates all modules.
2. Dependency Injection
To ensure modules remain independent while sharing functionality, we use Dependency
Injection (DI). DI allows injecting dependencies into modules instead of hardcoding them.
Example using get_it for DI:
import 'package:get_it/get_it.dart';

final locator = GetIt.instance;

void setupLocator() {
locator.registerLazySingleton<AuthService>(() => AuthService());
}

class AuthService {
void login() {
print("User logged in!");
}
}

110
3. Shared Components
 UI Components: Shared buttons, text fields, or app bars used across modules.
 Services: Shared authentication, API handling, or database logic.
 State Management: Shared global state using Provider, GetX, or BLoC.
3.3.3. Perform Microapp Build Configuration
1. Configure Each Module’s pubspec.yaml File
Each module needs its own pubspec.yaml file to manage dependencies.
Example (auth_module/pubspec.yaml):
name: auth_module
dependencies:
flutter:
sdk: flutter
http: ^0.13.4
Then, add the module as a dependency in the main app’s pubspec.yaml:
dependencies:
flutter:
sdk: flutter
auth_module:
path: ./modules/auth_module
2. Android Gradle Configuration
For Android, update settings.gradle to include microapps:
include ':auth_module'
project(':auth_module').projectDir = new
File(rootProject.projectDir, '../modules/auth_module')
3. iOS Build Settings
For iOS, ensure each module is registered in Podfile:
target 'Runner' do
use_frameworks!
pod 'AuthModule', :path => '../modules/auth_module'
end

3.4. Perform Error Handling in Flutter


Error handling in Flutter ensures that unexpected issues (e.g., API failures, invalid user input, or
system crashes) are managed gracefully without breaking the app experience.
3.4.1. Description

111
1. Definition of Error Handling
Error handling refers to detecting, catching, and managing errors that occur during an app’s
execution. It prevents crashes and allows for proper user notifications or recovery mechanisms.

2. Error Classes in Flutter


Errors in Flutter fall into two main categories:
 Errors (Error): Serious issues that usually indicate a problem in the code (e.g.,
AssertionError).
 Exceptions (Exception): Runtime issues that can be handled and recovered from (e.g.,
FormatException).
Example:
void main() {
try {
int result = 10 ~/ 0; // Division by zero
print(result);
} catch (e) {
print('Error occurred: $e');
}
}
Output:
Error occurred: IntegerDivisionByZeroException
3.4.2. Exception Management
1. Using try-catch Block
The try-catch block is used to catch exceptions and prevent crashes.
void fetchData() {
try {
throw Exception('Network failure');
} catch (e) {
print('Caught an exception: $e');
}
}

void main() {
fetchData();
}
Output:
Caught an exception: Exception: Network failure

112
2. Using onError with Future
Used for handling errors in asynchronous code, particularly with Futures.
Future<void> fetchData() async {
await Future.delayed(Duration(seconds: 1));
throw Exception('API error');
}

void main() {
fetchData().then((_) => print('Success')).onError((error, stackTrace) {
print('Handled error: $error');
});
}
Output:
Handled error: Exception: API error
3. Using catchError with Future
Another way to catch exceptions in asynchronous operations.
Future<void> fetchData() async {
return Future.delayed(Duration(seconds: 1), () {
throw Exception('API request failed');
});
}

void main() {
fetchData().catchError((error) {
print('Error caught: $error');
});
}
Output:
Error caught: Exception: API request failed

3.4.3. Rethrowing Exceptions


1. Using finally Block
The finally block executes code regardless of whether an exception occurred. It is useful for
cleaning up resources.
void processFile() {
try {
print('Opening file...');

113
throw Exception('File not found');
} catch (e) {
print('Error: $e');
} finally {
print('Closing file...');
}
}

void main() {
processFile();
}
Output:
Opening file...
Error: Exception: File not found
Closing file...
2. Using on Clause
The on clause catches specific exceptions instead of all errors.
void divideNumbers() {
try {
int result = 10 ~/ 0;
print(result);
} on IntegerDivisionByZeroException {
print('Cannot divide by zero!');
}
}

void main() {
divideNumbers();
}
Output:
Cannot divide by zero!
3.5. Perform Testing in Flutter
3.5.1. Description
1. Definition
Testing in Flutter ensures that an app works correctly without crashes, bugs, or performance
issues. It involves writing and executing tests to check if the app’s functionalities behave as
expected.

114
2. Importance of Testing
 Detects bugs early before reaching users.
 Ensures app reliability and performance.
 Reduces maintenance costs by avoiding future issues.
 Improves code quality by enforcing best practices.
 Increases confidence in deploying updates safely.
3. Testing Levels
Flutter testing is divided into three main levels:
 Unit Testing – Tests small pieces of code (functions, methods).
 Widget Testing – Tests UI components to ensure they render correctly.
 Integration Testing – Tests the entire app flow, including API calls and database
operations.
3.5.2. Implement Types of Testing in Flutter
1. Unit Tests
 Test individual functions, classes, or methods without relying on UI or external
dependencies.
 Example: Testing a function that calculates the sum of two numbers.

import 'package:flutter_test/flutter_test.dart';

int add(int a, int b) => a + b;

void main() {
test('Addition function test', () {
expect(add(3, 2), 5);
});
}
2. Widget Tests
 Tests UI components to verify they render correctly.
 Example: Checking if a button is displayed.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

void main() {
testWidgets('Button should be present', (WidgetTester tester)
async {

115
await tester.pumpWidget(MaterialApp(home:
ElevatedButton(onPressed: () {}, child: Text('Click Me'))));
expect(find.text('Click Me'), findsOneWidget);
});
}
3. Integration Tests
 Tests app workflows, including API calls and database interactions.
 Example: Testing login functionality.
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Login test', (tester) async {


// Simulate user entering credentials
await tester.enterText(find.byKey(Key('emailField')),
'[email protected]');
await tester.enterText(find.byKey(Key('passwordField')),
'password123');

// Simulate button press


await tester.tap(find.byKey(Key('loginButton')));
await tester.pumpAndSettle();

// Verify the login screen disappears


expect(find.text('Dashboard'), findsOneWidget);
});
}
4. Functional Tests
 Ensures that each feature works as expected.
 Example: Testing if an item is added to the cart.
5. UI Tests
 Validates the look and feel of the app across different screen sizes.
6. Performance Tests
 Measures app speed, memory usage, and response time.
 Example: Checking how long a screen takes to load.

7. Regression Tests

116
 Ensures new updates don’t break existing features.
 Used before releasing updates.
8. Cross-Platform Testing
 Tests app behavior on Android, iOS, web, and desktop.
9. Security Testing
 Checks for data vulnerabilities, secure authentication, and authorization.
10. End-to-End (E2E) Tests
 Tests the entire user journey, from login to checkout.
11. Mocking and Stubbing
 Mocks API responses instead of making real API calls.
 Example: Mocking a user API.
import 'package:mockito/mockito.dart';

class MockApi extends Mock {


Future<String> fetchData() async => 'Mock Data';
}
12. Code Coverage Tests
 Measures how much of the code is covered by tests.
 Helps improve testing completeness.
3.5.3. Test Device Responsiveness
Testing device responsiveness ensures that a Flutter app works seamlessly across different screen
sizes, orientations, and devices. This involves using various testing tools, emulators, simulators,
and real devices to verify UI adaptability and performance.
1. Select Testing Tools
Before testing responsiveness, choose appropriate tools:
 Flutter DevTools – Debug UI and performance issues.
 Android Studio Emulator – Simulate various Android devices.
 Xcode Simulator – Test on different iOS devices.
 Chrome DevTools – Test web responsiveness.
 Firebase Test Lab – Run tests on real devices in the cloud.
 BrowserStack/Sauce Labs – Test apps on multiple devices remotely.

117
2. Test Using Emulator and Simulator
Flutter apps can be tested on virtual devices before running them on real devices.
Android Emulator (Android Studio)
 Open Android Studio > Tools > AVD Manager.
 Create an emulator for different screen sizes (phones, tablets).
 Run the app and check scalability, layout, and touch gestures.
iOS Simulator (Xcode)
 Open Xcode > Devices and Simulators.
 Choose an iPhone/iPad model to test responsiveness.
3. Test Using a Physical Device
 Testing on real Android or iOS devices ensures the app works with actual hardware.
 Connect via USB debugging (Android) or Wireless debugging (iOS).
 Test gesture inputs, network conditions, and real-time performance.
4. Perform Manual Testing
Manual testing helps identify UI bugs that automated tests might miss.
Steps for Manual Testing:
 Test different screen sizes (small, medium, large).
 Rotate the device (portrait/landscape mode).
 Check for overlapping text, cut-off buttons, and scrolling issues.
 Simulate low battery, network loss, and background app switching.
5. Perform Automated Testing
Automated tests reduce human effort and ensure the app functions correctly across devices.
Automated Testing Methods:
 Unit Tests – Validate individual logic.
 Widget Tests – Ensure UI components behave correctly.
 Integration Tests – Simulate real-world user interactions.
 Golden Tests – Compare UI snapshots to detect layout changes.
Example: Running Automated Tests in Flutter
flutter test
or
flutter drive --target=test_driver/app.dart

118
3.5.4. Test Reliability
Testing reliability ensures that a Flutter app functions correctly under different conditions,
remains stable, and meets user expectations. This process involves several testing techniques,
including unit testing, integration testing, stress testing, and user acceptance testing (UAT).
1. Unit and Widget Testing
Unit Testing
Unit tests verify individual functions, methods, or classes to ensure they work as expected.
 Example: Testing a function that adds two numbers.
import 'package:flutter_test/flutter_test.dart';

int add(int a, int b) => a + b;

void main() {
test('Check if add() returns correct sum', () {
expect(add(2, 3), 5);
});
}
Widget Testing
Widget tests validate how individual UI components behave.
 Example: Testing if a button renders correctly.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('Button test', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home:
ElevatedButton(onPressed: () {}, child: Text('Click Me'))));

expect(find.text('Click Me'), findsOneWidget);


});
}
2. Integration and End-to-End Testing
Integration Testing
 Ensures multiple app modules (UI, database, network) work together.
 Example: Checking if a login screen correctly interacts with an API.
End-to-End (E2E) Testing

119
 Simulates real user interactions.
 Example: Testing user signup, login, and profile update in one flow.
Example of E2E Testing in Flutter using flutter_driver
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
group('App E2E Test', () {
FlutterDriver? driver;

setUpAll(() async {
driver = await FlutterDriver.connect();
});

test('Verify Login Button Exists', () async {


expect(await
driver?.getText(find.byValueKey('loginButton')), 'Login');
});

tearDownAll(() async {
driver?.close();
});
});
}
3. Edge Case and Stress Testing
Edge Case Testing
 Tests unexpected user inputs (e.g., long text, special characters).
 Example: Checking if an input field accepts 1000 characters without breaking.
Stress Testing
 Simulates heavy app usage (e.g., 1000 users logging in simultaneously).
 Example: Running multiple API requests to check app stability.
4. Performance Testing and User Acceptance Testing (UAT)
Performance Testing
1. Measures app speed, responsiveness, and resource usage.
2. Example: Checking how fast the app loads a large list.
User Acceptance Testing (UAT)
 Final testing by real users to check if the app meets business needs.
 Example: A client tests the app before launch to ensure usability.

120
3.6. Debug Codebase Issues in Flutter
Debugging is an essential part of software development, especially when working with a codebase
in Flutter. Below is a description of key terms related to debugging in Flutter:
3.6.1. Description of Key Terms
1. Codebase
 The codebase refers to the entire collection of source code, resources, and
configuration files that make up a Flutter application.
 It includes Dart files, assets, dependencies, and other files necessary for the app to
function.
 Debugging a codebase involves identifying and fixing issues within this collection
of code.
2. Debug
 Debugging is the process of identifying, analyzing, and resolving bugs or issues in
the codebase.
 In Flutter, debugging can involve using tools like Flutter DevTools, breakpoints,
and logging to trace and fix errors.
3. Logging
 Logging is the practice of recording messages or events during the execution of a
program.
 In Flutter, developers often use print() statements or the debugPrint() function to
log messages to the console for debugging purposes.
 Logging helps track the flow of the application and identify where issues might be
occurring.
4. Flutter DevTools
 Flutter DevTools is a suite of performance and debugging tools provided by the
Flutter team.
 It includes features like inspecting the widget tree, viewing logs, analyzing
performance, and debugging memory issues.
 DevTools can be accessed via a browser and is integrated with IDEs like Android
Studio and VS Code.
5. Isolation

121
 In Dart (the language used by Flutter), an Isolate is a separate memory space that
runs code independently of the main thread.
 Debugging issues in isolates can be challenging because they don't share memory
with the main thread.
 Tools like Flutter DevTools can help debug isolates by providing insights into their
behavior.
6. Assertion
 An assertion is a statement used to check for conditions that should always be true
during development.
 In Flutter, assertions are often used in assert() statements to validate assumptions
in the code.
 If the condition in an assertion is false, the program will throw an error, helping
developers catch issues early.
7. Breakpoints
 Breakpoints are markers set in the code where the execution of the program will
pause, allowing developers to inspect the state of the application.
 In Flutter, breakpoints can be set in IDEs like Android Studio or VS Code to debug
specific parts of the code.
 When the program hits a breakpoint, developers can inspect variables, step through
code, and analyze the call stack.
8. Print Statement
 A print statement is a simple way to output messages to the console for debugging
purposes.
 In Flutter, the print() function is commonly used to log messages, but it’s
recommended to use debugPrint() for better handling of large outputs.
 While print statements are useful for quick debugging, they should be used
sparingly in production code.
How These Terms Relate to Debugging in Flutter
 Codebase: The entire application you’re debugging.
 Debug: The process of finding and fixing issues in the codebase.
 Logging: Using print() or debugPrint() to track the flow of the app.

122
 Flutter DevTools: A powerful toolset for debugging and performance analysis.
 Isolation: Debugging concurrent or parallel code execution.
 Assertion: Validating assumptions in the code during development.
 Breakpoints: Pausing execution to inspect the app’s state.
 Print Statement: A quick way to log messages for debugging.
3.6.2. Applying Debugging Methods
Debugging in Flutter involves a combination of tools, techniques, and best practices to identify
and resolve issues in the codebase. Here are some common debugging methods:
1. Using Print Statements
 Add print() or debugPrint() statements in your code to log variable values, function
calls, or flow of execution.
 Example:
void fetchData() {
print('Fetching data...');
// Your code here
}
2. Setting Breakpoints
 Use breakpoints in your IDE (e.g., Android Studio or VS Code) to pause execution
at specific lines of code.
 Inspect variables, step through code, and analyze the call stack to identify issues.
3. Using Flutter DevTools
 Open Flutter DevTools in your browser and connect it to your running app.
 Use the Widget Inspector to visualize the widget tree and identify UI issues.
 Use the Logging tab to view logs and debug output.
 Use the Performance tab to analyze app performance and identify bottlenecks.
4. Assertions
 Use assert() statements to validate assumptions in your code.
 Example:
void updateAge(int age) {
assert(age > 0, 'Age must be greater than 0');
// Your code here
}
5. Debugging Isolates

123
 Use Flutter DevTools to debug isolates and analyze their behavior.
 Log messages from isolates using print() or debugPrint().
6. Handling Exceptions
 Use try-catch blocks to catch and handle exceptions.
 Log exceptions for debugging purposes.
 Example:
try {
// Risky code
} catch (e) {
print('Exception caught: $e');
}
7. Hot Reload and Hot Restart
 Use Hot Reload to quickly test changes in the UI.
 Use Hot Restart to reset the app state and test changes from the beginning.

3.6.3. Code Reviews


Code reviews are a critical part of maintaining a healthy codebase and ensuring high-quality code.
Here’s how to conduct effective code reviews in Flutter:
1. Purpose of Code Reviews
 Identify bugs and potential issues early.
 Ensure code follows best practices and coding standards.
 Share knowledge and improve team collaboration.
2. What to Look For During Code Reviews
 Code Quality: Is the code clean, readable, and maintainable?
 Functionality: Does the code work as intended?
 Performance: Are there any performance bottlenecks?
 Error Handling: Are exceptions and edge cases handled properly?
 Testing: Are there sufficient unit and integration tests?
 Documentation: Is the code well-documented?
3. Tools for Code Reviews
 Use version control systems like Git and platforms like GitHub or GitLab for pull
requests and code reviews.

124
 Use static analysis tools like Dart Analyzer or Flutter Lints to catch common
issues.
4. Best Practices for Code Reviews
 Be constructive and respectful in feedback.
 Focus on the code, not the person.
 Provide clear and actionable suggestions.
 Review small chunks of code at a time for better focus.

3.6.4. Prepare Documentation

Documentation is essential for maintaining a codebase and ensuring that other developers (or your
future self) can understand and work with the code. Here’s how to prepare effective documentation
for a Flutter project:
1. Types of Documentation
 Code Comments: Use inline comments to explain complex logic or decisions.

// Calculate the sum of two numbers


int sum(int a, int b) {
return a + b;
}
 API Documentation: Use Dart’s documentation syntax (///) to describe classes,

methods, and parameters.

/// A class representing a user.


class User {
/// The user's name.
final String name;

/// Creates a new [User] instance.


User(this.name);
}
 README File: Provide an overview of the project, setup instructions, and usage

examples in the README.md file.


 Architecture Documentation: Document the app’s architecture, including
diagrams and explanations of key components.
 Testing Documentation: Explain how to run tests and add new ones.
2. Best Practices for Documentation

125
 Keep documentation up-to-date with the codebase.
 Use clear and concise language.
 Include examples and code snippets where applicable.
 Document edge cases and potential pitfalls.
3. Tools for Documentation
 Use Dartdoc to generate API documentation from your code.
 Use Markdown for writing README files and other documentation.
 Use tools like Draw.io or Lucidchart for creating architecture diagrams.
4. Example README Structure

# Project Name

## Description
A brief description of the project.

## Setup
Steps to set up the project locally:
1. Clone the repository.
2. Run `flutter pub get` to install dependencies.
3. Run `flutter run` to start the app.

## Usage
Examples of how to use the app.

## Testing
Instructions for running tests:
- Run `flutter test` for unit tests.
- Run `flutter drive` for integration tests.

## Contributing
Guidelines for contributing to the project.

## License
Information about the project's license.

126
Learning outcome 4: Publish Application
4.1. Generation of Installable Files
4.1.1. Description
1. Types of Builds
 Debug Build:
 Used during development.
 Includes debugging information and tools like Hot Reload.
 Not optimized for performance or size.
 Release Build:
 Used for deploying the app to users.
 Optimized for performance and size.
 Debugging tools and information are removed.
 Profile Build:
 Used for performance testing and profiling.
 Includes some debugging information but is optimized for performance.
2. Installable File (Android & iOS)
 Android:
 The installable file is an APK (Android Package) or AAB (Android App
Bundle).
 APK is a single file that contains all the app’s code and resources.
 AAB is a publishing format that Google Play uses to generate optimized
APKs for different devices.
 iOS:
 The installable file is an IPA (iOS App Store Package).
 IPA contains the app’s binary and resources, signed with a provisioning
profile.
4.1.2. Compilation Models
1. Just-in-Time (JIT) Compilation
 Used during development.
 Code is compiled at runtime, enabling features like Hot Reload.

127
 Provides faster development cycles but slower runtime performance.
2. Ahead-of-Time (AOT) Compilation
 Used in release builds.
 Code is compiled to native machine code before execution.
 Provides better runtime performance and smaller app size.
3. Hot Reload and Hot Restart Mechanisms
 Hot Reload:
 Updates the UI in real-time without restarting the app.
 Preserves the app’s state.
 Works with JIT compilation.
 Hot Restart:
 Restarts the app and resets its state.
 Takes longer than Hot Reload but ensures a clean start.
4.1.3. Perform Builds Generation
1. iOS (IPA)
 Prerequisites:
 Apple Developer account.
 Xcode installed on a macOS machine.
 Valid provisioning profile and signing certificate.
 Steps to Generate IPA:
1. Open the terminal and navigate to your Flutter project.
2. Run the following command to build the IPA:
flutter build ipa
3. The IPA file will be generated in the build/ios/ipa directory.
4. Use Xcode or the Apple App Store to distribute the IPA.
2. Android (APK)
 Prerequisites:
 Android SDK installed.
 Keystore for signing the APK (for release builds).
 Steps to Generate APK:
1. Open the terminal and navigate to your Flutter project.

128
2. Run the following command to build the APK:
flutter build apk
3. The APK file will be generated in the build/app/outputs/flutter-
apk directory.
4. Distribute the APK manually or upload it to the Google Play Store.
 Generating Android App Bundle (AAB):
1. Run the following command:
flutter build appbundle
2. The AAB file will be generated in
the build/app/outputs/bundle/release directory.
3. Upload the AAB to the Google Play Console for distribution.
4.2. Submission of Application Files
4.2.1. Prepare Store Assets
Preparing store assets is a critical step in making your app visually appealing and engaging for
potential users.
1. The app icon is the first thing users see, so it must be high-quality and follow platform-specific
guidelines. For example, Android requires icons in multiple sizes (e.g., 48x48, 72x72, 96x96
pixels) for different screen densities, while iOS needs icons in sizes like 1024x1024 pixels for
the App Store and 180x180 pixels for the home screen. A well-designed icon reflects your
app’s purpose and branding.
2. App screenshots provide a visual preview of your app’s features and functionality. Both
Google Play and the Apple App Store require screenshots in specific dimensions. For instance,
Google Play recommends 1080x1920 pixels for phone screenshots, while iOS requires
1242x2688 pixels for iPhone Pro Max displays. These screenshots should highlight key
features, such as a clean UI or unique functionality. For example, a fitness app might showcase
workout tracking, progress charts, and goal-setting features.
3. App promotional materials, such as videos or banners, can further enhance your store listing.
A short promotional video (15-30 seconds) can demonstrate the app’s functionality and user
experience. For example, a game app might include gameplay footage to entice users.
Additionally, you may need to prepare promotional text, descriptions, and keywords to

129
optimize your app’s discoverability. For instance, using keywords like “fitness tracker” or
“workout planner” can help users find your app more easily.
4.2.2. Create Developer Account Registration
To submit your app-to-app stores, you need to register for a developer account.
1. For the Google Developer Console, create a Google account and pay a one-time registration
fee of $25. Once registered, you gain access to the Google Play Console, where you can upload
your app, manage releases, and monitor performance metrics. For example, you can track
downloads, user ratings, and crash reports to improve your app.
2. For the Apple Developer Account, enroll in the Apple Developer Program using your Apple
ID and pay an annual fee of $99. This grants access to App Store Connect, where you can upload
your app, manage certificates, and submit it for review. Apple also requires additional steps, such
as setting up certificates, identifiers, and provisioning profiles. For example, you will need to
create a distribution certificate and an App ID to sign your app before submission.
Both platforms provide detailed documentation to guide you through the process. For example,
Google Play offers a step-by-step guide for creating a store listing, while Apple provides resources
for configuring App Store Connect. Once your accounts are set up, you can proceed with uploading
your app and preparing it for review.
4.2.3. Generate App Release Builds (.ipa, .aab)
Generating release builds is a crucial step before submitting your app to the app stores. For iOS,
you need to create an .ipa file. Open your terminal, navigate to your Flutter project, and run the
command flutter build ipa. This generates an IPA file in the build/ios/ipa directory. Ensure you
have a valid Apple Developer account and provisioning profile to sign the build.
For Android, you can generate either an .apk or an .aab file. Use the command flutter build
apk to create an APK file, which is suitable for direct installation. However, for Google Play, it’s
recommended to generate an .aab (Android App Bundle) using the command flutter build
appbundle. The AAB file, located in the build/app/outputs/bundle/release directory, is optimized
for distribution on the Play Store.
4.2.4. Configure App Settings
1. App ID (Android & iOS)
 For Android, the App ID is defined in the build.gradle file as the applicationId. It
uniquely identifies your app on the Google Play Store.

130
Example: applicationId "com.example.myapp"
 For iOS, the App ID is set in Xcode under the project settings. It must match the
Bundle Identifier in your Apple Developer account.
Example: com.example.myapp
2. App Description
 Write a clear and engaging description for your app. Highlight its features, benefits,
and unique selling points.
Example: "MyApp is a fitness tracker that helps you achieve your goals with
personalized workout plans and progress tracking."
3. Set Up App Listing
 Add app details like the name, category, and keywords to improve discoverability.
 Upload store assets such as the app icon, screenshots, and promotional materials.
Example: For a fitness app, include screenshots of workout plans, progress charts, and
a promotional video demonstrating its features.
4. Distribution
 For Android, configure distribution settings in the Google Play Console, such as
release tracks (e.g., production, beta).
 For iOS, set up distribution certificates and provisioning profiles in App Store
Connect. Choose distribution options like TestFlight for beta testing or the App
Store for public release.
4.2.5. Upload App Bundles (Android & iOS)
1. Android (AAB)
 Log in to the Google Play Console.
 Create a new app or select an existing one.
 Navigate to the "Release" section and upload the .aab file.
 Fill in the required details, such as release notes and target countries.
 Submit the app for review. Once approved, it will be published on the Google Play
Store.
2. iOS (IPA)
 Log in to App Store Connect.
 Create a new app or select an existing one.

131
 Use Xcode or the Transporter app to upload the .ipa file.
 Fill in the app details, such as description, screenshots, and pricing.
 Submit the app for review. Once approved, it will be available on the Apple App
Store.
4.3. Address Post-Deployment Issues
4.3.1. Monitor Crash Reports (UXCam, Sentry)
After deploying your app, monitoring crash reports is essential to ensure a smooth user experience.
Tools like UXCam and Sentry help track crashes, errors, and user behavior. For example, Sentry
provides detailed error logs and stack traces, making it easier to identify and fix issues. Regularly
review these reports to address critical bugs and improve app stability.
4.3.2. Performance Degradation
Performance issues, such as slow loading times or high battery usage, can negatively impact user
satisfaction. Use tools like Flutter DevTools or Firebase Performance Monitoring to identify
bottlenecks. For example, if your app’s UI is lagging, optimize widget rebuilds or reduce
unnecessary computations. Regularly test your app on different devices to ensure consistent
performance.
4.3.3. Applying App Store Optimization (ASO)
App Store Optimization (ASO) improves your app’s visibility in the app stores. Focus on
optimizing the app title, description, and keywords. For example, use relevant keywords like
"fitness tracker" or "workout planner" to improve discoverability. Regularly update your app with
new features and encourage users to leave positive reviews. Monitor your app’s ranking and adjust
your ASO strategy accordingly.
4.3.4. Compatibility Problems
1. Based on Operating System Type
 Ensure your app works seamlessly on both Android and iOS. Test for platform-
specific issues, such as UI inconsistencies or functionality gaps. For example,
Android uses Material Design, while iOS follows Human Interface Guidelines.
Adapt your app’s design and behavior to match each platform.
2. Based on Operating System Version (API Level, iOS Version)
 Test your app on different OS versions to ensure compatibility. For Android, check
the minimum API level in your build.gradle file. For iOS, specify the minimum

132
deployment target in Xcode. For example, if your app uses features available only
in iOS 15 or Android 12, ensure it gracefully handles older versions.
4.3.5. Perform Hot Fixing
Hot fixing allows you to address critical issues without requiring users to download a new version.
For Android, you can use tools like Firebase Remote Config to push updates or disable
problematic features. For iOS, hot fixing is more restricted due to App Store policies, but you can
use server-side updates or feature toggles. For example, if a specific feature is causing crashes,
you can temporarily disable it until a fix is deployed.

END

133

You might also like