Skip to content

1. Introduction to Dart

Now that you've explored your app ideas and created designs in Figma, it's time to dive into the world of programming. Before we start turning your ideas into actual apps, we'll take a step back and introduce you to Dart and Flutter — two powerful tools that will bring your creations to life.

In this unit, you'll learn the basics of Dart, the programming language behind Flutter, and then get familiar with Flutter itself, a framework that helps you build beautiful apps. By the end of this school year, you'll have a foundational understanding of how to start developing your own app and how to implement key features using Dart and Flutter. Let's get started and take your app idea to the next level!

1.1 Dart

This section will help you understand and practice the basic concepts of Dart programming.

1.1.1 Concept 1: Print Statements

In Dart, you can use the print() function to output text to the console. This is often used to display results or messages.

Example:

print("Hello, World!");
This will print:
Hello, World!

case sensitive

Be mindful of case sensitivity when writing code.

To experiment with Dart code easily, you can use DartPad, an online tool that allows you to write, run, and share Dart programs directly in your browser. It's a great way to practice without needing to install anything on your computer.

DartPad can be frustrating at times.

Sometimes, your code may be correct, but DartPad still shows a script error. If possible, try running your code locally, use a different browser, or simply hope that the DartPad issue gets resolved soon.

1.1.2 main()-Method

In Dart, every program needs to have a main() method, which is the entry point of the program. When you run a Dart program, the code inside main() is executed first. It’s like the starting point for your program. Without it, Dart wouldn't know where to begin.

Here’s an example with the main() method:

void main() {
  print("Hello, World!");
}
  • void: In Dart, void indicates that the function does not return any value. It’s used for functions that perform an action but don’t return anything to the caller. In this case, the main() function doesn't need to return anything, it simply executes the code inside it.

  • {}: The curly braces {} define the body of the function. Everything inside these braces is the code that will be executed when the function is called. In this case, the body contains print("Hello, World!");, which outputs the text to the console. Generally, curly braces are used to group multiple lines of code together.

  • ; Each line of code requires a semicolon at the end to signal to the compiler, which translates your code into machine code, that the statement has ended.

For the following tasks, use DartPad and modify the main() function as needed.

Task 1: First Dart Program

Open DartPad in your browser and delete the example code visible on the left. Replace it with the code from the example above. Instead of copying and pasting, type the code out yourself. This will help you get more familiar with the syntax, like braces {} and semicolons ;. Once you’ve typed it all, click ‘Run’ to start the application. You should see the app running on the right side of the screen. Now, modify the output to display a message of your choice.

Documentation

Create a learning-dart.md file to document your progress and save the solutions to the tasks. Since DartPad doesn't support file saving, this markdown file will help you keep track of your work. Markdown is well-suited for documenting code. For more details on markdown syntax, refer to the markdown guide. The page you see here is written in markdown.

1.1.3 Concept 2: Variables

A variable is like a box or locker where you can store things for later use. Imagine you have a box labeled "age," and inside, you store the number 15. Later, you can replace it with 16 or any other value. In programming, variables hold data such as numbers, text, or even lists, and you can access or change that data throughout your program. Just like lockers come in different sizes to store different things, variables can hold different types of data, and each one serves a specific purpose in your code.

Variables in program code must be declared with their data type (int for whole numbers, positive or negative, double for decimal numbers, and String for text, which is anything in ""). They must be declared before they can be used in the program, for example:

Variables

int age = 14;
String name = 'Emma';
double pocketMoney = 15.75;

1.1.4 Mathematical Equality vs. Variable Assignment

In math, = means equality. For example, x = 5 means x is equal to 5.

In programming, = is used to assign a value to a variable. For example, age = 15 means you're setting the variable age to 15, like putting 15 into a locker labeled "age." It doesn’t mean age is 15 mathematically; it means age holds the value 15.

This leads to expressions like age = age + 1, which makes sense in programming but not in math. If age is 15, then age + 1 calculates 16, and that new value is assigned to age. It’s not an equation; it’s like taking the 15 out of the locker, adding 1, and putting the new value, 16, back into the locker.

1.1.5 Concept 3: String Interpolation

String interpolation allows you to embed variables inside strings by using $ followed by the variable name inside double quotes.

String Interpolation

void main() {
  int age = 14;
  String name = 'Emma';
  double pocketMoney = 15.75; 

  print('My name is $name, I am $age years old, and I receive pocket money of €$pocketMoney per week.');
}
Output:
My name is Emma, I am 14 years old, and I receive pocket money of €15.75 per week.

If you want to print a $ or other special characters with formatting meaning, use a backslash (\) to escape them, e.g., \$.

Task 2: Variables and String Interpolation

