100 Days Of Coding Challenge ( Week 4 Summary): Exploring Dart

Ayiko AndrewAyiko Andrew
11 min read

First, I apologize for not updating the series last week. I was on a trip across the Eastern part of my country, Uganda, and couldn't find time to write. But now I'm back home as you read this. Today, I will talk about control flows in Dart.

Control flow

When we dive into programming, one of the basic concepts we encounter is control flow. Control flow is the order in which individual statements, instructions, or function calls are executed or evaluated. Understanding how control flow works in a programming language is essential for writing efficient, readable, and maintainable code.

At its core, control flow dictates how a program makes decisions, loops through instructions, and handles different execution paths. Just as a map provides directions for a journey, control flow constructs guide the program's execution path, allowing it to respond to various inputs and conditions dynamically.

In this article, we'll explore the primary control flow mechanism of loops. I will show you how to control the flow of your Dart code using loops and supporting statements:

  • for loops

  • while and do-while loops

  • break and continue

You can also control the flow in Dart using:

  • Branching statements like if and switch

  • Exception handling with try, catch, and throw

Loops

For loops

The for loop is an essential tool for executing a block of code multiple times. Whether you need to iterate over a range of numbers, traverse a list, or repeat an action a specific number of times, the for loop provides a clear and concise way to do this.

Basic Syntax of the for Loop

The basic structure of a for loop in Dart consists of three parts: initialization, condition, and increment/decrement expression. Here’s the general syntax:

for (initialization; condition; increment/decrement) {
  // code to be executed
}
  • Initialization: This part runs once at the start of the loop. It’s usually used to declare and set up a loop control variable.

  • Condition: Before each iteration, this condition is checked. If it’s true, the loop body runs. If it’s false, the loop stops.

  • Increment/Decrement: This expression runs after each loop iteration. It’s typically used to update the loop control variable.

Example

This is a simple example where we print numbers from 1 to 5:

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

In this example, the loop starts with i being initialized to 1. The condition i <= 5 means that as long as i is less than or equal to 5, the loop continues to execute. The last part increments i by 1.

for - in loop

Dart provides a convenient way to iterate over collections with the for-in loop. Here’s an example:

  List<String> names = ['Kevin', 'Andrew', 'John', 'Sid'];

  for (var name in names) {
    print(name);
  }
// output
// Kevin
// Andrew
// John
// Sid

Understanding Closures and Index Capturing in Dart's for Loops

When working with for loops in Dart, a common confusion arises with closures capturing the loop variable (the index). To understand this concept better, let's break it down step by step.

What is a Closure?

A closure is a function that captures variables from its surrounding scope. This means the closure "remembers" the values of the variables when it was created, even if those variables change later.

Note

In Dart, when you create a closure inside a for loop, the closure captures the loop variable (index) by reference, not by value.

An example is below

void main(List<String> args) {
  var functions = <Function>[];

  for (var i = 0; i < 3; i++) {
    functions.add(() => print(i));
  }

  for (final f in functions) {
    f();
  }
}
// 0 1 2 output

while loop

The while loop repeatedly runs a block of code as long as a given condition is true. The condition is checked before the loop body is executed, so the loop body may not run at all if the condition is false from the start.

Syntax:

while (condition) {
  // Code to be executed
}

Example:

void main() {
  int counter = 1;

  while (counter <= 5) {
    print('Counter: $counter');
    counter++;
  }
}
// Output
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5

do-while

The do-while loop is similar to the while loop, but with one key difference. The condition is checked after the loop body is executed. This means the loop body will always run at least once, regardless of whether the condition is initially true or false.

Syntax:

do {
  // Code to be executed
} while (condition);

Example:

void main() {
  int counter = 1;

  do {
    print('Counter: $counter');
    counter++;
  } while (counter <= 5);
}
// Output
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5

Break and continue

In programming, the break and continue statements are used to change the flow of loops (for, while, and do-while loops). Knowing how to use these statements well can help you write more controlled and readable code.

The break Statement

The break statement is used to immediately end the nearest enclosing loop. When a break statement is encountered inside a loop, the loop stops, and the program continues with the next statement after the loop.

Syntax:

break;

Example:

void main() {
  for (int i = 1; i <= 10; i++) {
    if (i == 5) {
      break;
    }
    print(i);
  }
  print('Loop exited');
}
// Output
1
2
3
4
Loop exited

The continue Statement

The continue statement skips the rest of the code inside the current iteration of the loop and moves on to the next iteration. It doesn't end the loop but jumps to the next cycle, ignoring the remaining code in the current loop iteration.

Syntax:

continue;

Example:

void main() {
  for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
      continue;
    }
    print(i);
  }
}
// Output
1
3
5
7
9

Branching

Branching statements allow a program to make decisions and execute different code paths based on certain conditions. In Dart, the primary branching statements and elements are if and switch.

if

The if statement is used to execute a block of code if a specified condition is true.

Syntax:

if (condition) {
  // Code to be executed if the condition is true
}

Example:

void main() {
  int number = 10;

  if (number > 0) {
    print('$number is positive');
  }
}
// Output
10 is positive

Switch statement

The switch statement is used to execute one of many possible blocks of code based on the value of an expression. It is often more readable than using multiple else if statements when you need to handle many possible values.

Syntax:

switch (expression) {
  case value1:
    // Code to be executed if expression == value1
    break;
  case value2:
    // Code to be executed if expression == value2
    break;
  // More cases as needed
  default:
    // Code to be executed if no case matches
}

Example:

void main() {
  String grade = 'B';

  switch (grade) {
    case 'A':
      print('Excellent');
      break;
    case 'B':
      print('Good');
      break;
    case 'C':
      print('Fair');
      break;
    case 'D':
      print('Poor');
      break;
    case 'F':
      print('Fail');
      break;
    default:
      print('Invalid grade');
  }
}
// Output
Good

