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:

LetterPrincipleGoal
SSingle Responsibility Principle (SRP)A class should have only one reason to change
OOpen/Closed Principle (OCP)Classes should be open for extension but closed for modification
LLiskov Substitution Principle (LSP)Subtypes must be substitutable for their base types
IInterface Segregation Principle (ISP)No class should be forced to implement interfaces it doesn’t use
DDependency 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

Categories: Flutter

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *