Mobile App Using Flutter - Handout
Mobile App Using Flutter - Handout
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.
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.
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:
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;
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';
// 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");
// 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 and elements
Map<String, int> mapWithElements2 = {
'key1': 1,
12
'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;
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.
15
// Assigning value to geek variable
dynamic geek = "Geeks For Geeks";
// 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);
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);
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
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;
23
print(x);
print(y);
}
Output:
Condition false
6
6
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:
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;
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;
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;
}
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;
}
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;
}
}
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() {
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() {
34
}
Output:
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.
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.
Example:
void gfg1(int g1, [ var g2 ])
{
// Creating function 1
print("g1 is $g1");
print("g2 is $g2");
}
38
print("g2 is $g2");
}
void main()
{
// Calling the function with optional parameter
print("Calling the function with optional parameter:");
gfg1(01);
void main()
{
// input
var i = 20;
39
print('fibonacci($i) = ${fibonacci(i)}');
}
Output:
For input as 20
fibonacci(20) = 6765
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.
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.
// Driver
void main(){
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(){
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(){
44
// from the provided function.
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.
// Main Function
void main() {
Output:
Hello GeeksForGeeks
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:
47
print(s1.stud_age);
}
Output:
Age should be greater than 5
Nitin
Null
Example 2:
print("Feed cat.");
cat.isHungry = false;
class Cat {
bool _isHungry = true;
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).
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.
50
Instagram (parts built with React Native)
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.
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.
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.
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.
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());
}
64
}
}
@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"),
),
],
);
}
}
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)),
);
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,
),
);
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"),
);
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';
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());
}
@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';
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(),
),
);
}
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';
void main() {
runApp(MyApp());
}
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),
);
},
),
);
}
}
78
3. Create a State:
class CounterState {
final int counter;
CounterState(this.counter);
}
4. Create the Bloc:
import 'package:bloc/bloc.dart';
void main() {
runApp(MyApp());
}
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),
),
);
}
}
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 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';
void main() {
runApp(ProviderScope(child: MyApp()));
}
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),
),
);
}
}
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"),
],
),
);
}
}
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")),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: MaterialExample()));
86
}
void main() {
runApp(CupertinoApp(home: CupertinoExample()));
}
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: () {},
),
),
);
}
}
void main() {
runApp(MaterialApp(home: IconExample()));
}
88
);
}
}
void main() {
runApp(MaterialApp(home: ToastExample()));
}
@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';
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
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.
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'.
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.
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.
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.
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.
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';
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;
97
return User(id: json['id'], name: json['name'], email:
json['email']);
}
}
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');
}
}
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';
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';
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;
}
}
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"),
),
],
),
),
);
}
}
103
final String userEmail;
final AuthService authService = AuthService();
HomeScreen({required this.userEmail});
@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;
104
),
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';
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';
107
await storage.write(key: "user_password", value:
"encrypted_password_here");
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';
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';
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
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.
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
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';
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();
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';
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';
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'))));
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();
});
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.
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.
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.
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