Phase 1 — Dart Language Fundamentals

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).


  void main() {
    Map<String, dynamic> person = {
      "name": "Gagan",
      "age": 25,
      "city": "Delhi",
      "isEmployed": true,
    };

    print(person);
    print(person["name"]);       // Gagan
    print(person["age"]);        // 25

    // Adding a new key-value pair
    person["email"] = "gagan@example.com";
    print(person["email"]);      // gagan@example.com

    // Updating an existing value
    person["age"] = 26;
    print(person["age"]);        // 26

    // Removing a key
    person.remove("isEmployed");

    // Checking if a key exists
    print(person.containsKey("name"));    // true
    print(person.containsKey("phone"));   // false

    // Looping through a map
    person.forEach((key, value) {
      print("$key: $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:


  class Car {
    String brand;
    String color;
    int year;

    Car(this.brand, this.color, this.year);   // constructor

    void displayInfo() {
      print("$year $color $brand");
    }
  }

  void main() {
    Car car1 = Car("Toyota", "White", 2022);
    Car car2 = Car("Honda", "Black", 2023);

    car1.displayInfo();   // 2022 White Toyota
    car2.displayInfo();   // 2023 Black Honda
  }

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.


No comments:

Post a Comment

Phase 1 — Dart Language Fundamentals

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 Go...