OOP in Flutter — In Depth

Ahsi Dev
10 min readAug 10, 2023

--

Object-Oriented Programming (OOP) Concepts in Dart | Flutter

Object-Oriented Programming (OOP) Concepts in Flutter | Dart

1. Class and Object — Blueprint for the Future:

In OOP, a class serves as a blueprint or template to create objects. It defines the properties (attributes) and methods (behavior) that the objects of the class will possess. An object, on the other hand, is an instance of a class. In Dart, classes are defined using the class keyword.

class Person {
String name;
int age;

void sayHello() {
print("Hello, I'm $name!");
}
}

void main() {
var person1 = Person();
person1.name = "John";
person1.age = 30;
person1.sayHello(); // Output: "Hello, I'm John!"
}

— — —

2. Encapsulation — Securing the Treasure:

Encapsulation is the concept of bundling data (attributes) and methods (behavior) together within a class, hiding the internal implementation details from the outside world. It promotes data security and reduces code complexity. In Dart, you can use access modifiers like public, private, and protected to control the visibility of class members.

class BankAccount {
double _balance; // Private attribute

double get balance => _balance; // Getter for balance

void deposit(double amount) {
_balance += amount;
}

void withdraw(double amount) {
if (_balance >= amount) {
_balance -= amount;
} else {
print("Insufficient funds!");
}
}
}

void main() {
var account = BankAccount();
account.deposit(1000);
print("Balance: \$${account.balance}"); // Output: "Balance: $1000.0"
account.withdraw(500);
print("Balance: \$${account.balance}"); // Output: "Balance: $500.0"
}

— — —

3. Inheritance — Reusing the Power:

Inheritance is a mechanism where a class (subclass or derived class) inherits properties and methods from another class (superclass or base class). It promotes code reusability and hierarchical organization. In Dart, you can use the extends keyword to create a subclass.

class Animal {
String name;
void speak() {
print("Animal makes a sound");
}
}

class Dog extends Animal {
@override
void speak() {
print("Dog barks");
}
}

void main() {
var dog = Dog();
dog.name = "Buddy";
dog.speak(); // Output: "Dog barks"
}

— — —

4. Multiple Inheritance:

Multiple inheritance refers to a situation where a class can inherit properties and behavior from more than one superclass. In some programming languages, like C++, multiple inheritance is supported directly, but in Dart, it is achieved through the use of mixins.

class A {
void methodA() {
print("Method from A");
}
}

class B {
void methodB() {
print("Method from B");
}
}

class C with A, B {
void methodC() {
print("Method from C");
}
}

void main() {
var c = C();
c.methodA(); // Output: "Method from A"
c.methodB(); // Output: "Method from B"
c.methodC(); // Output: "Method from C"
}

In the example above, class C uses the with keyword to mixin both classes A and B, allowing it to inherit the methods from both superclasses.

— — —

5. Multilevel Inheritance:

Multilevel inheritance refers to a situation where a class can inherit from another class, which in turn inherits from yet another class. It creates a chain of inheritance, and the derived class has access to properties and methods from all its ancestors.

class A {
void methodA() {
print("Method from A");
}
}

class B extends A {
void methodB() {
print("Method from B");
}
}

class C extends B {
void methodC() {
print("Method from C");
}
}

void main() {
var c = C();
c.methodA(); // Output: "Method from A"
c.methodB(); // Output: "Method from B"
c.methodC(); // Output: "Method from C"
}

In this example, class C extends class B, which, in turn, extends class A, creating a multilevel inheritance chain.

— — —

6. Polymorphism — The Many Faces of OOP

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It enables dynamic method dispatch and is achieved through method overriding and method overloading. In Dart, method overriding is achieved using the @override annotation.

class Shape {
void draw() {
print("Drawing a shape");
}
}

class Circle extends Shape {
@override
void draw() {
print("Drawing a circle");
}
}

class Square extends Shape {
@override
void draw() {
print("Drawing a square");
}
}

void main() {
Shape circle = Circle();
Shape square = Square();

circle.draw(); // Output: "Drawing a circle"
square.draw(); // Output: "Drawing a square"
}

=> Method Overriding and Super Keyword — Fine-Tuning Behavior:

Method overriding is a powerful feature in OOP that allows a subclass to provide a specific implementation for a method defined in its superclass. It enables customization of behavior based on the subclass’s requirements. The super keyword plays a crucial role in method overriding, allowing access to the superclass's methods and properties.