Create two variables: name and pet. Assign your name to the name variable and your pet's name to the pet variable. Then, use print to display a message that says: "My name is [your name] and my pet's name is [your pet's name]." Use string interpolation to insert the variable values into the message.

For an example solution see Variables and String Interpolation.

1.1.6 Concept 4: If Statements

An if statement is used to make decisions in your program. It checks whether a condition is true and, if so, runs the code inside it.

If-Else

void main() {
  int temperature = 30;
  if (temperature > 25) {
    print("It's hot outside!");
  } else {
    print("It's a nice day.");
  }
}

In this example:

  • If temperature is greater than 25, the program prints "It's hot outside!". In the code above temperature is set to 30 and hence the program will print "It's hot outside!".
  • If not, it prints "It's a nice day."

Simple if

Type the example into dartpad. Modify the value of temperature and/or modify the conditions.

Dart also supports the else if syntax, which allows you to test multiple conditions in a sequence.

else-if

void main() {
  int temperature = 20;

  if (temperature < 0) {
    print("It's freezing!");
  } else if (temperature <= 15) {
    print("It's cold.");
  } else {
    print("It's warm.");
  }
}

Task 3: if-statement

Create a variable age and assign your age to it. Use an if statement to check if your age is 18 or older. If it is, print "You are an adult.". If not, print "You are a minor.".

For an example solution see If-Statements 1.

dartpad Look at the messages at the bottom for any issues in your code. If there are no errors, use the Format button to improve the readability of your code. This is especially helpful for formatting if-statements, making it easier to follow and understand the logic.

Task 4: if-statement

Create a variable score and assign a value to it (e.g., 75). Use an if-else statement to check if the score is greater than or equal to 50. If it is, print "You passed the exam!". If it is below 50, print "You failed the exam.". Test the program by changing the value of the score variable to different numbers.

For an example solution see If-Statements 2.

Task 5: complex if-statement

Create a variable age and assign your age to it. Based on the value of age, display the following output:

  • Under 13: "Child"
  • Between 13 and 17: "Teenager"
  • Between 18 and 64: "Adult"
  • 65 and above: "Senior"

For an example solution see If-Statements 3.

1.1.7 Concept 5: For Loops

A for loop allows you to repeat a set of actions efficiently without having to write the same code multiple times. It defines a starting point, a condition, and an increment, controlling how many times the action executes.

Consider the following 10 print statements that display the squares of numbers from 1 to 10:

void main() {
  print("i=1, 1*1=${1 * 1}");
  print("i=2, 2*2=${2 * 2}");
  print("i=3, 3*3=${3 * 3}");
  print("i=4, 4*4=${4 * 4}");
  print("i=5, 5*5=${5 * 5}");
  print("i=6, 6*6=${6 * 6}");
  print("i=7, 7*7=${7 * 7}");
  print("i=8, 8*8=${8 * 8}");
  print("i=9, 9*9=${9 * 9}");
  print("i=10, 10*10=${10 * 10}");
}
Output
i=1, 1*1=1
i=2, 2*2=4
i=3, 3*3=9
i=4, 4*4=16
i=5, 5*5=25
i=6, 6*6=36
i=7, 7*7=49
i=8, 8*8=64
i=9, 9*9=81
i=10, 10*10=100
Identify what stays the same and what varies, and how.

What stays the same: The structure of each print statement is identical. The only thing that changes are the actual numbers. Hence, by introducing a variable i that goes from 1 to 10, we could write print("i=$i, $i*$i=${i*i}"); and i must be incremented each time after printing, going from 1, 2, 3, up to 10.

Here’s the corrected version:

Time for the for statement, which lets us write the same code much more concisely.

For Loops

void main() {
  for (int i = 1; i <= 10; i++) {
    print("i=$i, $i*$i=${i*i}");
  }
}

This loop will print the numbers from 1 to 5. Here's what each part of the loop does:

  • int i = 1;: This is the initialization step.
    • It creates a variable i and sets it to 1. This is where the loop starts.
    • i is often called the loop counter because it keeps track of how many times the loop has run.
  • i <= 5;: This is the condition.
    • As long as this condition is true, the loop will keep running
    • The loop will run as long as i is less than or equal to 5.
    • If i becomes greater than 5, the loop will stop.
  • i++: This is the increment part.
    • It means "increase i by 1" after each iteration (each time the loop runs). i++ is a shorthand for i = i + 1. It’s not a mathematical equation, but an operation that increments the value of the variable i by 1.
    • So, after each time the code inside the loop runs, i will increase by 1. This keeps track of how many times the loop has repeated.
  • The block {}: Inside the curly braces {} is the code that will be repeated. In this case, whatever you put inside the loop will run 5 times, with i increasing each time.

And here is a diagram for the code

graph TD
    %% Nodes
    A(("Start"))
    B("i=1")
    C{"i<=10?"}
    D[/"print"/]
    E("i = i + 1")
    F(("End"))

    subgraph loop
        direction TB
        B --> C
        C -- Yes --> D --> E --> C  
    end

    %% Edges
    A --> B
    C -- No --> F

Task 6: for-loop 1

Write a program that prints the multiplication table for a given number, from 1 to 10.

For an example solution see multiplication table.

Task 7: for-loop 2

Create a table of values for the functions 2x+5 and another function of your choice for the range of values from -5 to 5.

For an example solution see value table.

modulo operator

The modulo operator % returns the remainder of a division. For example, 20 % 2 results in 0 (because 20 is divisible by 2 with no remainder), and 5 % 2 results in 1 (because 5 divided by 2 leaves a remainder of 1). To check if a number is even, you can use == 0. So, if number % 2 == 0, the number stored in the variable number is even; otherwise, it is odd.

Task 8: for loop and modulo operator

Create a program that outputs each number from 1 to 100 and indicates whether it is even or odd.

For an example solution see modulo operator.

In a for loop, it doesn't always have to be i++. For example, you could print all even numbers from 0 to 100 by using i = i + 2 instead:

void main() {
  for (int i = 0; i <= 100; i = i + 2) {
    print(i);
  }
}

There are also other types of loops, like while loops, but the for loop is the most commonly used in Dart, so we'll focus on that for now.

1.1.8 Plan Before You Code

The FizzBuzz task is a common programming exercise: Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.1

In general it is a good idea to think and sketch before you start coding, i.e.

    graph LR
        B[Read] --> C[Analyze] --> D[Sketch] --> E[Code] --> F[Test]
E.g., for the FizzBuzz problem

  • I need a loop
    • variable i
    • [1..100]
  • A number divisible by 3 and 5 is divisible by 15.
  • For the printing logic, I sketch the following
    graph LR
        B{Is i divisible by 15?}
        B -- Yes --> C[Print FizzBuzz]
        B -- No --> D{Is i divisible by 3?}
        D -- Yes --> E[Print Fizz]
        D -- No --> F{Is i divisible by 5?}
        F -- Yes --> G[Print Buzz]
        F -- No --> H[Print the number]

Here's one solution in Dart:

void main() {
  for (int i = 1; i <= 100; i++) {
    if (i % 15 == 0) { 
      print("FizzBuzz");
    } else if (i % 3 == 0) {
      print("Fizz");
    } else if (i % 5 == 0) {
      print("Buzz");
    } else {
      print(i);
    }
  }
}

1.1.9 Concept 6: Functions

A function is like a vending machine. When you press a button (call the function), the machine needs you to insert money (provide the parameters). After the machine processes your money, it gives you back a snack (the result). The return statement is like the moment the vending machine gives you your snack. It’s the point where the function stops working and hands you back the value or result you’ve asked for.

For example, if you put in money for a chocolate bar (input), the vending machine (function) gives you the chocolate bar (output) using the return statement to give it back to you. Without the return statement, the machine would keep your money without giving you anything back! In fact, that's exactly what happens with void functions - they perform an action or task, but they don't return anything back. The function just does its job and doesn't provide any result to the caller.

Functions are helpful for organizing your code and performing repetitive tasks efficiently.

Simple Function/Action/Procedure

// define the function
void greet() {
  print("Hello!");
}

void main() {
  greet(); // Calling the function
} 
This will print:
Hello!

In Dart, // is used to add single-line comments to your code. Comments are lines that are ignored by the program and are used to explain the code or add notes for developers (including yourself) reading the code later.

Modify the given example by calling greet twice. Print something in the main code between the calls, for example:

// define the function
void greet() {
  print("Hello!");
}

void main() {
  greet(); 
  print("======");
  greet();
  greet();
} 
This will print:
Hello!
======
Hello!
Hello!

Functions can also accept parameters (inputs), which allow you to pass values into the function when you call it. Additionally, functions may return values (if a function doesn’t return anything, it’s marked with void). Think back to the sandwich and vending machine examples: the ingredients or money you provide (parameters) are used to produce a result (output).

Functions with parameters and return value

int sum(int a, int b) {
  return a + b;
}
void main() {
  int result = sum(3, 5);
  print(result); // This will print 8
  result = sum(-1, 5);
  print(result); // This will print 4
}

Reusing Variables

In the example, we reused the variable result for the second call to the sum function. This means you declare a variable once with its type, and then you can reuse it.

Make sure to modify the code and fully understand this concept before proceeding.

Task 9: function with parameter

Write a function greeting that takes a parameter name and prints "Hello, [name]" to the console, using the provided name.

For an example solution see greeting function.

Task 10: age calculation function

Create a function that calculates the current age based on the birth year. Tip: You can use DateTime.now().year to get the current year.

Bonus: Instead of using just the birth year, use a DateTime object for the birthdate and account for the current month and day to determine if the birthday has occurred yet this year.

For an example solution see age calculation function.

Task 11: palindrome

Write a function that checks if a given word is a palindrome (a word that reads the same forwards and backwards). Tip: word.split('').reversed.join('') returns the reversed version of the string stored in the variable word.

For an example solution see palindrome checker.

Many programming languages distinguish between float and double for representing floating-point numbers, where double typically offers more precision than float. However, in Dart, both float and double are represented using the same double type. Despite the increased precision of double, it’s important to remember that all floating-point numbers in computers are approximations due to the way they are represented in binary (0s and 1s), leading to small rounding errors.

floating-point imprecision

void main() {
  // Example showing floating-point imprecision
  double a = 0.1;
  double b = 0.2;
  double calculation = a + b;

  print("$a + $b = $calculation");
  // prints: 0.1 + 0.2 = 0.30000000000000004
}

Task 12: Temperature Converter

Create a function that converts temperature from Celsius to Fahrenheit. Hint: fahrenheit = (celsius * 9/5) + 32.

For an example solution see temperature converter.

In Dart and many other programming languages, the &&, ||, ! and == operators are used for logical operations

  • && (AND): Returns true if both conditions are true.
  • || (OR): Returns true if at least one condition is true.
  • ! (NOT): Reverses the boolean value, turning true to false and vice versa.
  • == (EQUALITY): Returns true if the left and right hand side are equal.

Let’s consider a real-world example: You want to check if a person is eligible for a discount based on age and membership status. The person is eligible if they are either under 18 (child) or over 65 (senior), and they must be an active member.

If with complex conditions

void checkDiscount(int age, String membershipStatus) {
  if ((age < 18 || age > 65) && membershipStatus == "active") {
    print("You are eligible for a discount!");
  } else {
    print("Sorry, you're not eligible for a discount.");
  }
}

Explanation of the Code

  • (age < 18 || age > 65): This condition checks if the person is either a child (under 18) or a senior (over 65).
  • membershipStatus == "active": This condition checks if the person has an "active" membership.
  • &&: Both conditions must be true for the discount to be granted.

1.2 Testing Functions Automatically

Automatically testing your functions is an excellent practice, as it allows you to quickly spot mistakes and verify that your code works as expected. With simple test functions, you can check the correctness of your code and receive immediate feedback if something goes wrong. Here’s an example of how you can use an assertTrue function to test a function like isEven automatically.

// A simple function to automatically test any function
void assertTrue(bool expected, bool actual, String title) {
  if (expected == actual) {
    print("Test: $title: Passed");
  } else {
    print("Test: $title: Failed (Expected: $expected, Actual: $actual)");
  }
}

// The function to be tested automatically
bool isEven(int number) {
  return number % 2 == 0;
}

void main() {
  // Running test cases for the isEven function
  assertTrue(true, isEven(4), "4 is even");
  assertTrue(false, isEven(5), "5 is odd");
  assertTrue(true, isEven(0), "0 is even");
  assertTrue(false, isEven(-5), "-5 is odd");
}

Output

Test: 4 is even: Passed
Test: 5 is odd: Passed
Test: 0 is even: Passed
Test: -5 is odd: Passed

Explanation

  1. assertTrue Function: This function compares the expected result with the actual result from the function you're testing. It will print "Passed" if both values match, and "Failed" with the expected and actual values if they don’t -- adding title etc.

  2. isEven Function: This function checks whether the number is even by using the modulus operator (%), returning true if the number is divisible by 2 (even), and false otherwise (odd).

Why is this useful?

  • By writing tests like this, you can ensure your functions behave correctly without having to manually check them every time you change your code.
  • The test provides immediate feedback and highlights issues early, making it easier to debug and maintain your code.

Obviously, void functions are not testable. In general it is good practice to

separate logic and printing

E.g. for the example of extract the logic into one function

bool isEligibleForDiscount(int age, String membershipStatus) {
  return (age < 18 || age > 65) && membershipStatus == "active";
}

You should test the function with different edge cases, e.g.

void main() {
  runTests();
}

void runTests() {
  // Test cases for isEligibleForDiscount
  assertTrue(true, isEligibleForDiscount(10, "active"), "Under 18, active membership");
  assertTrue(true, isEligibleForDiscount(70, "active"), "Over 65, active membership");
  assertTrue(false, isEligibleForDiscount(25, "active"), "Age 25, active membership");
  assertTrue(false, isEligibleForDiscount(10, "inactive"), "Under 18, inactive membership");
  assertTrue(false, isEligibleForDiscount(70, "inactive"), "Over 65, inactive membership");
  assertTrue(false, isEligibleForDiscount(25, "inactive"), "Age 25, inactive membership");
  assertTrue(false, isEligibleForDiscount(18, "active"), "Age 18 (edge case), inactive membership");
  assertTrue(false, isEligibleForDiscount(65, "active"), "Age 65 (edge case), inactive membership");
}

Task 13: Automatic Tests

Write meaningful and automated tests for all your functions to ensure they work correctly and handle a variety of input values. Tip: Consider modifying your functions to return values instead of printing results to the console, which makes them easier to test.

For an example solution see automatic tests.

testing double values

Due to the inherent imprecision of floating-point (double) values, it is not safe to compare them using the == operator. Instead, you should check whether the difference between the two values is smaller than a specified precision (e.g., |a - b| < precision). This approach accounts for small rounding errors that may occur during calculations.

To address the issue of comparing floating-point numbers with a tolerance for precision, we can update the assertTrue function to support this.

void assertDoubleEquals(double expected, double actual, String title) {
  // this precision will fail with 0.1+0.2!
  const precision = 0.0000000000000000001;
  if ((expected - actual).abs() < precision) {
    print("Test: $title: Passed");
  } else {
    print("Test: $title: Failed (Expected: $expected, Actual: $actual)");
  }
}

void main() {
  // Example usage of the updated assert function for floating-point comparison
  double result = 0.1 + 0.2;
  double expected = 0.3;
  // Test comparing two floating-point values
  assertDoubleEquals(expected, result, "Floating point comparison for 0.1 + 0.2");
}

We've used our own simple testing functions here. However, every programming language offers built-in libraries for automated testing. For Dart, you can explore the Dart testing documentation for more information.

1.3 Commenting Functions

Structured comments makes your code easier to understand and maintain by clearly explaining the function’s purpose, parameters, and behavior. It improves readability and helps others (or your future self) quickly grasp how to use the function correctly.

Structured Comments

/// Checks if a person is eligible for a discount.
/// 
/// A person is eligible for a discount if their [age] is either under 18 or over 65,
/// and their [membershipStatus] is "active".
bool isEligibleForDiscount(int age, String membershipStatus) {
  return (age < 18 || age > 65) && membershipStatus == "active";
}

comments

Document your functions using structured comments.

1.4 Lists

In Dart, a List is an ordered collection of elements. Lists can store values of any data type, including int, String, and even custom objects. Dart allows you to create lists that can hold different types of values, making them flexible and powerful. However, I strongly recommend putting only one type in a list and not mixing types.

lists

void main() {
  // List of integers
  List<int> numbers = [1, 2, 3, 4, 5];
  print("Numbers: $numbers");

  // List of strings
  List<String> names = ["Alice", "Bob", "Charlie"];
  print("Names: $names");

  // List with mixed types (heterogeneous list)
  List<dynamic> mixedList = [1, "Hello", true, 3.14];
  print("Mixed List: $mixedList");

  // Accessing elements by index
  print("First number: ${numbers[0]}");
  print("Second name: ${names[1]}");
  print("First element in mixed list: ${mixedList[0]}");
}

Explanation

  • List of integers: List<int> contains only integers.
  • List of strings: List<String> contains only strings.
  • Mixed list: List<dynamic> can hold any type of object (integer, string, boolean, etc.). This list is heterogeneous, meaning it can store multiple types of values.

Task 15: Sum All Integers in a List

Given a list of integers, write a program to calculate the sum of all the integers in the list and print the result to the console.

For an example solution see list sum.

Task 16: Filter Even Numbers from a List

Given a list of integers, write a function to filter and return only the even numbers.

For an example solution see list even numbers.

1.5 Object-Oriented-Programming

cookie cutters

Object-Oriented Programming (OOP) is like baking cookies using cookie cutters. Think of a class as the cookie cutter or a blueprint for creating objects (the cookies). The class defines the shape and structure, just like a blueprint outlines how something should be built. Each time you use the cutter (the class), you create a new cookie (an object). These cookies (objects) can have different shapes, sizes, and flavors (properties and methods), but they all follow the same basic design from the blueprint (class).

In Dart, an object-oriented language, we can define these blueprints (classes) and then create many cookies (objects) based on them. Objects in OOP have two key characteristics:

  • The state represents the object's properties or variables - essentially, its data or characteristics.
  • The behavior is defined by its methods, or the actions it can perform.

The object's state is like the ingredients of the cookie. It can only be accessed or modified through its methods, which act as the "gatekeepers" to the internal state. Just as you wouldn’t typically grab a cookie’s ingredients directly, you interact with the object’s state using its methods. This ensures everything stays consistent and controlled.

1.5.1 Key Concepts in Simple Terms

  • Class: Like a cookie cutter. It defines the shape and style of cookies (objects) but doesn't create one.
  • Object: A cookie made using the cookie cutter. It's an instance of the class.
  • Constructor: A constructor is a special function in a class that initializes an object with its initial properties. Think of it like the dough, setting the foundation by assigning default values - such as size or sweetness - to the object.
  • Methods: Methods define the actions you can perform with your object. For instance, decorating or eating a cookie would be considered methods -- things you can do to interact with it.
  • Inheritance: When you create a new cookie cutter based on an existing one, but with some small tweaks (like changing the shape or adding a new design).

Think of a Pet as a cookie shaped by a "pet" cookie cutter. When you use the cutter, you get a new pet (cookie) with its own name and species.

class Pet {
  // Properties of the pet (like the type of cookie)
  String name;
  String species;

  // Constructor: The dough for the cookie
  Pet(this.name, this.species);

  // Method: The action you can do with the pet (like decorating your cookie)
  void printInfo() {
    print("Pet's name: $name, Species: $species");
  }
}

void main() {
  // Using the "Pet" cutter to create a pet object
  Pet myDog = Pet("Buddy", "Dog");
  // And another pet object
  Pet myCat = Pet("Mimi", "Cat")

  // Printing the pet's information (decorating the cookie with info)
  myDog.printInfo();  // Output: Pet's name: Buddy, Species: Dog
  myCat.printInfo();  // Output: Pet's name: Mimi, Species: Cat
}

Here, the Pet class is the cookie cutter, and the pet object (myPet) is the actual cookie created from it.

1.5.3 Class Student (Using the Blueprint Analogy)

Let's now switch to the blueprint analogy. A class is like a blueprint. Each object is like a house built from the blueprint. You can create multiple houses (objects) based on the same design (class), each having its own unique characteristics but following the same basic structure. The blueprint (class) provides the plan, and the houses (objects) are the actual instances created from that plan.

For example, a Student class outlines the properties (such as name, age, and grade) and methods (such as displaying information) that a student can have.

class Student {
  String name;
  int age;
  String grade;

  // Constructor: Setting up the initial properties for the student object
  Student(this.name, this.age, this.grade);

  // Method: A blueprint for displaying student information
  void displayInfo() {
    print("Name: $name, Age: $age, Grade: $grade");
  }
}

void main() {
  // Using the Student blueprint to create a student object
  Student student1 = Student("Alice", 20, "A");

  // Using the blueprint to display student info
  student1.displayInfo();  // Output: Name: Alice, Age: 20, Grade: A
}

1.5.4 Inheritance

Now, we will use inheritance, which is like having a general blueprint for a sport and then creating a more specialized blueprint for a specific sport, like Tennis. The Tennis class inherits from the Sports class, meaning it gets all the properties and methods from Sports but can add its own custom details.

class Sports {
  String name;
  int numberOfPlayers;

  // Constructor: A blueprint for creating sports objects
  Sports(this.name, this.numberOfPlayers);

  // Method: A blueprint for playing any sport
  void play() {
    print("Playing $name with $numberOfPlayers players.");
  }
}

class Tennis extends Sports {
  // Constructor: A specialized blueprint for Tennis
  Tennis() : super("Tennis", 2);

  // Custom method: Tennis-specific play action
  @override
  void play() {
    print("Playing Tennis with 2 players on the court.");
  }
}

void main() {
  // Using the Tennis blueprint to create a tennis game object
  Tennis tennisGame = Tennis();

  // Using the blueprint to play the tennis game
  tennisGame.play();  // Output: Playing Tennis with 2 players on the court.
}

1.5.5 Inheritance with Extension

In addition to inheriting properties and methods, you can also extend a class's functionality by adding new properties and methods. This allows you to adapt and enrich the behavior of a class. For instance, let's add a property gameType to specify whether the game is "indoor" or "beach" for Volleyball:

// class sport as in the previous section
class Volleyball extends Sports {
  // A specific property for the type of volleyball game (indoor or beach)
  String gameType;

  // Constructor: A specialized blueprint for Volleyball with game type
  Volleyball(String gameType) : this.gameType = gameType, super("Volleyball", 6);

  // Custom method: Volleyball-specific play action
  @override
  void play() {
    print("Playing Volleyball with 6 players in a $gameType game.");
  }

  // New method to display the game type (indoor or beach)
  void showGameType() {
    print("This match is $gameType volleyball.");
  }
}

void main() {
  // Using the Volleyball blueprint to create a volleyball game object with a specific game type
  Volleyball volleyballGame = Volleyball("indoor");
  volleyballGame.play();  // Output: Playing Volleyball with 6 players in a indoor game.
  volleyballGame.showGameType();  // Output: This match is indoor volleyball.
}

Class diagrams are useful for visualizing the structure of object-oriented systems. They show how classes are related, their properties, and methods, helping to clarify the design of a system.

The following figures shows the just defined classes Sports, Tennis and Volleyball.

classDiagram
    class Sports {
        +String name
        +int numberOfPlayers
        +Sports(String name, int numberOfPlayers)
        +void play()
    }

    class Tennis {
        +Tennis()
        +void play()
    }

    class Volleyball {
        +String gameType
        +Volleyball(String gameType)
        +void play()
        +void showGameType()
    }

    Sports <|-- Tennis
    Sports <|-- Volleyball

By using classes and objects, you can organize and manage your code more effectively.

1.5.6 Private Properties and Methods

In Dart, private members (properties and methods) are indicated by a leading underscore (_) before their name. This ensures that they are not accessible outside the class they are defined in.

class BankAccount {
  String accountHolder ="";
  double _balance=0; // Private property

  // Constructor to initialize the account holder and balance
  BankAccount(String this.accountHolder, double initialBalance):
    _balance = initialBalance; 

  // Public method to deposit money into the account
  void deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
    }
  }

  // Public method to withdraw money
  bool withdraw(double amount) {
    if (_canWithdraw(amount)) {
      _balance -= amount;
      return true;
    }
    return false;
  }

  // Public method to get the balance (without modifying it)
  double getBalance() {
    return _balance;
  }

  // Private method to check if the withdrawal is possible
  bool _canWithdraw(double amount) {
    return amount > 0 && _balance >= amount;
  }
}