Errors and Exceptions

Errors and exceptions are similar concepts, but they have a key difference. Exceptions are conditions that can be caught and handled by your program, while errors are typically issues that are not meant to be caught.

An exception occurs when your program encounters an unexpected situation, such as a file not being found or an invalid input that the program can anticipate and manage. For example, if a function expects a number between 1 and 100 and you provide a number outside this range, an exception can be raised and handled appropriately.

On the other hand, errors are usually more severe issues that arise from programming mistakes or system failures, such as syntax errors or out-of-memory errors. These are not meant to be caught by your program.

Let's go through how to work with this. I will introduce new concepts like classes, constructors, and Futures. Don't worry, I will explain them so you can understand everything we are going to learn.

Throwing in class constructors

We can catch exceptions in class constructors. Constructors are special functions that create instances of classes. We will create a class called Person with an instance variable age. Then, we will check if the age entered by the user is within the acceptable range. If the age is not within the acceptable range then we can catch the exception. Let us go to the code.

class Person {
  final int age; // instance variable

  Person({required this.age}) {
    // checking the range that is acceptable for the program
    if (age < 0) {
      throw Exception("Age should not be negative");
    } else if (age > 140) {
      throw "Age should be less than 140";
    }
  }
}

Let me explain what is happening in the code above, especially the keyword throw. The throw keyword is used to indicate that an error has occurred. When you use throw, it creates an exception, signaling that something unexpected happened and the normal flow of the program should be interrupted. You can use throw to create an exception with any type of object.

Let's create a method that calls the Person class. We will then call this method in the main function. Here's how we will create the method:

void tryCreatingPerson({required int age}) {
  try {
    print(Person(age: age).age); // print the age
  } catch (e) {
    print(e.runtimeType);
    print(e);
  }
  print('----------------------');
}

We defined a method tryCreatingPerson that takes in a named parameter of age which must be an integer. The code inside the try-catch block is executed. If an exception occurs the control flow is then transferred to catch block.

print(Person(age: age).age); This line attempts to create an instance of the Person class with the provided age and then prints the age property of the created Person object.

catch (e) { ... } If an exception occurs in the try block, it is caught here, and the code inside the catch block is executed.

  • print(e.runtimeType); This prints the type of the exception that was caught.

  • print(e); This prints the exception message or object itself.

Inside the catch block, you can pass another argument like this: catch(error, stackTrace) {...}. The new concept here is stackTrace. The error is similar to what we encountered before as e, so don't worry much about it. So, what does stackTrace do?

the stackTrace provides a trace of the function calls that were active at the time an exception was thrown. It shows the sequence of function calls leading up to the point where the error occurred, which can be extremely useful for debugging. This is how stackTrace looks like.

Let us go to the main function and then call the tryCreatingPerson method.

void main(List<String> args) {
  tryCreatingPerson(age: 30);
  tryCreatingPerson(age: -1);
  tryCreatingPerson(age: 150);
}

This main function calls the tryCreatingPerson function three times with different age values: 30, -1, and 150. Each call will produce different outputs. Go ahead and run the code. I'll be here .

Custom exception class

We are going to use the same example we have worked with before but only tweak some few changes with our code. First let us create a custom exception class that we will define. We are calling it InvalidAgeException that will implement Dart's Exception class.

class InvalidAgeException implements Exception {
  final int age;
  final String message;

  InvalidAgeException(this.age, this.message);

  @override
  String toString() => 'InvalidAgeException: $message $age';
}

So, what this does is it takes in the age and the message that explains the type of error or its cause, then returns it as a string. Let us go to the tryCreatingPerson and tweak some changes from there.

void tryCreatingPerson({required int age}) {
  try {
    print(Person(age: age).age);
  } on InvalidAgeException catch (exceptions, stackTrace) {
    print(exceptions);
    print(stackTrace);
  }
  print('----------------------');
}

on InvalidAgeException catch (exceptions, stackTrace) { ... } This block catches exceptions of type InvalidAgeException.

  • print(exceptions); Prints the exception itself, which usually includes a message.

  • print(stackTrace); Prints the stack trace, which shows the sequence of function calls that led to the exception.

Then we alse tweak the Person class by introducing the InvalidAgeException class. Like so.

class Person {
  final int age;

  Person({required this.age}) {
    if (age < 0) {
      throw InvalidAgeException(age, "Age should not be negative");
    } else if (age > 140) {
      throw InvalidAgeException(age, "Age should be less than 140");
    }
  }
}

This will throw the custom exception class we created. Then go ahead in the main function.

void main(List<String> args) {
  tryCreatingPerson(age: 30);
  tryCreatingPerson(age: -1);
  tryCreatingPerson(age: 150);
}

This time the errors will be coming from our own made exception class. Cool right!.

Note:

I found Vandad Nahavandipoor's YouTube tutorial series "Dart Crash Course" to be incredibly helpful in understanding various concepts of Dart programming. His detailed explanations and practical examples were instrumental in the completion of this article.

In conclusion, understanding control flow in Dart is fundamental for writing efficient and maintainable code. By mastering loops, branching statements, and exception handling, we can create dynamic and responsive programs. Whether we're iterating over collections with for loops, making decisions with if and switch statements, or managing errors with try, catch, and throw, these control flow mechanisms are essential tools in our Dart programming toolkit. As we continue our coding journey, let's keep experimenting with these concepts to deepen our understanding and enhance our coding skills. Till we meet again. Happy coding!

10
Subscribe to my newsletter

Read articles from Ayiko Andrew directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ayiko Andrew
Ayiko Andrew

I am a developer from Uganda. Who wants to see really change in technology in my society. Working towards that goal.