Conditional statements are fundamental building blocks in programming, allowing developers to control the flow of their code based on certain conditions. Dart, a language developed by Google, provides several powerful and flexible conditional statements that empower developers to create dynamic and responsive applications. In this article, we will explore Dart conditional statements, and provide code examples to solidify your understanding.
Introduction to Conditional Statements
Conditional statements are essential for making decisions in your code. They enable you to execute specific blocks of code based on whether a certain condition is true or false. Dart supports several types of conditional statements, including if, else, else if, and more. Let’s explore each of these to understand their functionality and use cases.
The if Statement
The if statement is a fundamental conditional statement that allows you to execute a block of code if a specified condition is true. Let’s consider a simple example:
void main() {
int number = 42;
if (number > 0) {
print('The number is positive.');
}
}
In this example, the print statement will only be executed if the variable number is greater than 0.
The else Statement
To handle the case where the condition in the if statement is false, Dart provides the else statement. Let’s extend our previous example:
void main() {
int number = -5;
if (number > 0) {
print('The number is positive.');
} else {
print('The number is not positive.');
}
}
Now, if the number is not greater than 0, the message in the else block will be printed.
The else if Statement
For scenarios where multiple conditions need to be checked, the else if statement comes in handy. Consider the following example:
void main() {
int number = 0;
if (number > 0) {
print('The number is positive.');
} else if (number < 0) {
print('The number is negative.');
} else {
print('The number is zero.');
}
}
Here, the program will check each condition sequentially and execute the block of code associated with the first true condition. You can have more “else if” statements as per your program requirements.
The if case Statement
The if-case statement in Dart is a powerful tool for making decisions based on a specific pattern. It provides a concise and expressive way to match and destructure values within a single branch.
void main() {
var coordinates = [3, 5];
if (coordinates case [int x, int y]) {
print('{x: $x, y: $y}');
} else {
print('Invalid coordinates.');
}
}
In this example, the if-case statement checks whether the variable “coordinates” matches the pattern [int x, int y]. This essentially verifies if the “coordinates” represent a list of two integers. If the pattern matches, the first element is assigned to the variable x, and the second element is assigned to y. Subsequently, the code inside the block executes, printing the validated coordinates. Conversely, if the pattern does not match, the else block is executed, signaling that the provided coordinates are invalid, as “coordinates” does not conform to the expected structure of a list containing two integers.
void main() {
var data = 45;
if (data case int value) {
print("Data is an integer and holds the value $value.");
} else if (data case String value) {
print("Data is a string and holds the value $value.");
} else {
print("Data is not a known type.");
}
}
In this example, the if-case statement evaluates whether the variable “data” matches the pattern int value in the first case and String value in the second case. If the pattern matches, the value is assigned to the variable “value,” and the corresponding block executes, printing a message about the type and value of the data. If none of the patterns match, the else block is executed, indicating that the data is not of a known type.
Matching Classes and Objects
Pattern matching isn’t limited to simple data structures; it can also be applied to class instances and objects. Let’s consider a scenario where we have different types of shapes and want to identify and handle each type:
abstract class Shape {}
class Circle extends Shape {
final double radius;
Circle(this.radius);
}
class Square extends Shape {
final double side;
Square(this.side);
}
void main() {
var shape = Square(5.0);
if (shape case Circle c) {
print('Processing a circle with radius ${c.radius}');
} else if (shape case Square s) {
print('Processing a square with side length ${s.side}');
} else {
print('Unknown shape type.');
}
}
In this example, the if-case statements not only check the type of the shape object but also destructure it, assigning the extracted values to respective variables (c and s). This allows for dynamic and type-specific handling based on the actual type of the object.
When the if-case statement matches the shape object with the pattern Circle c, it not only confirms that the object is of type Circle but also extracts the Circle instance and assigns it to the variable c. Similarly, when the pattern matches Square s, it assigns the Square instance to the variable s.
This powerful combination of type-checking and destructure assignment streamlines the code, providing a clear and concise way to handle different types of objects. It enhances readability and allows for easy access to the properties of the matched objects within the corresponding branches of the if-case statement.
void main() {
var myObject = {'name': 'John', 'age': 30};
if (myObject case {'name': String name, 'age': int age}) {
print('Name: $name, Age: $age');
} else {
print('Invalid object structure.');
}
}
In this example, the pattern { ‘name’: String name, ‘age’: int age } matches the structure of myObject, allowing for the extraction of the name and age values for further processing. This pattern matching capability enhances the expressiveness of Dart code and makes it well-suited for working with diverse data types.
Ternary Operator
Dart allows developers to express conditional statements more concisely using the ternary operator (? :). The ternary operator is particularly handy for simple conditions where you want to assign a value based on a condition. It is a shorthand way of writing if-else statements.
void main() {
var temperature = 25;
var weatherType = (temperature > 30) ? 'hot' : 'cold';
print('It\'s a $weatherType day.');
}
Here, the value of weatherType is determined based on whether the temperature is greater than 30. If true, it is assigned the value ‘hot’; otherwise, it is assigned ‘cold’.
Null-aware Operators
Dart introduces null-aware operators (?? and ??=) that simplify working with potentially null values. The null-aware coalescing operator (??) returns the right-hand operand if the left-hand operand is null:
void main() {
String? name; // Note the nullable type
String result = name ?? 'Guest';
print('Hello, $result');
}
In this example, if name is null, the string ‘Guest’ will be assigned to result. This helps in handling null values more efficiently.
Conditional Member Access (?.)
The ?. operator allows you to access members of an object if the object is not null. Consider the following example:
void main() {
String? name; // Note the nullable type
int length = name?.length ?? 0;
print('Name length: $length');
}
Here, if name is not null, its length is accessed. Otherwise, the default value of 0 is used.
The Switch Statement
A switch statement provide an efficient way to handle multiple conditions, especially when the conditions are based on the value of a single variable. Dart supports both traditional switch statements and more concise switch expressions.
Basic Switch Statement
A switch statement allows for testing the value of a variable against multiple possible values. Each case represents a value to compare, and the code block associated with the first matching case is executed.
void main() {
var grade = 'A';
switch (grade) {
case 'A':
print('Excellent!');
case 'B':
print('Good job!');
case 'C':
print('Satisfactory.');
case 'D':
print('Needs improvement.');
default:
print('Invalid grade');
}
}
For Dart, unlike in some other programming languages, the ‘break’ statement isn’t required to prevent fall-through in switch statements. This is true except in instances where the case is empty; only then does it fall through, and a ‘break’ is required to prevent fall-through. However, when the case isn’t empty, execution stops at the corresponding case without falling through to the next.
In our example above, execution stops at the case where it matches the grade without falling through to the next case. The above example can be rewritten as, but the ‘break’ statement is redundant, considering the case statements aren’t empty:
void main() {
var grade = 'A';
switch (grade) {
case 'A':
print('Excellent!');
break;
case 'B':
print('Good job!');
break;
case 'C':
print('Satisfactory.');
break;
case 'D':
print('Needs improvement.');
break;
default:
print('Invalid grade');
}
}
This example produces the same output as the one above. However, if you want execution to fall through to the next case or to any other case, you’re going to have to use the continue statement along with a label. For instance, suppose the grade is ‘B,’ but you also want to convey ‘Needs improvement.’ The above program can be rewritten as follows:
void main() {
var grade = 'B';
switch (grade) {
case 'A':
print('Excellent!');
case 'B':
print('Good job!');
continue needsImprovement; // Continue execution to 'needsImprovement' label
case 'C':
print('Satisfactory.');
needsImprovement:
case 'D':
print('Needs improvement.');
default:
print('Invalid grade');
}
}
In this example, when the grade is ‘B,’ the program prints ‘Good job!’ and then continues to the ‘needsImprovement’ label. It executes the associated code block that prints ‘Needs improvement.’ This allows for a more flexible control flow in switch statements.
As mentioned, the break is not needed; however, when you have empty case statements, execution falls through to the next case, as demonstrated in the code below:
void main() {
var grade = 'A';
switch (grade) {
case 'A':
case 'B':
print('Good job!');
case 'C':
case 'D':
print('Needs improvement.');
default:
print('Invalid grade');
}
}
In this example, case ‘A’ is empty. Since the grade is ‘A’, it matches case ‘A’, and execution falls through to case ‘B’. Since case ‘B’ isn’t empty, the program prints ‘Good job!’ and stops execution. To prevent fall-through, you are going to have to explicitly provide the break statement, as demonstrated in the example below:
void main() {
var grade = 'A';
switch (grade) {
case 'A':
break;
case 'B':
print('Good job!');
case 'C':
case 'D':
print('Needs improvement.');
default:
print('Invalid grade');
}
}
In this example, a break statement has been added to case ‘A’ to prevent fall-through. Consequently, when the grade is ‘A’, it matches case ‘A’, and execution stops at the break statement. As a result, the program doesn’t print anything.
Conditional Expressions
Dart supports conditional expressions in case clauses, offering a more concise way to check for ranges or handle multiple values that share the same code, instead of using different empty cases. The use of conditional expressions allows for more expressive and streamlined switch statements. Consider the following example:
void main() {
var i = 1;
switch(i) {
case > 1 && < 10:
print("i is between 1 and 10.");
case 1 || 10:
print("i is either 1 or 10.");
default:
print("Not valid.");
}
}
In this example, the first case checks whether ‘i’ is greater than 1 and less than 10 using the conditional expression > 1 && < 10. The second case checks whether ‘i’ is 1 or 10. If the condition is true, it prints the corresponding message.
Destructuring
Dart supports pattern matching and destructuring in switch cases, similar to the features discussed earlier with if-case statements.
void main() {
var a = 1;
switch(a) {
// Destructure a and assign to i
case int i when i > 1 && i < 10:
// This is executed when i > 1 and < 10
print("a is an integer between 1 and 10.");
case int i:
print("a is an integer and holds the value $i");
default:
print("a is not an integer.");
}
}
In this example, the switch case checks if a is an integer in the first case. If it is, it is destructured and assigned to the variable i. Then, it checks if it matches the conditional expression i > 1 && i < 10. The corresponding message is printed if the conditions are met. In the second case, if a is an integer, the value is printed. This demonstrates how Dart’s switch case supports pattern matching and destructuring.
The default Clause
The default clause in a switch statement serves as a catch-all case. When none of the specified cases match the evaluated value, the control flow transfers to the default block. It provides a way to handle situations where none of the explicitly defined cases is applicable.
void main() {
var grade = 'F';
switch (grade) {
case 'A':
print('Excellent!');
case 'B':
print('Good job!');
case 'C':
print('Satisfactory.');
case 'D':
print('Needs improvement.');
default:
print('Invalid grade');
}
}
In this example, if grade is neither ‘A’, ‘B’, ‘C’, nor ‘D’, the default block is executed, and it prints ‘Invalid grade’. The default clause is useful for handling unexpected or unspecified cases in a switch statement.
Dart also supports a wildcard _ clause that serves the same purpose as the default clause. Instead of using default, the above program could be rewritten as follows:
void main() {
var grade = 'F';
switch (grade) {
case 'A':
print('Excellent!');
case 'B':
print('Good job!');
case 'C':
print('Satisfactory.');
case 'D':
print('Needs improvement.');
case _:
print('Invalid grade');
}
}
The case _ acts as a catch-all case, producing the same output as the above program with the default clause.
Switch Expressions
Switch expressions in Dart provide a concise way to handle multiple conditions and produce a result based on a specified value. Introduced in Dart 3.0, switch expressions offer a more compact and expressive syntax compared to traditional switch statements.
void main() {
var grade = 'B';
String result = getGradeMessage(grade);
print('Message: $result');
}
String getGradeMessage(String grade) {
String message = switch (grade) {
'A' => 'Excellent!', // Return 'Excellent!' if grade is 'A' and so on...
'B' => 'Good job!',
'C' => 'Satisfactory.',
'D' => 'Needs improvement.',
_ => 'Invalid grade', // If grade is neither 'A', 'B', 'C' nor 'D' return 'Invalid grade'
};
return message;
}
In this example, the getGradeMessage function uses a switch expression to determine the message based on the grade. The syntax is more concise, and the result is assigned directly to the result variable.
Switch expressions are particularly useful when the goal is to compute a value based on the input, providing a cleaner and more readable alternative to if-else chains or switch statements.
Remember to use switch expressions when the conditions are simple and can be expressed concisely. For more complex scenarios, traditional if-else statements or switch statements may be more appropriate.
Conclusion
Dart’s conditional statements provide developers with a rich set of tools to control the flow of their programs. Whether using traditional if statements, ternary operators, or the powerful switch statements and expressions, developers have the tools needed to control the flow of their programs effectively. Consider the specific requirements of your code when choosing between these conditional constructs, and use the strengths of each to create robust and readable Dart applications.