Your Code, Your Rules: Controlling Flow and Objects in Dart

Maxim Gorin
7 min readAug 27, 2024

--

In the previous article, “Dart for Beginners: Core Concepts and a Simple Program”, we got familiar with the basics of Dart — things like variables, data types, and functions. Now, it’s time to push further.

When developing software, understanding core programming concepts is essential. Dart, as a modern language, offers a blend of simplicity and power, especially when it comes to control flow and object-oriented programming (OOP). This article will walk through these concepts using fresh and practical examples, moving beyond the typical templates seen in many tutorials.

Dart programming language | Dart

If you’ve ever wondered how apps manage different scenarios or wanted your code to do more than just run in a straight line, these are the concepts that make it possible. By understanding how to structure and guide the flow of your program, you’ll start writing code that not only works but feels smart. Whether you’re looking to build interactive apps, automate tasks, or just solve more interesting problems, these are the skills that will take your coding from basic to something you can really be proud of.

And remember, this course is all about mastering the essentials of Flutter and Dart using DartPad. We’ll keep things practical and hands-on, so you can start seeing results right away.

Making Decisions with Control Flow

Programming often involves making decisions or repeating actions. This is where control flow statements come into play, allowing your code to react differently based on given conditions or to loop through tasks until goals are met.

Conditional Logic

Programs frequently need to make decisions. Conditional statements help execute different pieces of code depending on specific circumstances.

String trafficLight = 'green';

if (trafficLight == 'green') {
print('Go!');
} else if (trafficLight == 'yellow') {
print('Slow down.');
} else {
print('Stop!');
}

Conditions like these allow software to adapt dynamically, making it responsive to real-time input and events.

Conditional Logic

Loops

Repeating actions is another common need in programming. Loops make this efficient by running the same code multiple times under specific conditions.

For Loop: Ideal when the number of iterations is known ahead of time.

for (int i = 1; i <= 3; i++) {
print('Alarm $i: Time to wake up!');
}
For Loop

While Loop: Useful when the number of repetitions depends on a condition that might change during execution.

int snoozeCount = 0;

while (snoozeCount < 3) {
print('Snoozing...');
snoozeCount++;
}

Whether setting alarms, processing data, or controlling a game loop, these tools give your program the ability to act repeatedly with precision.

While Loop

Object-Oriented Programming: Building Real-World Models

OOP helps to structure programs around real-world entities and behaviors, making code more intuitive, reusable, and easier to manage as it scales.

Defining Real Entities with Classes and Objects

A class serves as a blueprint for creating objects, which are specific instances of that class. Each object has attributes (variables) and behaviors (methods) defined by the class.

class CoffeeMaker {
String model;
bool isOn;

CoffeeMaker(this.model, this.isOn);

void brew() {
if (isOn) {
print('$model is brewing coffee.');
} else {
print('Turn on $model to start brewing.');
}
}
}

void main() {
CoffeeMaker machine = CoffeeMaker('BrewMaster 3000', false);
machine.brew(); // Output: Turn on BrewMaster 3000 to start brewing.
machine.isOn = true;
machine.brew(); // Output: BrewMaster 3000 is brewing coffee.
}

This approach allows you to model real-world items — like a coffee maker — with properties and functions that mirror their actual use.

Defining Real Entities with Classes and Ob

Customizing Behavior with Methods

Methods in classes encapsulate actions that objects can perform. They allow you to manage an object’s internal state and behavior.

class MusicPlayer {
String song;
bool isPlaying = false;

MusicPlayer(this.song);

void play() {
if (!isPlaying) {
isPlaying = true;
print('Now playing: $song');
} else {
print('$song is already playing.');
}
}

void stop() {
if (isPlaying) {
isPlaying = false;
print('Stopped playing $song.');
} else {
print('No music is playing.');
}
}
}

void main() {
MusicPlayer player = MusicPlayer('Song of the Century');
player.play(); // Output: Now playing: Song of the Century
player.stop(); // Output: Stopped playing Song of the Century
}

Whether managing media, adjusting device settings, or handling user interactions, methods let you define the precise actions your software can perform.

Customizing Behavior with Methods

