Lecture 1 / 12
Lecture 01 ยท Fundamentals

Introduction to Dart & Setup

Beginner ~50 min

What is Dart?

Dart is a modern, object-oriented programming language developed by Google. It is optimized for client-side development and powers Flutter, the leading framework for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Dart was designed to make app development fast, productive, and scalable. It combines simplicity with powerful programming features, making it suitable for both beginners and professional developers.

Using Dart, developers can create:

  • Mobile applications
  • Web applications
  • Desktop software
  • Backend services
  • Command-line tools

Why Dart?

  • Fast development with hot reload
  • Excellent performance
  • Strong type system with null safety
  • Cross-platform with Flutter

Dart is beginner-friendly while still being powerful enough for large-scale production applications.

Key Features of Dart

1. Easy to Learn

Dart syntax is clean and easy to understand. If you have experience with languages like JavaScript, Java, or C++, Dart will feel familiar.

2. Object-Oriented

Dart is fully object-oriented. Everything in Dart is treated as an object, including numbers and functions.

3. Fast Execution

Dart uses Just-In-Time (JIT) compilation during development for fast iteration and Ahead-Of-Time (AOT) compilation for optimized production performance.

4. Null Safety

Dart includes sound null safety which helps developers avoid many common runtime errors caused by null values.

5. Great for Flutter

Dart is the primary language used with Flutter. Together they allow developers to create beautiful cross-platform applications using a single codebase.

Where Dart is Used

Dart is widely used in modern app development.

Area Usage
Mobile Apps Android and iOS applications using Flutter
Web Apps Interactive browser-based applications
Desktop Apps Windows, macOS, and Linux applications
Backend Server-side APIs and tools

Installing Dart

To start programming in Dart, you need to install the Dart SDK. You can either:

  • Install Dart SDK separately
  • Install Flutter (recommended for app development)

Flutter already includes Dart, so you do not need to install both separately.

Installation

Download the Dart SDK from dart.dev or install Flutter (which includes Dart).

terminal
dart --version
flutter --version

These commands check whether Dart and Flutter are installed correctly.

Recommended Code Editor

Although Dart code can be written in any text editor, modern code editors provide a much better experience.

Popular editors for Dart development:

  • Visual Studio Code
  • Android Studio
  • IntelliJ IDEA

Install the Dart and Flutter extensions for better development support such as:

  • Syntax highlighting
  • Auto-completion
  • Error detection
  • Debugging tools
  • Code formatting

Understanding the Dart Program Structure

Every Dart program starts execution from the main() function.

structure.dart
void main() {

}

Let us understand this:

  • void means the function does not return any value
  • main() is the entry point of the program
  • Curly braces { } contain the code block

Your First Dart Program

hello.dart
void main() {
  print("Hello, Dart Mastery!");
  print("Ready to build cross-platform apps!");
}
Output
Hello, Dart Mastery!
Ready to build cross-platform apps!

Understanding the Code

Let us break the program line by line.

void main() {
  print("Hello, Dart Mastery!");
}
  • main() is the starting point of the program
  • print() displays output on the console
  • Text values are written inside quotation marks
  • Every statement ends with a semicolon ;

๐Ÿ’ป Try It Yourself - Multi-Language Compiler

Practice Dart and many other programming languages right here in your browser! Switch between languages, modify the code, and click "Run" to see results instantly.

๐Ÿ’ก Practice Tips:

  • Switch to Dart in the language selector and try the Flutter development examples
  • Experiment with Dart's strong typing and null safety features
  • Try other mobile languages like Swift, Kotlin, or compare with TypeScript
  • Use the "Load Example" button to see Dart-specific code samples
  • Use Ctrl+Enter to quickly run your code

Running a Dart Program

Save your file with the .dart extension.

Example:

hello.dart

Open the terminal in the same folder and run:

terminal
dart run hello.dart

Comments in Dart

Comments are notes written inside code for explanation. They are ignored by the compiler.

Single-Line Comment

// This is a comment

print("Hello");

Multi-Line Comment

/*
This is a
multi-line comment
*/

Basic Printing Examples

The print() function is one of the most commonly used functions in Dart.

void main() {

  print("Welcome to Dart");

  print(100);

  print(true);

}
Output
Welcome to Dart
100
true

Dart File Extensions

Dart source files always use the .dart extension.

app.dart
main.dart
calculator.dart

Common Beginner Mistakes

// Mistake 1: Missing semicolon
// print("Hello")

// Correct
print("Hello");

// Mistake 2: Wrong function name
// Print("Hello");

// Correct
print("Hello");

// Mistake 3: Missing quotes
// print(Hello)

// Correct
print("Hello");

Real-World Analogy

Think of a Dart program like a recipe.

  • The main() function is the starting instruction
  • Statements are the cooking steps
  • The compiler follows instructions line by line

Mini Practice

Try running the following programs yourself.

// Exercise 1
void main() {

  print("My first Dart program");

}
// Exercise 2
void main() {

  print("Learning Dart is fun!");

  print("Flutter uses Dart");

}
๐ŸŽฏ Exercise 1.1

Install Dart SDK or Flutter. Create a new file hello.dart and run it with dart run hello.dart.

Modify the program and print:

  • Your name
  • Your favorite programming language
  • A motivational message

Summary

  • Dart is a modern programming language developed by Google
  • Dart powers Flutter applications
  • The main() function is the entry point of every program
  • print() displays output to the console
  • Dart files use the .dart extension
  • Semicolons are required after statements
  • Comments improve code readability
Lecture 02 ยท Fundamentals

Variables & Data Types

Beginner ~55 min

Introduction to Variables

Variables are used to store data in memory. They act like containers that hold values which can later be used, modified, or displayed in a program.

In Dart, variables can store:

  • Numbers
  • Text
  • Boolean values
  • Lists
  • Objects

Variables make programs dynamic because values can change while the program runs.

Creating Variables

Dart provides multiple ways to create variables.

variables.dart
void main() {

  var name = "Aman";

  var age = 21;

  print(name);

  print(age);

}
Output
Aman
21

In the example above:

  • name stores text
  • age stores a number
  • var tells Dart to automatically detect the data type

What is a Data Type?

A data type defines the kind of value a variable can store.

Different types of data require different amounts of memory and support different operations.

Dart is a strongly typed language, which means every variable has a specific type.

Common Data Types in Dart

Data Type Description Example
int Whole numbers 10
double Decimal numbers 10.5
String Text values "Hello"
bool True or false values true
dynamic Can store any type Anything

Integer Data Type

The int type stores whole numbers without decimals.

void main() {

  int students = 50;

  int year = 2026;

  print(students);

  print(year);

}
Output
50
2026

Double Data Type

The double type stores decimal numbers.

void main() {

  double price = 99.99;

  double temperature = 36.5;

  print(price);

  print(temperature);

}

String Data Type

The String type stores text values. Strings must be written inside quotation marks.

void main() {

  String country = "India";

  String message = "Welcome to Dart";

  print(country);

  print(message);

}

Boolean Data Type

The bool type stores only two values:

  • true
  • false

Boolean values are commonly used in conditions and decision making.

void main() {

  bool isLoggedIn = true;

  bool isAdmin = false;

  print(isLoggedIn);

  print(isAdmin);

}

Using var Keyword

The var keyword allows Dart to automatically infer the data type.

var name = "Rahul";

var marks = 95;

var percentage = 85.5;

Once Dart determines the type, it cannot be changed later.

var city = "Delhi";

// Invalid
// city = 100;

Using dynamic Keyword

The dynamic keyword allows variables to change types during runtime.

dynamic value = "Hello";

print(value);

value = 100;

print(value);

value = true;

print(value);

Use dynamic carefully because it reduces type safety.

Difference Between var and dynamic

Feature var dynamic
Type Fixed Yes No
Type Safety Strong Weak
Recommended for Beginners Yes Limited Use

Variable Naming Rules

Variable names should be meaningful and readable.

  • Variable names can contain letters, numbers, and underscores
  • Variable names cannot start with a number
  • Variable names are case-sensitive
  • Spaces are not allowed
// Valid variable names

String firstName = "Aman";

int totalMarks = 500;

double account_balance = 1500.50;

// Invalid variable names

// int 1age = 20;

// String user name = "Rahul";

Updating Variables

Variable values can be changed after creation.

void main() {

  int score = 50;

  score = 80;

  print(score);

}
Output
80

Constants in Dart

Constants are variables whose values cannot be changed.

Using final

The value is assigned only once during runtime.

final country = "India";

print(country);

Using const

The value must be known at compile time.

const pi = 3.14159;

print(pi);

String Interpolation

String interpolation allows variables to be inserted directly inside strings.

String name = "Aman";

int age = 20;

print("My name is $name");

print("I am $age years old");
Output
My name is Aman
I am 20 years old

Basic Arithmetic Operations

Numeric variables support mathematical operations.

int a = 10;

int b = 5;

print(a + b);

print(a - b);

print(a * b);

print(a / b);
Output
15
5
50
2.0

Type Checking

Dart allows checking the type of variables.

String language = "Dart";

print(language.runtimeType);
Output
String

Real-World Example

Let us create a simple student information program.

void main() {

  String studentName = "Rahul";

  int age = 21;

  double marks = 88.5;

  bool passed = true;

  print("Student Name: $studentName");

  print("Age: $age");

  print("Marks: $marks");

  print("Passed: $passed");

}

Important Notes

  • Dart is a strongly typed language
  • Use meaningful variable names
  • Strings must be inside quotes
  • var automatically detects types
  • dynamic allows changing types
  • final and const create constants

Common Beginner Mistakes

// Mistake 1: Missing quotes

// String city = Delhi;

// Correct

String city = "Delhi";

// Mistake 2: Wrong boolean values

// bool isActive = True;

// Correct

bool isActive = true;

// Mistake 3: Invalid variable name

// int 1marks = 90;

Mini Practice

Try the following exercises yourself:

// Exercise 1

String name = "Arjun";

int age = 19;

print(name);

print(age);

// Exercise 2

double price = 150.75;

bool inStock = true;

print(price);

print(inStock);

// Exercise 3

final country = "India";

print(country);

Summary

  • Variables store data in memory
  • Dart supports multiple data types
  • int stores whole numbers
  • double stores decimal numbers
  • String stores text
  • bool stores true or false values
  • var automatically infers types
  • dynamic allows flexible typing
  • final and const create constants
Lecture 04 ยท Fundamentals

Control Statements

Beginner ~60 min

Introduction to Control Statements

Control statements determine how a program makes decisions and repeats actions.

By default, Dart executes code line by line from top to bottom. However, real-world applications require logic and decision-making.

Control statements allow programs to:

  • Make decisions
  • Repeat tasks automatically
  • Skip certain operations
  • Stop loops when needed
  • Create dynamic applications

Types of Control Statements

In Dart, control statements are mainly divided into:

  • Conditional Statements
  • Looping Statements
  • Jump Statements

Conditional Statements

Conditional statements allow a program to make decisions based on conditions.

If Statement

The if statement executes a block of code only if a condition is true.

if_statement.dart
void main() {

  int age = 20;

  if (age >= 18) {

    print("You are eligible to vote");

  }

}
Output
You are eligible to vote

The code inside the if block executes only when the condition is true.

Understanding Conditions

Conditions always return either:

  • true
  • false
print(10 > 5);

print(5 == 10);

print(8 != 3);
Output
true
false
true

Comparison Operators

Operator Meaning
== Equal to
!= Not equal to
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to

If-Else Statement

The if-else statement is used when there are two possible outcomes.

if_else.dart
void main() {

  int number = 7;

  if (number % 2 == 0) {

    print("Even Number");

  } else {

    print("Odd Number");

  }

}
Output
Odd Number

Else If Ladder

Multiple conditions can be checked using else if.

void main() {

  int marks = 82;

  if (marks >= 90) {

    print("Grade A");

  } else if (marks >= 75) {

    print("Grade B");

  } else if (marks >= 50) {

    print("Grade C");

  } else {

    print("Fail");

  }

}
Output
Grade B

Nested If Statements

An if statement inside another if statement is called nesting.

void main() {

  int age = 22;

  bool hasID = true;

  if (age >= 18) {

    if (hasID) {

      print("Entry Allowed");

    }

  }

}

Logical Operators

Logical operators combine multiple conditions.

Operator Meaning
&& Logical AND
|| Logical OR
! Logical NOT

Using AND Operator

bool isLoggedIn = true;

bool isAdmin = true;

if (isLoggedIn && isAdmin) {

  print("Access Granted");

}

Using OR Operator

bool hasCoupon = false;

bool isPremiumUser = true;

if (hasCoupon || isPremiumUser) {

  print("Discount Applied");

}

Switch Statement

The switch statement is used when multiple possible values are checked.

