Sitemap

--

SOLID Principles Explained with Real Flutter Examples
Software projects often start small and manageable, but as features grow, code can quickly become difficult to maintain. This is where SOLID principles help. SOLID is a set of five object-oriented design principles that make code more scalable, maintainable, testable, and easier to understand.
Although SOLID principles apply to many programming languages, this article demonstrates them using Flutter and Dart examples.

What is SOLID?

SOLID stands for:
S – Single Responsibility Principle (SRP)
O – Open/Closed Principle (OCP)
L – Liskov Substitution Principle (LSP)
I – Interface Segregation Principle (ISP)
D – Dependency Inversion Principle (DIP)

Let's explore each principle with practical Flutter examples.

1. Single Responsibility Principle (SRP)

A class should have only one reason to change.

Bad Example
A service class handling both API calls and local storage:

class UserService {
Future<User> fetchUser() async {
// API call
}

Future<void> saveUser(User user) async {
// Save to local storage
}
}

This class has multiple responsibilities.

Good Example

Separate concerns into dedicated classes:

class UserApiService {
Future<User> fetchUser() async {
// API call
}
}

class UserLocalStorage {
Future<void> saveUser(User user) async {
// Save locally
}
}

Benefits
Easier testing
Better maintainability
Reduced side effects

2. Open/Closed Principle (OCP)

Software entities should be open for extension but closed for modification.

Bad Example

class PaymentProcessor {
void process(String type) {
if (type == 'card') {
print('Card Payment');
} else if (type == 'paypal') {
print('PayPal Payment');
}
}
}

Every new payment method requires modifying existing code.

Good Example

abstract class PaymentMethod {
void pay();
}
class CardPayment implements PaymentMethod {
@override
void pay() {
print('Card Payment');
}
}
class PaypalPayment implements PaymentMethod {
@override
void pay() {
print('PayPal Payment');
}
}

Usage:

void processPayment(PaymentMethod method) {
method.pay();
}

Adding a new payment method requires creating a new class without changing existing code.

3. Liskov Substitution Principle (LSP)

Subclasses should be replaceable with their parent classes without breaking behavior.

Bad Example

abstract class Bird {
void fly();
}

class Sparrow extends Bird {
@override
void fly() {
print('Flying');
}
}

class Penguin extends Bird {
@override
void fly() {
throw Exception('Penguins cannot fly');
}
}

The Penguin violates expectations of Bird.

Good Example

abstract class Bird {}

abstract class FlyingBird extends Bird {
void fly();
}

class Sparrow extends FlyingBird {
@override
void fly() {
print('Flying');
}
}

class Penguin extends Bird {}

Now the hierarchy correctly represents behavior.

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on methods they do not use.

Bad Example

abstract class Worker {
void work();
void eat();
}

class Robot implements Worker {

@override
void work() {}

@override
void eat() {
throw UnimplementedError();
}
}
A robot shouldn't be required to implement eat().

Good Example

abstract class Workable {
void work();
}

abstract class Eatable {
void eat();
}

class Human implements Workable, Eatable {
@override
void work() {}

@override
void eat() {}
}

class Robot implements Workable {
@override
void work() {}
}

Each class only implements what it needs.

5. Dependency Inversion Principle (DIP)

High-level modules should depend on abstractions, not concrete implementations.

Bad Example

class FirebaseAuthService {
void login() {}
}

class LoginController {
final FirebaseAuthService authService =
FirebaseAuthService();
}

The controller is tightly coupled to Firebase.

Good Example

abstract class AuthService {
void login();
}
class FirebaseAuthService implements AuthService {
@override
void login() {}
}
class LoginController {
final AuthService authService;
LoginController(this.authService);
}

Usage:

final controller = LoginController(
FirebaseAuthService(),
);

Now the authentication provider can be replaced easily for testing or future changes.

Applying SOLID in Flutter Projects

Common Flutter architecture patterns that benefit from SOLID:
Clean Architecture
MVVM
BLoC Pattern
Repository Pattern
Feature-Based Architecture

Examples include:
Separating UI from business logic
Using repositories for data access
Injecting dependencies with GetIt or Riverpod
Keeping widgets focused on presentation

Common Mistakes
Creating large service classes with many responsibilities.
Directly calling APIs from widgets.
Tight coupling between layers.
Using inheritance where composition is a better choice.
Ignoring abstractions because the project is initially small.

Conclusion
SOLID principles are not Flutter-specific; they are universal software design principles. However, applying them in Flutter can significantly improve code quality, scalability, and maintainability.
When building Flutter applications, think beyond making features work. Design your code so it remains clean and adaptable as the application grows.
Following SOLID principles today can save countless hours of refactoring tomorrow.

--

--