Introduction to Dart & Setup
What is Dart?
Dart is a modern, object-oriented programming language developed by Google. It is optimized for client-side development and powers Flutter, the leading framework for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
Dart was designed to make app development fast, productive, and scalable. It combines simplicity with powerful programming features, making it suitable for both beginners and professional developers.
Using Dart, developers can create:
- Mobile applications
- Web applications
- Desktop software
- Backend services
- Command-line tools
Why Dart?
- Fast development with hot reload
- Excellent performance
- Strong type system with null safety
- Cross-platform with Flutter
Dart is beginner-friendly while still being powerful enough for large-scale production applications.
Key Features of Dart
1. Easy to Learn
Dart syntax is clean and easy to understand. If you have experience with languages like JavaScript, Java, or C++, Dart will feel familiar.
2. Object-Oriented
Dart is fully object-oriented. Everything in Dart is treated as an object, including numbers and functions.
3. Fast Execution
Dart uses Just-In-Time (JIT) compilation during development for fast iteration and Ahead-Of-Time (AOT) compilation for optimized production performance.
4. Null Safety
Dart includes sound null safety which helps developers avoid many common runtime errors caused by null values.
5. Great for Flutter
Dart is the primary language used with Flutter. Together they allow developers to create beautiful cross-platform applications using a single codebase.
Where Dart is Used
Dart is widely used in modern app development.
| Area | Usage |
|---|---|
| Mobile Apps | Android and iOS applications using Flutter |
| Web Apps | Interactive browser-based applications |
| Desktop Apps | Windows, macOS, and Linux applications |
| Backend | Server-side APIs and tools |
Installing Dart
To start programming in Dart, you need to install the Dart SDK. You can either:
- Install Dart SDK separately
- Install Flutter (recommended for app development)
Flutter already includes Dart, so you do not need to install both separately.
Installation
Download the Dart SDK from dart.dev or install Flutter (which includes Dart).
dart --version flutter --version
These commands check whether Dart and Flutter are installed correctly.
Recommended Code Editor
Although Dart code can be written in any text editor, modern code editors provide a much better experience.
Popular editors for Dart development:
- Visual Studio Code
- Android Studio
- IntelliJ IDEA
Install the Dart and Flutter extensions for better development support such as:
- Syntax highlighting
- Auto-completion
- Error detection
- Debugging tools
- Code formatting
Understanding the Dart Program Structure
Every Dart program starts execution from the main() function.
void main() { }
Let us understand this:
voidmeans the function does not return any valuemain()is the entry point of the program- Curly braces
{ }contain the code block
Your First Dart Program
void main() { print("Hello, Dart Mastery!"); print("Ready to build cross-platform apps!"); }
Ready to build cross-platform apps!
Understanding the Code
Let us break the program line by line.
void main() { print("Hello, Dart Mastery!"); }
main()is the starting point of the programprint()displays output on the console- Text values are written inside quotation marks
- Every statement ends with a semicolon
;
๐ป Try It Yourself - Multi-Language Compiler
Practice Dart and many other programming languages right here in your browser! Switch between languages, modify the code, and click "Run" to see results instantly.
๐ก Practice Tips:
- Switch to Dart in the language selector and try the Flutter development examples
- Experiment with Dart's strong typing and null safety features
- Try other mobile languages like Swift, Kotlin, or compare with TypeScript
- Use the "Load Example" button to see Dart-specific code samples
- Use Ctrl+Enter to quickly run your code
Running a Dart Program
Save your file with the .dart extension.
Example:
hello.dart
Open the terminal in the same folder and run:
dart run hello.dart
Comments in Dart
Comments are notes written inside code for explanation. They are ignored by the compiler.
Single-Line Comment
// This is a comment print("Hello");
Multi-Line Comment
/*
This is a
multi-line comment
*/
Basic Printing Examples
The print() function is one of the most commonly used functions in Dart.
void main() { print("Welcome to Dart"); print(100); print(true); }
100
true
Dart File Extensions
Dart source files always use the .dart extension.
app.dart main.dart calculator.dart
Common Beginner Mistakes
// Mistake 1: Missing semicolon // print("Hello") // Correct print("Hello"); // Mistake 2: Wrong function name // Print("Hello"); // Correct print("Hello"); // Mistake 3: Missing quotes // print(Hello) // Correct print("Hello");
Real-World Analogy
Think of a Dart program like a recipe.
- The
main()function is the starting instruction - Statements are the cooking steps
- The compiler follows instructions line by line
Mini Practice
Try running the following programs yourself.
// Exercise 1 void main() { print("My first Dart program"); }
// Exercise 2 void main() { print("Learning Dart is fun!"); print("Flutter uses Dart"); }
Install Dart SDK or Flutter.
Create a new file hello.dart and run it with
dart run hello.dart.
Modify the program and print:
- Your name
- Your favorite programming language
- A motivational message
Summary
- Dart is a modern programming language developed by Google
- Dart powers Flutter applications
- The
main()function is the entry point of every program print()displays output to the console- Dart files use the
.dartextension - Semicolons are required after statements
- Comments improve code readability
Variables & Data Types
Introduction to Variables
Variables are used to store data in memory. They act like containers that hold values which can later be used, modified, or displayed in a program.
In Dart, variables can store:
- Numbers
- Text
- Boolean values
- Lists
- Objects
Variables make programs dynamic because values can change while the program runs.
Creating Variables
Dart provides multiple ways to create variables.
void main() { var name = "Aman"; var age = 21; print(name); print(age); }
21
In the example above:
namestores textagestores a numbervartells Dart to automatically detect the data type
What is a Data Type?
A data type defines the kind of value a variable can store.
Different types of data require different amounts of memory and support different operations.
Dart is a strongly typed language, which means every variable has a specific type.
Common Data Types in Dart
| Data Type | Description | Example |
|---|---|---|
int |
Whole numbers | 10 |
double |
Decimal numbers | 10.5 |
String |
Text values | "Hello" |
bool |
True or false values | true |
dynamic |
Can store any type | Anything |
Integer Data Type
The int type stores whole numbers without decimals.
void main() { int students = 50; int year = 2026; print(students); print(year); }
2026
Double Data Type
The double type stores decimal numbers.
void main() { double price = 99.99; double temperature = 36.5; print(price); print(temperature); }
String Data Type
The String type stores text values.
Strings must be written inside quotation marks.
void main() { String country = "India"; String message = "Welcome to Dart"; print(country); print(message); }
Boolean Data Type
The bool type stores only two values:
truefalse
Boolean values are commonly used in conditions and decision making.
void main() { bool isLoggedIn = true; bool isAdmin = false; print(isLoggedIn); print(isAdmin); }
Using var Keyword
The var keyword allows Dart to automatically infer the data type.
var name = "Rahul"; var marks = 95; var percentage = 85.5;
Once Dart determines the type, it cannot be changed later.
var city = "Delhi"; // Invalid // city = 100;
Using dynamic Keyword
The dynamic keyword allows variables to change types during runtime.
dynamic value = "Hello"; print(value); value = 100; print(value); value = true; print(value);
Use dynamic carefully because it reduces type safety.
Difference Between var and dynamic
| Feature | var | dynamic |
|---|---|---|
| Type Fixed | Yes | No |
| Type Safety | Strong | Weak |
| Recommended for Beginners | Yes | Limited Use |
Variable Naming Rules
Variable names should be meaningful and readable.
- Variable names can contain letters, numbers, and underscores
- Variable names cannot start with a number
- Variable names are case-sensitive
- Spaces are not allowed
// Valid variable names String firstName = "Aman"; int totalMarks = 500; double account_balance = 1500.50; // Invalid variable names // int 1age = 20; // String user name = "Rahul";
Updating Variables
Variable values can be changed after creation.
void main() { int score = 50; score = 80; print(score); }
Constants in Dart
Constants are variables whose values cannot be changed.
Using final
The value is assigned only once during runtime.
final country = "India"; print(country);
Using const
The value must be known at compile time.
const pi = 3.14159;
print(pi);
String Interpolation
String interpolation allows variables to be inserted directly inside strings.
String name = "Aman"; int age = 20; print("My name is $name"); print("I am $age years old");
I am 20 years old
Basic Arithmetic Operations
Numeric variables support mathematical operations.
int a = 10; int b = 5; print(a + b); print(a - b); print(a * b); print(a / b);
5
50
2.0
Type Checking
Dart allows checking the type of variables.
String language = "Dart"; print(language.runtimeType);
Real-World Example
Let us create a simple student information program.
void main() { String studentName = "Rahul"; int age = 21; double marks = 88.5; bool passed = true; print("Student Name: $studentName"); print("Age: $age"); print("Marks: $marks"); print("Passed: $passed"); }
Important Notes
- Dart is a strongly typed language
- Use meaningful variable names
- Strings must be inside quotes
varautomatically detects typesdynamicallows changing typesfinalandconstcreate constants
Common Beginner Mistakes
// Mistake 1: Missing quotes // String city = Delhi; // Correct String city = "Delhi"; // Mistake 2: Wrong boolean values // bool isActive = True; // Correct bool isActive = true; // Mistake 3: Invalid variable name // int 1marks = 90;
Mini Practice
Try the following exercises yourself:
// Exercise 1 String name = "Arjun"; int age = 19; print(name); print(age); // Exercise 2 double price = 150.75; bool inStock = true; print(price); print(inStock); // Exercise 3 final country = "India"; print(country);
Summary
- Variables store data in memory
- Dart supports multiple data types
intstores whole numbersdoublestores decimal numbersStringstores textboolstores true or false valuesvarautomatically infers typesdynamicallows flexible typingfinalandconstcreate constants
Control Statements
Introduction to Control Statements
Control statements determine how a program makes decisions and repeats actions.
By default, Dart executes code line by line from top to bottom. However, real-world applications require logic and decision-making.
Control statements allow programs to:
- Make decisions
- Repeat tasks automatically
- Skip certain operations
- Stop loops when needed
- Create dynamic applications
Types of Control Statements
In Dart, control statements are mainly divided into:
- Conditional Statements
- Looping Statements
- Jump Statements
Conditional Statements
Conditional statements allow a program to make decisions based on conditions.
If Statement
The if statement executes a block of code only if a condition is true.
void main() { int age = 20; if (age >= 18) { print("You are eligible to vote"); } }
The code inside the if block executes only when the condition is true.
Understanding Conditions
Conditions always return either:
truefalse
print(10 > 5); print(5 == 10); print(8 != 3);
false
true
Comparison Operators
| Operator | Meaning |
|---|---|
== |
Equal to |
!= |
Not equal to |
> |
Greater than |
< |
Less than |
>= |
Greater than or equal to |
<= |
Less than or equal to |
If-Else Statement
The if-else statement is used when there are two possible outcomes.
void main() { int number = 7; if (number % 2 == 0) { print("Even Number"); } else { print("Odd Number"); } }
Else If Ladder
Multiple conditions can be checked using else if.
void main() { int marks = 82; if (marks >= 90) { print("Grade A"); } else if (marks >= 75) { print("Grade B"); } else if (marks >= 50) { print("Grade C"); } else { print("Fail"); } }
Nested If Statements
An if statement inside another if statement is called nesting.
void main() { int age = 22; bool hasID = true; if (age >= 18) { if (hasID) { print("Entry Allowed"); } } }
Logical Operators
Logical operators combine multiple conditions.
| Operator | Meaning |
|---|---|
&& |
Logical AND |
|| |
Logical OR |
! |
Logical NOT |
Using AND Operator
bool isLoggedIn = true; bool isAdmin = true; if (isLoggedIn && isAdmin) { print("Access Granted"); }
Using OR Operator
bool hasCoupon = false; bool isPremiumUser = true; if (hasCoupon || isPremiumUser) { print("Discount Applied"); }
Switch Statement
The switch statement is used when multiple possible values are checked.
void main() { String day = "Monday"; switch (day) { case "Monday": print("Start of the week"); break; case "Friday": print("Weekend is near"); break; default: print("Normal day"); } }
Looping Statements
Loops are used to repeat code multiple times automatically.
For Loop
The for loop is used when the number of repetitions is known.
void main() { for (int i = 1; i <= 5; i++) { print(i); } }
2
3
4
5
Understanding the For Loop
for (int i = 1; i <= 5; i++)
int i = 1โ Starting valuei <= 5โ Conditioni++โ Increment value
While Loop
The while loop runs as long as the condition remains true.
void main() { int count = 1; while (count <= 5) { print(count); count++; } }
Do-While Loop
The do-while loop executes the code at least once.
void main() { int number = 1; do { print(number); number++; } while (number <= 3); }
Break Statement
The break statement immediately stops a loop.
for (int i = 1; i <= 10; i++) { if (i == 6) { break; } print(i); }
2
3
4
5
Continue Statement
The continue statement skips the current iteration.
for (int i = 1; i <= 5; i++) { if (i == 3) { continue; } print(i); }
2
4
5
Infinite Loops
A loop that never stops is called an infinite loop.
// Infinite loop example // while (true) { // print("Running forever"); // }
Infinite loops can freeze programs if not handled carefully.
Real-World Example
Let us create a simple login system.
void main() { String username = "admin"; String password = "1234"; if (username == "admin" && password == "1234") { print("Login Successful"); } else { print("Invalid Credentials"); } }
Important Notes
- Conditions must return true or false
- Use
==for comparison, not= - Loops reduce repetitive code
breakstops loops immediatelycontinueskips the current iteration- Improper loop conditions may cause infinite loops
Common Beginner Mistakes
// Mistake 1: Using = instead of == // if (x = 5) // Correct // if (x == 5) // Mistake 2: Infinite loop // while (true) // Mistake 3: Missing braces // if (true) // print("Hello")
Mini Practice
Try the following exercises yourself:
// Exercise 1 int number = 10; if (number > 0) { print("Positive Number"); } // Exercise 2 for (int i = 1; i <= 3; i++) { print(i); } // Exercise 3 int marks = 45; if (marks >= 50) { print("Pass"); } else { print("Fail"); }
Summary
- Control statements manage program flow
ifstatements make decisionsswitchhandles multiple choices- Loops repeat code automatically
for,while, anddo-whileare common loopsbreakandcontinuecontrol loop execution- Logical operators combine multiple conditions
Functions
Introduction to Functions
Functions are reusable blocks of code designed to perform specific tasks.
Instead of writing the same code repeatedly, we can place the code inside a function and call it whenever needed.
Functions make programs:
- Cleaner
- More organized
- Reusable
- Easier to debug
- Easier to maintain
Why Functions are Important
Imagine building a large application without functions. You would need to repeat the same logic many times, making the code difficult to manage.
Functions solve this problem by allowing code reuse.
Basic Function Syntax
In Dart, a function is defined using:
- Return type
- Function name
- Parameters (optional)
- Function body
returnType functionName() { // Function body }
Creating Your First Function
Let us create a simple function that prints a welcome message.
void greet() { print("Welcome to Dart Functions!"); } void main() { greet(); }
Understanding the Function
void greet()
voidmeans the function returns nothinggreetis the function name()contains parameters
Calling a Function
A function executes only when it is called.
greet();
This statement calls the function and runs its code.
Functions with Parameters
Parameters allow functions to accept input values.
void greetUser(String name) { print("Hello, $name"); } void main() { greetUser("Aman"); greetUser("Rahul"); }
Hello, Rahul
Multiple Parameters
Functions can accept multiple parameters.
void studentInfo(String name, int age) { print("Name: $name"); print("Age: $age"); } void main() { studentInfo("Arjun", 20); }
Functions with Return Values
Functions can return data using the return keyword.
int addNumbers(int a, int b) { return a + b; } void main() { int result = addNumbers(10, 5); print(result); }
Understanding Return Types
The return type specifies the type of value a function returns.
| Return Type | Description |
|---|---|
void |
No value returned |
int |
Returns integer value |
double |
Returns decimal value |
String |
Returns text |
bool |
Returns true or false |
Arrow Functions
Dart provides a shorter syntax for simple functions using the arrow operator =>.
int square(int number) => number * number; void main() { print(square(5)); }
Optional Parameters
Optional parameters are not required when calling a function.
void greet(String name, [String? city]) { print("Name: $name"); print("City: $city"); } void main() { greet("Aman"); }
Named Parameters
Named parameters improve readability.
void createUser({ required String name, required int age }) { print("Name: $name"); print("Age: $age"); } void main() { createUser(name: "Rahul", age: 21); }
Default Parameter Values
Parameters can also have default values.
void welcome({String message = "Welcome User"}) { print(message); } void main() { welcome(); }
Anonymous Functions
Anonymous functions are functions without names.
var numbers = [1, 2, 3];
numbers.forEach((number) {
print(number);
});
Scope in Functions
Variables created inside a function are local to that function.
void test() { int value = 100; print(value); }
The variable value cannot be accessed outside the function.
Real-World Example
Let us create a calculator function.
double calculateArea(double length, double width) { return length * width; } void main() { double area = calculateArea(10.5, 5.0); print("Area: $area"); }
Recursion
Recursion happens when a function calls itself.
int factorial(int n) { if (n == 1) { return 1; } return n * factorial(n - 1); } void main() { print(factorial(5)); }
Important Notes
- Functions improve code reusability
- Use meaningful function names
- Functions can accept parameters
- Functions can return values
voidmeans no return value- Arrow functions simplify short functions
Common Beginner Mistakes
// Mistake 1: Forgetting parentheses // greet; // Correct greet(); // Mistake 2: Missing return statement // int add() { // } // Mistake 3: Wrong parameter types // addNumbers("10", "20")
Mini Practice
Try the following exercises yourself:
// Exercise 1 void sayHello() { print("Hello World"); } // Exercise 2 int multiply(int a, int b) { return a * b; } // Exercise 3 String fullName(String first, String last) { return "$first $last"; }
Summary
- Functions are reusable blocks of code
- Functions reduce repetition
- Parameters allow input values
- Return values send data back from functions
- Dart supports optional and named parameters
- Arrow functions simplify small functions
- Recursion allows functions to call themselves
Collections
Introduction to Collections
Collections are used to store and manage multiple values in a single variable.
Instead of creating separate variables for every value, collections allow us to organize data efficiently.
Collections are extremely important in real-world applications because applications often handle:
- Lists of users
- Products
- Messages
- Orders
- Student records
- Game scores
Types of Collections in Dart
Dart mainly provides three important collection types:
- List
- Set
- Map
What is a List?
A List stores multiple values in an ordered way.
Lists are similar to arrays in other programming languages.
Each item in a list has an index position.
Creating a List
void main() { List fruits = ["Apple", "Banana", "Mango"]; print(fruits); }
Accessing List Elements
List items are accessed using indexes.
Indexes start from 0.
List colors = ["Red", "Green", "Blue"]; print(colors[0]); print(colors[1]); print(colors[2]);
Green
Blue
List Index Visualization
| Index | Value |
|---|---|
| 0 | Red |
| 1 | Green |
| 2 | Blue |
Updating List Elements
List values can be modified using indexes.
List numbers = [10, 20, 30];
numbers[1] = 50;
print(numbers);
Adding Elements to a List
The add() method inserts a new item into the list.
List students = ["Aman", "Rahul"]; students.add("Arjun"); print(students);
Adding Multiple Elements
List items = [1, 2];
items.addAll([3, 4, 5]);
print(items);
Removing Elements from a List
Elements can be removed using:
remove()removeAt()
List fruits = ["Apple", "Banana", "Mango"]; fruits.remove("Banana"); print(fruits); fruits.removeAt(0); print(fruits);
[Mango]
Length of a List
The length property returns the total number of items.
List names = ["A", "B", "C"]; print(names.length);
Looping Through a List
Loops are commonly used with collections.
List cities = ["Delhi", "Mumbai", "Chandigarh"]; for (int i = 0; i < cities.length; i++) { print(cities[i]); }
For-Each Loop
Dart provides a cleaner way to iterate through collections.
List numbers = [1, 2, 3, 4];
numbers.forEach((number) {
print(number);
});
What is a Set?
A Set is a collection of unique values.
Duplicate values are automatically removed.
Set numbers = {1, 2, 3, 3, 4};
print(numbers);
Adding Values to a Set
Set countries = {"India", "USA"}; countries.add("Canada"); print(countries);
Removing Values from a Set
Set items = {1, 2, 3};
items.remove(2);
print(items);
Difference Between List and Set
| Feature | List | Set |
|---|---|---|
| Duplicate Values | Allowed | Not Allowed |
| Order | Maintained | Not Guaranteed |
| Indexes | Available | Not Available |
What is a Map?
A Map stores data in key-value pairs.
Each value is connected to a unique key.
Maps are widely used in APIs, databases, and JSON data.
Creating a Map
Map student = { "name": "Aman", "age": 21, "marks": 88.5 }; print(student);
Accessing Map Values
Map user = { "username": "admin", "password": "1234" }; print(user["username"]);
Adding Data to a Map
Map car = { "brand": "Toyota" }; car["model"] = "Corolla"; print(car);
Updating Map Values
Map product = { "name": "Laptop", "price": 50000 }; product["price"] = 55000; print(product);
Removing Map Values
Map employee = { "name": "Rahul", "salary": 40000 }; employee.remove("salary"); print(employee);
Map Properties
Map user = { "name": "Aman", "age": 20 }; print(user.keys); print(user.values); print(user.length);
Strongly Typed Collections
Dart allows defining the exact type of data stored in collections.
List<int> numbers = [1, 2, 3]; Set<String> names = {"Aman", "Rahul"}; Map<String, int> marks = { "Math": 95, "Science": 88 };
Nested Collections
Collections can contain other collections.
List students = [ { "name": "Aman", "marks": 90 }, { "name": "Rahul", "marks": 85 } ]; print(students);
Real-World Example
Let us create a simple shopping cart system.
void main() { List cart = ["Laptop", "Mouse"]; cart.add("Keyboard"); print(cart); Map user = { "name": "Aman", "cartItems": cart.length }; print(user); }
{name: Aman, cartItems: 3}
Important Notes
- Lists maintain order and allow duplicates
- Sets store unique values only
- Maps use key-value pairs
- Indexes start from 0 in Lists
- Collections can be nested
- Strong typing improves safety
Common Beginner Mistakes
// Mistake 1: Invalid index // List numbers = [1, 2, 3]; // print(numbers[5]); // Mistake 2: Duplicate values in Set // Set items = {1, 1, 2}; // Mistake 3: Wrong Map key // print(user["email"]);
Mini Practice
Try the following exercises yourself:
// Exercise 1 List names = ["Aman", "Rahul"]; names.add("Arjun"); print(names); // Exercise 2 Set numbers = {1, 2, 2, 3}; print(numbers); // Exercise 3 Map student = { "name": "Aman", "marks": 90 }; print(student["name"]);
Summary
- Collections store multiple values
- Lists maintain order and indexes
- Sets remove duplicate values
- Maps store key-value pairs
- Collections support loops and nesting
- Strongly typed collections improve code safety
Classes & Objects
Object-Oriented Programming (OOP) is a programming style where we organize code using classes and objects. Instead of writing everything inside functions, OOP allows us to model real-world entities like users, cars, products, and bank accounts.
Dart is a fully object-oriented language, meaning almost everything in Dart is an object. Using classes makes programs cleaner, reusable, and easier to maintain.
In this lecture, we will learn:
- How to create classes and objects
- How constructors initialize data
- How inheritance works
- How getters and setters control access
- How OOP improves code structure
Defining a Class
A class acts like a blueprint or template for creating objects. It defines properties (variables) and behaviors (methods) that objects will have.
An object is an actual instance created from the class.
class Person {
// Properties (instance variables)
String name;
int age;
// Constructor
Person(this.name, this.age);
// Method
void greet() {
print("Hi, I'm $name");
}
}
void main() {
// Creating object
var p = Person("Ahmed", 25);
// Calling method
p.greet();
}
Understanding the Example
- class Person creates a blueprint
- name and age are properties
- Person() is the constructor
- greet() is a method
- p is an object of the Person class
Creating Multiple Objects
A single class can create many objects with different values.
class Student {
String name;
int marks;
Student(this.name, this.marks);
void showInfo() {
print("$name scored $marks");
}
}
void main() {
var s1 = Student("Ali", 90);
var s2 = Student("Sara", 85);
s1.showInfo();
s2.showInfo();
}
Constructors
Constructors are special methods used to initialize objects when they are created. They automatically run whenever a new object is made.
Dart supports different types of constructors:
- Default constructors
- Named constructors
- Factory constructors
class Point {
double x, y;
// Default constructor
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = 0,
y = 0;
// Named constructor with custom values
Point.custom(double a, double b)
: x = a,
y = b;
}
void main() {
var p1 = Point(2, 5);
var p2 = Point.origin();
var p3 = Point.custom(10, 20);
print(p1.x);
print(p2.x);
print(p3.x);
}
Why Constructors Are Important
- Initialize object data automatically
- Reduce repetitive code
- Ensure objects start with valid values
- Improve readability
Inheritance
Inheritance allows one class to reuse features from another class. The child class inherits properties and methods from the parent class.
This promotes code reuse and avoids duplication.
class Animal {
void speak() {
print("Animal makes sound");
}
}
class Dog extends Animal {
@override
void speak() {
print("Woof!");
}
}
void main() {
var d = Dog();
d.speak();
}
Understanding Inheritance
- Animal is the parent class
- Dog is the child class
- extends creates inheritance
- @override replaces parent behavior
Benefits of Inheritance
- Code reusability
- Cleaner program structure
- Easy maintenance
- Supports scalable applications
Getters & Setters
Getters and setters provide controlled access to class properties. Instead of directly modifying variables, getters and setters allow validation and custom behavior.
class Rectangle {
double w, h;
Rectangle(this.w, this.h);
// Getter
double get area {
return w * h;
}
// Setter
set width(double val) {
if (val > 0) {
w = val;
}
}
}
void main() {
var r = Rectangle(5, 10);
print(r.area);
r.width = 20;
print(r.area);
}
Why Use Getters & Setters?
- Protect object data
- Add validation logic
- Prevent invalid values
- Improve encapsulation
Encapsulation
Encapsulation means hiding internal object data and exposing only necessary functionality. This protects data from accidental modification.
class BankAccount {
double _balance = 0;
// Getter
double get balance => _balance;
// Deposit method
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
}
}
}
void main() {
var acc = BankAccount();
acc.deposit(500);
print(acc.balance);
}
Best Practices
- Use meaningful class names
- Keep classes focused on one responsibility
- Use constructors for initialization
- Protect important variables using encapsulation
- Reuse code through inheritance
Create a BankAccount class with:
- A private balance variable
- deposit() method
- withdraw() method
- A balance getter
- Validation to prevent negative balance
Null Safety & Asynchronous Programming
In modern Dart development, Null Safety and Asynchronous Programming are two extremely important concepts. They help developers write safer, cleaner, and more efficient applications.
Null Safety helps prevent unexpected null errors, while asynchronous programming allows applications to perform time-consuming tasks like API requests, file handling, and database operations without freezing the app.
Null Safety Basics
Before Null Safety was introduced, applications often crashed because variables contained unexpected null values. Dart introduced Null Safety to reduce these runtime errors and improve code reliability.
In Dart, variables are non-nullable by default. This means they must contain a value unless explicitly marked as nullable using ?.
String? maybeNull; // nullable String definitely = "hello"; // non-nullable print(maybeNull?.length); // null-aware: prints null print(maybeNull ?? "default"); // if-null operator int? len = maybeNull?.length; // nullable chain
In this example:
- String? allows the variable to store null values
- String definitely must always contain a value
- ?. safely accesses a property if the object is not null
- ?? provides a default value if the variable is null
Why Null Safety Matters
Null-related crashes are one of the most common problems in application development. Null Safety helps developers detect these issues during compilation instead of runtime.
- Reduces application crashes
- Improves code readability
- Makes debugging easier
- Provides safer memory handling
- Improves application performance
Null Assertion Operator
Sometimes developers are completely sure that a nullable variable contains a value.
In such cases, the Null Assertion Operator ! can be used.
String? username = "Alex"; print(username!.length);
The ! operator tells Dart that the value is definitely not null.
However, using it incorrectly may still cause runtime errors.
Late & Required
The late keyword is used when a variable will be initialized later,
while required forces users to provide values for named parameters.
class User {
late String name; // initialized later
User({required this.name});
}
Here:
- late delays initialization until the variable is used
- required ensures that the parameter must be passed during object creation
Introduction to Asynchronous Programming
Asynchronous programming allows Dart applications to perform long-running operations without blocking the execution of other code.
For example, when downloading data from the internet, the app should continue responding to the user instead of freezing until the download completes.
Understanding Futures
A Future represents a value that will become available sometime in the future. It is commonly used for:
- API requests
- Database operations
- File reading and writing
- Timers and delays
- Network communication
Futures (Async/Await)
The async and await keywords simplify asynchronous programming
and make the code easier to read.
FuturefetchData() async { await Future.delayed(Duration(seconds: 2)); return "Data loaded!"; } void main() async { print("Loading..."); String result = await fetchData(); print(result); // Data loaded! }
In this example:
- Future<String> means the function will return a String later
- async marks the function as asynchronous
- await pauses execution until the Future completes
- Future.delayed() creates an artificial delay
Execution Flow of Async Code
The program first prints:
Loading...
After waiting for 2 seconds, the Future completes and prints:
Data loaded!
This demonstrates how asynchronous code allows the program to continue running while waiting for background tasks.
Error Handling
Errors may occur during asynchronous operations such as failed network requests or missing files.
Dart uses try-catch blocks to handle these exceptions safely.
try {
var data = await fetchData();
} catch (e) {
print("Error: $e");
} finally {
print("Done.");
}
Here:
- try contains risky code
- catch handles exceptions
- finally always executes whether an error occurs or not
Real-World Uses of Async Programming
- Fetching data from REST APIs
- User authentication systems
- Cloud database operations
- Downloading images and files
- Real-time chat applications
- Background processing tasks
Best Practices
- Use Null Safety to avoid crashes
- Avoid unnecessary use of the
!operator - Handle all possible exceptions
- Use async/await for readable asynchronous code
- Keep asynchronous operations lightweight and optimized
Write an async function that fetches user data, handles network errors, and returns a default value on failure.
Try adding:
- A loading message before fetching data
- A delay using
Future.delayed() - A custom error message inside the catch block
- A fallback default value if the request fails
Summary
In this lecture, we learned about Null Safety and Asynchronous Programming in Dart.
We explored nullable and non-nullable variables, null-aware operators, the late keyword, and required parameters.
We also learned how Futures, async, and await work together to handle time-consuming operations efficiently. Finally, we explored error handling techniques using try-catch-finally blocks.
Generics & Extensions
As applications grow larger, writing reusable and flexible code becomes extremely important. Dart provides powerful features like Generics, Extensions, and Mixins to help developers write cleaner and more reusable programs.
These features are heavily used in Flutter development, APIs, libraries, and large-scale applications because they reduce code duplication and improve maintainability.
In this lecture, we will learn:
- How generic classes work
- How generic functions increase flexibility
- How extensions add functionality to existing classes
- How mixins share reusable behaviors
- How these concepts improve code reusability
Generic Classes
Generics allow classes to work with different data types while keeping type safety. Instead of creating separate classes for integers, strings, or doubles, we can create one reusable class using generics.
Generic types are represented using angle brackets <T>.
class Box<T> {
T value;
Box(this.value);
T get() {
return value;
}
}
void main() {
// Integer box
var intBox = Box<int>(42);
// String box
var strBox = Box<String>("hello");
print(intBox.get());
print(strBox.get());
}
Understanding the Example
- T represents a generic type
- The same class works with different data types
- Dart ensures type safety at compile time
- No need to create multiple similar classes
Benefits of Generics
- Reusable code
- Better type safety
- Cleaner architecture
- Less duplication
Generic List Example
Dart collections like List, Map, and Set also use generics internally.
void main() {
List<int> numbers = [1, 2, 3];
List<String> names = ["Ali", "Sara"];
print(numbers);
print(names);
}
Generic Functions
Generics can also be used with functions. A generic function can work with multiple data types while maintaining type safety.
T first<T>(List<T> items) {
return items[0];
}
void main() {
print(first<int>([1, 2, 3]));
print(first(["a", "b", "c"]));
}
Type Inference
Dart is smart enough to automatically detect data types in many cases. This is called type inference.
- first<int>() explicitly provides the type
- first(["a","b"]) allows Dart to infer the type automatically
Another Generic Function Example
void printItem<T>(T item) {
print(item);
}
void main() {
printItem<String>("Hello");
printItem<int>(100);
printItem<double>(5.5);
}
Extensions
Extensions allow us to add new functionality to existing classes without modifying the original class source code.
This is extremely useful when working with built-in classes like String, List, or DateTime.
extension StringX on String {
String get reversed {
return split('').reversed.join();
}
bool get isEmail {
return contains('@');
}
}
void main() {
print("hello".reversed);
print("a@b.com".isEmail);
}
Understanding Extensions
- extension StringX on String extends the String class
- reversed creates a custom getter
- isEmail checks if the string contains '@'
- The original String class remains unchanged
Extension on List
Extensions can also add custom functionality to lists.
extension NumberList on List<int> {
int get total {
int sum = 0;
for (var n in this) {
sum += n;
}
return sum;
}
}
void main() {
var nums = [1, 2, 3, 4];
print(nums.total);
}
Mixins
Mixins allow classes to reuse methods and properties from multiple sources without using inheritance.
They are commonly used for sharing reusable behaviors between classes.
mixin Logger {
void log(String msg) {
print("[LOG] $msg");
}
}
class Service with Logger {
void run() {
log("Service started");
}
}
void main() {
var s = Service();
s.run();
}
Why Use Mixins?
- Share reusable functionality
- Avoid duplicate methods
- Keep code modular
- Support multiple behavior sharing
Multiple Mixins
Dart allows a class to use multiple mixins together.
mixin A {
void methodA() {
print("Method A");
}
}
mixin B {
void methodB() {
print("Method B");
}
}
class Test with A, B {}
void main() {
var t = Test();
t.methodA();
t.methodB();
}
Best Practices
- Use generics to create reusable components
- Keep extensions focused on one responsibility
- Avoid adding unnecessary extension methods
- Use mixins for reusable behavior sharing
- Prefer composition and mixins over deep inheritance
Create the following:
- A generic Stack<T> class with push() and pop()
- An extension on List that adds a sum getter
- A mixin called Printer that prints log messages
Streams & Reactive Programming
Streams are one of the most powerful features in Dart and Flutter for handling asynchronous data. They are widely used in real-time applications such as chat systems, live notifications, stock market updates, and user interactions.
Reactive Programming is a programming approach where applications react automatically to changing data. Instead of constantly checking for updates, streams push new data whenever it becomes available.
What are Streams?
Streams deliver a sequence of asynchronous data events. Think of them as a pipe where data flows over time โ perfect for real-time data, user input, or file I/O.
Unlike a Future, which returns only one value, a Stream can return multiple values over time.
- Future โ returns a single value once
- Stream โ returns multiple values continuously
Streams are commonly used when data changes continuously or arrives over time.
Real-World Uses of Streams
- Live chat applications
- Real-time notifications
- Location tracking
- Audio and video streaming
- Sensor data processing
- User input handling
- Firebase real-time databases
Creating Streams
Streams can be created manually using asynchronous generator functions with the async* keyword.
The yield keyword sends values one by one into the stream.
StreamcountStream(int max) async* { for (int i = 1; i <= max; i++) { await Future.delayed(Duration(seconds: 1)); yield i; } } void main() async { await for (var val in countStream(5)) { print(val); // 1, 2, 3, 4, 5 (one per second) } }
In this example:
- Stream<int> creates a stream of integers
- async* marks the function as a stream generator
- yield emits values into the stream
- await for listens to stream events one by one
How Stream Execution Works
The function waits for one second before sending each number. This creates a continuous flow of data instead of returning everything at once.
Output:
1 2 3 4 5
Each value appears one second apart, demonstrating asynchronous event handling.
Single Subscription vs Broadcast Streams
Dart provides two main types of streams:
- Single Subscription Stream โ Can only be listened to once
- Broadcast Stream โ Multiple listeners can receive the same events
Broadcast streams are commonly used in Flutter applications where multiple widgets need access to the same data source.
Listening to Streams
Streams become useful when listeners subscribe to receive emitted events.
The listen() method is commonly used for this purpose.
Stream.fromIterable([1, 2, 3]).listen((value) {
print(value);
});
The listener automatically reacts whenever new data arrives in the stream.
StreamController
A StreamController provides full control over streams.
Developers can manually add data, errors, or close the stream whenever needed.
import 'dart:async';
void main() {
var controller = StreamController();
controller.stream.listen((data) => print("Got: $data"));
controller.add("Hello");
controller.add("World");
controller.close();
}
In this example:
- StreamController creates and manages the stream
- add() sends data into the stream
- listen() receives emitted data
- close() stops the stream and frees resources
Handling Stream Errors
Streams can also emit errors. Error handling is important for preventing application crashes.
controller.stream.listen(
(data) => print(data),
onError: (err) => print("Error: $err"),
onDone: () => print("Stream closed"),
);
Here:
- onError handles stream errors
- onDone executes when the stream closes
Stream Transformers
Streams can be modified before the data reaches the listener. This process is called stream transformation.
Dart provides methods like map(), where(), and expand() for transforming data streams.
var stream = Stream.fromIterable([1, 2, 3, 4, 5]); stream .where((n) => n % 2 == 0) // filter even .map((n) => n * 10) // multiply .listen(print); // 20, 40
This example:
- Filters only even numbers
- Multiplies each value by 10
- Prints the transformed data
Reactive Programming in Flutter
Flutter uses reactive programming heavily. Whenever stream data changes, widgets automatically rebuild and update the UI.
This creates smooth and responsive applications without manually refreshing screens.
StreamBuilder in Flutter
The StreamBuilder widget listens to streams and rebuilds the UI whenever new data arrives.
StreamBuilder( stream: countStream(10), builder: (context, snapshot) { if (!snapshot.hasData) return CircularProgressIndicator(); return Text("Count: ${snapshot.data}"); }, )
In this widget:
- stream provides the data source
- builder rebuilds the UI whenever data changes
- snapshot stores the latest stream data
- hasData checks if data is available
Advantages of Streams
- Efficient real-time data handling
- Supports reactive programming
- Improves application responsiveness
- Useful for event-driven architectures
- Works perfectly with Flutter widgets
Best Practices
- Always close StreamControllers to avoid memory leaks
- Use StreamBuilder for real-time UI updates
- Handle stream errors properly
- Keep stream transformations simple and readable
- Use broadcast streams only when necessary
Create a stream that emits a random number every second. Display it in a Flutter StreamBuilder.
Try adding:
- A button to start and stop the stream
- Error handling for invalid data
- A loading indicator while waiting for values
- A transformation that doubles the random number
Summary
In this lecture, we learned about Streams and Reactive Programming in Dart and Flutter. We explored how streams emit asynchronous data over time and how listeners receive stream events.
We also learned about StreamController, stream transformations, error handling, and the StreamBuilder widget for building reactive Flutter interfaces.
Streams are an essential concept for developing modern real-time Flutter applications.
Flutter Basics
What is Flutter?
Flutter is Google's UI toolkit for building natively compiled apps for mobile, web, and desktop from a single Dart codebase. Everything in Flutter is a widget.
Your First Flutter App
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("My App")),
body: Center(child: Text("Hello Flutter!")),
),
);
}
}Common Widgets
| Widget | Purpose |
|---|---|
| Container | Box with padding, margin, decoration |
| Row / Column | Horizontal / vertical layout |
| ListView | Scrollable list |
| Stack | Overlapping widgets |
| TextField | Text input |
| ElevatedButton | Material button |
Hot Reload
Flutter's hot reload lets you see code changes instantly without losing app state โ one of its best features for rapid development.
Create a Flutter app with an AppBar, a TextField, and a button that displays the entered text below.
State Management
StatefulWidget
class Counter extends StatefulWidget {
@override _CounterState createState() => _CounterState();
}
class _CounterState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(children: [
Text("$count"),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text("+"),
),
]);
}
} Provider Pattern
class CartModel extends ChangeNotifier {
List items = [];
void add(String item) { items.add(item); notifyListeners(); }
}
// In widget tree:
ChangeNotifierProvider(create: (_) => CartModel(), child: MyApp());
// In any widget:
var cart = context.watch();
print(cart.items.length); Riverpod (Modern)
final counterProvider = StateProvider((ref) => 0); // In widget: final count = ref.watch(counterProvider); ref.read(counterProvider.notifier).state++;
Build a todo list app using Provider โ add, remove, and toggle completion of items.
Firebase & Backend Integration
Firebase Setup
Add Firebase to your Flutter app using flutterfire configure. This sets up Firebase Core, Authentication, Firestore, and more.
Firestore CRUD
import 'package:cloud_firestore/cloud_firestore.dart';
// Create
await FirebaseFirestore.instance.collection('users').add({
'name': 'Ahmed', 'age': 25
});
// Read
var snapshot = await FirebaseFirestore.instance
.collection('users').get();
for (var doc in snapshot.docs) print(doc.data());
// Real-time listener
FirebaseFirestore.instance.collection('users')
.snapshots().listen((snapshot) { /* update UI */ });Firebase Auth
import 'package:firebase_auth/firebase_auth.dart';
// Sign up
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: "a@b.com", password: "123456");
// Sign in
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: "a@b.com", password: "123456");
// Check auth state
FirebaseAuth.instance.authStateChanges().listen((user) {
if (user != null) print("Logged in: ${user.email}");
});REST API Calls
import 'dart:convert'; import 'package:http/http.dart' as http; Future
Build a chat app screen that reads/writes messages to Firestore in real-time.
Navigation & Routing
Basic Navigation
Navigator.push(context, MaterialPageRoute( builder: (_) => DetailScreen())); Navigator.pop(context); Navigator.pushNamed(context, '/detail');
Passing Data
final result = await Navigator.push(context,
MaterialPageRoute(builder: (_) => DetailScreen(id: 42)));
print("Returned: $result");GoRouter
final router = GoRouter(routes: [
GoRoute(path: '/', builder: (_, __) => HomeScreen()),
GoRoute(path: '/user/:id', builder: (_, s) =>
UserScreen(id: s.pathParameters['id']!)),
]);
context.go('/user/42');Build 3-screen app with named routes, passing data and returning results.
Testing & Debugging
Unit Tests
import 'package:test/test.dart';
void main() {
test('addition', () {
expect(2 + 2, equals(4));
});
}Widget Tests
testWidgets('has title', (tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('My App'), findsOneWidget);
});Flutter DevTools
Use flutter run --debug and open DevTools for widget inspector, performance profiling, and memory analysis.
Write unit tests for a Counter class and widget tests for your app.
Platform Channels
Method Channels
const channel = MethodChannel('com.app/battery');
final level = await channel.invokeMethod('getBatteryLevel');
print("Battery: $level%");Android (Kotlin)
MethodChannel(flutterEngine.dartExecutor, "com.app/battery")
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
result.success(75)
}
}Event Channels (Streams)
Use EventChannel for continuous data streams from native to Flutter (sensors, location updates).
Create a platform channel that gets device battery level and displays it in Flutter.
App Deployment
Build for Release
flutter build apk --release flutter build ios --release flutter build web
App Signing
storeFile=key.jks storePassword=**** keyAlias=upload keyPassword=****
Google Play & App Store
- Create developer accounts ($25 Google, $99 Apple/year)
- Fill store listing, screenshots, description
- Upload AAB (Android) or IPA (iOS)
- Submit for review
Build a release APK, sign it, and prepare a Play Store listing.
Design Patterns in Dart
Singleton
class Database {
Database._();
static final instance = Database._();
}Factory & Builder
class Button {
final String label;
Button._(this.label);
factory Button.primary(String l) => Button._(l);
}Repository Pattern
class UserRepo {
final api = ApiService();
final cache = LocalCache();
Future getUser(int id) async {
try { return await api.fetchUser(id); }
catch (_) { return cache.getUser(id); }
}
} BLoC Pattern
class CounterBloc extends Bloc{ CounterBloc() : super(0) { on ((event, emit) => emit(state + 1)); } }
Refactor your app to use Repository + BLoC pattern for state management.
Capstone Project: Full Flutter App
Project: Task Manager
Build a complete Flutter app with Firebase auth, Firestore, Provider/Riverpod, navigation, and clean architecture.
Features
- Email/password auth
- CRUD tasks with Firestore
- Filter by status
- User profile
- Dark mode
Architecture
lib/ main.dart models/ # Data models services/ # Auth, Firestore, API screens/ # UI screens widgets/ # Reusable widgets blocs/ # State management
Bonus
- Push notifications
- Deploy to Play Store / App Store
- Add widget + integration tests
Build the full app, deploy it, share the GitHub repo!