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!");
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, themain()
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 containsprint("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.');
}
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.
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}");
}
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
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 fori = i + 1
. It’s not a mathematical equation, but an operation that increments the value of the variablei
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.
- It means "increase i by 1" after each iteration (each time the loop runs).
- 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
}
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();
}
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): Returnstrue
if both conditions are true.||
(OR): Returnstrue
if at least one condition is true.!
(NOT): Reverses the boolean value, turningtrue
tofalse
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
-
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. -
isEven
Function: This function checks whether the number is even by using the modulus operator (%
), returningtrue
if the number is divisible by 2 (even), andfalse
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
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).
1.5.2 Class Pet (Using the Cookie Cutter Analogy)
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.
- Define a
Clothing
class with:- Properties:
type
,size
,color
,fabric
- Constructor to initialize the values.
- Properties:
- 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.
- A list of
- Create instances of
Clothing
(e.g., a shirt, pants, and shoes) and add them to anOutfit
. - 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.
- Define a
Movie
class with:- Properties:
title
,genre
,rating
. - Constructor to initialize the values.
- Properties:
- 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.
- A list of
- Create instances of
Movie
, add them toMovieLibrary
, 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 aplay()
method. - Both
Hockey
andBadminton
classes implement this interface and are required to provide their own version of theplay()
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();
}
-
Reginald Braithwaite. Don't overthink fizzbuzz. 2007. URL: http://weblog.raganwald.com/2007/01/dont-overthink-fizzbuzz.html (visited on 28.01.2025). ↩