void main() {
  BankAccount account = BankAccount("John Doe", 1000);

  // Perform operations and print results separately
  account.deposit(500);
  print("Deposited: \$500. Current balance: \$${account.getBalance()}");

  bool withdrawn = account.withdraw(200);
  if (withdrawn) {
    print("Withdrew: \$200. Current balance: \$${account.getBalance()}");
  } else {
    print("Insufficient funds or invalid withdrawal amount.");
  }
}

Task 17: Design a Fashion Outfit Program

Create a program that defines a Clothing class with properties for the type, size, color, and fabric of clothing items. Then, define an Outfit class that can hold a list of Clothing objects and display the outfit's details.

  1. Define a Clothing class with:
    • Properties: type, size, color, fabric
    • Constructor to initialize the values.
  2. Define an Outfit class that has:
    • A list of Clothing items.
    • A method addClothing() to add clothing items to the outfit.
    • A method displayOutfit() to print out the details of all clothing items in the outfit.
  3. Create instances of Clothing (e.g., a shirt, pants, and shoes) and add them to an Outfit.
  4. Display the details of the outfit.

For an example solution see fashion.

Task 18: Create a Movie Recommendation System

Create a simple movie recommendation system. Define a Movie class with properties like title, genre, and rating. Create a MovieLibrary class that holds a list of movies and includes methods to add movies, display movies by genre, and show top-rated movies.

  1. Define a Movie class with:
    • Properties: title, genre, rating.
    • Constructor to initialize the values.
  2. Define a MovieLibrary class with:
    • A list of Movie objects.
    • A method addMovie() to add a movie to the library.
    • A method displayByGenre() to display movies of a certain genre.
    • A method topRatedMovies() to show movies with a rating of 4 or higher.
  3. Create instances of Movie, add them to MovieLibrary, and display recommendations based on genre or rating.