switch.dart
void main() {

  String day = "Monday";

  switch (day) {

    case "Monday":
      print("Start of the week");
      break;

    case "Friday":
      print("Weekend is near");
      break;

    default:
      print("Normal day");

  }

}
Output
Start of the week

Looping Statements

Loops are used to repeat code multiple times automatically.

For Loop

The for loop is used when the number of repetitions is known.

for_loop.dart
void main() {

  for (int i = 1; i <= 5; i++) {

    print(i);

  }

}
Output
1
2
3
4
5

Understanding the For Loop

for (int i = 1; i <= 5; i++)
  • int i = 1 โ†’ Starting value
  • i <= 5 โ†’ Condition
  • i++ โ†’ Increment value

While Loop

The while loop runs as long as the condition remains true.

void main() {

  int count = 1;

  while (count <= 5) {

    print(count);

    count++;

  }

}

Do-While Loop

The do-while loop executes the code at least once.

void main() {

  int number = 1;

  do {

    print(number);

    number++;

  } while (number <= 3);

}

Break Statement

The break statement immediately stops a loop.

for (int i = 1; i <= 10; i++) {

  if (i == 6) {

    break;

  }

  print(i);

}
Output
1
2
3
4
5

Continue Statement

The continue statement skips the current iteration.

for (int i = 1; i <= 5; i++) {

  if (i == 3) {

    continue;

  }

  print(i);

}
Output
1
2
4
5

Infinite Loops

A loop that never stops is called an infinite loop.

// Infinite loop example

// while (true) {

//   print("Running forever");

// }

Infinite loops can freeze programs if not handled carefully.

Real-World Example

Let us create a simple login system.

void main() {

  String username = "admin";

  String password = "1234";

  if (username == "admin" && password == "1234") {

    print("Login Successful");

  } else {

    print("Invalid Credentials");

  }

}

Important Notes

  • Conditions must return true or false
  • Use == for comparison, not =
  • Loops reduce repetitive code
  • break stops loops immediately
  • continue skips the current iteration
  • Improper loop conditions may cause infinite loops

Common Beginner Mistakes

// Mistake 1: Using = instead of ==

// if (x = 5)

// Correct

// if (x == 5)

// Mistake 2: Infinite loop

// while (true)

// Mistake 3: Missing braces

// if (true)
//   print("Hello")

Mini Practice

Try the following exercises yourself:

// Exercise 1

int number = 10;

if (number > 0) {

  print("Positive Number");

}

// Exercise 2

for (int i = 1; i <= 3; i++) {

  print(i);

}

// Exercise 3

int marks = 45;

if (marks >= 50) {

  print("Pass");

} else {

  print("Fail");

}

Summary

  • Control statements manage program flow
  • if statements make decisions
  • switch handles multiple choices
  • Loops repeat code automatically
  • for, while, and do-while are common loops
  • break and continue control loop execution
  • Logical operators combine multiple conditions
Lecture 05 ยท Fundamentals

Functions

Beginner ~60 min

Introduction to Functions

Functions are reusable blocks of code designed to perform specific tasks.

Instead of writing the same code repeatedly, we can place the code inside a function and call it whenever needed.

Functions make programs:

  • Cleaner
  • More organized
  • Reusable
  • Easier to debug
  • Easier to maintain

Why Functions are Important

Imagine building a large application without functions. You would need to repeat the same logic many times, making the code difficult to manage.

Functions solve this problem by allowing code reuse.

Basic Function Syntax

In Dart, a function is defined using:

  • Return type
  • Function name
  • Parameters (optional)
  • Function body
function_structure.dart
returnType functionName() {

  // Function body

}

Creating Your First Function

Let us create a simple function that prints a welcome message.

simple_function.dart
void greet() {

  print("Welcome to Dart Functions!");

}

void main() {

  greet();

}
Output
Welcome to Dart Functions!

Understanding the Function

void greet()
  • void means the function returns nothing
  • greet is the function name
  • () contains parameters

Calling a Function

A function executes only when it is called.

greet();

This statement calls the function and runs its code.

Functions with Parameters

Parameters allow functions to accept input values.

parameters.dart
void greetUser(String name) {

  print("Hello, $name");

}

void main() {

  greetUser("Aman");

  greetUser("Rahul");

}
Output
Hello, Aman
Hello, Rahul

Multiple Parameters

Functions can accept multiple parameters.

void studentInfo(String name, int age) {

  print("Name: $name");

  print("Age: $age");

}

void main() {

  studentInfo("Arjun", 20);

}

Functions with Return Values

Functions can return data using the return keyword.

return_function.dart
int addNumbers(int a, int b) {

  return a + b;

}

void main() {

  int result = addNumbers(10, 5);

  print(result);

}
Output
15

Understanding Return Types

The return type specifies the type of value a function returns.

Return Type Description
void No value returned
int Returns integer value
double Returns decimal value
String Returns text
bool Returns true or false

Arrow Functions

Dart provides a shorter syntax for simple functions using the arrow operator =>.

int square(int number) => number * number;

void main() {

  print(square(5));

}
Output
25

Optional Parameters

Optional parameters are not required when calling a function.

void greet(String name, [String? city]) {

  print("Name: $name");

  print("City: $city");

}

void main() {

  greet("Aman");

}

Named Parameters

Named parameters improve readability.

void createUser({
  required String name,
  required int age
}) {

  print("Name: $name");

  print("Age: $age");

}

void main() {

  createUser(name: "Rahul", age: 21);

}

Default Parameter Values

Parameters can also have default values.

void welcome({String message = "Welcome User"}) {

  print(message);

}

void main() {

  welcome();

}
Output
Welcome User

Anonymous Functions

Anonymous functions are functions without names.

var numbers = [1, 2, 3];

numbers.forEach((number) {

  print(number);

});

Scope in Functions

Variables created inside a function are local to that function.

void test() {

  int value = 100;

  print(value);

}

The variable value cannot be accessed outside the function.

Real-World Example

Let us create a calculator function.

double calculateArea(double length, double width) {

  return length * width;

}

void main() {

  double area = calculateArea(10.5, 5.0);

  print("Area: $area");

}
Output
Area: 52.5

Recursion

Recursion happens when a function calls itself.

int factorial(int n) {

  if (n == 1) {

    return 1;

  }

  return n * factorial(n - 1);

}

void main() {

  print(factorial(5));

}
Output
120

Important Notes

  • Functions improve code reusability
  • Use meaningful function names
  • Functions can accept parameters
  • Functions can return values
  • void means no return value
  • Arrow functions simplify short functions

Common Beginner Mistakes

// Mistake 1: Forgetting parentheses

// greet;

// Correct

greet();

// Mistake 2: Missing return statement

// int add() {
// }

// Mistake 3: Wrong parameter types

// addNumbers("10", "20")

Mini Practice

Try the following exercises yourself:

// Exercise 1

void sayHello() {

  print("Hello World");

}

// Exercise 2

int multiply(int a, int b) {

  return a * b;

}

// Exercise 3

String fullName(String first, String last) {

  return "$first $last";

}

Summary

  • Functions are reusable blocks of code
  • Functions reduce repetition
  • Parameters allow input values
  • Return values send data back from functions
  • Dart supports optional and named parameters
  • Arrow functions simplify small functions
  • Recursion allows functions to call themselves
Lecture 06 ยท Fundamentals

Collections

Beginner ~70 min

Introduction to Collections

Collections are used to store and manage multiple values in a single variable.

Instead of creating separate variables for every value, collections allow us to organize data efficiently.

Collections are extremely important in real-world applications because applications often handle:

  • Lists of users
  • Products
  • Messages
  • Orders
  • Student records
  • Game scores

Types of Collections in Dart

Dart mainly provides three important collection types:

  • List
  • Set
  • Map

What is a List?

A List stores multiple values in an ordered way.

Lists are similar to arrays in other programming languages.

Each item in a list has an index position.

Creating a List

list_example.dart
void main() {

  List fruits = ["Apple", "Banana", "Mango"];

  print(fruits);

}
Output
[Apple, Banana, Mango]

Accessing List Elements

List items are accessed using indexes.

Indexes start from 0.

List colors = ["Red", "Green", "Blue"];

print(colors[0]);

print(colors[1]);

print(colors[2]);
Output
Red
Green
Blue

List Index Visualization

Index Value
0 Red
1 Green
2 Blue

Updating List Elements

List values can be modified using indexes.

List numbers = [10, 20, 30];

numbers[1] = 50;

print(numbers);
Output
[10, 50, 30]

Adding Elements to a List

The add() method inserts a new item into the list.

List students = ["Aman", "Rahul"];

students.add("Arjun");

print(students);
Output
[Aman, Rahul, Arjun]

Adding Multiple Elements

List items = [1, 2];

items.addAll([3, 4, 5]);

print(items);
Output
[1, 2, 3, 4, 5]

Removing Elements from a List

Elements can be removed using:

  • remove()
  • removeAt()
List fruits = ["Apple", "Banana", "Mango"];

fruits.remove("Banana");

print(fruits);

fruits.removeAt(0);

print(fruits);
Output
[Apple, Mango]
[Mango]

Length of a List

The length property returns the total number of items.

List names = ["A", "B", "C"];

print(names.length);
Output
3

Looping Through a List

Loops are commonly used with collections.

List cities = ["Delhi", "Mumbai", "Chandigarh"];

for (int i = 0; i < cities.length; i++) {

  print(cities[i]);

}

For-Each Loop

Dart provides a cleaner way to iterate through collections.

List numbers = [1, 2, 3, 4];

numbers.forEach((number) {

  print(number);

});

What is a Set?

A Set is a collection of unique values.

Duplicate values are automatically removed.

set_example.dart
Set numbers = {1, 2, 3, 3, 4};

print(numbers);
Output
{1, 2, 3, 4}

Adding Values to a Set

Set countries = {"India", "USA"};

countries.add("Canada");

print(countries);

Removing Values from a Set

Set items = {1, 2, 3};

items.remove(2);

print(items);
Output
{1, 3}

Difference Between List and Set

Feature List Set
Duplicate Values Allowed Not Allowed
Order Maintained Not Guaranteed
Indexes Available Not Available

What is a Map?

A Map stores data in key-value pairs.

Each value is connected to a unique key.

Maps are widely used in APIs, databases, and JSON data.

Creating a Map

map_example.dart
Map student = {

  "name": "Aman",

  "age": 21,

  "marks": 88.5

};

print(student);
Output
{name: Aman, age: 21, marks: 88.5}

Accessing Map Values

Map user = {

  "username": "admin",

  "password": "1234"

};

print(user["username"]);
Output
admin

Adding Data to a Map

Map car = {

  "brand": "Toyota"

};

car["model"] = "Corolla";

print(car);

Updating Map Values

Map product = {

  "name": "Laptop",

  "price": 50000

};

product["price"] = 55000;

print(product);

Removing Map Values

Map employee = {

  "name": "Rahul",

  "salary": 40000

};

employee.remove("salary");

print(employee);

Map Properties

Map user = {

  "name": "Aman",

  "age": 20

};

print(user.keys);

print(user.values);

print(user.length);

Strongly Typed Collections

Dart allows defining the exact type of data stored in collections.

List<int> numbers = [1, 2, 3];

Set<String> names = {"Aman", "Rahul"};

Map<String, int> marks = {

  "Math": 95,

  "Science": 88

};

Nested Collections

Collections can contain other collections.

List students = [

  {

    "name": "Aman",

    "marks": 90

  },

  {

    "name": "Rahul",

    "marks": 85

  }

];

print(students);

Real-World Example

Let us create a simple shopping cart system.

void main() {

  List cart = ["Laptop", "Mouse"];

  cart.add("Keyboard");

  print(cart);

  Map user = {

    "name": "Aman",

    "cartItems": cart.length

  };

  print(user);

}
Output
[Laptop, Mouse, Keyboard]
{name: Aman, cartItems: 3}

Important Notes

  • Lists maintain order and allow duplicates
  • Sets store unique values only
  • Maps use key-value pairs
  • Indexes start from 0 in Lists
  • Collections can be nested
  • Strong typing improves safety

Common Beginner Mistakes

// Mistake 1: Invalid index

// List numbers = [1, 2, 3];

// print(numbers[5]);

// Mistake 2: Duplicate values in Set

// Set items = {1, 1, 2};

// Mistake 3: Wrong Map key

// print(user["email"]);

Mini Practice

Try the following exercises yourself:

// Exercise 1

List names = ["Aman", "Rahul"];

names.add("Arjun");

print(names);

// Exercise 2

Set numbers = {1, 2, 2, 3};

print(numbers);

// Exercise 3

Map student = {

  "name": "Aman",

  "marks": 90

};

print(student["name"]);

Summary

  • Collections store multiple values
  • Lists maintain order and indexes
  • Sets remove duplicate values
  • Maps store key-value pairs
  • Collections support loops and nesting
  • Strongly typed collections improve code safety
Lecture 06 ยท Core Concepts

Classes & Objects

Intermediate ~45 min

Object-Oriented Programming (OOP) is a programming style where we organize code using classes and objects. Instead of writing everything inside functions, OOP allows us to model real-world entities like users, cars, products, and bank accounts.

Dart is a fully object-oriented language, meaning almost everything in Dart is an object. Using classes makes programs cleaner, reusable, and easier to maintain.

In this lecture, we will learn:

  • How to create classes and objects
  • How constructors initialize data
  • How inheritance works
  • How getters and setters control access
  • How OOP improves code structure

Defining a Class

A class acts like a blueprint or template for creating objects. It defines properties (variables) and behaviors (methods) that objects will have.

An object is an actual instance created from the class.

person.dart
class Person {

  // Properties (instance variables)
  String name;
  int age;

  // Constructor
  Person(this.name, this.age);

  // Method
  void greet() {
    print("Hi, I'm $name");
  }
}

void main() {

  // Creating object
  var p = Person("Ahmed", 25);

  // Calling method
  p.greet();
}

Understanding the Example

  • class Person creates a blueprint
  • name and age are properties
  • Person() is the constructor
  • greet() is a method
  • p is an object of the Person class

Creating Multiple Objects

A single class can create many objects with different values.

multiple_objects.dart
class Student {

  String name;
  int marks;

  Student(this.name, this.marks);

  void showInfo() {
    print("$name scored $marks");
  }
}

void main() {

  var s1 = Student("Ali", 90);
  var s2 = Student("Sara", 85);

  s1.showInfo();
  s2.showInfo();
}

Constructors

Constructors are special methods used to initialize objects when they are created. They automatically run whenever a new object is made.

Dart supports different types of constructors:

  • Default constructors
  • Named constructors
  • Factory constructors
constructors.dart
class Point {

  double x, y;

  // Default constructor
  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = 0,
        y = 0;

  // Named constructor with custom values
  Point.custom(double a, double b)
      : x = a,
        y = b;
}

void main() {

  var p1 = Point(2, 5);

  var p2 = Point.origin();

  var p3 = Point.custom(10, 20);

  print(p1.x);
  print(p2.x);
  print(p3.x);
}

Why Constructors Are Important

  • Initialize object data automatically
  • Reduce repetitive code
  • Ensure objects start with valid values
  • Improve readability

Inheritance

Inheritance allows one class to reuse features from another class. The child class inherits properties and methods from the parent class.

This promotes code reuse and avoids duplication.

inherit.dart
class Animal {

  void speak() {
    print("Animal makes sound");
  }
}

class Dog extends Animal {

  @override
  void speak() {
    print("Woof!");
  }
}

void main() {

  var d = Dog();

  d.speak();
}

Understanding Inheritance

  • Animal is the parent class
  • Dog is the child class
  • extends creates inheritance
  • @override replaces parent behavior

Benefits of Inheritance

  • Code reusability
  • Cleaner program structure
  • Easy maintenance
  • Supports scalable applications

Getters & Setters

Getters and setters provide controlled access to class properties. Instead of directly modifying variables, getters and setters allow validation and custom behavior.

getset.dart
class Rectangle {

  double w, h;

  Rectangle(this.w, this.h);

  // Getter
  double get area {
    return w * h;
  }

  // Setter
  set width(double val) {

    if (val > 0) {
      w = val;
    }
  }
}

void main() {

  var r = Rectangle(5, 10);

  print(r.area);

  r.width = 20;

  print(r.area);
}

Why Use Getters & Setters?

  • Protect object data
  • Add validation logic
  • Prevent invalid values
  • Improve encapsulation

Encapsulation

Encapsulation means hiding internal object data and exposing only necessary functionality. This protects data from accidental modification.

encapsulation.dart
class BankAccount {

  double _balance = 0;

  // Getter
  double get balance => _balance;

  // Deposit method
  void deposit(double amount) {

    if (amount > 0) {
      _balance += amount;
    }
  }
}

void main() {

  var acc = BankAccount();

  acc.deposit(500);

  print(acc.balance);
}

Best Practices

  • Use meaningful class names
  • Keep classes focused on one responsibility
  • Use constructors for initialization
  • Protect important variables using encapsulation
  • Reuse code through inheritance
Practice

Create a BankAccount class with:

  • A private balance variable
  • deposit() method
  • withdraw() method
  • A balance getter
  • Validation to prevent negative balance
Lecture 07 ยท Core Concepts

Null Safety & Asynchronous Programming

Intermediate ~45 min

In modern Dart development, Null Safety and Asynchronous Programming are two extremely important concepts. They help developers write safer, cleaner, and more efficient applications.

Null Safety helps prevent unexpected null errors, while asynchronous programming allows applications to perform time-consuming tasks like API requests, file handling, and database operations without freezing the app.

Null Safety Basics

Before Null Safety was introduced, applications often crashed because variables contained unexpected null values. Dart introduced Null Safety to reduce these runtime errors and improve code reliability.

In Dart, variables are non-nullable by default. This means they must contain a value unless explicitly marked as nullable using ?.

nullsafe.dart
String? maybeNull;           // nullable
String definitely = "hello"; // non-nullable

print(maybeNull?.length);      // null-aware: prints null
print(maybeNull ?? "default"); // if-null operator

int? len = maybeNull?.length;  // nullable chain

In this example:

  • String? allows the variable to store null values
  • String definitely must always contain a value
  • ?. safely accesses a property if the object is not null
  • ?? provides a default value if the variable is null

Why Null Safety Matters

Null-related crashes are one of the most common problems in application development. Null Safety helps developers detect these issues during compilation instead of runtime.

  • Reduces application crashes
  • Improves code readability
  • Makes debugging easier
  • Provides safer memory handling
  • Improves application performance

Null Assertion Operator

Sometimes developers are completely sure that a nullable variable contains a value. In such cases, the Null Assertion Operator ! can be used.

assertion.dart
String? username = "Alex";

print(username!.length);

The ! operator tells Dart that the value is definitely not null. However, using it incorrectly may still cause runtime errors.

Late & Required

The late keyword is used when a variable will be initialized later, while required forces users to provide values for named parameters.

late.dart
class User {
  late String name;  // initialized later

  User({required this.name});
}

Here:

  • late delays initialization until the variable is used
  • required ensures that the parameter must be passed during object creation