class Animal {
void makeSound() {
print("Animal makes a sound");
}
}

class Dog extends Animal {
@override
void makeSound() {
print("Dog barks");
super.makeSound(); // Call to superclass method
}
}

void main() {
var dog = Dog();
dog.makeSound();
// Output:
// "Dog barks"
// "Animal makes a sound"
}

In this example, we have a superclass Animal with a method makeSound(). The subclass Dog extends Animal and overrides the makeSound() method using the @override annotation. When we create an object of the Dog class and call makeSound(), the overridden method in Dog is executed, printing "Dog barks."

— —

=> Method Overloading:

Method overloading is a feature in some programming languages that allows a class to have multiple methods with the same name but different parameter lists. The methods can perform similar operations but with different inputs, making the code more flexible and readable.

Dart does not support traditional method overloading, as it uses optional named parameters and default values to achieve similar functionality. Let’s see an example:

class MathOperations {
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}
}

void main() {
var math = MathOperations();
int result1 = math.add(5, 10);
double result2 = math.add(3.14, 2.71);

print("Result 1: $result1"); // Output: "Result 1: 15"
print("Result 2: $result2"); // Output: "Result 2: 5.85"
}

In this example, the MathOperations class has two methods named add. One takes two int parameters and returns an int, while the other takes two double parameters and returns a double. Although Dart doesn't have traditional method overloading, we achieve a similar effect by defining methods with different parameter types.

— — —

7. Abstract Classes:

An abstract class in Dart is a class that cannot be instantiated directly. It serves as a blueprint for other classes, providing a common set of methods (functionality) that must be implemented in the subclasses. Abstract classes are used to define a common structure for related classes, ensuring that all subclasses have certain methods.

In the context of coding, an abstract class is created using the abstract keyword, and it may or may not have abstract methods (methods without implementation). Subclasses of an abstract class must override the abstract methods to provide their own implementation.

// Abstract class definition
abstract class Shape {
// Abstract method without implementation
void draw();

// Non-abstract method
void calculateArea() {
print("Calculating area of the shape");
}
}

// Subclass Circle that extends Shape
class Circle extends Shape {
@override
void draw() {
print("Drawing a circle");
}
}

// Subclass Square that extends Shape
class Square extends Shape {
@override
void draw() {
print("Drawing a square");
}
}

void main() {
var circle = Circle();
var square = Square();

circle.draw(); // Output: "Drawing a circle"
square.draw(); // Output: "Drawing a square"

circle.calculateArea(); // Output: "Calculating area of the shape"
square.calculateArea(); // Output: "Calculating area of the shape"
}

In this example, we define an abstract class Shape with an abstract method draw() and a non-abstract method calculateArea(). Both Circle and Square classes extend the abstract class Shape and provide their own implementation of the draw() method. They also inherit the non-abstract method calculateArea() from the abstract class.

— — —

8. Interfaces:

An interface in Dart represents a contract that classes must adhere to. It defines a set of method signatures (function names and their parameters) without providing the implementation. In other words, an interface specifies what methods a class must have but does not specify how those methods are implemented.

Unlike some other languages, Dart does not have a dedicated interface keyword. Instead, any class can act as an interface, and other classes can implement it by providing the required methods.

// Interface for a Flyable object
class Flyable {
void fly() {
print("Flying...");
}
}

// Bird class implements the Flyable interface
class Bird implements Flyable {
@override
void fly() {
print("Bird is flying");
}
}

// Airplane class implements the Flyable interface
class Airplane implements Flyable {
@override
void fly() {
print("Airplane is flying");
}
}

void main() {
var bird = Bird();
var airplane = Airplane();

bird.fly(); // Output: "Bird is flying"
airplane.fly(); // Output: "Airplane is flying"
}

In this example, we define a Flyable class with a method fly(). Both Bird and Airplane classes implement the Flyable interface by providing their own implementation of the fly() method. The Flyable class acts as an interface, defining the method signature that must be present in classes that implement it.

— — —

9. Constructors — Building Objects with Life

Constructors in Dart are special methods that are called when an object is created. They allow you to initialize object properties and perform setup operations. Dart provides default constructors and named constructors for greater flexibility.

class Person {
String name;
int age;

Person(this.name, this.age); // Default constructor

Person.fromBirthYear(int birthYear) {
name = "Unknown";
age = DateTime.now().year - birthYear;
} // Named constructor

void sayHello() {
print("Hello, I'm $name!");
}
}

void main() {
var person1 = Person("John", 30); // Using default constructor
var person2 = Person.fromBirthYear(1995); // Using named constructor

person1.sayHello(); // Output: "Hello, I'm John!"
person2.sayHello(); // Output: "Hello, I'm Unknown!"
}

— —

=> Static Members and Constructors — Class-Wide Behavior

Static members in Dart belong to the class rather than individual objects. They are shared across all instances of the class and can be accessed using the class name. Static constructors, also known as factory constructors, are special constructors that return an instance of the class based on specific conditions.

class MathUtils {
static const double pi = 3.14159;

static double multiply(double a, double b) {
return a * b;
}

factory MathUtils.fromRadius(double radius) {
return MathUtils.multiply(radius, radius) * pi;
}
}

void main() {
var area = MathUtils.fromRadius(5);
print("Area of circle with radius 5: $area"); // Output: "Area of circle with radius 5: 78.53975"
}

In this example, the MathUtils class has a static constant pi and a static method multiply(). Additionally, it has a factory constructor fromRadius() that calculates the area of a circle based on the radius using the static multiply() method.

— —

=> Named Constructors and Factory Constructors — Customized Creation

In Dart, you can have multiple constructors for a class. Named constructors allow you to create specialized constructors with custom names. Factory constructors are used to return an instance of the class based on specific conditions.

class Person {
String name;
int age;

Person(this.name, this.age);

Person.fromBirthYear(int birthYear) {
name = "Unknown";
age = DateTime.now().year - birthYear;
}

factory Person.createGuest() {
return Person("Guest", 0);
}
}

void main() {
var person1 = Person("John", 30);
var person2 = Person.fromBirthYear(1995);
var guest = Person.createGuest();

print("Person 1: ${person1.name} (${person1.age})"); // Output: "Person 1: John (30)"
print("Person 2: ${person2.name} (${person2.age})"); // Output: "Person 2: Unknown (28)"
print("Guest: ${guest.name} (${guest.age})"); // Output: "Guest: Guest (0)"
}

In this example, the Person class has multiple constructors. The default constructor Person(this.name, this.age) initializes the name and age properties. The named constructor Person.fromBirthYear(int birthYear) creates a Person object with the given birth year. The factory constructor Person.createGuest() returns a predefined Guest person.

— — —

10. Final and Const — Immutable Variables

In Dart, you can make variables immutable using the final and const keywords. final variables can be assigned a value only once, whereas const variables are compile-time constants and must have a constant value at compile-time.

void main() {
final int age = 30;
final String name = "John";
// age = 31; // Error: The final variable 'age' can only be set once.

const double pi = 3.14159;
// const double radius = getRadius(); // Error: Constant variables must be initialized with a constant value at compile-time.
}

In the example, age and name are final variables, and once assigned, their values cannot be changed. On the other hand, pi is a const variable, and it must be assigned a constant value at compile-time.

— — —

11. Mixins — Reusable Code Blocks

Mixins are a way to reuse a class’s code in multiple class hierarchies. They allow you to extend a class’s functionality without using inheritance. In Dart, you can create mixins using the mixin keyword.

mixin Flying {
void fly() {
print("Flying...");
}
}

class Bird with Flying {
void chirp() {
print("Bird is chirping");
}
}

class Plane with Flying {
void takeOff() {
print("Plane is taking off");
}
}

void main() {
var bird = Bird();
var plane = Plane();

bird.fly(); // Output: "Flying..."
plane.fly(); // Output: "Flying..."
plane.takeOff(); // Output: "Plane is taking off"
}

In this example, we define a Flying mixin with a fly() method. Both the Bird and Plane classes use this mixin to gain the functionality of flying, demonstrating code reuse without inheritance.

— — —

Conclusion

All these topics are frequently asked in interviews as well. You can use this article to prepare for your next interview. I’ve explained almost all the topics along with coding examples.

If you have any questions or queries, feel free to ask me. Follow the provided links and visit my profile for more articles.

Linkedin: https://www.linkedin.com/in/ahsan-saeed-11a787183/

Follow for more Ahsi Dev 💙

HAPPY CODING!

Photo by Fotis Fotopoulos on Unsplash

--

--

Ahsi Dev

I am a Developer that writes about Software Development