1.5.7 Interfaces in Dart

Dart does not have the interface keyword as in some other languages (like Java or C#). Instead, any class can act as an interface. A class can be implemented by another class using the implements keyword.

When a class implements another class, it must provide implementations for all of the methods defined in the class being implemented (the interface). This helps enforce a contract that ensures the implementing class provides the required behavior.

Example:

// Abstract class Playable, representing the general contract for a sport
abstract class Playable {
  void play();
}

class Hockey implements Playable {
  final int numberOfPlayers;
  final String gameType;  // Ice or Field Hockey

  Hockey(this.gameType) : numberOfPlayers = 6;

  @override
  void play() {
    print("Playing $gameType Hockey with $numberOfPlayers players on the rink.");
  }
}

class Badminton implements Playable {
  final String matchType;  // Single or Doubles

  Badminton(this.matchType);

  @override
  void play() {
    print("Playing $matchType Badminton on the court.");
  }
}

// A method that accepts a Playable instance and makes use of it
void startGame(Playable sport) {
  print("Starting the game...");
  sport.play();  // Invoke the play method of the passed sport
  print("Game Over!");
}

void main() {
  // Create instances of different sports
  Playable hockey = Hockey("Ice");  
  Playable badminton = Badminton("Doubles");  
  // Pass these instances to the startGame method
  startGame(hockey);  // Passing Hockey to the method
  startGame(badminton);  // Passing Badminton to the method
}
  • Here, Playable acts as an interface that defines a play() method.
  • Both Hockey and Badminton classes implement this interface and are required to provide their own version of the play() method.

Great! You made it and are ready to learn Flutter, which means you'll be using Dart to create apps.

1.6 Material

Check out Dart Course or exercism for more exercises.

1.7 Example Solutions

There is always more than one way to solve the tasks.

1.7.1 Task 2: Variables and String-Interpolation

void main() {
  String name = "John";
  String pet = "Buddy";
  print("My name is $name and my pet's name is $pet.");
}

1.7.2 Task 3: If-Statements 1

void main() {
  int age = 20;  // Change this to your actual age
  if (age >= 18) {
    print("You are an adult.");
  } else {
    print("You are a minor.");
  }
}

1.7.3 Task 4: If-Statements 2

void main() {
  int score = 75;  // You can change this value to test with different numbers
  if (score >= 50) {
    print("You passed the exam!");
  } else {
    print("You failed the exam.");
  }
}

1.7.4 Task 5: If-Statements 3

void main() {
  int age = 65;  // Replace with your actual age

  if (age < 13) {
    print("Child");
  } else if (age < 18) {
    print("Teenager");
  } else if (age < 65) {
    print("Adult");
  } else {
    print("Senior");
  }
}

1.7.5 Task 6: multiplication table

void main() {
  int number = 7;  // Replace with any number you want to test
  for (int i = 1; i <= 10; i++) {
    print('$number x $i = ${number * i}');
  }
}

1.7.6 Task 7: print function values

void main() {
  for (int x = -5; x <= 5; x++) {
    int result = 2 * x + 5;
    print('x = $x : 2 * $x + 5 = $result');
  }
}

1.7.7 Task 8: modulo operator

void main() {
  for (int i = 1; i <= 100; i++) {
    if (i % 2 == 0) {
      print('$i is Even');
    } else {
      print('$i is Odd');
    }
  }
}

1.7.8 Task 9: greeting function

void greeting(String name) {
  print("Hello, $name");
}

void main() {
  greeting("Alice");  
}

1.7.9 Task 10: age calculation function

int calculateAge(int birthYear) {
  int currentYear = DateTime.now().year;
  return currentYear - birthYear;
}

void main() {
  int birthYear = 1999;
  int age = calculateAge(birthYear);
  print("Your age is: $age");
}

Bonus

int calculateAgeForBirthdate(DateTime birthDate) {
  DateTime currentDate = DateTime.now();
  // Calculate the preliminary age based on years
  int age = currentDate.year - birthDate.year;
  // Adjust the age if the birthday hasn't occurred yet this year
  if (currentDate.month < birthDate.month ||
      (currentDate.month == birthDate.month &&
          currentDate.day < birthDate.day)) {
    age--;
  }

  return age;
}

void main() {
  DateTime birthDate = DateTime(1990, 12, 24); // Example: December 24, 1990
  int age = calculateAge(birthDate);

  print("Your age is: $age");
}

1.7.10 Task 11: palindrome checker

bool isPalindrome(String word) {
  // Convert the word to lowercase to make the check case-insensitive
  word = word.toLowerCase();
  String reversedWord = word.split('').reversed.join('');
  return word == reversedWord;
}

void main() {
  print(isPalindrome("madam"));  // true
  print(isPalindrome("racecar"));  // true
  print(isPalindrome("hello"));  // false
  print(isPalindrome("Aibohphobia"));  // true
}

1.7.11 Task 12: Celsius to Fahrenheit

double celsiusToFahrenheit(double celsius) {
  return (celsius * 9 / 5) + 32;
}

void main() {
  double celsius = 25.0;
  double fahrenheit = celsiusToFahrenheit(celsius);
  print('$celsius°C is equal to $fahrenheit°F');
}

1.7.12 Task 13: automatic tests

void testIsPalindrome() {
  print("\n=== tests for isPalindrome ===");
  String word1 = "madam";
  bool expected1 = true;
  bool actual1 = isPalindrome(word1);
  assertTrue(expected1 == actual1, true, "Palindrome test for 'madam'");
  String word2 = "racecar";
  bool expected2 = true;
  bool actual2 = isPalindrome(word2);
  assertTrue(expected2 == actual2, true, "Palindrome test for 'racecar'");
  String word3 = "hello";
  bool expected3 = false;
  bool actual3 = isPalindrome(word3);
  assertTrue(expected3 == actual3, true, "Palindrome test for 'hello'");
}

void testCelsiusToFahrenheit() {
  print("\n=== tests for celsiusToFahrenheit ===");
  // Test case 1: Celsius 0 (Freezing point of water)
  double celsius1 = 0;
  double expectedFahrenheit1 = 32.0;
  double actualFahrenheit1 = celsiusToFahrenheit(celsius1);
  assertDoubleEquals(
      expectedFahrenheit1, actualFahrenheit1, "Celsius to Fahrenheit for 0°C");

  // Test case 3: Celsius 100 (Boiling point of water)
  double celsius3 = 100.0;
  double expectedFahrenheit3 = 212.0;
  double actualFahrenheit3 = celsiusToFahrenheit(celsius3);
  assertDoubleEquals(expectedFahrenheit3, actualFahrenheit3,
      "Celsius to Fahrenheit for 100°C");
}

void testFloatingPointProblem() {
  print("\n=== tests for celsiusToFahrenheit ===");
  assertDoubleEquals(0.3, 0.1 + 0.2, "test 0.1 + 0.2");
}

// run the tests
void main() {
  testCalculateAge();
  testIsPalindrome();
  testCelsiusToFahrenheit();
  testFloatingPointProblem();
}

1.7.13 Task 15: Lists-Sum

int sumOfIntegers(List<int> numbers) {
  int sum = 0;
  for (int number in numbers) {
    sum += number;
  }
  return sum;
}

void main() {
  List<int> numbers = [10, 20, 30, 40, 50];
  int total = sumOfIntegers(numbers);
  print("The sum of all integers in the list is: $total");
}

1.7.14 Task 16: Lists-Even-Numbers

```dart
List<int> getEvenNumbers(List<int> numbers) {
  List<int> evenNumbers = [];
  for (int number in numbers) {
    if (number % 2 == 0) {
      evenNumbers.add(number);
    }
  }
  return evenNumbers;
}

void main() {
  // Example list of numbers
  List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  // Call the function to get even numbers
  List<int> evenNumbers = getEvenNumbers(numbers);

  // Print the even numbers
  print("Even numbers: $evenNumbers");  // Output: Even numbers: [2, 4, 6, 8, 10]
}

1.7.15 Task 17: Fashion

class Clothing {
  String type;
  String size;
  String color;
  String fabric;

  Clothing(this.type, this.size, this.color, this.fabric);

  void display() {
    print('$type (Size: $size, Color: $color, Fabric: $fabric)');
  }
}

class Outfit {
  List<Clothing> clothingItems = [];

  void addClothing(Clothing item) {
    clothingItems.add(item);
  }

  void displayOutfit() {
    print('Outfit Details:');
    for (Clothing item in clothingItems) {
      item.display();
    }
  }
}

void main() {
  // Create clothing items
  Clothing shirt = Clothing('Shirt', 'M', 'Blue', 'Cotton');
  Clothing pants = Clothing('Pants', 'L', 'Black', 'Denim');
  Clothing shoes = Clothing('Shoes', '9', 'White', 'Leather');

  // Create an outfit and add clothing items
  Outfit myOutfit = Outfit();
  myOutfit.addClothing(shirt);
  myOutfit.addClothing(pants);
  myOutfit.addClothing(shoes);

  // Display the outfit
  myOutfit.displayOutfit();
}

1.7.16 Task 17: Movie Recomendation

class Movie {
  String title;
  String genre;
  double rating;

  Movie(this.title, this.genre, this.rating);

  void display() {
    print('$title (Genre: $genre, Rating: $rating)');
  }
}

class MovieLibrary {
  List<Movie> movies = [];

  void addMovie(Movie movie) {
    movies.add(movie);
  }

  void displayByGenre(String genre) {
    print('Movies in $genre genre:');
    for (Movie movie in movies) {
      if (movie.genre == genre) {
        movie.display();
      }
    }
  }

  void topRatedMovies() {
    print('Top Rated Movies (Rating >= 4):');
    for (Movie movie in movies) {
      if (movie.rating >= 4) {
        movie.display();
      }
    }
  }
}

void main() {
  // Create movies
  Movie movie1 = Movie('The Lion King', 'Animation', 4.5);
  Movie movie2 = Movie('Inception', 'Sci-Fi', 4.8);
  Movie movie3 = Movie('The Notebook', 'Romance', 3.9);

  // Create movie library and add movies
  MovieLibrary myLibrary = MovieLibrary();
  myLibrary.addMovie(movie1);
  myLibrary.addMovie(movie2);
  myLibrary.addMovie(movie3);

  // Display movies by genre
  myLibrary.displayByGenre('Sci-Fi');

  // Display top-rated movies
  myLibrary.topRatedMovies();
}

  1. Reginald Braithwaite. Don't overthink fizzbuzz. 2007. URL: http://weblog.raganwald.com/2007/01/dont-overthink-fizzbuzz.html (visited on 28.01.2025).