Before anything else — Where do you write Dart code?
Go to dartpad.dev in your browser. This is the official online Dart editor made by Google. Delete whatever code is already there and you are ready to write. No installation needed for Phase 1. We will set up Flutter on your machine when we reach Phase 2.
Every program you see in this phase — type it yourself on dartpad, run it, and see the output. Reading is not enough. Your fingers need to type the code.
Lesson 1 — Your First Program
Delete everything on dartpad and type this:
void main() { print("Hello, I am learning Dart"); }
Click Run. You will see:
Hello, I am learning Dart
Now let's understand what you just wrote line by line.
void main() is the entry point of every Dart program. When you run a Dart file, Dart looks for a function called main and starts executing from there. The word void means this function does not return any value — it just runs and does its job.
{ } are curly braces. They act as a container. Everything inside them belongs to the main function.
print( ) is a built-in function in Dart that displays output on the screen. Whatever you put inside the parentheses gets printed.
"Hello, I am learning Dart" is a piece of text. In Dart, any text you write must be wrapped in either double quotes or single quotes.
; is a semicolon. Every single statement in Dart must end with one. Think of it as the full stop at the end of a sentence. Forget it and Dart will throw an error.
Lesson 2 — Variables and Data Types
A variable is a named box that holds data. You give the box a name, put data inside it, and refer to it later using that name.
In Dart, when you create a variable you also tell Dart what type of data it will hold. This is important because Dart is a statically typed language — meaning types are checked before the program runs, which prevents a whole category of bugs.
int — Whole Numbers
Use int when you want to store a number without any decimal point.
void main() { int age = 25; int year = 2024; int temperature = -10;
print(age); print(year); print(temperature); }
Simple. The box named age holds the number 25. year holds 2024. temperature holds -10. Negative numbers work fine with int.
double — Decimal Numbers
Use double when you need a number with a decimal point.
void main() { double price = 99.99; double weight = 72.5; double pi = 3.14159;
print(price); print(weight); print(pi); }
String — Text
Any piece of text in Dart is called a String. Always wrap it in quotes.
void main() { String name = "Gagan"; String city = "Delhi"; String message = "I am learning Flutter";
print(name); print(city); print(message); }
You can use single quotes too. Both work:
String name = "Gagan"; // works
String name = 'Gagan'; // also works
bool — True or False
A bool (short for boolean) can only hold one of two values: true or false. Nothing else.
void main() { bool isLoggedIn = true; bool hasInternet = false; bool isAdult = true;
print(isLoggedIn); print(hasInternet); }
You will use bool constantly in Flutter — to show or hide widgets, to check if a user is authenticated, to toggle dark mode, etc.
Lesson 3 — var, final, and const
These three keywords give you different ways to declare variables.
var — Let Dart Guess the Type
Instead of writing the type yourself, write var and Dart will figure out the type by looking at the value you assign.
void main() { var name = "Gagan"; // Dart sees "Gagan" and decides: this is a String var age = 25; // Dart sees 25 and decides: this is an int var price = 49.99; // Dart sees 49.99 and decides: this is a double var isActive = true; // Dart sees true and decides: this is a bool
print(name); print(age); print(price); print(isActive); }
This is called type inference. Dart is smart enough to look at the value and decide the type on its own.
One important rule — once Dart assigns a type using var, you cannot store a different type in that variable later:
void main() { var name = "Gagan"; name = 42; // ERROR — name was decided as String, you cannot put an int in it }
final — Set Once, Never Changed
If you know a value will be assigned once and never changed after that, use final.
void main() { final String userName = "Gagan"; final int maxScore = 100;
print(userName); print(maxScore);
userName = "Someone else"; // ERROR — cannot reassign a final variable }
A real world example: after a user logs in, you store their email in a final variable. It will not change during that session.
const — Known Before the Program Runs
const is stricter than final. The value must be a fixed, hardcoded value that is known at compile time — meaning before the program even starts executing.
void main() { const double taxRate = 0.18; const String appName = "MyApp"; const int maxRetries = 3;
print(taxRate); print(appName); }
The key difference between final and const:
final — the value can be determined when the program is running. For example, getting the current date and time is a final-type scenario because the value is only known when the program runs.
const — the value must be hardcoded. The compiler must know it before running anything.
void main() { final DateTime now = DateTime.now(); // OK — value determined at runtime const DateTime now = DateTime.now(); // ERROR — DateTime.now() is not known at compile time }
In Flutter, you will use const a lot for widgets that never change. It makes your app faster because Flutter knows it does not need to re-render those widgets.
Lesson 4 — String Operations
Strings are one of the most used data types. Dart gives you a lot of tools to work with them.
Joining Strings — Concatenation
You can join two strings using the + operator:
void main() { String firstName = "Gagan"; String lastName = "Singh"; String fullName = firstName + " " + lastName;
print(fullName); // Gagan Singh }
The Better Way — String Interpolation
Instead of using +, Dart gives you a cleaner way. Use the $ sign inside a string to directly insert a variable's value:
void main() { String name = "Gagan"; int age = 25; String city = "Delhi";
print("My name is $name"); print("I am $age years old"); print("I live in $city"); print("My name is $name and I am $age years old, from $city"); }
Output:
My name is Gagan
I am 25 years old
I live in Delhi
My name is Gagan and I am 25 years old, from Delhi
If you want to put an expression (like a calculation) inside the string, use ${ } with curly braces:
void main() { int a = 10; int b = 20;
print("Sum is ${a + b}"); // Sum is 30 print("Is a greater? ${a > b}"); // Is a greater? false print("Name length: ${"Gagan".length}"); // Name length: 5 }
Useful String Properties and Methods
void main() { String name = "gagan singh";
print(name.length); // 11 — total number of characters including space print(name.toUpperCase()); // GAGAN SINGH print(name.toLowerCase()); // gagan singh print(name.contains("gagan")); // true print(name.replaceAll("gagan", "Arjun")); // Arjun singh print(name.trim()); // removes extra spaces from start and end print(name.split(" ")); // [gagan, singh] — splits into a list }
Multi-line Strings
If your text spans multiple lines, use triple quotes:
void main() { String address = """ House No. 42, Green Park, New Delhi - 110016 """;
print(address); }
Output:
House No. 42,
Green Park,
New Delhi - 110016
Lesson 5 — Arithmetic and Operators
Basic Math Operators
void main() { int a = 10; int b = 3;
print(a + b); // 13 — addition print(a - b); // 7 — subtraction print(a * b); // 30 — multiplication print(a / b); // 3.3333 — division, ALWAYS returns a double print(a ~/ b); // 3 — integer division, drops the decimal part print(a % b); // 1 — modulus, gives the remainder }
The ~/ operator is unique to Dart. It divides but throws away the decimal — so 10 ~/ 3 gives 3, not 3.333.
The % (modulus) operator is very useful. It tells you the remainder after division. 10 % 3 = 1 because 3 goes into 10 three times with 1 left over. You will use this to check if a number is even or odd: if number % 2 == 0, it is even.
Comparison Operators
These compare two values and return a bool (true or false):
void main() { int a = 10; int b = 20;
print(a == b); // false — is a equal to b? print(a != b); // true — is a NOT equal to b? print(a > b); // false — is a greater than b? print(a < b); // true — is a less than b? print(a >= 10); // true — is a greater than or equal to 10? print(a <= 10); // true — is a less than or equal to 10? }
Assignment Shortcut Operators
Instead of writing x = x + 5, Dart lets you write x += 5. These are just shortcuts:
void main() { int score = 100;
score += 10; // same as score = score + 10, now score is 110 score -= 5; // same as score = score - 5, now score is 105 score *= 2; // same as score = score * 2, now score is 210 score ~/= 3; // same as score = score ~/ 3, now score is 70
print(score); // 70 }
Increment and Decrement
void main() { int count = 0;
count++; // adds 1, same as count = count + 1 count++; count++; print(count); // 3
count--; // subtracts 1 print(count); // 2 }
Logical Operators — Combining Conditions
void main() { bool hasTicket = true; bool hasID = false; int age = 17;
// && means AND — both conditions must be true print(hasTicket && hasID); // false — hasID is false
// || means OR — at least one condition must be true print(hasTicket || hasID); // true — hasTicket is true
// ! means NOT — flips true to false and false to true print(!hasTicket); // false print(!hasID); // true
// Real example — can this person enter a venue? bool canEnter = hasTicket && hasID && age >= 18; print("Can enter: $canEnter"); // false — age and hasID fail }
Lesson 6 — Null Safety
This is one of the most important concepts in modern Dart. Understanding this will save you from countless bugs.
What is Null?
Null means "no value", "nothing", "empty". It is the absence of a value.
In older programming languages, any variable could be null at any time, without warning. This caused apps to crash with the famous "Null Pointer Exception" — one of the most common bugs in software history.
Dart solved this with Null Safety.
How Null Safety Works in Dart
In Dart, by default, a variable cannot be null. Period.
void main() { String name = null; // ERROR — String cannot be null int age = null; // ERROR — int cannot be null }
If you want a variable to possibly hold null, you must explicitly say so by adding a ? after the type:
void main() { String? name = null; // OK — the ? declares that this String can be null int? age = null; // OK double? price; // OK — uninitialized nullable variables default to null
print(name); // null print(age); // null print(price); // null }
The ? is your explicit promise to Dart: "I know this might be null, I am handling it."
The Problem with Nullable Variables
If a variable might be null, you cannot use it directly as if it has a value:
void main() { String? name = null; print(name.length); // ERROR — name might be null, .length would crash }
Dart forces you to handle the null case before using the variable.
Way 1 — Null Check with if
void main() { String? name = "Gagan";
if (name != null) { print(name.length); // Safe — we checked it is not null } else { print("Name is not provided"); } }
Way 2 — The ?. Operator (Null-Aware Access)
The ?. operator means: "if this is not null, access the property. If it is null, just return null instead of crashing."
void main() { String? name = null; print(name?.length); // prints: null — no crash String? city = "Delhi"; print(city?.length); // prints: 5 — works fine because city is not null }
Way 3 — The ?? Operator (Null Fallback)
The ?? operator means: "if the left side is null, use the right side as the fallback value."
void main() { String? name = null; String displayName = name ?? "Guest"; print(displayName); // Guest — because name was null, fallback was used
String? city = "Delhi"; String displayCity = city ?? "Unknown"; print(displayCity); // Delhi — because city was not null, fallback was ignored }
You will use ?? extremely often in Flutter. For example, showing "Guest" if no user is logged in, showing 0 if a count is null, showing a placeholder image if no image URL exists.
Way 4 — The ! Operator (Force Unwrap, Use Carefully)
If you are 100% sure a nullable variable is not null at a specific point, you can use ! to tell Dart "trust me, it is not null":
void main() { String? name = "Gagan"; print(name!.length); // 5 — you forced Dart to treat it as non-null }
Be careful with this. If you use ! on something that actually is null, your app will crash at that exact line.
Lesson 7 — Control Flow (if, else, switch)
Control flow is how you make decisions in your program. "If this condition is true, do this. Otherwise do that."
if and else
void main() { int age = 20;
if (age >= 18) { print("You are an adult"); } else { print("You are a minor"); } }
else if — Multiple Conditions
void main() { int marks = 75;
if (marks >= 90) { print("Grade: A"); } else if (marks >= 75) { print("Grade: B"); } else if (marks >= 60) { print("Grade: C"); } else if (marks >= 40) { print("Grade: D"); } else { print("Grade: F — Failed"); } }
Dart checks conditions from top to bottom. The moment one condition is true, it runs that block and skips the rest.
Ternary Operator — Short if/else
If your condition is simple and you just want one of two values, use the ternary operator:
// condition ? valueIfTrue : valueIfFalse
void main() { int age = 20; String status = age >= 18 ? "Adult" : "Minor"; print(status); // Adult
int score = 45; String result = score >= 50 ? "Pass" : "Fail"; print(result); // Fail }
switch — When You Have Many Specific Cases
When you are checking one variable against many possible exact values, switch is cleaner than a long chain of else if:
void main() { String day = "Monday";
switch (day) { case "Monday": print("Start of work week"); break; case "Friday": print("End of work week"); break; case "Saturday": case "Sunday": print("Weekend!"); break; default: print("A regular weekday"); } }
The break at the end of each case tells Dart to stop and exit the switch block. If you forget break, Dart will continue executing into the next case. The default block runs if none of the cases match — it is like the else at the end.
Lesson 8 — Loops
Loops let you repeat a block of code multiple times without writing it again and again.
for Loop — When You Know How Many Times
void main() { for (int i = 1; i <= 5; i++) { print("Count: $i"); } }
Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Breaking down the for loop: the first part (int i = 1) runs once at the start and creates the counter variable. The second part (i <= 5) is the condition — loop keeps running as long as this is true. The third part (i++) runs after every iteration and increments the counter.
while Loop — When You Don't Know How Many Times Upfront
The while loop keeps running as long as a condition is true. You use it when the number of repetitions depends on something that changes dynamically.
void main() { int count = 0;
while (count < 5) { print("count is $count"); count++; }
print("Loop finished"); }
Be careful with while loops. If the condition never becomes false, the loop runs forever (infinite loop) and your program freezes.
do-while Loop — Run At Least Once
The do-while loop is like while, but it always runs the code block at least once before checking the condition:
void main() { int number = 10;
do { print("This runs at least once: $number"); number++; } while (number < 5); }
Output:
This runs at least once: 10
Even though number (10) was never less than 5, the block ran once. That is the difference.
break and continue
break — immediately exits the loop:
void main() { for (int i = 1; i <= 10; i++) { if (i == 5) { break; // stop the loop when i reaches 5 } print(i); } // prints: 1, 2, 3, 4 }
continue — skips the current iteration and moves to the next:
void main() { for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { continue; // skip even numbers } print(i); } // prints: 1, 3, 5, 7, 9 }
Lesson 9 — Functions
A function is a reusable block of code. Instead of writing the same logic over and over, you write it once in a function and call it whenever needed.
Basic Function
void greet() { print("Hello! Welcome to the app."); }
void main() { greet(); // calling the function greet(); // calling it again greet(); // and again }
Output:
Hello! Welcome to the app.
Hello! Welcome to the app.
Hello! Welcome to the app.
The word void before the function name means this function does not return any value — it just runs and does its job.
Function with Parameters
You can pass data into a function using parameters:
void greetUser(String name) { print("Hello, $name! Welcome."); }
void main() { greetUser("Gagan"); greetUser("Arjun"); greetUser("Priya"); }
Output:
Hello, Gagan! Welcome.
Hello, Arjun! Welcome.
Hello, Priya! Welcome.
Function with Multiple Parameters
void showInfo(String name, int age, String city) { print("$name is $age years old and lives in $city"); }
void main() { showInfo("Gagan", 25, "Delhi"); showInfo("Priya", 22, "Mumbai"); }
Function that Returns a Value
Instead of void, you write the return type. The function then must return a value of that type using the return keyword:
int add(int a, int b) { return a + b; }
double calculateArea(double length, double width) { return length * width; }
String getGreeting(String name) { return "Hello, $name!"; }
void main() { int result = add(10, 20); print(result); // 30
double area = calculateArea(5.5, 3.0); print(area); // 16.5
String msg = getGreeting("Gagan"); print(msg); // Hello, Gagan! }
Named Parameters — Flutter Uses These Everywhere
In Flutter, almost every widget uses named parameters. So understanding this is critical.
Named parameters are wrapped in curly braces. When you call the function, you write the parameter name along with the value:
void createProfile({String name = "", int age = 0, String city = ""}) { print("Name: $name, Age: $age, City: $city"); }
void main() { createProfile(name: "Gagan", age: 25, city: "Delhi"); createProfile(age: 30, name: "Priya"); // order does not matter with named params createProfile(name: "Rahul"); // other params use their default values }
Output:
Name: Gagan, Age: 25, City: Delhi
Name: Priya, Age: 30, City:
Name: Rahul, Age: 0, City:
The = values after each parameter are the default values — used when the caller does not provide that argument.
Optional Positional Parameters
Wrap parameters in square brackets to make them optional:
void introduce(String name, [int? age, String? city]) { print("Name: $name"); if (age != null) print("Age: $age"); if (city != null) print("City: $city"); }
void main() { introduce("Gagan", 25, "Delhi"); introduce("Priya", 22); introduce("Rahul"); }
Output:
Name: Gagan
Age: 25
City: Delhi
Name: Priya
Age: 22
Name: Rahul
Arrow Functions — Single Line Functions
If a function has just one expression to return, you can write it in one line using =>:
int multiply(int a, int b) => a * b; String greet(String name) => "Hello, $name!"; bool isEven(int n) => n % 2 == 0;
void main() { print(multiply(4, 5)); // 20 print(greet("Gagan")); // Hello, Gagan! print(isEven(6)); // true print(isEven(7)); // false }
Lesson 10 — Collections: List, Map, Set
Collections are data structures that hold multiple values. These three are the most important ones in Dart.
List — Ordered Collection
A List is like an array. It holds multiple values in order, and each item has an index starting from 0.
void main() { List<String> fruits = ["Apple", "Banana", "Mango", "Orange"];
print(fruits); // [Apple, Banana, Mango, Orange] print(fruits[0]); // Apple — first item, index 0 print(fruits[2]); // Mango — third item, index 2 print(fruits.length); // 4
// Adding items fruits.add("Grapes"); print(fruits); // [Apple, Banana, Mango, Orange, Grapes]
// Removing items fruits.remove("Banana"); print(fruits); // [Apple, Mango, Orange, Grapes]
// Checking if an item exists print(fruits.contains("Mango")); // true print(fruits.contains("Banana")); // false
// Looping through a list for (String fruit in fruits) { print(fruit); } }
The <String> inside the angle brackets is called a type parameter. It tells Dart that this list will only hold String values. You can have List<int>, List<double>, List<bool>, etc.
Map — Key-Value Pairs
A Map stores data as key-value pairs. Like a dictionary — you look up a word (key) and get its meaning (value).
The <String, dynamic> means keys are Strings and values can be anything (dynamic). In real apps you will often have <String, String> or <String, int> depending on what you are storing.
Set — Unique Values Only
A Set is like a List but it does not allow duplicate values. If you try to add a duplicate, it is silently ignored.
void main() { Set<String> tags = {"flutter", "dart", "mobile", "flutter"};
print(tags); // {flutter, dart, mobile} — duplicate "flutter" removed automatically
tags.add("web"); tags.add("dart"); // already exists, ignored
print(tags); // {flutter, dart, mobile, web} print(tags.contains("dart")); // true print(tags.length); // 4 }
Use Set when you want to store items but guarantee no duplicates. For example — a list of selected filter tags, a collection of unique user IDs.
Lesson 11 — Object Oriented Programming (OOP)
OOP is a way of organizing your code around real-world entities called objects. A Flutter app is basically a collection of widgets, and widgets are objects. So understanding OOP is non-negotiable.
Classes and Objects
A class is a blueprint. An object is something you create from that blueprint.
Think of a class like an architectural plan for a house. The plan defines how many rooms, what size, where the doors are. An object is an actual house built from that plan. You can build many houses from one plan.
class Car { String brand = ""; String color = ""; int year = 0;
void startEngine() { print("$brand engine started!"); }
void displayInfo() { print("$year $color $brand"); } }
void main() { Car myCar = Car(); // creating an object from the Car class myCar.brand = "Toyota"; myCar.color = "White"; myCar.year = 2022;
myCar.displayInfo(); // 2022 White Toyota myCar.startEngine(); // Toyota engine started!
Car anotherCar = Car(); // another object from the same class anotherCar.brand = "Honda"; anotherCar.color = "Black"; anotherCar.year = 2023; anotherCar.displayInfo(); // 2023 Black Honda }
Constructors — Initializing Objects Cleanly
Instead of setting each property one by one after creating the object, a constructor lets you pass all values at the time of creation:
The this.brand syntax inside the constructor means "assign the passed value to this object's brand property."
Named Constructors
You can have multiple constructors with different names:
class Car { String brand; String color; int year;
Car(this.brand, this.color, this.year);
Car.defaultCar() : brand = "Unknown", color = "White", year = 2020; }
void main() { Car car1 = Car("Toyota", "Red", 2022); Car car2 = Car.defaultCar();
print(car1.brand); // Toyota print(car2.brand); // Unknown }
Getters and Setters
Getters and setters let you control how properties are accessed and modified:
class BankAccount { double _balance = 0; // the underscore means this is "private" by convention
double get balance => _balance; // getter — allows reading the balance
set deposit(double amount) { // setter — allows adding money with validation if (amount > 0) { _balance += amount; } else { print("Invalid deposit amount"); } } }
void main() { BankAccount account = BankAccount(); account.deposit = 5000; account.deposit = 2000; account.deposit = -100; // Invalid deposit amount
print(account.balance); // 7000 }
Inheritance — One Class Extending Another
Inheritance lets one class reuse the properties and methods of another class:
class Animal { String name; int age;
Animal(this.name, this.age);
void breathe() { print("$name is breathing"); }
void eat() { print("$name is eating"); } }
class Dog extends Animal { String breed;
Dog(String name, int age, this.breed) : super(name, age);
void bark() { print("$name says: Woof!"); } }
class Cat extends Animal { Cat(String name, int age) : super(name, age);
void meow() { print("$name says: Meow!"); } }
void main() { Dog dog = Dog("Bruno", 3, "Labrador"); dog.breathe(); // Bruno is breathing — inherited from Animal dog.eat(); // Bruno is eating — inherited from Animal dog.bark(); // Bruno says: Woof! — Dog's own method
Cat cat = Cat("Whiskers", 2); cat.breathe(); // Whiskers is breathing — inherited cat.meow(); // Whiskers says: Meow! }
The extends keyword means Dog inherits from Animal. The super(name, age) call passes the name and age up to the Animal constructor.
Method Overriding
A child class can override a parent's method to give it different behavior:
class Animal { String name; Animal(this.name);
void makeSound() { print("$name makes a generic sound"); } }
class Dog extends Animal { Dog(String name) : super(name);
@override void makeSound() { print("$name barks: Woof!"); } }
class Cat extends Animal { Cat(String name) : super(name);
@override void makeSound() { print("$name meows: Meow!"); } }
void main() { Animal a = Animal("Generic Animal"); Dog d = Dog("Bruno"); Cat c = Cat("Whiskers");
a.makeSound(); // Generic Animal makes a generic sound d.makeSound(); // Bruno barks: Woof! c.makeSound(); // Whiskers meows: Meow! }
The @override annotation is not required but it is good practice — it tells Dart and other developers that this method intentionally overrides a parent method.
Abstract Classes
An abstract class cannot be instantiated directly. It is a pure blueprint — meant only to be extended by other classes. It can define abstract methods that child classes must implement:
abstract class Shape { double getArea(); // abstract method — no body, must be implemented by children double getPerimeter();
void describe() { print("I am a shape with area ${getArea()}"); } }
class Circle extends Shape { double radius; Circle(this.radius);
@override double getArea() => 3.14159 * radius * radius;
@override double getPerimeter() => 2 * 3.14159 * radius; }
class Rectangle extends Shape { double width, height; Rectangle(this.width, this.height);
@override double getArea() => width * height;
@override double getPerimeter() => 2 * (width + height); }
void main() { Circle c = Circle(5); print(c.getArea()); // 78.53975 c.describe(); // I am a shape with area 78.53975
Rectangle r = Rectangle(4, 6); print(r.getArea()); // 24 print(r.getPerimeter()); // 20 }
Lesson 12 — Asynchronous Programming
This is one of the most important topics for Flutter development. Almost everything in a real app is async — loading data from an API, reading a file, querying a database. If you do not understand async, your Flutter apps will either freeze or not work correctly.
What is Synchronous vs Asynchronous?
Synchronous means one thing happens at a time. Step 1 completes, then Step 2, then Step 3. If Step 2 takes 5 seconds, everything waits.
Asynchronous means you can start a task, and while waiting for it to complete, you continue doing other things. When the task finishes, you come back to it.
In a Flutter app, if you fetch data from the internet synchronously, the entire app would freeze while waiting. With async, the app stays interactive while data loads in the background.
Future — A Promise of a Future Value
A Future represents a value that is not available yet but will be available in the future. Think of it like ordering food at a restaurant — you place the order (start the Future) and you get a token (the Future object). When the food is ready (task completes), you get your food (the value).
Future<String> fetchUserName() { return Future.delayed(Duration(seconds: 2), () { return "Gagan"; // this value will be available after 2 seconds }); }
void main() { print("Fetching user name..."); fetchUserName().then((name) { print("User name is: $name"); }); print("This line runs immediately, without waiting"); }
Output:
Fetching user name...
This line runs immediately, without waiting
User name is: Gagan (appears after 2 seconds)
async and await — The Clean Way
Using .then() gets messy quickly. The async/await syntax is much cleaner and reads almost like synchronous code:
Future<String> fetchUserName() async { await Future.delayed(Duration(seconds: 2)); return "Gagan"; }
Future<int> fetchUserAge() async { await Future.delayed(Duration(seconds: 1)); return 25; }
void main() async { print("Starting...");
String name = await fetchUserName(); // waits here until name is available int age = await fetchUserAge(); // waits here until age is available
print("Name: $name, Age: $age"); print("Done."); }
Output:
Starting...
Name: Gagan, Age: 25 (appears after ~3 seconds total)
Done.
Rules to remember:
- A function that uses await must be marked with async
- An async function automatically returns a Future
- await can only be used inside an async function
Error Handling with try-catch
Network requests fail. APIs go down. Users have no internet. You must handle errors:
Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); throw Exception("No internet connection"); // simulating an error }
void main() async { try { String data = await fetchData(); print("Data: $data"); } catch (error) { print("Something went wrong: $error"); } finally { print("This always runs, error or not"); } }
Output:
Something went wrong: Exception: No internet connection
This always runs, error or not
Running Multiple Futures at Once
If you have two independent async operations, you do not need to wait for one before starting the other. Use Future.wait to run them simultaneously:
Future<String> fetchName() async { await Future.delayed(Duration(seconds: 2)); return "Gagan"; }
Future<int> fetchAge() async { await Future.delayed(Duration(seconds: 1)); return 25; }
void main() async { print("Starting...");
// This takes 3 seconds total (2 + 1, sequential) // String name = await fetchName(); // int age = await fetchAge();
// This takes only 2 seconds (both run at the same time) List results = await Future.wait([fetchName(), fetchAge()]);
print("Name: ${results[0]}, Age: ${results[1]}"); print("Done."); }
Stream — Multiple Values Over Time
A Future gives you one value in the future. A Stream gives you multiple values over time — like a live feed.
Stream<int> countDown() async* { for (int i = 5; i >= 1; i--) { await Future.delayed(Duration(seconds: 1)); yield i; // yield sends a value into the stream } }
void main() async { print("Countdown starting:");
await for (int value in countDown()) { print(value); }
print("Blast off!"); }
Output (one number per second):
Countdown starting:
5
4
3
2
1
Blast off!
You will use Streams in Flutter for real-time data — chat messages, location updates, Firebase data changes, etc.
Phase 1 Complete — What You Now Know
You have learned the entire Dart foundation. Let's look at what you covered:
Variables and data types — int, double, String, bool, var, final, const. You know how to store data and why types matter.
Null safety — You understand that variables cannot be null by default, how to declare nullable types with ?, and how to handle null values with ?., ??, and !.
Operators — Arithmetic, comparison, logical, assignment shortcuts. You can write expressions and conditions.
Control flow — if/else, switch, and all three types of loops. You can make decisions and repeat code.
Functions — Regular functions, functions with return values, named parameters, and arrow functions. You can write reusable blocks of code.
Collections — List for ordered items, Map for key-value pairs, Set for unique items. You know how to store and access grouped data.
OOP — Classes, objects, constructors, inheritance, abstract classes, and method overriding. You understand the blueprint-and-object mental model.
Async programming — Future, async/await, error handling with try-catch, and Streams. You can write code that does not freeze the app while waiting.
What's Next — Phase 2
Phase 2 is where Flutter actually begins. We will install Flutter on your computer, understand how Flutter works under the hood (Widget tree, rendering), write your first real Flutter app, and start building with the core widgets.