Inheritance: Leveraging Shared Characteristics

Inheritance allows new classes to adopt properties and behaviors from existing ones, promoting code reuse and logical structure.

class Employee {
String name;
int id;

Employee(this.name, this.id);

void work() {
print('$name is working.');
}
}

class Developer extends Employee {
String programmingLanguage;

Developer(String name, int id, this.programmingLanguage) : super(name, id);

@override
void work() {
print('$name is coding in $programmingLanguage.');
}
}

void main() {
Developer dev = Developer('Alice', 101, 'Dart');
dev.work(); // Output: Alice is coding in Dart.
}

This makes it easy to create specialized types of employees or other entities while maintaining a shared foundation of behavior.

Inheritance

Additional Advanced Concepts

Encapsulation for Better Control

By restricting access to an object’s internal state, encapsulation ensures that the internal workings of an object are hidden from the outside world. This keeps the object’s state safe from unintended changes.

class SecureVault {
String _password; // Private variable

SecureVault(this._password);

bool unlock(String input) {
return input == _password;
}
}

void main() {
SecureVault vault = SecureVault('secure123');
print(vault.unlock('guess123')); // Output: false
print(vault.unlock('secure123')); // Output: true
}

Encapsulation safeguards your data, allowing only intended interactions with an object’s internal state.

Encapsulation

Polymorphism: Flexibility at Scale

Polymorphism enables objects to be treated as instances of their parent class, allowing for more versatile code.

class Notification {
void notifyUser() {
print('You have a new notification.');
}
}

class EmailNotification extends Notification {
@override
void notifyUser() {
print('You have a new email.');
}
}

class SMSNotification extends Notification {
@override
void notifyUser() {
print('You have a new SMS message.');
}
}

void main() {
List<Notification> notifications = [
EmailNotification(),
SMSNotification(),
];

for (var notification in notifications) {
notification.notifyUser();
}
}

This approach lets your code adapt easily to different object types, enhancing flexibility and simplifying maintenance.

Polymorphism

Project Example: Personal Assistant

Let’s bring all these concepts together in a personal assistant application that can handle tasks like managing to-do lists, sending notifications, and controlling smart devices.

class Task {
String description;
bool isCompleted = false;

Task(this.description);

void markAsCompleted() {
isCompleted = true;
print('Task "$description" marked as completed.');
}
}

class Notification {
void sendNotification(String message) {
print('Notification: $message');
}
}

class SmartDevice {
String name;
bool isOn;
SmartDevice(this.name, this.isOn);
void togglePower() {
isOn = !isOn;
print('$name is now ${isOn ? 'on' : 'off'}.');
}
}

void main() {
// Task management
Task task = Task('Buy groceries');
task.markAsCompleted();
// Sending a notification
Notification notification = Notification();
notification.sendNotification('Meeting at 3 PM');
// Controlling a smart device
SmartDevice coffeeMaker = SmartDevice('Coffee Maker', false);
coffeeMaker.togglePower();
}

Explanation

Project Example: Personal Assistant
  • The Task class helps manage to-do items, with methods to track completion.
  • The Notification class sends alerts.
  • The SmartDevice class controls smart home gadgets, with actions like turning them on or off.
  • The main() function demonstrates the personal assistant’s capabilities by managing tasks, sending a notification, and controlling a smart device.

Wrapping Up

This article took a fresh approach to explaining key Dart programming concepts like control flow and object-oriented design, illustrating how they can be applied to create useful, real-world applications. As you continue building with Dart and Flutter, these principles will serve as the backbone of more complex and robust software.

Remember, learning is most effective when it’s shared. I encourage you to discuss your experiences, share your thoughts, and showcase the projects you build using these concepts. Whether it’s through blog posts, online communities, or collaborating with peers, your insights and feedback can help others on their journey and solidify your own understanding. Don’t hesitate to be part of the conversation — your contributions can inspire and guide others in the developer community.

--

--

Maxim Gorin
Maxim Gorin

Written by Maxim Gorin

Team lead in mobile development with a passion for Fintech and Flutter. Sharing insights and stories from the tech and dev world on this blog.

No responses yet