Mastering Dependency Inversion Principle in Flutter

Flutterdynasty
4 min readNov 26, 2023

The Dependency Inversion Principle (DIP) is one of the SOLID principles of object-oriented design. According to DIP, high-level modules should not depend on low-level modules, but both should depend on abstractions. In other words, the principle suggests that the direction of dependency should be towards abstractions, rather than concrete implementations.

To understand high-level and low-level modules in the context of DIP, you can think of high-level modules as modules that contain the more abstract, policy-related, or business logic of an application. These modules typically don’t concern themselves with the specific details of how things are implemented. On the other hand, low-level modules are the ones that deal with the specific implementation details and are more concerned with the mechanics or specifics of how things work.

Here’s a breakdown:

  1. High-level modules: These are modules that contain the business logic or application-specific rules. They are often more abstract and focus on the overall flow and functionality of the application.
  2. Low-level modules: These are modules that deal with the implementation details, like interacting with databases, file systems, external services, etc. They are more concrete and specific in nature.

Now, the reason why high-level modules are considered high-level and low-level modules are considered low-level in DIP is related to the direction of dependency. High-level modules should not depend on low-level modules because it introduces a high degree of coupling and makes the high-level module sensitive to changes in the low-level module. Instead, both high-level and low-level modules should depend on abstractions (interfaces or abstract classes), which allows for flexibility and easier maintenance.

In Flutter development, you can apply DIP by designing your application with a clear separation of concerns. Create abstract interfaces or classes that define the interactions between high-level and low-level modules. High-level modules should depend on these abstractions, and low-level modules should implement these abstractions.

Now, for a more in-depth exploration of the Dependency Inversion Principle with real examples, you might find this article helpful:

Certainly! Let’s consider a real-world example in the context of a Flutter application, moving away from the classic “ducks” example. Imagine a simple task management application where you have tasks that need to be persisted in a database.

Example: Task Management App

High-Level Module: TaskManager

The TaskManager is responsible for managing the overall flow of tasks in the application. It decides how tasks should be displayed, handles user interactions, and orchestrates the business logic.

class TaskManager {
final TaskRepository _taskRepository;

TaskManager(this._taskRepository);

// High-level business logic
List<Task> getTasks() {
return _taskRepository.getAllTasks();
}

void addTask(Task task) {
_taskRepository.saveTask(task);
}
}

Low-Level Module: TaskRepository

The TaskRepository is responsible for handling the data storage details. It interacts with a database or some other form of persistent storage to retrieve and save tasks.

class TaskRepository {
// Low-level data storage details
List<Task> getAllTasks() {
// Logic to retrieve tasks from a database
}

void saveTask(Task task) {
// Logic to save a task to a database
}
}

Abstraction: Task

The Task class serves as an abstraction that both the high-level TaskManager and low-level TaskRepository depend on.

class Task {
final String title;
final String description;

Task(this.title, this.description);
}

In this example:

  • The TaskManager is a high-level module as it focuses on the overall application flow and business logic.
  • The TaskRepository is a low-level module as it deals with specific implementation details, such as data storage.
  • The Task class is an abstraction that both modules depend on. It represents the data structure that is used to represent a task.

Applying the Dependency Inversion Principle here, the TaskManager depends on the abstraction (Task), and the low-level module (TaskRepository) also depends on the same abstraction. This allows for flexibility; if you want to switch to a different storage mechanism, you can create a new repository that adheres to the same abstraction without changing the high-level business logic in TaskManager.

Hope you like it…

--

--

Flutterdynasty

Flutter Developer - Dedicated to Flutter, I craft cross-platform apps that outshine expectations with speed and beauty from a unified codebase.