Introduction to Asynchronous Programming

Asynchronous programming allows Dart applications to perform long-running operations without blocking the execution of other code.

For example, when downloading data from the internet, the app should continue responding to the user instead of freezing until the download completes.

Understanding Futures

A Future represents a value that will become available sometime in the future. It is commonly used for:

  • API requests
  • Database operations
  • File reading and writing
  • Timers and delays
  • Network communication

Futures (Async/Await)

The async and await keywords simplify asynchronous programming and make the code easier to read.

async.dart
Future fetchData() async {
  await Future.delayed(Duration(seconds: 2));

  return "Data loaded!";
}

void main() async {
  print("Loading...");

  String result = await fetchData();

  print(result); // Data loaded!
}

In this example:

  • Future<String> means the function will return a String later
  • async marks the function as asynchronous
  • await pauses execution until the Future completes
  • Future.delayed() creates an artificial delay

Execution Flow of Async Code

The program first prints:

Loading...

After waiting for 2 seconds, the Future completes and prints:

Data loaded!

This demonstrates how asynchronous code allows the program to continue running while waiting for background tasks.

Error Handling

Errors may occur during asynchronous operations such as failed network requests or missing files. Dart uses try-catch blocks to handle these exceptions safely.

errors.dart
try {
  var data = await fetchData();

} catch (e) {
  print("Error: $e");

} finally {
  print("Done.");
}

Here:

  • try contains risky code
  • catch handles exceptions
  • finally always executes whether an error occurs or not

Real-World Uses of Async Programming

  • Fetching data from REST APIs
  • User authentication systems
  • Cloud database operations
  • Downloading images and files
  • Real-time chat applications
  • Background processing tasks

Best Practices

  • Use Null Safety to avoid crashes
  • Avoid unnecessary use of the ! operator
  • Handle all possible exceptions
  • Use async/await for readable asynchronous code
  • Keep asynchronous operations lightweight and optimized
Practice

Write an async function that fetches user data, handles network errors, and returns a default value on failure.

Try adding:

  • A loading message before fetching data
  • A delay using Future.delayed()
  • A custom error message inside the catch block
  • A fallback default value if the request fails

Summary

In this lecture, we learned about Null Safety and Asynchronous Programming in Dart. We explored nullable and non-nullable variables, null-aware operators, the late keyword, and required parameters.

We also learned how Futures, async, and await work together to handle time-consuming operations efficiently. Finally, we explored error handling techniques using try-catch-finally blocks.

Lecture 08 ยท Core Concepts

Generics & Extensions

Intermediate ~45 min

As applications grow larger, writing reusable and flexible code becomes extremely important. Dart provides powerful features like Generics, Extensions, and Mixins to help developers write cleaner and more reusable programs.

These features are heavily used in Flutter development, APIs, libraries, and large-scale applications because they reduce code duplication and improve maintainability.

In this lecture, we will learn:

  • How generic classes work
  • How generic functions increase flexibility
  • How extensions add functionality to existing classes
  • How mixins share reusable behaviors
  • How these concepts improve code reusability

Generic Classes

Generics allow classes to work with different data types while keeping type safety. Instead of creating separate classes for integers, strings, or doubles, we can create one reusable class using generics.

Generic types are represented using angle brackets <T>.

generic.dart
class Box<T> {

  T value;

  Box(this.value);

  T get() {
    return value;
  }
}

void main() {

  // Integer box
  var intBox = Box<int>(42);

  // String box
  var strBox = Box<String>("hello");

  print(intBox.get());

  print(strBox.get());
}

Understanding the Example

  • T represents a generic type
  • The same class works with different data types
  • Dart ensures type safety at compile time
  • No need to create multiple similar classes

Benefits of Generics

  • Reusable code
  • Better type safety
  • Cleaner architecture
  • Less duplication

Generic List Example

Dart collections like List, Map, and Set also use generics internally.

generic_list.dart
void main() {

  List<int> numbers = [1, 2, 3];

  List<String> names = ["Ali", "Sara"];

  print(numbers);

  print(names);
}

Generic Functions

Generics can also be used with functions. A generic function can work with multiple data types while maintaining type safety.

genfunc.dart
T first<T>(List<T> items) {

  return items[0];
}

void main() {

  print(first<int>([1, 2, 3]));

  print(first(["a", "b", "c"]));
}

Type Inference

Dart is smart enough to automatically detect data types in many cases. This is called type inference.

  • first<int>() explicitly provides the type
  • first(["a","b"]) allows Dart to infer the type automatically

Another Generic Function Example

swap.dart
void printItem<T>(T item) {

  print(item);
}

void main() {

  printItem<String>("Hello");

  printItem<int>(100);

  printItem<double>(5.5);
}

Extensions

Extensions allow us to add new functionality to existing classes without modifying the original class source code.

This is extremely useful when working with built-in classes like String, List, or DateTime.

extension.dart
extension StringX on String {

  String get reversed {

    return split('').reversed.join();
  }

  bool get isEmail {

    return contains('@');
  }
}

void main() {

  print("hello".reversed);

  print("a@b.com".isEmail);
}

Understanding Extensions

  • extension StringX on String extends the String class
  • reversed creates a custom getter
  • isEmail checks if the string contains '@'
  • The original String class remains unchanged

Extension on List

Extensions can also add custom functionality to lists.

list_extension.dart
extension NumberList on List<int> {

  int get total {

    int sum = 0;

    for (var n in this) {
      sum += n;
    }

    return sum;
  }
}

void main() {

  var nums = [1, 2, 3, 4];

  print(nums.total);
}

Mixins

Mixins allow classes to reuse methods and properties from multiple sources without using inheritance.

They are commonly used for sharing reusable behaviors between classes.

mixin.dart
mixin Logger {

  void log(String msg) {

    print("[LOG] $msg");
  }
}

class Service with Logger {

  void run() {

    log("Service started");
  }
}

void main() {

  var s = Service();

  s.run();
}

Why Use Mixins?

  • Share reusable functionality
  • Avoid duplicate methods
  • Keep code modular
  • Support multiple behavior sharing

Multiple Mixins

Dart allows a class to use multiple mixins together.

multi_mixin.dart
mixin A {

  void methodA() {
    print("Method A");
  }
}

mixin B {

  void methodB() {
    print("Method B");
  }
}

class Test with A, B {}

void main() {

  var t = Test();

  t.methodA();

  t.methodB();
}

Best Practices

  • Use generics to create reusable components
  • Keep extensions focused on one responsibility
  • Avoid adding unnecessary extension methods
  • Use mixins for reusable behavior sharing
  • Prefer composition and mixins over deep inheritance
Practice

Create the following:

  • A generic Stack<T> class with push() and pop()
  • An extension on List that adds a sum getter
  • A mixin called Printer that prints log messages
Lecture 09 ยท Core Concepts

Streams & Reactive Programming

Intermediate ~45 min

Streams are one of the most powerful features in Dart and Flutter for handling asynchronous data. They are widely used in real-time applications such as chat systems, live notifications, stock market updates, and user interactions.

Reactive Programming is a programming approach where applications react automatically to changing data. Instead of constantly checking for updates, streams push new data whenever it becomes available.

What are Streams?

Streams deliver a sequence of asynchronous data events. Think of them as a pipe where data flows over time โ€” perfect for real-time data, user input, or file I/O.

Unlike a Future, which returns only one value, a Stream can return multiple values over time.

  • Future โ†’ returns a single value once
  • Stream โ†’ returns multiple values continuously

Streams are commonly used when data changes continuously or arrives over time.

Real-World Uses of Streams

  • Live chat applications
  • Real-time notifications
  • Location tracking
  • Audio and video streaming
  • Sensor data processing
  • User input handling
  • Firebase real-time databases

Creating Streams

Streams can be created manually using asynchronous generator functions with the async* keyword. The yield keyword sends values one by one into the stream.

stream.dart
Stream countStream(int max) async* {

  for (int i = 1; i <= max; i++) {

    await Future.delayed(Duration(seconds: 1));

    yield i;
  }
}

void main() async {

  await for (var val in countStream(5)) {

    print(val); // 1, 2, 3, 4, 5 (one per second)
  }
}

In this example:

  • Stream<int> creates a stream of integers
  • async* marks the function as a stream generator
  • yield emits values into the stream
  • await for listens to stream events one by one

How Stream Execution Works

The function waits for one second before sending each number. This creates a continuous flow of data instead of returning everything at once.

Output:

1
2
3
4
5

Each value appears one second apart, demonstrating asynchronous event handling.

Single Subscription vs Broadcast Streams

Dart provides two main types of streams:

  • Single Subscription Stream โ†’ Can only be listened to once
  • Broadcast Stream โ†’ Multiple listeners can receive the same events

Broadcast streams are commonly used in Flutter applications where multiple widgets need access to the same data source.

Listening to Streams

Streams become useful when listeners subscribe to receive emitted events. The listen() method is commonly used for this purpose.

listen.dart
Stream.fromIterable([1, 2, 3]).listen((value) {

  print(value);
});

The listener automatically reacts whenever new data arrives in the stream.

StreamController

A StreamController provides full control over streams. Developers can manually add data, errors, or close the stream whenever needed.

controller.dart
import 'dart:async';

void main() {

  var controller = StreamController();

  controller.stream.listen((data) => print("Got: $data"));

  controller.add("Hello");
  controller.add("World");

  controller.close();
}

In this example:

  • StreamController creates and manages the stream
  • add() sends data into the stream
  • listen() receives emitted data
  • close() stops the stream and frees resources

Handling Stream Errors

Streams can also emit errors. Error handling is important for preventing application crashes.

errors.dart
controller.stream.listen(

  (data) => print(data),

  onError: (err) => print("Error: $err"),

  onDone: () => print("Stream closed"),
);

Here:

  • onError handles stream errors
  • onDone executes when the stream closes

Stream Transformers

Streams can be modified before the data reaches the listener. This process is called stream transformation.

Dart provides methods like map(), where(), and expand() for transforming data streams.

transform.dart
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);

stream
  .where((n) => n % 2 == 0)   // filter even
  .map((n) => n * 10)         // multiply
  .listen(print);             // 20, 40

This example:

  • Filters only even numbers
  • Multiplies each value by 10
  • Prints the transformed data

Reactive Programming in Flutter

Flutter uses reactive programming heavily. Whenever stream data changes, widgets automatically rebuild and update the UI.

This creates smooth and responsive applications without manually refreshing screens.

StreamBuilder in Flutter

The StreamBuilder widget listens to streams and rebuilds the UI whenever new data arrives.

streambuilder.dart
StreamBuilder(

  stream: countStream(10),

  builder: (context, snapshot) {

    if (!snapshot.hasData)
      return CircularProgressIndicator();

    return Text("Count: ${snapshot.data}");
  },
)

In this widget:

  • stream provides the data source
  • builder rebuilds the UI whenever data changes
  • snapshot stores the latest stream data
  • hasData checks if data is available

Advantages of Streams

  • Efficient real-time data handling
  • Supports reactive programming
  • Improves application responsiveness
  • Useful for event-driven architectures
  • Works perfectly with Flutter widgets

Best Practices

  • Always close StreamControllers to avoid memory leaks
  • Use StreamBuilder for real-time UI updates
  • Handle stream errors properly
  • Keep stream transformations simple and readable
  • Use broadcast streams only when necessary
Practice

Create a stream that emits a random number every second. Display it in a Flutter StreamBuilder.

Try adding:

  • A button to start and stop the stream
  • Error handling for invalid data
  • A loading indicator while waiting for values
  • A transformation that doubles the random number

Summary

In this lecture, we learned about Streams and Reactive Programming in Dart and Flutter. We explored how streams emit asynchronous data over time and how listeners receive stream events.

We also learned about StreamController, stream transformations, error handling, and the StreamBuilder widget for building reactive Flutter interfaces.

Streams are an essential concept for developing modern real-time Flutter applications.

Lecture 10 ยท Flutter

Flutter Basics

Advanced~45 min

What is Flutter?

Flutter is Google's UI toolkit for building natively compiled apps for mobile, web, and desktop from a single Dart codebase. Everything in Flutter is a widget.

Your First Flutter App

main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("My App")),
        body: Center(child: Text("Hello Flutter!")),
      ),
    );
  }
}

Common Widgets

WidgetPurpose
ContainerBox with padding, margin, decoration
Row / ColumnHorizontal / vertical layout
ListViewScrollable list
StackOverlapping widgets
TextFieldText input
ElevatedButtonMaterial button

Hot Reload

Flutter's hot reload lets you see code changes instantly without losing app state โ€” one of its best features for rapid development.

Practice

Create a Flutter app with an AppBar, a TextField, and a button that displays the entered text below.

Lecture 11 ยท Flutter

