0% found this document useful (0 votes)
25 views71 pages

DART

The document provides a detailed overview of a Dart and Flutter course, aimed at teaching app development across multiple platforms using Flutter. It covers essential topics such as Dart programming language fundamentals, including variables, operators, and null safety, along with practical applications like a currency converter, weather app, and shopping app. The course emphasizes building a strong foundation in Dart before progressing to Flutter's advanced features and state management.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views71 pages

DART

The document provides a detailed overview of a Dart and Flutter course, aimed at teaching app development across multiple platforms using Flutter. It covers essential topics such as Dart programming language fundamentals, including variables, operators, and null safety, along with practical applications like a currency converter, weather app, and shopping app. The course emphasizes building a strong foundation in Dart before progressing to Flutter's advanced features and state management.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 71

This is a comprehensive breakdown of the topics you requested from the provided YouTube

video, including explanations, code snippets, and examples. This information should help you
ace these topics and confidently explain them to your juniors.

🎥 Course Overview (00:00:00)


The video offers a comprehensive course on Dart and Flutter, designed for both
non-programmers and those with prior coding experience. The primary goal is to teach app
development for multiple platforms (Android, iOS, web, and desktop) from a single
codebase using the Flutter framework.

💱
The course is structured around building three practical applications:

🌦️
●​ A simple currency converter app .

🛍️
●​ A weather app that fetches live data from the web.
●​ A shopping app , which is the most complex, covering advanced topics like State
Management and navigation.
Throughout these projects, you'll learn various aspects of Flutter, including widgets, layouts,
responsive design, and State Management. The video also provides an overview of the
internal workings of Flutter, such as rendering and State Management behind the scenes.
Crucially, it emphasizes building a solid foundation in Dart before diving into Flutter.

🎯 What is Dart? (00:02:16)


Dart is an open-source, client-optimized programming language developed by Google.
It's designed for building fast apps on any platform, specifically optimized for UI development.
●​ Similarities: Dart shares syntax similarities with popular languages like JavaScript and
Java, making it easier for developers familiar with these languages to pick up.
●​ Why Dart for Flutter? The primary reason many choose Dart is its tight integration with
Flutter. Flutter exclusively uses Dart, enabling developers to build applications for iOS,
Android, web, and desktop from a single codebase.
●​ Beyond UI: While popular for Flutter, Dart isn't limited to UI development. It can also be
used for backend development (servers) and other general-purpose programming
tasks.
●​ Compilation Processes: Dart features two main compilation processes:
○​ Just-In-Time (JIT) Compilation: Used during development, allowing for fast
iteration and immediate feedback (hot reload). Changes in code are seen
instantly without long recompilation times.
○​ Ahead-Of-Time (AOT) Compilation: Used for deployment, compiling Dart code
into optimized native machine code. This results in faster execution and
improved performance for released applications.
🛠️ Dart SDK (00:03:52)
An SDK (Software Development Kit) is a "kit for software development." For Dart, the SDK
provides the necessary tools to write, test, and run Dart code.
There are three primary ways to access the Dart SDK:
1.​ Installing Flutter SDK: The Flutter SDK includes the Dart SDK. This is the
recommended approach if you plan to build Flutter applications, as it provides
everything you need for both Dart and Flutter projects.
2.​ Installing Dart SDK from dart.dev: You can download and install the standalone Dart
SDK. This allows you to write and run Dart code but not build Flutter applications.
3.​ Using DartPad: For quick experimentation and learning, DartPad (available at
dartpad.dev) allows you to write and execute Dart code directly in your web browser
without any local installation. This is initially used in the course to focus solely on Dart
fundamentals.

📝 Print Statement (00:06:57)


The print() function is a fundamental part of Dart (and most programming languages) used to
display output to the console.

Code Example:

Dart

void main() {​
print('Hello World!'); // Prints a string​
print(123); // Prints an integer​
print(true); // Prints a boolean​
}​

Output:

Hello World!​
123​
true​

Important Notes:

●​ Strings: Textual data must be enclosed in single quotes (') or double quotes ("). If
not, Dart will interpret it as a variable name, leading to an error if the variable isn't
defined.
●​ Numbers/Booleans: Numeric and boolean values can be printed directly without
quotes.
●​ Semicolon: Every statement in Dart (like a print() call or variable declaration) must end
with a semicolon (;). This acts like a full stop, indicating the end of a command.

➕ Operators (00:09:59)
Operators are special symbols that perform operations on values and variables.

Types of Operators Covered:

1.​ Arithmetic Operators: Perform mathematical calculations.


○​ + (addition)
○​ - (subtraction)
○​ * (multiplication - use *, not x)
○​ / (division - returns a double)
○​ ~/ (truncating division - returns an int by discarding the decimal part)
○​ % (modulo - returns the remainder of a division)
Code Example:Dart​
void main() {​
print(3 + 2); // Output: 5​
print(10 - 4); // Output: 6​
print(5 * 3); // Output: 15​
print(10 / 3); // Output: 3.3333333333333335 (double)​
print(10 ~/ 3); // Output: 3 (integer)​
print(10 % 3); // Output: 1​
}​

2.​ Operator Precedence (BODMAS/PEMDAS): Dart follows standard mathematical rules


for the order of operations:
○​ Brackets (Parentheses)
○​ Orders (Exponents/Powers)
○​ Division / Multiplication (from left to right)
○​ Addition / Subtraction (from left to right)
Code Example:Dart​
void main() {​
print(5 + 2 * 3); // Output: 11 (2 * 3 is calculated first)​
print((5 + 2) * 3); // Output: 21 (5 + 2 is calculated first due to brackets)​
}​

3.​ String Concatenation: The + operator can also be used to join strings (text).​
Code Example:​
Dart​
void main() {​
print('Hello ' + 'World'); // Output: Hello World​
}​

○​ Important: Trying to use + with a string and a number directly will result in an
error if not handled properly. Dart treats numbers inside quotes as strings.

✍️ Comments (00:14:39)
Comments are non-executable lines of text in your code, used to explain what the code does,
make notes, or temporarily disable code. They are ignored by the Dart compiler.

Types of Comments:

1.​ Single-line Comment: Starts with two forward slashes (//). Everything from // to the
end of the line is a comment.​
Dart​
// This is a single-line comment​
print('Hello'); // This also works​

2.​ Multi-line Comment: Enclosed within /* and */. Useful for longer explanations or
commenting out blocks of code.​
Dart​
/*​
This is a​
multi-line comment.​
It can span several lines.​
*/​
print('World');​
3.​ Documentation Comment: Starts with three forward slashes (///) or /** ... */. Used to
generate API documentation. When you hover over a function or class with
documentation comments in an IDE, the description appears.​
Dart​
/// This is a function that prints a greeting.​
/// It takes no parameters.​
void greet() {​
print('Greetings!');​
}​

IDE Shortcut:

●​ You can often select a block of code and press Ctrl + / (Windows/Linux) or Cmd + /
(Mac) to toggle comments (add or remove // for each line).

📦 Variables (00:17:31)
Variables are named storage locations that hold data. Their values can vary (change) during
program execution.

Why use Variables?

●​ Data Storage: To store values that might be used multiple times or need to be
modified.
●​ Flexibility: Allows changing data without altering every instance in the code (e.g.,
updating a price variable once instead of 100 times).
●​ Readability: Gives meaningful names to data, making code easier to understand.

Declaring Variables (Syntax):

DataType VariableName = Value;​


●​ DataType: Specifies the type of data the variable will hold (e.g., int, String).
●​ VariableName: A unique identifier for the variable (e.g., firstName, age). Follows
camelCase convention (first word lowercase, subsequent words start with uppercase).
●​ = (Assignment Operator): Assigns a Value to the VariableName.
●​ Value: The actual data being stored.

Data Types (00:22:10):

●​ int (Integer): Stores whole numbers (positive, negative, or zero) without decimal points.​
Dart​
int age = 30;​
int temperature = -5;​

●​ double: Stores numbers with decimal points (floating-point numbers).​


Dart​
double price = 19.99;​
double pi = 3.14159;​

○​ Note: double can also store whole numbers, e.g., double wholeNum = 10.0;
●​ String: Stores sequences of characters (text). Must be enclosed in single (') or double
(") quotes.​
Dart​
String name = 'Alice';​
String message = "Hello, Dart!";​

●​ bool (Boolean): Stores true or false values. Used for logical conditions.​
Dart​
bool isLoggedIn = true;​
bool hasPermission = false;​

○​ Convention: Often prefixed with "is" or "has" (e.g., isAdult, hasData).


●​ dynamic: A special type that can hold any type of value. Its type is determined at
runtime.​
Dart​
dynamic unknownValue = 10;​
unknownValue = "Dart";​
unknownValue = true;​

○​ Warning: While flexible, dynamic disables Dart's strong type checking, which
can lead to runtime errors that a static type (int, String) would catch at
compile-time. Avoid dynamic unless absolutely necessary.
Reassigning Variable Values (Mutability):

Once declared, var variables can be reassigned with new values of the same data type.

Dart

void main() {​
int score = 100;​
print(score); // Output: 100​

score = 150; // Reassigning the value​
print(score); // Output: 150​

// score = 'high score'; // Error: A value of type 'String' can't be assigned to a variable of type
'int'.​
}​

Shorthand Assignment Operators:

Simplify operations where a variable's value is updated based on its current value.
●​ += (add and assign)
●​ -= (subtract and assign)
●​ *= (multiply and assign)
●​ /= (divide and assign)
●​ ++ (increment by 1)
●​ -- (decrement by 1)
Code Example:

Dart

void main() {​
int count = 5;​
count += 3; // Equivalent to: count = count + 3; (count is now 8)​
print(count); // Output: 8​

count--; // Equivalent to: count = count - 1; (count is now 7)​
print(count); // Output: 7​
}​

String Interpolation (00:41:09):

A convenient way to embed expressions (variables, calculations, function calls) directly within
a string.
●​ Syntax:
○​ $variableName for a single variable.
○​ ${expression} for more complex expressions.
Code Example:

Dart

void main() {​
String name = 'Bob';​
int age = 25;​
String message = 'Hello, my name is $name and I am $age years old.';​
print(message); // Output: Hello, my name is Bob and I am 25 years old.​

print('Next year I will be ${age + 1} years old.'); // Output: Next year I will be 26 years old.​
print('My name has ${name.length} letters.'); // Output: My name has 3 letters.​

// To print a literal dollar sign, use a backslash:​
print('The price is \$10.00'); // Output: The price is $10.00​
}​

Multi-line Strings (00:45:18):

Enclose strings in triple single quotes (''') or triple double quotes (""") to span multiple lines.
Code Example:

Dart

void main() {​
String poem = '''​
Roses are red,​
Violets are blue,​
Dart is awesome,​
And so are you!​
''';​
print(poem);​

// Using \n for new lines in a single-line string​
String singleLineWithNewlines = 'First line.\nSecond line.';​
print(singleLineWithNewlines);​
}​

Type Inference (var, final, const) (00:49:00):

Dart's type system can often infer the type of a variable based on its initial value.
●​ var:
○​ Inferred type. The variable's type is set once at initialization and cannot change.
○​ Its value can be reassigned.
Dart​
var count = 10; // Inferred as int​
count = 20; // Valid​
// count = 'thirty'; // Error: A value of type 'String' can't be assigned to a variable of type
'int'.​

●​ final:
○​ Inferred type. The variable's value can only be set once.
○​ It's a runtime constant: its value is determined when the code runs, but it cannot
be changed after that first assignment.
○​ Common for values fetched from an API or calculated at runtime.
Dart​
final currentTime = DateTime.now(); // Value set at runtime​
// currentTime = DateTime.now(); // Error: The final variable 'currentTime' can only be set
once.​

●​ const:
○​ Inferred type. The variable's value must be a compile-time constant.
○​ Its value must be known before the program runs.
○​ Cannot be reassigned.
○​ Offers performance optimizations.
Dart​
const pi = 3.14159; // Value known at compile time​
// const now = DateTime.now(); // Error: Constant expression expected. DateTime.now() is
runtime.​

Nullable Variables (Null Safety) (01:00:19):

Dart's sound null safety system ensures that variables cannot be null unless explicitly declared
as nullable. This prevents common "null reference" errors.
●​ ? (Nullable Type): Appended to a type to indicate that the variable can hold null in
addition to its declared type.​
Dart​
String? message; // message can be null or a String​
int? count = null; // count can be null or an int​

○​ If you don't initialize a nullable variable, it defaults to null.


○​ If you don't initialize a non-nullable variable, Dart will show a compile-time error.
●​ Null-Aware Operators:
○​ ?. (Null-aware access): Accesses a property or method only if the object is not
null. If the object is null, the entire expression evaluates to null.​
Dart​
String? name;​
print(name?.length); // Output: null (no error)​

name = 'Alice';​
print(name?.length); // Output: 5​

○​ ?? (Null-coalescing operator): Provides a default value if the expression on its


left is null.​
Dart​
String? user = null;​
String displayName = user ?? 'Guest';​
print(displayName); // Output: Guest​

user = 'Bob';​
displayName = user ?? 'Guest';​
print(displayName); // Output: Bob​

○​ ! (Null assertion operator): Asserts that an expression is not null. Use with
caution, as if the value is null, it will throw a runtime error.​
Dart​
String? maybeName = 'Charlie';​
print(maybeName!.toUpperCase()); // Output: CHARLIE​

maybeName = null;​
// print(maybeName!.toUpperCase()); // Runtime error: Null check operator used
on a null value​

○​ ??= (Null-aware assignment): Assigns a value to a variable only if it is currently


null.​
Dart​
String? greeting;​
greeting ??= 'Hello'; // greeting is now 'Hello'​
print(greeting);​

greeting ??= 'Hi'; // greeting remains 'Hello' (not null)​
print(greeting);​

🚦 Control Flow (01:11:35)


Control flow statements determine the order in which instructions are executed in a program.

1. If Statements (if, else if, else)

Execute code blocks conditionally based on boolean expressions.


●​ Syntax:​
Dart​
if (condition1) {​
// Code to execute if condition1 is true​
} else if (condition2) {​
// Code to execute if condition2 is true (and condition1 was false)​
} else {​
// Code to execute if all conditions are false​
}​

●​ Conditions: Must be a boolean expression (evaluates to true or false).


●​ Execution Flow: Only one block (if, else if, or else) will execute. Once a condition is met,
subsequent else if and else blocks are skipped.
Code Example:

Dart
void main() {​
int age = 20;​

if (age >= 21) {​
print('You can legally drink alcohol.');​
} else if (age >= 18) {​
print('You are an adult, but cannot drink alcohol.');​
} else {​
print('You are a minor.');​
}​

// Output: You are an adult, but cannot drink alcohol.​
}​

2. Relational Operators (used in conditions)

Compare two values and return a boolean.


●​ == (equal to)
●​ != (not equal to)
●​ > (greater than)
●​ < (less than)
●​ >= (greater than or equal to)
●​ <= (less than or equal to)
Code Example:

Dart

void main() {​
int num1 = 10;​
int num2 = 5;​

print(num1 == num2); // false​
print(num1 != num2); // true​
print(num1 > num2); // true​
}​

3. Logical Operators (combining conditions)


Combine boolean expressions.
●​ && (AND): Returns true if both conditions are true.
●​ || (OR): Returns true if at least one condition is true.
●​ ! (NOT): Reverses the boolean value of a condition.
Code Example:

Dart

void main() {​
bool hasLicense = true;​
int speed = 70;​

if (hasLicense && speed < 80) { // true && true = true​
print('You are driving safely.');​
}​

if (speed > 100 || !hasLicense) { // false || false = false (if hasLicense is true)​
print('You are speeding or do not have a license.');​
}​
}​

4. Scope (01:26:38)

Defines the region of code where a variable is accessible. Variables declared inside a block
(like an if statement or function) are only accessible within that block.
Code Example:

Dart

void main() {​
String globalMessage = 'Visible everywhere in main';​

if (true) {​
String localMessage = 'Visible only inside this if block';​
print(globalMessage); // Valid​
print(localMessage); // Valid​
}​

// print(localMessage); // Error: Undefined name 'localMessage'.​
}​

5. Ternary Operator (01:29:45)

A shorthand if-else statement for assigning a value or returning an expression.


●​ Syntax:​
condition ? expressionIfTrue : expressionIfFalse;​

Code Example:

Dart

void main() {​
int score = 85;​
String result = (score >= 60) ? 'Pass' : 'Fail';​
print(result); // Output: Pass​

String status = (score < 50) ? 'Needs improvement' : 'Good job!';​
print(status); // Output: Good job!​
}​

6. Switch Statements (01:32:06)

Used for performing different actions based on different conditions for a single variable's
value. It checks for equality.
●​ Syntax:​
Dart​
switch (variable) {​
case value1:​
// Code if variable == value1​
// break; // Not always required in Dart 3+ for non-empty cases​
case value2:​
// Code if variable == value2​
break;​
default:​
// Code if no case matches​
}​

●​ case: Followed by a value to compare with the switch variable.


●​ break: (Prior to Dart 3.0, break was required at the end of each non-empty case to
prevent "fall-through." In Dart 3.0 and later, break is only required if a case block is
empty and you don't want it to fall through.)
●​ default: (Optional) Executes if no case matches.
●​ when (Dart 3.0+): Allows adding additional conditions to a case.
Code Example:

Dart

void main() {​
String day = 'Monday';​

switch (day) {​
case 'Monday':​
print('Start of the week.');​
case 'Friday': // No break here, will fall through if previous case was empty​
print('Almost weekend!');​
break;​
default:​
print('Another day.');​
}​
// Output: Start of the week.​

int score = 95;​
switch (score) {​
case 100:​
case 90 when score > 90: // Dart 3.0+ 'when' clause​
print('Excellent!');​
break;​
case 80:​
print('Very Good!');​
break;​
default:​
print('Keep learning.');​
}​
// Output: Excellent!​
}​
🏋️ Exercise 1 (01:37:52)
Challenge: Develop a program to calculate the shipping cost based on the destination zone
and the weight of the package.
Given:
●​ destinationZone (String): e.g., 'PQR', 'XYZ', 'ABC'
●​ weightInKgs (double): e.g., 6.0
Conditions:
●​ If destinationZone is 'XYZ': Shipping cost is $5 per kilogram.
●​ If destinationZone is 'ABC': Shipping cost is $7 per kilogram.
●​ If destinationZone is 'PQR': Shipping cost is $10 per kilogram.
●​ If destinationZone is none of the above: Display an error message "Invalid destination
Zone".

Solution using if-else if-else:

Dart

void main() {​
String destinationZone = 'PQR';​
double weightInKgs = 6.0;​
double shippingCost = 0.0;​
String message = '';​

if (destinationZone == 'XYZ') {​
shippingCost = weightInKgs * 5;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
} else if (destinationZone == 'ABC') {​
shippingCost = weightInKgs * 7;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
} else if (destinationZone == 'PQR') {​
shippingCost = weightInKgs * 10;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
} else {​
message = 'Invalid destination Zone';​
}​

print(message);​
}​

Test with destinationZone = 'PQR', weightInKgs = 6.0:

Shipping cost for PQR: $60.0​

Test with destinationZone = 'NYC', weightInKgs = 4.5:

Invalid destination Zone​

Solution using switch statement:

Dart

void main() {​
String destinationZone = 'ABC';​
double weightInKgs = 4.5;​
double shippingCost = 0.0;​
String message = '';​

switch (destinationZone) {​
case 'XYZ':​
shippingCost = weightInKgs * 5;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
break; // break is needed here if code blocks are not empty in older Dart versions​
case 'ABC':​
shippingCost = weightInKgs * 7;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
break;​
case 'PQR':​
shippingCost = weightInKgs * 10;​
message = 'Shipping cost for $destinationZone: \$$shippingCost';​
break;​
default:​
message = 'Invalid destination Zone';​
}​

print(message);​
}​

Test with destinationZone = 'ABC', weightInKgs = 4.5:

Shipping cost for ABC: $31.5​

🔄 Loops (01:46:06)
Loops are used to execute a block of code repeatedly as long as a certain condition is met.

1. for Loop

Used when you know exactly how many times you want to loop.
●​ Syntax:​
Dart​
for (initialization; condition; increment/decrement) {​
// Code to execute repeatedly​
}​

○​ Initialization: Executed once at the beginning (e.g., int i = 0;).


○​ Condition: Checked before each iteration. If true, the loop continues. If false, the
loop terminates.
○​ Increment/Decrement: Executed after each iteration (e.g., i++, i--, i += 2).
Code Example:

Dart

void main() {​
// Print "Hello World!" 5 times with index​
for (int i = 0; i < 5; i++) {​
print('Hello World! ${i + 1}');​
}​
// Output:​
// Hello World! 1​
// Hello World! 2​
// Hello World! 3​
// Hello World! 4​
// Hello World! 5​

String greeting = 'Dart';​
// Loop through characters of a string​
for (int i = 0; i < greeting.length; i++) {​
print(greeting[i]); // Accessing character by index​
}​
// Output:​
// D​
// a​
// r​
// t​
}​

2. for-in Loop (Enhanced for loop)

Used for iterating over collections (like Lists, Sets, Maps). Simplifies looping when you need
the elements themselves, not their indices.
●​ Syntax:​
Dart​
for (DataType variable in collection) {​
// Code to execute for each element​
}​

Code Example:

Dart

void main() {​
List<String> fruits = ['Apple', 'Banana', 'Cherry'];​

for (String fruit in fruits) {​
print('I like $fruit.');​
}​
// Output:​
// I like Apple.​
// I like Banana.​
// I like Cherry.​
}​

3. while Loop

Used when you don't know the exact number of iterations, but want to loop as long as a
condition remains true. It's an entry-controlled loop (checks condition before the first
execution).
●​ Syntax:​
Dart​
while (condition) {​
// Code to execute​
// Must include logic to eventually make the condition false​
}​

●​ Caution: Without proper increment/decrement, while loops can easily become infinite
loops, crashing your program.
Code Example:

Dart

void main() {​
int count = 0;​
while (count < 3) {​
print('Counting: $count');​
count++; // Increment to eventually make condition false​
}​
// Output:​
// Counting: 0​
// Counting: 1​
// Counting: 2​
}​
4. do-while Loop

Similar to while, but guarantees that the loop body executes at least once before the
condition is checked. It's an exit-controlled loop.
●​ Syntax:​
Dart​
do {​
// Code to execute at least once​
// Must include logic to eventually make the condition false​
} while (condition);​

Code Example:

Dart

void main() {​
int i = 5;​
do {​
print('Number: $i');​
i++;​
} while (i < 3); // Condition is false, but loop ran once​

// Output: Number: 5​
}​

5. break and continue Keywords

●​ break: Immediately terminates the innermost loop (or switch statement) and continues
execution after the loop.​
Code Example:​
Dart​
void main() {​
for (int i = 0; i < 5; i++) {​
if (i == 2) {​
break; // Exit loop when i is 2​
}​
print(i);​
}​
// Output:​
// 0​
// 1​
}​

●​ continue: Skips the rest of the current loop iteration and proceeds to the next iteration.​
Code Example:​
Dart​
void main() {​
for (int i = 0; i < 5; i++) {​
if (i == 2) {​
continue; // Skip printing when i is 2​
}​
print(i);​
}​
// Output:​
// 0​
// 1​
// 3​
// 4​
}​

⚙️ Functions (02:10:49)
Functions are reusable blocks of code that perform a specific task. They promote modularity,
readability, and code reuse.

Function Definition (Syntax):

Dart

ReturnType functionName(ParameterType parameterName, ...) {​


// Function body​
// ...​
return value; // Optional, if ReturnType is not void​
}​
●​ ReturnType: Specifies the type of value the function will return (e.g., int, String, void).
○​ void: Means the function does not return any value.
○​ Returning null vs. void: void means nothing is returned. A function returning a
nullable type (e.g., String?) can explicitly return null.
●​ functionName: A unique identifier for the function (follows camelCase).
●​ Parameters (Optional): Values passed into the function (arguments).
●​ Function Body: The code executed when the function is called.
●​ return statement: (Optional) Specifies the value to send back from the function.

Calling Functions:

To execute a function, simply use its name followed by parentheses () and pass any required
arguments.
Code Example:

Dart

// 1. Function with no parameters and void return type​


void sayHello() {​
print('Hello!');​
}​

// 2. Function with parameters and a return type​
String greet(String name) {​
return 'Hi, $name!';​
}​

// 3. Function with multiple parameters​
int add(int a, int b) {​
return a + b;​
}​

void main() {​
sayHello(); // Calling sayHello()​
String greeting = greet('Alice'); // Calling greet() and storing its return value​
print(greeting); // Output: Hi, Alice!​
print(add(10, 5)); // Calling add() directly within print()​

// Function calls as part of expressions​
int result = add(add(1, 2), 3); // Nested function calls​
print(result); // Output: 6​
}​

Returning Multiple Values (Records - Dart 3.0+) (02:19:44):

Dart 3.0 introduced records, allowing functions to return multiple values as a single,
lightweight object. Think of them as anonymous, immutable data structures.
●​ Syntax:​
Dart​
(Type1 field1, Type2 field2, ...) functionName() {​
return (value1, value2, ...);​
}​

●​ Accessing Record Fields: You can access fields by their position (.$1, .$2, etc.) or by
name if defined.
Code Example:

Dart

// Function returning a record​


(int, String, bool) getUserInfo() {​
return (25, 'Bob', true);​
}​

// Function returning a named record (for better readability)​
({int age, String name, bool isActive}) getUserDetails() {​
return (age: 30, name: 'Charlie', isActive: false);​
}​

void main() {​
var info = getUserInfo();​
print('Age: ${info.$1}, Name: ${info.$2}, Active: ${info.$3}'); // Output: Age: 25, Name: Bob,
Active: true​

var details = getUserDetails();​
print('Age: ${details.age}, Name: ${details.name}, Active: ${details.isActive}'); // Output: Age:
30, Name: Charlie, Active: false​
}​
Function Parameters:

Functions can accept parameters in different ways:


1.​ Positional Parameters:
○​ Passed in the order they are declared.
○​ Cannot be skipped.
○​ Default behavior for parameters.
Dart​
void display(String msg, int count) {​
print('$msg $count');​
}​

void main() {​
display('Item', 5); // Must pass 'Item' first, then 5​
}​

2.​ Named Parameters (02:35:12):


○​ Enclosed in curly braces {} in the function signature.
○​ Passed by their name, order doesn't matter.
○​ By default, they are optional and nullable.
○​ To make them mandatory, use the required keyword (Dart 2.12+).
Code Example:Dart​
void greetUser({String? firstName, String? lastName}) {​
print('Hello, ${firstName ?? 'Guest'} ${lastName ?? ''}!');​
}​

void main() {​
greetUser(firstName: 'Alice', lastName: 'Smith'); // Order doesn't matter​
greetUser(lastName: 'Doe'); // firstName is null, uses default 'Guest'​
}​

// With 'required' keyword for mandatory named parameters​
void createUser({required String username, required String email}) {​
print('User: $username, Email: $email');​
}​

// createUser(username: 'test'); // Error: The named parameter 'email' is required.​

3.​ Combining Positional and Named Parameters (02:40:07):


○​ Positional parameters come first, followed by named parameters.
Code Example:Dart​
void orderPizza(String crust, String size, {required List<String> toppings, bool extraCheese =
false}) {​
print('Ordering a $size $crust pizza with ${toppings.join(', ')}.');​
if (extraCheese) {​
print('With extra cheese!');​
}​
}​

void main() {​
orderPizza('thin', 'large', toppings: ['pepperoni', 'mushrooms']);​
orderPizza('thick', 'medium', toppings: ['pineapple'], extraCheese: true);​
}​

Arrow Functions (=>) (02:45:58):

A shorthand syntax for functions that contain a single expression (not a block of
statements). The return keyword is implicit.
●​ Syntax:​
Dart​
ReturnType functionName(parameters) => expression;​

Code Example:

Dart

int multiply(int a, int b) => a * b;​



void main() {​
print(multiply(4, 5)); // Output: 20​
}​

🏗️ Classes (02:46:53)
Classes are blueprints or templates for creating objects. They define the structure
(properties/variables) and behavior (methods/functions) that objects of that class will have.

Analogy: Bakery (02:47:11)

●​ Class: Cake (the recipe/blueprint)


○​ Properties: flavor, size, frostingType
○​ Methods: bake(), decorate(), slice()
●​ Object (Instance): An actual cake (e.g., a "Chocolate Cake with Vanilla Frosting")
○​ Each object has its own unique set of values for the properties.

Class Definition (Syntax):

Dart

class ClassName {​
// Properties (variables)​
DataType propertyName;​

// Constructor (optional)​
ClassName(parameters) {​
// Initialization logic​
}​

// Methods (functions)​
ReturnType methodName(parameters) {​
// Method body​
}​
}​

●​ ClassName: Follows PascalCase convention (all words start with uppercase).


●​ Properties: Variables that define the characteristics of an object.
●​ Methods: Functions that define the actions or behaviors of an object.

Creating Objects (Instantiation) (02:53:23):

To use a class, you create an instance (an object) of that class.

Dart

class Cookie {​
String shape = 'Circle';​
double size = 15.2; // in cm​

void bake() {​
print('Baking has started!');​
}​

bool isCooling() {​
return false;​
}​
}​

void main() {​
// Create an instance (object) of the Cookie class​
Cookie myCookie = Cookie(); // 'new' keyword is optional in Dart​

print(myCookie.shape); // Accessing a property: Output: Circle​
myCookie.bake(); // Calling a method: Output: Baking has started!​
}​

Constructors (03:06:56):

Special methods within a class that are automatically called when an object is created. They
are used to initialize the object's properties.
1.​ Default Constructor: If you don't define a constructor, Dart provides a default,
no-argument constructor.
2.​ Custom Constructor (Positional):​
Dart​
class Cookie {​
String shape;​
double size;​

// Custom positional constructor​
Cookie(this.shape, this.size); // Shorthand for assigning parameters to properties​

void bake() {​
print('$shape cookie of size $size is baking!');​
}​
}​

void main() {​
Cookie roundCookie = Cookie('Round', 10.5);​
roundCookie.bake(); // Output: Round cookie of size 10.5 is baking!​
}​

○​ this keyword: Refers to the current instance of the class. this.shape refers to the
shape property of the Cookie object.
○​ Shorthand: Cookie(this.shape, this.size); is syntactic sugar for Cookie(String
shape, double size) { this.shape = shape; this.size = size; }
3.​ Named Constructors:
○​ Allow you to create multiple constructors for a single class, each with a different
name.
○​ Useful for creating objects in different ways or with different initializations.
Dart​
class Point {​
double x, y;​

Point(this.x, this.y); // Main constructor​

// Named constructor for origin point​
Point.origin() : this(0.0, 0.0); // Calls the main constructor​

// Named constructor from a map​
Point.fromMap(Map<String, double> map) : x = map['x']!, y = map['y']!;​
}​

void main() {​
Point p1 = Point(10, 20);​
Point origin = Point.origin(); // Using named constructor​
Point p2 = Point.fromMap({'x': 5.0, 'y': 7.0});​
}​

4.​ Constant Constructors:


○​ Marked with const.
○​ Allows creating compile-time constant objects.
○​ All instance variables must be final.
○​ Improves performance in Flutter by preventing unnecessary widget rebuilds.
Dart​
class ImmutablePoint {​
final double x;​
final double y;​

const ImmutablePoint(this.x, this.y);​
}​

void main() {​
const p1 = ImmutablePoint(1.0, 2.0);​
const p2 = ImmutablePoint(1.0, 2.0);​
print(p1 == p2); // Output: true (because they are compile-time constants with identical
values)​
}​

Immutable Classes (03:18:45):

A class whose instances cannot be modified after they are created. All properties of an
immutable class must be declared as final.

Dart

class ReadOnlyData {​
final String id;​
final String value;​

const ReadOnlyData(this.id, this.value);​

// No methods to change id or value​
}​

void main() {​
final data = ReadOnlyData('1', 'Initial');​
// data.value = 'New'; // Error: 'value' is a final field.​
}​

Private Variables & Methods (03:23:21):

In Dart, there is no private keyword. Instead, members (variables, methods, classes) are made
private to their library (file) by prefixing their names with an underscore (_).
●​ If a variable _name is in my_file.dart, it can be accessed by any other code within
my_file.dart, but not from another file that imports my_file.dart.
Code Example (my_class.dart):

Dart

// my_class.dart​
class MyClass {​
String publicProperty = 'Public';​
String _privateProperty = 'Private to my_class.dart'; // Private to this file​

void publicMethod() {​
print('Public method called. Accessing $_privateProperty');​
}​

void _privateMethod() { // Private method to this file​
print('Private method called.');​
}​
}​

void main() {​
MyClass obj = MyClass();​
print(obj.publicProperty); // Valid​
print(obj._privateProperty); // Valid (within the same file)​
obj.publicMethod();​
obj._privateMethod();​
}​

Getters and Setters (03:27:43):

Special methods that provide read (get) and write (set) access to an object's properties, often
for private variables. They allow you to control how properties are accessed and modified,
adding validation or computation.
●​ Getter (read-only access):​
Dart​
class Circle {​
double _radius; // Private property​

Circle(this._radius);​

double get diameter => _radius * 2; // Getter for diameter​

// Custom getter with more logic​
bool get isLarge => _radius > 10;​
}​

void main() {​
Circle c = Circle(5);​
print(c.diameter); // Output: 10.0​
print(c.isLarge); // Output: false​
}​

●​ Setter (write-only or controlled write access):​


Dart​
class Temperature {​
double _celsius;​

Temperature(this._celsius);​

// Setter for Celsius, with validation​
set celsius(double value) {​
if (value < -273.15) { // Absolute zero​
_celsius = -273.15;​
} else {​
_celsius = value;​
}​
}​

double get celsius => _celsius;​
}​

void main() {​
Temperature temp = Temperature(25);​
print(temp.celsius); // Output: 25.0​

temp.celsius = -300; // Value is clamped by setter​
print(temp.celsius); // Output: -273.15​
}​

Static Variables and Functions (03:33:17):

Members declared with the static keyword belong to the class itself, not to any specific
instance (object) of the class.
●​ Static Variables: Shared by all instances of the class. Accessed directly using
ClassName.variableName.
●​ Static Methods: Can be called directly on the class without creating an object. They
cannot access instance-specific (non-static) members.
Code Example:

Dart

class Constants {​
static const String appName = 'MyAwesomeApp'; // Static variable​
static int _counter = 0; // Private static variable​

static void incrementCounter() { // Static method​
_counter++;​
print('Counter: $_counter');​
}​

static int get currentCounter => _counter; // Static getter​
}​

void main() {​
print(Constants.appName); // Access static variable directly​
Constants.incrementCounter(); // Call static method​
print(Constants.currentCounter); // Access static getter​
}​

●​ Use Cases: Utility functions, constants, factory methods that don't need object state.

👨‍👩‍👧‍👦 Inheritance (03:41:10)


Inheritance is an OOP mechanism where a new class (subclass/child class) can inherit
properties and methods from an existing class (superclass/parent class). This promotes code
reuse and establishes an "is-a" relationship.

extends Keyword:

Used to create a subclass that inherits from a superclass.


●​ Syntax:​
Dart​
class Subclass extends Superclass {​
// Subclass-specific properties and methods​
}​
Code Example:

Dart

class Vehicle { // Superclass​


int speed = 0;​
bool isEngineWorking = false;​

void accelerate() {​
speed += 10;​
print('Vehicle accelerating to $speed km/h');​
}​
}​

class Car extends Vehicle { // Subclass inherits from Vehicle​
int numberOfWheels = 4;​

void honk() {​
print('Beep beep!');​
}​
}​

class Bike extends Vehicle { // Another subclass​
int numberOfWheels = 2;​
// Bike implicitly has speed, isEngineWorking, accelerate()​
}​

void main() {​
Car myCar = Car();​
print(myCar.speed); // Inherited: Output: 0​
myCar.accelerate(); // Inherited: Output: Vehicle accelerating to 10 km/h​
myCar.honk(); // Car-specific: Output: Beep beep!​
print(myCar.numberOfWheels); // Car-specific: Output: 4​
}​

Method Overriding (@override) (03:56:47):

A subclass can provide its own implementation for a method that is already defined in its
superclass. The @override annotation is optional but highly recommended for clarity and
compile-time checks.

Dart

class Dog extends Animal {​


// Override the sound() method from Animal​
@override​
void makeSound() {​
print('Woof!');​
}​
}​

void main() {​
Dog myDog = Dog();​
myDog.makeSound(); // Output: Woof!​
}​

super Keyword (04:04:55):

Used within a subclass to refer to members (properties, methods, constructors) of its


immediate superclass.
●​ Calling Superclass Method: super.methodName()
●​ Calling Superclass Constructor: super(arguments) (often implicitly called if not
explicitly defined).
Code Example:

Dart

class Animal {​
String species;​
Animal(this.species);​
void makeSound() {​
print('Generic animal sound');​
}​
}​

class Cat extends Animal {​
Cat(String species) : super(species); // Calls Animal's constructor​

@override​
void makeSound() {​
super.makeSound(); // Call the superclass's makeSound()​
print('Meow!');​
}​
}​

void main() {​
Cat myCat = Cat('Feline');​
myCat.makeSound();​
// Output:​
// Generic animal sound​
// Meow!​
}​

Dart's Single Inheritance (03:52:48):

Dart does not support multiple inheritance (a class cannot directly extend from more than
one superclass). This prevents ambiguity problems (the "diamond problem") where a class
might inherit conflicting members from multiple parents.
●​ However, a class can be part of an inheritance chain (e.g., Class C extends Class B,
Class B extends Class A).

🤝 implements Keyword (03:59:58)


The implements keyword is used to enforce a contract on a class. When a class implements
another class, it must provide concrete implementations for all the methods and
properties of the implemented class. It signifies a "has-a contract" or "behaves-like-a"
relationship, rather than an "is-a" relationship like extends.
●​ Mechanism: When a class implements another, the implementing class essentially
treats the implemented class as an interface.
●​ No Inheritance of Implementation: Unlike extends, implements does not inherit the
implementation of methods. It only inherits the method signatures and property
declarations.
Code Example:

Dart
class Drivable { // This class acts as an interface​
void drive() {​
// No implementation here, just a contract​
}​
int get wheels; // Contract for a getter​
}​

class Car implements Drivable { // Car must implement all Drivable members​
@override​
void drive() {​
print('Car is driving.');​
}​

@override​
int get wheels => 4; // Must provide an implementation for wheels​
}​

// class Bike implements Drivable {​
// // Error: Missing concrete implementation of 'Drivable.drive' and 'Drivable.wheels'.​
// }​

extends vs. implements:

●​ extends: Inherits both interface and implementation. Used for "is-a" relationships. A
subclass can call super to reuse parent's logic.
●​ implements: Inherits only the interface (contract). Used for "has-a contract"
relationships. The implementing class must provide all implementations itself; super
cannot be used to call implemented methods from the "interface" class directly.

👻 Abstract Classes (04:10:13)


Abstract classes are classes that cannot be instantiated directly. They serve as blueprints for
other classes, defining common behaviors and properties that subclasses must implement.
●​ abstract keyword: Used to declare an abstract class.
●​ Abstract Methods: Methods declared without an implementation (just a signature
followed by a semicolon). Subclasses must override and implement all abstract
methods.
●​ Concrete Methods: Abstract classes can also have regular (concrete) methods with
implementations.
●​ No Direct Instantiation: You cannot create an object of an abstract class.
Code Example:

Dart

abstract class Shape { // Abstract class​


void draw(); // Abstract method (no implementation)​
String get name; // Abstract getter​

void displayInfo() { // Concrete method​
print('This is a shape.');​
}​
}​

class Circle extends Shape {​
@override​
void draw() {​
print('Drawing a circle.');​
}​

@override​
String get name => 'Circle';​
}​

// class Square extends Shape {​
// // Error: Missing concrete implementation of 'Shape.draw' and 'Shape.name'​
// }​

void main() {​
Circle myCircle = Circle();​
myCircle.draw(); // Output: Drawing a circle.​
myCircle.displayInfo(); // Output: This is a shape.​
print(myCircle.name); // Output: Circle​

// Shape s = Shape(); // Error: Abstract classes can't be instantiated.​
}​

extends vs. implements with Abstract Classes:


●​ You can extend an abstract class. The subclass must then implement all abstract
methods from the abstract class (unless the subclass itself is abstract).
●​ You can implement an abstract class. This is essentially treating the abstract class
purely as an interface, and the implementing class must provide all implementations for
all methods and getters/setters, including concrete ones from the abstract class (as
implements only inherits the interface).

👨‍💻 Object-Oriented Programming (OOP) in Dart


(04:15:03)

OOP is a programming paradigm based on the concept of "objects," which can contain data
(properties) and code (methods). Dart is an object-oriented language. The video highlights
four core OOP principles:

1. Polymorphism (04:17:09)

●​ Definition: The ability of an object to take on many forms. It allows objects of different
classes to be treated as objects of a common superclass.
●​ How it's achieved: Through inheritance and method overriding.
Code Example:

Dart

class Animal {​
void speak() {​
print('Animal makes a sound.');​
}​
}​

class Dog extends Animal {​
@override​
void speak() {​
print('Woof!');​
}​
}​

class Cat extends Animal {​
@override​
void speak() {​
print('Meow!');​
}​
}​

void main() {​
Animal animal1 = Dog(); // A Dog object is treated as an Animal​
Animal animal2 = Cat(); // A Cat object is treated as an Animal​

animal1.speak(); // Calls Dog's speak() - Output: Woof!​
animal2.speak(); // Calls Cat's speak() - Output: Meow!​

List<Animal> farmAnimals = [Dog(), Cat(), Animal()];​
for (Animal animal in farmAnimals) {​
animal.speak(); // Polymorphism in action​
}​
// Output:​
// Woof!​
// Meow!​
// Animal makes a sound.​
}​

2. Abstraction (04:20:52)

●​ Definition: Hiding the internal details and complexity of an object, exposing only the
essential features and functionalities to the outside world.
●​ How it's achieved: Primarily through abstract classes and interfaces (which can be
normal classes used with implements).
Code Example:

Dart

abstract class RemoteControl { // Abstract class hides implementation details​


void turnOn();​
void turnOff();​
void changeChannel(int channel);​
}​

class TVRemote extends RemoteControl {​
@override​
void turnOn() {​
print('TV is turning on...');​
}​

@override​
void turnOff() {​
print('TV is turning off...');​
}​

@override​
void changeChannel(int channel) {​
print('Changing TV channel to $channel.');​
}​
}​

void main() {​
TVRemote myRemote = TVRemote();​
myRemote.turnOn(); // We know *what* it does, not *how*​
myRemote.changeChannel(5);​
}​

3. Inheritance (04:23:12)

●​ Definition: (Revisited) The mechanism by which one class acquires the properties and
methods of another class. It's about establishing an "is-a" relationship (e.g., "A Car is a
Vehicle").
●​ How it's achieved: Using the extends keyword.

4. Encapsulation (04:23:36)

●​ Definition: Bundling data (properties) and methods that operate on that data into a
single unit (a class). It involves hiding the internal state of an object and restricting
direct access to it, typically by using private members.
●​ How it's achieved: By making properties and methods private to a file using the _
(underscore) prefix, and providing controlled access through public getters and
setters.
Code Example:
Dart

class BankAccount {​
double _balance; // Private data​

BankAccount(this._balance);​

// Public getter to read balance​
double get balance => _balance;​

// Public method to deposit (controlled modification)​
void deposit(double amount) {​
if (amount > 0) {​
_balance += amount;​
print('Deposited \$$amount. New balance: \$$_balance');​
} else {​
print('Deposit amount must be positive.');​
}​
}​

// Public method to withdraw (controlled modification)​
void withdraw(double amount) {​
if (amount > 0 && _balance >= amount) {​
_balance -= amount;​
print('Withdrew \$$amount. New balance: \$$_balance');​
} else if (amount <= 0) {​
print('Withdrawal amount must be positive.');​
} else {​
print('Insufficient funds.');​
}​
}​
}​

void main() {​
BankAccount account = BankAccount(100.0);​
print(account.balance); // Output: 100.0​

account.deposit(50.0); // Output: Deposited $50.0. New balance: $150.0​
account.withdraw(75.0); // Output: Withdrew $75.0. New balance: $75.0​
account.withdraw(100.0); // Output: Insufficient funds.​

// print(account._balance); // Error: The setter '_balance' isn't defined for the type
'BankAccount'.​
}​

OOP Brief (04:25:11)

A quick recap of the four pillars:


●​ Polymorphism: "Many forms" (method overriding, inheritance).
●​ Abstraction: Hiding complexity (abstract classes, interfaces).
●​ Inheritance: "Is-a" relationship (extends keyword).
●​ Encapsulation: "Bundling data and methods" (private members, getters/setters).

🔄 Mixins (04:26:14)
Mixins are a way to reuse a class's code in multiple class hierarchies. They allow you to "mix
in" a set of functionalities into a class without using traditional inheritance. Think of them as a
way to copy/paste methods and properties into a class.
●​ mixin keyword: Used to define a mixin.
●​ with keyword: Used by a class to incorporate a mixin.
●​ No Parent-Child Relationship: Unlike extends, with does not establish an "is-a" or
parent-child relationship. The class simply gains the mixin's members.
●​ Multiple Mixins: A class can use multiple mixins.
Code Example:

Dart

mixin Jump { // Define a mixin​


int jumpHeight = 10;​
void performJump() {​
print('Jumping to $jumpHeight cm!');​
}​
}​

mixin Fly { // Another mixin​
void performFly() {​
print('Flying high!');​
}​
}​

class SuperHero with Jump, Fly { // Mix in Jump and Fly functionalities​
String name;​
SuperHero(this.name);​

void introduce() {​
print('I am $name.');​
}​
}​

class Rabbit with Jump { // Rabbit also gets Jump functionality​
void hop() {​
print('Hopping around!');​
}​
}​

void main() {​
SuperHero superman = SuperHero('Superman');​
superman.introduce();​
superman.performJump(); // Accesses mixin method​
superman.performFly(); // Accesses another mixin method​
print(superman.jumpHeight); // Accesses mixin property​

Rabbit bugs = Rabbit();​
bugs.performJump();​
}​

on Keyword for Mixins:

●​ You can restrict which types can use a mixin by using the on keyword. This specifies
that the mixin can only be used by classes that either extend or implement the specified
type.

Dart

mixin Swimmer on Animal { // Swimmer can only be used on classes that are (or extend)
Animal​
void swim() {​
print('Swimming!');​
}​
}​

class Fish extends Animal with Swimmer { // Valid: Fish extends Animal​
Fish(String species) : super(species);​
}​

// class Car with Swimmer { // Error: 'Swimmer' can only be mixed in to classes that implement
'Animal'.​
// void drive() {}​
// }​

🎛️ Class Modifiers (Dart 3.0+) (04:33:40)


Dart 3.0 introduced several new class modifiers that provide more control over how classes
can be used (extended, implemented, or mixed in).
1.​ sealed:
○​ Implicitly abstract: Cannot be instantiated directly.
○​ Cannot be extended/implemented outside its library: All direct
subclasses/implementations must be defined in the same library (file, or group of
files).
○​ Exhaustive switch checking: Enables the compiler to ensure that switch
statements covering a sealed class's subtypes are exhaustive (cover all possible
direct subtypes), leading to compile-time warnings if a case is missed.
○​ Use case: Representing a closed set of types (e.g., states in a finite state
machine).
Dart​
// In library 'shapes.dart'​
sealed class Shape {​
void describe();​
}​

class Circle extends Shape {​
@override​
void describe() => print('A circle.');​
}​

class Square extends Shape {​
@override​
void describe() => print('A square.');​
}​

// In another file/library (would cause compile error if extending/implementing directly)​
// class Triangle extends Shape { ... } // Error if not in 'shapes.dart'​

void processShape(Shape shape) {​
switch (shape) {​
case Circle():​
print('Processing Circle');​
case Square():​
print('Processing Square');​
// If Triangle was in the same library, Dart would warn if this case was missing​
}​
}​

2.​ final:
○​ Cannot be extended or implemented by any other class, even within the same
library.
○​ Can be instantiated.
○​ Use case: Creating classes that are entirely self-contained and should not be
modified by inheritance (similar to final classes in Java).
Dart​
final class Configuration {​
final String apiKey;​
const Configuration(this.apiKey);​
}​

// class MyConfig extends Configuration { } // Error: 'Configuration' can't be extended.​
// class ImplementedConfig implements Configuration { } // Error: 'Configuration' can't
be implemented.​

3.​ base:
○​ Can be extended but not implemented.
○​ All classes that extend a base class must also be marked base, final, or sealed.
○​ Ensures constructor calls: Guarantees that the base class's constructor is
always called when a subclass is created.
○​ Use case: When you want to ensure a base class's implementation details are
used, but still allow polymorphism through extension.
Dart​
base class Vehicle {​
void startEngine() => print('Engine started.');​
}​

base class Car extends Vehicle { // Subclass must be base, final, or sealed​
@override​
void startEngine() {​
super.startEngine();​
print('Car engine roaring.');​
}​
}​

// class Bike implements Vehicle { } // Error: 'Vehicle' can't be implemented.​
// class Truck extends Vehicle { } // Error: Subclass 'Truck' must be marked 'base', 'final',
or 'sealed'.​

4.​ interface:
○​ Can be implemented but not extended.
○​ Can be instantiated.
○​ Acts like an explicit interface: The implementing class must provide all its own
implementations.
○​ Use case: Defining an API or a contract that other classes can fulfill without
sharing any implementation. If combined with abstract, it becomes a "true"
interface (cannot be instantiated).
Dart​
interface class Loggable {​
void log(String message);​
}​

class FileLogger implements Loggable {​
@override​
void log(String message) {​
print('Logging to file: $message');​
}​
}​

// class ConsoleLogger extends Loggable { } // Error: 'Loggable' can't be extended.​

abstract interface class TrueInterface { // Cannot be instantiated​
void action();​
}​

5.​ mixin class:


○​ A class that can be used both as a regular class (extended, implemented) and as
a mixin (with).
○​ Breaking change in Dart 3.0: Previously, any class could be used as a mixin.
Now, only mixin class or explicit mixin declarations can be used with with.
○​ Use case: When a set of reusable behaviors can sometimes stand alone as a
class and sometimes needs to be mixed into others.
Dart​
mixin class Logger { // Can be a mixin or a class​
void logInfo(String message) => print('INFO: $message');​
}​

class AppManager with Logger { // Used as a mixin​
void manage() => logInfo('App management started.');​
}​

class ReportingService extends Logger { // Used as a class (extended)​
void generateReport() => logInfo('Report generated.');​
}​

📜 Lists (04:40:48)
Lists are ordered collections of objects (elements). They are similar to arrays in other
languages. The order of elements in a list matters, and elements can be accessed by their
zero-based index.

Creating Lists:

Dart

void main() {​
// 1. List of dynamic type (can hold any type of object) - generally discouraged​
List dynamicList = [10, 'Hello', true, 19.99];​
print(dynamicList); // Output: [10, Hello, true, 19.99]​

// 2. List with a specific type (using Generics) - recommended​
List<int> numbers = [10, 20, 30];​
print(numbers); // Output: [10, 20, 30]​

List<String> names = ['Alice', 'Bob', 'Charlie'];​
print(names); // Output: [Alice, Bob, Charlie]​

// 3. Empty list​
List<double> prices = []; // Or: List<double> prices = List.empty();​
print(prices); // Output: []​

// 4. List of custom objects (Classes)​
// Assuming a 'Student' class is defined elsewhere​
// class Student { final String name; final int marks; Student(this.name, this.marks); @override
String toString() => 'Student(name: $name, marks: $marks)'; }​
List<Student> students = [​
Student('Rivan', 95),​
Student('Naman', 88),​
];​
print(students); // Output: [Student(name: Rivan, marks: 95), Student(name: Naman, marks:
88)]​
}​

Accessing List Elements:

Use square brackets [] with the zero-based index.

Dart

void main() {​
List<String> names = ['Alice', 'Bob', 'Charlie'];​

print(names[0]); // Output: Alice​
print(names[1]); // Output: Bob​

// print(names[3]); // Runtime Error: RangeError (index): Index out of range: no primary at
index 3​
}​

List Properties and Methods:

●​ length: Returns the number of elements in the list.


●​ add(element): Adds an element to the end of the list.
●​ addAll(iterable): Adds all elements from another iterable (like another list) to the end.
●​ insert(index, element): Inserts an element at a specific index.
●​ remove(element): Removes the first occurrence of a specified element.
○​ Important: For custom objects, remove() checks for instance equality. You need
to pass the exact same object instance or override == and hashCode in your
custom class for it to work as expected with different instances representing the
"same" data.
●​ removeAt(index): Removes the element at a specific index.
●​ clear(): Removes all elements from the list.
●​ contains(element): Returns true if the list contains the specified element.
●​ indexOf(element): Returns the index of the first occurrence of the element, or -1 if not
found.
●​ first, last: Get the first/last element.
●​ isEmpty, isNotEmpty: Check if the list is empty.
●​ reversed: Returns an Iterable with elements in reverse order.
●​ where(test): Filters elements based on a condition and returns a new Iterable.
●​ map(function): Transforms each element using a function and returns a new Iterable.
●​ toList(): Converts an Iterable (returned by where, map, reversed) into a List.
●​ toSet(): Converts a List into a Set (removing duplicates).
Code Example:

Dart

class Student {​
final String name;​
final int marks;​
const Student(this.name, this.marks);​

@override​
String toString() => 'Student(name: $name, marks: $marks)';​
}​

void main() {​
List<Student> students = [​
Student('Rivan', 10),​
Student('Naman', 20),​
Student('Rakesh', 30),​
Student('Sonal', 40),​
];​

print('Original students: $students');​

// Add an element​
students.add(Student('Priya', 25));​
print('After adding: $students');​

// Remove an element (by instance)​
Student naman = Student('Naman', 20); // This is a new instance​
students.remove(naman); // Might not work as expected without == override in Student​
print('After removing (by new instance): $students');​

// To remove 'Naman' reliably:​
students.removeWhere((student) => student.name == 'Naman');​
print('After removing (by condition): $students');​


// Filter elements (marks >= 30)​
List<Student> highAchievers = students.where((student) => student.marks >= 30).toList();​
print('High achievers: $highAchievers');​

// Iterate using for-in loop​
print('Names:');​
for (Student student in students) {​
print(student.name);​
}​
}​

📚 Sets (05:23:04)
Sets are unordered collections of unique elements. Unlike lists, a set cannot contain duplicate
values.

Creating Sets:

Dart

void main() {​
// 1. Set with specific type​
Set<int> uniqueNumbers = {1, 2, 3, 2, 1};​
print(uniqueNumbers); // Output: {1, 2, 3} (duplicates automatically removed)​

// 2. Empty set​
Set<String> categories = {}; // Or: Set<String> categories = Set<String>();​
print(categories); // Output: {}​

// 3. Converting a List to a Set to remove duplicates​
List<int> numbersWithDuplicates = [1, 2, 2, 3, 4, 4, 1];​
Set<int> uniqueNumbersFromList = numbersWithDuplicates.toSet();​
print(uniqueNumbersFromList); // Output: {1, 2, 3, 4}​
}​

Set Properties and Methods:

Many methods are similar to Lists:


●​ length, isEmpty, isNotEmpty
●​ add(element): Adds an element. If the element already exists, nothing happens.
●​ addAll(iterable)
●​ remove(element)
●​ clear()
●​ contains(element)
●​ difference(otherSet): Returns a new set with elements not in otherSet.
●​ intersection(otherSet): Returns a new set with common elements.
●​ union(otherSet): Returns a new set with all elements from both sets.
Code Example:

Dart

void main() {​
Set<String> fruits = {'Apple', 'Banana', 'Orange'};​
Set<String> berries = {'Strawberry', 'Blueberry', 'Banana'};​

fruits.add('Mango');​
print(fruits); // Output: {Apple, Banana, Orange, Mango}​

fruits.add('Apple'); // No change, 'Apple' already exists​
print(fruits); // Output: {Apple, Banana, Orange, Mango}​

print(fruits.intersection(berries)); // Output: {Banana}​
print(fruits.union(berries)); // Output: {Apple, Banana, Orange, Mango, Strawberry,
Blueberry}​
print(fruits.difference(berries)); // Output: {Apple, Orange, Mango}​
}​

🗺️ Maps (05:25:39)
Maps (also known as dictionaries or hash maps) are collections of key-value pairs, where
each key must be unique. They are used to store and retrieve data based on a specific
identifier (the key).

Creating Maps:

Dart

void main() {​
// 1. Map of dynamic types (discouraged)​
Map dynamicMap = {​
'name': 'Alice',​
1: true,​
'price': 29.99​
};​
print(dynamicMap); // Output: {name: Alice, 1: true, price: 29.99}​

// 2. Map with specific types (using Generics) - recommended​
Map<String, int> studentScores = {​
'Alice': 95,​
'Bob': 88,​
'Charlie': 76,​
};​
print(studentScores); // Output: {Alice: 95, Bob: 88, Charlie: 76}​

// 3. Empty map​
Map<String, String> capitals = {}; // Or: Map<String, String> capitals = Map<String, String>();​
print(capitals); // Output: {}​

// 4. Map with mixed value types (using dynamic for value type)​
Map<String, dynamic> userInfo = {​
'name': 'Dave',​
'age': 30,​
'isStudent': false,​
};​
}​
Accessing Map Elements:

Use square brackets [] with the key.

Dart

void main() {​
Map<String, int> studentScores = {​
'Alice': 95,​
'Bob': 88,​
};​

print(studentScores['Alice']); // Output: 95​

// Accessing a non-existent key returns null​
print(studentScores['Charlie']); // Output: null​

// Null-aware access for safety​
print(studentScores['Charlie']?.isEven); // Output: null (no error)​
print(studentScores['Alice']!.isEven); // Output: false (Alice's score is 95, which is odd)​
}​

Map Properties and Methods:

●​ length: Number of key-value pairs.


●​ keys: Returns an Iterable of all keys.
●​ values: Returns an Iterable of all values.
●​ containsKey(key): Checks if a key exists.
●​ containsValue(value): Checks if a value exists.
●​ putIfAbsent(key, ifAbsent): Adds a key-value pair only if the key doesn't already exist.
●​ remove(key): Removes a key-value pair.
●​ clear(): Clears all entries.
●​ forEach(action): Iterates over each key-value pair.
●​ map(convert): Transforms each entry into a new map.
Code Example:

Dart
void main() {​
Map<String, int> productStock = {​
'Apple': 100,​
'Banana': 150,​
};​

print('Initial stock: $productStock');​

// Add a new entry​
productStock['Orange'] = 200;​
print('After adding Orange: $productStock');​

// Update an existing entry​
productStock['Apple'] = 120;​
print('After updating Apple: $productStock');​

// Add all entries from another map​
productStock.addAll({'Mango': 50, 'Grape': 75});​
print('After adding more fruits: $productStock');​

// Remove an entry​
productStock.remove('Banana');​
print('After removing Banana: $productStock');​

// Iterate over map​
print('Product Stock Details:');​
productStock.forEach((key, value) {​
print('$key: $value units');​
});​
// Output:​
// Apple: 120 units​
// Orange: 200 units​
// Mango: 50 units​
// Grape: 75 units​

// List of Maps (common for JSON data)​
List<Map<String, dynamic>> orders = [​
{'id': 1, 'item': 'Laptop', 'price': 1200.0},​
{'id': 2, 'item': 'Mouse', 'price': 25.0},​
];​

for (var order in orders) {​
print('Order ID: ${order['id']}, Item: ${order['item']}');​
}​
}​

🏷️ Enums (Enumerations) (05:50:32)


Enums (enumerations) define a fixed, limited set of named constant values. They improve
code clarity, prevent bugs by restricting input, and provide strong type safety.
●​ enum keyword: Used to declare an enum.
●​ Convention: Enum names use PascalCase, and their values typically use
lowerCamelCase (or SCREAMING_SNAKE_CASE if they are constants).
●​ Enums are globally declared, outside any class or function.

Basic Enums:

Dart

enum EmployeeType { // Declaring an enum​


softwareEngineer,​
finance,​
marketing,​
hr,​
}​

class Employee {​
String name;​
EmployeeType type; // Using enum as a type​

Employee(this.name, this.type);​
}​

void main() {​
Employee emp1 = Employee('Alice', EmployeeType.softwareEngineer);​
Employee emp2 = Employee('Bob', EmployeeType.finance);​

print(emp1.type); // Output: EmployeeType.softwareEngineer​

// Using enums in switch statements (exhaustive checking in Dart 3+)​
switch (emp1.type) {​
case EmployeeType.softwareEngineer:​
print('${emp1.name} is a Software Engineer.');​
break;​
case EmployeeType.finance:​
print('${emp1.name} works in Finance.');​
break;​
case EmployeeType.marketing:​
print('${emp1.name} is in Marketing.');​
break;​
case EmployeeType.hr:​
print('${emp1.name} is in HR.');​
break;​
// No 'default' needed if all enum cases are covered (Dart 3+)​
}​
// Output: Alice is a Software Engineer.​
}​

●​ Benefit: Prevents invalid string inputs like Employee('Charlie', 'hahaha') at compile time.

Enhanced Enums (Dart 2.15+) (05:58:22):

Enums can have members (fields, methods, and constructors), making them more powerful
and class-like.
●​ Syntax:​
Dart​
enum EnumName {​
value1(arg1: val1, arg2: val2),​
value2(arg1: val3, arg2: val4); // Semicolon required here​

final DataType field1;​
final DataType field2;​

const EnumName({required this.field1, required this.field2}); // Constructor​

// Optional methods​
void describe() => print('This is $field1 with $field2.');​
}​

Code Example:
Dart

enum EmployeeType {​
softwareEngineer(salary: 200000, department: 'Engineering'),​
finance(salary: 150000, department: 'Finance'),​
marketing(salary: 120000, department: 'Marketing');​

final int salary;​
final String department;​

const EmployeeType({required this.salary, required this.department});​

void displayRole() {​
print('This role earns \$$salary in the ${department} department.');​
}​
}​

void main() {​
EmployeeType type = EmployeeType.softwareEngineer;​
print(type.salary); // Output: 200000​
print(type.department); // Output: Engineering​
type.displayRole(); // Output: This role earns $200000 in the Engineering department.​

// Accessing all enum values​
print(EmployeeType.values); // Output: [EmployeeType.softwareEngineer,
EmployeeType.finance, EmployeeType.marketing]​
}​

🚧 Exception Handling (06:03:03)


Exception handling is a mechanism to gracefully manage and respond to runtime errors
(exceptions) that occur during program execution. It prevents your program from crashing
unexpectedly.
●​ Exception: An event that disrupts the normal flow of a program.
●​ Logical Error: A bug in your code's logic that does not necessarily throw an exception
(e.g., 10 + 'hello' won't throw an exception in dynamic Dart, but 10 * 'hello' will).
Exception handling is for unexpected runtime issues, not all logical errors.
try-catch Block:

Used to enclose code that might throw an exception and define how to handle it.
●​ try: Contains the code that might throw an exception.
●​ catch (e): Catches any exception thrown within the try block. e is the exception object.
Code Example:

Dart

void main() {​
print('Program started.');​
try {​
int result = 10 ~/ 0; // Throws IntegerDivisionByZeroException​
print('Result: $result'); // This line won't be reached​
} catch (e) {​
print('An error occurred: $e');​
}​
print('Program finished.');​

// Output:​
// Program started.​
// An error occurred: IntegerDivisionByZeroException​
// Program finished.​
}​

on Clause:

Used to catch specific types of exceptions. You can have multiple on clauses for different
exception types.
Code Example:

Dart

void main() {​
try {​
// int result = int.parse('abc'); // Throws FormatException​
int result = 10 ~/ 0; // Throws IntegerDivisionByZeroException​
print(result);​
} on IntegerDivisionByZeroException {​
print('Cannot divide by zero!');​
} on FormatException catch (e) { // Catch FormatException specifically​
print('Invalid format: ${e.message}');​
} catch (e) { // Catch any other exception​
print('An unexpected error: $e');​
}​
}​

finally Block:

(Optional) A block of code that always executes, regardless of whether an exception was
thrown or caught in the try-catch block. Commonly used for cleanup tasks (e.g., closing files,
database connections).
Code Example:

Dart

void main() {​
try {​
int result = 10 ~/ 0;​
print(result);​
} catch (e) {​
print('Error: $e');​
} finally {​
print('Cleanup code executed.');​
}​

// Output:​
// Error: IntegerDivisionByZeroException​
// Cleanup code executed.​
}​

throw Keyword:

Used to explicitly throw an exception. This immediately stops the current execution flow and
propagates the exception up the call stack until it's caught by a try-catch block or crashes the
program.
Code Example:

Dart

void validateAge(int age) {​


if (age < 0) {​
throw FormatException('Age cannot be negative.'); // Throwing a custom exception​
} else if (age < 18) {​
throw ArgumentError('Must be 18 or older.');​
}​
print('Age is valid.');​
}​

void main() {​
try {​
validateAge(15);​
} on FormatException catch (e) {​
print('Validation Error: ${e.message}');​
} on ArgumentError catch (e) {​
print('Age Restriction: ${e.message}');​
} catch (e) {​
print('Generic Error: $e');​
}​

// Output: Age Restriction: Must be 18 or older.​
}​

⏳ Futures (06:11:45)
Futures are Dart's way of representing a value that may be available at some point in the
future. They are central to asynchronous programming, allowing your app to perform
time-consuming operations (like fetching data from the internet) without blocking the main UI
thread.
●​ Analogy: A Future is like ordering a coffee. You place the order (start an async
operation) and get a ticket (the Future). You don't stand there blocking the line; you go
do other things. When your coffee is ready (the Future completes), you get the result.
async and await Keywords:

●​ async: Marks a function as asynchronous. An async function always returns a Future.


●​ await: Can only be used inside an async function. It pauses the execution of the async
function until the Future it's waiting on completes. The UI thread remains unblocked.
Code Example:

Dart

import 'dart:async'; // For Future.delayed​



// An async function that returns a Future<String>​
Future<String> fetchData() async {​
await Future.delayed(Duration(seconds: 2)); // Simulate network delay​
print('Data fetched!');​
return 'Some data from the internet';​
}​

void main() async { // main function can also be async​
print('Program started.');​

// Option 1: Using await (pauses main's execution for fetchData)​
String data = await fetchData();​
print('Received data: $data');​

// Output:​
// Program started.​
// (2 second delay)​
// Data fetched!​
// Received data: Some data from the internet​
// Program finished.​

print('Program finished.');​
}​

Future.delayed():

●​ Creates a Future that completes after a specified Duration. Useful for simulating delays
or scheduling tasks.

Dart

// Example from above: await Future.delayed(Duration(seconds: 2));​

.then() Method (06:29:05):

●​ Allows you to register a callback function that will execute when the Future completes
successfully.
●​ An alternative to async/await (often used when you don't need to pause the current
function's execution).
Code Example:

Dart

Future<String> getUserName() {​
return Future.delayed(Duration(seconds: 1), () => 'Alice');​
}​

void main() {​
print('Fetching user name...');​
getUserName().then((name) { // .then() is called when getUserName() completes​
print('User name: $name');​
});​
print('Continuing other tasks...');​

// Output:​
// Fetching user name...​
// Continuing other tasks...​
// (1 second delay)​
// User name: Alice​
}​

HTTP Requests (06:31:00):


●​ Using the http package from pub.dev is the standard way to make network requests
(GET, POST, PUT, DELETE).
●​ Uri.parse() / Uri.https(): Helper functions to construct Uri objects, which http.get()
expects.
Code Example (install http in pubspec.yaml first: dependencies: http: ^latest_version):

Dart

import 'package:http/http.dart' as http; // Import with a prefix​


import 'dart:convert'; // For jsonDecode​

Future<Map<String, dynamic>> fetchUserData(int userId) async {​
final url = Uri.https('jsonplaceholder.typicode.com', '/users/$userId');​
final response = await http.get(url);​

if (response.statusCode == 200) {​
return jsonDecode(response.body) as Map<String, dynamic>;​
} else {​
throw Exception('Failed to load user data');​
}​
}​

void main() async {​
try {​
Map<String, dynamic> user = await fetchUserData(1);​
print('User name: ${user['name']}');​
print('User email: ${user['email']}');​
} catch (e) {​
print('Error: $e');​
}​
}​

🌊 Streams (06:56:08)
Streams are like asynchronous Iterables; they produce a sequence of values over time.
While a Future delivers a single result, a Stream can deliver zero, one, or many events.
●​ Analogy: A Stream is like a continuous delivery service or a podcast subscription. You
subscribe once, and new episodes (data events) keep coming as they are produced.
●​ Use Cases: Listening to user input, real-time data updates (e.g., chat messages, sensor
readings), continuously monitoring events.

async* and yield Keywords:

●​ async*: Marks a function as an asynchronous generator, meaning it returns a Stream.


●​ yield: Used inside async* functions to emit (produce) a value to the stream. Execution
of the function pauses after yield and resumes when the next value is requested.
Code Example:

Dart

Stream<int> countdown(int from) async* { // async* returns a Stream​


for (int i = from; i >= 0; i--) {​
await Future.delayed(Duration(seconds: 1)); // Simulate delay between values​
yield i; // Emit a value to the stream​
}​
}​

void main() {​
print('Starting countdown...');​
countdown(3).listen((number) { // Listen to the stream​
print('Current number: $number');​
}, onDone: () {​
print('Countdown finished!');​
}, onError: (error) {​
print('Error in countdown: $error');​
});​
print('Main function continuing...');​

// Output:​
// Starting countdown...​
// Main function continuing...​
// (1 second) Current number: 3​
// (1 second) Current number: 2​
// (1 second) Current number: 1​
// (1 second) Current number: 0​
// Countdown finished!​
}​
Stream.listen():

●​ Subscribes to a stream to receive its emitted values.


●​ Takes onData, onDone, and onError callback functions.
●​ Returns a StreamSubscription object, which can be used to pause(), resume(), or
cancel() the subscription.

StreamController (07:11:02):

●​ Provides more control over a stream. It allows you to create a stream, add data/errors to
it programmatically, and manage its lifecycle (e.g., close()).
Code Example:

Dart

import 'dart:async';​

void main() {​
final controller = StreamController<String>();​

// Listen to the stream​
controller.stream.listen((data) {​
print('Received: $data');​
}, onError: (error) {​
print('Error: $error');​
}, onDone: () {​
print('Stream closed.');​
});​

// Add data to the stream​
controller.sink.add('Event 1');​
controller.sink.add('Event 2');​
controller.sink.addError('Something went wrong!'); // Add an error​
controller.sink.add('Event 3');​

// Close the stream (important for cleanup)​
controller.close();​

// Output:​
// Received: Event 1​
// Received: Event 2​
// Error: Something went wrong!​
// Received: Event 3​
// Stream closed.​
}​

🗃️ (Bonus) Creating Records (07:19:46)


(This topic was covered earlier under Functions, but is presented here as a dedicated "Bonus"
section in the video's timeline.)
Records are anonymous, immutable, fixed-size data structures introduced in Dart 3.0,
allowing you to bundle multiple values into a single object.

Creating Records:

Dart

void main() {​
// Positional record​
var userInfo = ('Alice', 30, true);​
print(userInfo.$1); // Access by position: Alice​
print(userInfo.$2); // Access by position: 30​

// Named record (better readability)​
var product = (name: 'Laptop', price: 1200.0, inStock: true);​
print(product.name); // Access by name: Laptop​
print(product.price); // Access by name: 1200.0​

// Mixed positional and named​
var mixedData = ('Apple', quantity: 5, 'fruit');​
print(mixedData.$1); // Output: Apple​
print(mixedData.quantity); // Output: 5​
print(mixedData.$2); // Output: fruit (positional after named fields are done)​

// Records are type-safe​
({String name, int age}) person = (name: 'Bob', age: 25);​
// person = (name: 'Charlie', age: 'thirty'); // Error: Value type mismatch​
}​

Records Equality:

●​ Two records are equal if they have the same shape (same number of positional fields,
same names for named fields) and the corresponding fields have equal values.

Dart

void main() {​
var p1 = (x: 1, y: 2);​
var p2 = (x: 1, y: 2);​
print(p1 == p2); // Output: true​

var p3 = (a: 1, b: 2);​
print(p1 == p3); // Output: false (different named fields)​

var p4 = (1, 2);​
print(p1 == p4); // Output: false (different shapes - named vs positional)​
}​

🧩 (Bonus) Patterns & Pattern Matching (07:23:57)


(This topic is often introduced with Dart 3.0 due to its significant enhancements. It was
covered in various contexts in the video).
Patterns are a new syntax in Dart 3.0 that allow you to match values against a structure and
then destructure (extract) values from that structure. This simplifies code for working with
complex data.

Use Cases:

1.​ Destructuring Lists/Records:​


Dart​
void main() {​
List<int> numbers = [1, 2, 3, 4, 5];​

// Destructure first three elements and collect the rest​
var [a, b, c, ...rest] = numbers;​
print('a: $a, b: $b, c: $c, rest: $rest'); // Output: a: 1, b: 2, c: 3, rest: [4, 5]​

// Ignore elements with underscore​
var [first, _, third, ..._] = numbers;​
print('First: $first, Third: $third'); // Output: First: 1, Third: 3​

// Destructure named record​
({String name, int age}) person = (name: 'Alice', age: 30);​
var ({name: userName, age: userAge}) = person; // Renaming fields​
print('User: $userName, Age: $userAge'); // Output: User: Alice, Age: 30​
}​

2.​ Pattern Matching in if statements (If-case) (07:27:10):


○​ Allows destructuring and matching in a concise if condition.
Dart​
void main() {​
Map<String, dynamic> userData = {​
'id': 1,​
'title': 'Post 1',​
'body': 'Content of post 1'​
};​

if (userData case {'id': int userId, 'title': String postTitle}) {​
print('Post found! ID: $userId, Title: $postTitle');​
} else {​
print('Data format mismatch.');​
}​
// Output: Post found! ID: 1, Title: Post 1​

// Mismatched type will go to else​
if (userData case {'id': String userId, 'title': String postTitle}) {​
print('Post found! ID: $userId, Title: $postTitle');​
} else {​
print('Data format mismatch.');​
}​
// Output: Data format mismatch.​
}​

3.​ Pattern Matching in switch expressions/statements (07:29:01):


○​ Significantly enhances switch statements to match complex data structures.
Dart​
sealed class Event {}​
class LoginEvent extends Event { final String username; LoginEvent(this.username); }​
class LogoutEvent extends Event {}​
class DataEvent extends Event { final Map<String, dynamic> data; DataEvent(this.data); }​

void handleEvent(Event event) {​
switch (event) {​
case LoginEvent(username: var user):​
print('User $user logged in.');​
case LogoutEvent():​
print('User logged out.');​
case DataEvent(data: {'id': int id, 'type': String type} when type == 'user'): // 'when' clause
for additional filtering​
print('Received user data: ID $id');​
case DataEvent(): // Catch all other DataEvents​
print('Received generic data event.');​
default:​
print('Unknown event.');​
}​
}​

void main() {​
handleEvent(LoginEvent('Alice')); // Output: User Alice logged in.​
handleEvent(DataEvent({'id': 10, 'type': 'user', 'value': 'some value'})); // Output: Received user
data: ID 10​
handleEvent(DataEvent({'id': 20, 'type': 'product'})); // Output: Received generic data event.​
}​

⚙️ Extensions (07:36:11)
Extensions allow you to add new functionality (methods, getters, setters) to existing classes
without modifying their source code or creating subclasses. This is particularly useful for
adding utility methods to built-in Dart types (like String, int) or classes from external libraries.
●​ Syntax:​
Dart​
extension ExtensionName on TypeToExtend {​
// New methods, getters, setters​
}​

Key Concepts:

●​ extension keyword: Declares an extension.


●​ ExtensionName: A name for your extension (follows PascalCase).
●​ on TypeToExtend: Specifies the class or type to which you are adding functionality
(e.g., on String, on List<int>).
●​ this keyword: Within the extension, this refers to the instance of the TypeToExtend.
Code Example:

Dart

extension StringExtension on String {​


// Getter to capitalize the first letter of a string​
String get capitalizeFirstLetter {​
if (isEmpty) return this;​
return '${this[0].toUpperCase()}${substring(1)}';​
}​

// Method to check if a string is a valid email​
bool isValidEmail() {​
return RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(this);​
}​
}​

void main() {​
String name = 'alice';​
print(name.capitalizeFirstLetter); // Output: Alice​

String sentence = 'hello world';​
print(sentence.capitalizeFirstLetter); // Output: Hello world​

String email1 = '[email protected]';​
String email2 = 'invalid-email';​
print('$email1 is valid: ${email1.isValidEmail()}'); // Output: [email protected] is valid: true​
print('$email2 is valid: ${email2.isValidEmail()}'); // Output: invalid-email is valid: false​
}​

●​ Benefits: Improves code readability, reusability, and maintainability by keeping related


functionalities together. Avoids "utility classes" with static methods that take an
instance as an argument.

You might also like