DART
DART
video, including explanations, code snippets, and examples. This information should help you
ace these topics and confidently explain them to your juniors.
💱
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.
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.
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.
● 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.
● int (Integer): Stores whole numbers (positive, negative, or zero) without decimal points.
Dart
int age = 30;
int temperature = -5;
○ 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;
○ 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'.
}
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
}
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
}
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);
}
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.
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
○ ! (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
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.
}
Dart
void main() {
int num1 = 10;
int num2 = 5;
print(num1 == num2); // false
print(num1 != num2); // true
print(num1 > num2); // true
}
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'.
}
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!
}
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
}
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".
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);
}
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);
}
🔄 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
}
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
}
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
}
● 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.
Dart
Calling Functions:
To execute a function, simply use its name followed by parentheses () and pass any required
arguments.
Code Example:
Dart
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
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
🏗️ 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.
Dart
class ClassName {
// Properties (variables)
DataType propertyName;
// Constructor (optional)
ClassName(parameters) {
// Initialization logic
}
// Methods (functions)
ReturnType methodName(parameters) {
// Method body
}
}
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});
}
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.
}
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();
}
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
}
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.
extends Keyword:
Dart
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
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 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).
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: 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.
Dart
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
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'.
}
🔄 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
● 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() {}
// }
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();
}
📜 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)]
}
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
}
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}
}
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:
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)
}
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']}');
}
}
Basic Enums:
Dart
● Benefit: Prevents invalid string inputs like Employee('Charlie', 'hahaha') at compile time.
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]
}
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
⏳ 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:
Dart
Future.delayed():
● Creates a Future that completes after a specified Duration. Useful for simulating delays
or scheduling tasks.
Dart
● 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
}
Dart
🌊 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.
Dart
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.
}
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)
}
Use Cases:
⚙️ 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:
Dart