State Management

Advanced~45 min

StatefulWidget

counter.dart
class Counter extends StatefulWidget {
  @override _CounterState createState() => _CounterState();
}
class _CounterState extends State {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text("$count"),
      ElevatedButton(
        onPressed: () => setState(() => count++),
        child: Text("+"),
      ),
    ]);
  }
}

Provider Pattern

provider.dart
class CartModel extends ChangeNotifier {
  List items = [];
  void add(String item) { items.add(item); notifyListeners(); }
}
// In widget tree:
ChangeNotifierProvider(create: (_) => CartModel(), child: MyApp());
// In any widget:
var cart = context.watch();
print(cart.items.length);

Riverpod (Modern)

riverpod.dart
final counterProvider = StateProvider((ref) => 0);
// In widget:
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).state++;
Practice

Build a todo list app using Provider โ€” add, remove, and toggle completion of items.

Lecture 13 ยท Flutter

Firebase & Backend Integration

Advanced~45 min

Firebase Setup

Add Firebase to your Flutter app using flutterfire configure. This sets up Firebase Core, Authentication, Firestore, and more.

Firestore CRUD

firestore.dart
import 'package:cloud_firestore/cloud_firestore.dart';
// Create
await FirebaseFirestore.instance.collection('users').add({
  'name': 'Ahmed', 'age': 25
});
// Read
var snapshot = await FirebaseFirestore.instance
    .collection('users').get();
for (var doc in snapshot.docs) print(doc.data());
// Real-time listener
FirebaseFirestore.instance.collection('users')
    .snapshots().listen((snapshot) { /* update UI */ });

Firebase Auth

auth.dart
import 'package:firebase_auth/firebase_auth.dart';
// Sign up
await FirebaseAuth.instance.createUserWithEmailAndPassword(
    email: "a@b.com", password: "123456");
// Sign in
await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: "a@b.com", password: "123456");
// Check auth state
FirebaseAuth.instance.authStateChanges().listen((user) {
  if (user != null) print("Logged in: ${user.email}");
});

REST API Calls

api.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
Future fetchPost() async {
  final res = await http.get(Uri.parse('https://api.example.com/posts/1'));
  if (res.statusCode == 200) return jsonDecode(res.body);
  throw Exception('Failed');
}
Practice

Build a chat app screen that reads/writes messages to Firestore in real-time.

Lecture 12 ยท Flutter

Navigation & Routing

Advanced~45 min

Basic Navigation

navigate.dart
Navigator.push(context, MaterialPageRoute(
  builder: (_) => DetailScreen()));
Navigator.pop(context);
Navigator.pushNamed(context, '/detail');

Passing Data

data.dart
final result = await Navigator.push(context,
  MaterialPageRoute(builder: (_) => DetailScreen(id: 42)));
print("Returned: $result");

GoRouter

gorouter.dart
final router = GoRouter(routes: [
  GoRoute(path: '/', builder: (_, __) => HomeScreen()),
  GoRoute(path: '/user/:id', builder: (_, s) =>
    UserScreen(id: s.pathParameters['id']!)),
]);
context.go('/user/42');
Practice

Build 3-screen app with named routes, passing data and returning results.

Lecture 14 ยท Production

Testing & Debugging

Advanced~45 min

Unit Tests

test.dart
import 'package:test/test.dart';
void main() {
  test('addition', () {
    expect(2 + 2, equals(4));
  });
}

Widget Tests

widget_test.dart
testWidgets('has title', (tester) async {
  await tester.pumpWidget(MyApp());
  expect(find.text('My App'), findsOneWidget);
});

Flutter DevTools

Use flutter run --debug and open DevTools for widget inspector, performance profiling, and memory analysis.

Practice

Write unit tests for a Counter class and widget tests for your app.

Lecture 15 ยท Production

Platform Channels

Advanced~45 min

Method Channels

channel.dart
const channel = MethodChannel('com.app/battery');
final level = await channel.invokeMethod('getBatteryLevel');
print("Battery: $level%");

Android (Kotlin)

MainActivity.kt
MethodChannel(flutterEngine.dartExecutor, "com.app/battery")
  .setMethodCallHandler { call, result ->
    if (call.method == "getBatteryLevel") {
      result.success(75)
    }
  }

Event Channels (Streams)

Use EventChannel for continuous data streams from native to Flutter (sensors, location updates).

Practice

Create a platform channel that gets device battery level and displays it in Flutter.

Lecture 16 ยท Production

App Deployment

Advanced~45 min

Build for Release

build.sh
flutter build apk --release
flutter build ios --release
flutter build web

App Signing

key.properties
storeFile=key.jks
storePassword=****
keyAlias=upload
keyPassword=****

Google Play & App Store

  • Create developer accounts ($25 Google, $99 Apple/year)
  • Fill store listing, screenshots, description
  • Upload AAB (Android) or IPA (iOS)
  • Submit for review
Practice

Build a release APK, sign it, and prepare a Play Store listing.

Lecture 17 ยท Production

Design Patterns in Dart

Advanced~45 min

Singleton

singleton.dart
class Database {
  Database._();
  static final instance = Database._();
}

Factory & Builder

factory.dart
class Button {
  final String label;
  Button._(this.label);
  factory Button.primary(String l) => Button._(l);
}

Repository Pattern

repo.dart
class UserRepo {
  final api = ApiService();
  final cache = LocalCache();
  Future getUser(int id) async {
    try { return await api.fetchUser(id); }
    catch (_) { return cache.getUser(id); }
  }
}

BLoC Pattern

bloc.dart
class CounterBloc extends Bloc {
  CounterBloc() : super(0) {
    on((event, emit) => emit(state + 1));
  }
}
Practice

Refactor your app to use Repository + BLoC pattern for state management.

Lecture 18 ยท Production

Capstone Project: Full Flutter App

Advanced~60 min

Project: Task Manager

Build a complete Flutter app with Firebase auth, Firestore, Provider/Riverpod, navigation, and clean architecture.

Features

  • Email/password auth
  • CRUD tasks with Firestore
  • Filter by status
  • User profile
  • Dark mode

Architecture

structure.txt
lib/
  main.dart
  models/     # Data models
  services/   # Auth, Firestore, API
  screens/    # UI screens
  widgets/    # Reusable widgets
  blocs/      # State management

Bonus

  • Push notifications
  • Deploy to Play Store / App Store
  • Add widget + integration tests
Final Challenge

Build the full app, deploy it, share the GitHub repo!