Using SOLID principles in Flutter helps you write clean, scalable, and maintainable code — especially when building large apps with state management and clean architecture.
Let’s go step-by-step 👇
🧱 What Are SOLID Principles?
SOLID is an acronym for five object-oriented design principles:
| Letter | Principle | Goal |
|---|---|---|
| S | Single Responsibility Principle (SRP) | A class should have only one reason to change |
| O | Open/Closed Principle (OCP) | Classes should be open for extension but closed for modification |
| L | Liskov Substitution Principle (LSP) | Subtypes must be substitutable for their base types |
| I | Interface Segregation Principle (ISP) | No class should be forced to implement interfaces it doesn’t use |
| D | Dependency Inversion Principle (DIP) | Depend on abstractions, not concrete implementations |
🧩 1. Single Responsibility Principle (SRP)
Each class or file should have one responsibility.
❌ Bad Example:
class AuthManager {
void login(String email, String password) {
// handle API call
}
void saveUserToLocalStorage(String userData) {
// handle local DB save
}
void showSuccessMessage() {
// handle UI
}
}
This class handles API, storage, and UI — multiple responsibilities.
✅ Good Example:
class AuthRemoteDataSource {
Future<void> login(String email, String password) {
// API call
}
}
class AuthLocalDataSource {
Future<void> saveUser(String userData) {
// Save user locally
}
}
class AuthBloc {
// Handles UI state only
}
Each class now has one purpose — network, storage, or UI logic.
🧱 2. Open/Closed Principle (OCP)
Your classes should be open for extension but closed for modification.
You can add new features without modifying existing code.
✅ Example:
abstract class AuthRepository {
Future<void> login(String email, String password);
}
class FirebaseAuthRepository implements AuthRepository {
@override
Future<void> login(String email, String password) {
// Firebase logic
}
}
class ApiAuthRepository implements AuthRepository {
@override
Future<void> login(String email, String password) {
// REST API logic
}
}
If you switch from Firebase to REST API, you only create a new class — no need to modify existing ones.
🧠 3. Liskov Substitution Principle (LSP)
Subclasses should behave like their base classes — without breaking functionality.
✅ Example:
abstract class Payment {
void pay(double amount);
}
class CreditCardPayment extends Payment {
@override
void pay(double amount) {
print("Paid $amount with credit card");
}
}
class UPIPayment extends Payment {
@override
void pay(double amount) {
print("Paid $amount via UPI");
}
}
void processPayment(Payment payment) {
payment.pay(100.0);
}
Here, any Payment subclass can replace another without changing the calling code.
🧩 4. Interface Segregation Principle (ISP)
Don’t force classes to implement methods they don’t need.
❌ Bad Example:
abstract class SocialAuth {
void loginWithGoogle();
void loginWithFacebook();
void loginWithTwitter();
}
class GoogleAuth implements SocialAuth {
@override
void loginWithGoogle() {}
@override
void loginWithFacebook() {} // Unused
@override
void loginWithTwitter() {} // Unused
}
✅ Good Example:
abstract class GoogleAuth {
void loginWithGoogle();
}
abstract class FacebookAuth {
void loginWithFacebook();
}
Each interface now serves a specific purpose.
🏗 5. Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete implementations.
In Flutter, we often use Dependency Injection (DI) with GetIt or Riverpod.
✅ Example using GetIt
final sl = GetIt.instance;
abstract class AuthRepository {
Future<void> login(String email, String password);
}
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
AuthRepositoryImpl(this.remoteDataSource);
@override
Future<void> login(String email, String password) async {
await remoteDataSource.login(email, password);
}
}
// Dependency Injection setup
void setup() {
sl.registerLazySingleton<AuthRemoteDataSource>(() => AuthRemoteDataSource());
sl.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl(sl()));
}
Your app code now depends on AuthRepository (an abstraction),
not on a concrete AuthRepositoryImpl.
🧭 Putting It All Together (Clean Architecture Layers)
A Flutter Clean Architecture typically follows:
lib/
│
├── features/
│ ├── auth/
│ │ ├── data/ # Repositories, data sources
│ │ ├── domain/ # Entities, use cases
│ │ └── presentation/ # Bloc/Cubit + UI
│
├── core/ # Common utils, constants
└── main.dart
Each layer respects SOLID:
- SRP → each layer has one purpose.
- OCP & DIP → you can replace data sources easily.
- LSP & ISP → all interfaces and subclasses remain consistent.
🚀 Benefits of SOLID in Flutter
✅ Easier testing and debugging
✅ Reusable components
✅ More maintainable project structure
✅ Works perfectly with BLoC, Riverpod, or Provider
✅ Scales smoothly for large apps
0 Comments