DEV Community

Cover image for πŸ’‘ Understanding SOLID Principles in Flutter - Part 1 (S & O)
Hitesh Meghwal
Hitesh Meghwal

Posted on

πŸ’‘ Understanding SOLID Principles in Flutter - Part 1 (S & O)

πŸš€ What is SOLID?

SOLID is a set of five design principles that help developers write clean, maintainable, and scalable code. Originally introduced by Robert C. Martin (Uncle Bob), these principles are especially powerful in object-oriented programming β€” and yes, they apply beautifully in Flutter and Dart too!

The SOLID acronym stands for:

  • S – Single Responsibility Principle
  • O – Open/Closed Principle
  • L – Liskov Substitution Principle
  • I – Interface Segregation Principle
  • D – Dependency Inversion Principle

πŸ” Why Should Flutter Devs Care?

As your app grows, your codebase gets more complex. Without structure, you’ll end up with:

  • Widgets doing everything (UI, logic, API)
  • Spaghetti navigation code
  • Difficult-to-test classes
  • Painful refactoring when features change

SOLID helps prevent all that by encouraging:
βœ… Separation of concerns
βœ… Loose coupling
βœ… Easy unit testing
βœ… Flexible architecture
βœ… Long-term maintainability


πŸ“š Let's Break It Down – In 3 Parts

To keep things simple and digestible, we’ll explore SOLID in a 3-part blog series, with real-world analogies and Flutter code examples

🧩 Part 1: S & O – Writing Focused, Extendable Widgets
Avoiding God-widgets
Keeping UI, logic, and services separate
Making your classes open to grow, but stable in design

🧩 Part 2: L & I – Designing Safe and Focused Abstractions
Subclassing wisely
Creating lean interfaces
Avoiding broken contracts in inheritance

🧩 Part 3: D – Mastering Dependencies in Flutter Apps
Injecting services the clean way (Provider, GetIt)
Designing for flexibility and testability
Swapping Firebase or REST APIs without chaos


Part 1: S & O

S - Single Responsibility Principle (SRP)

πŸ“˜ Definition:

A class should have only one reason to change.

πŸ• Real-Life Analogy:

Imagine a pizza delivery guy who is also expected to:

  • Cook the pizza πŸ•
  • Take customer calls πŸ“ž
  • Drive the delivery vehicle πŸš—

If anything goes wrong in any one of these, his role needs to change. That's too many responsibilities!

Instead, we split the roles:

  • Chef πŸ‘¨β€πŸ³ cooks
  • Receptionist πŸ“ž takes orders
  • Delivery person πŸš— delivers

This is exactly what SRP encourages in your code.

πŸ’‘ Flutter-Specific Example:

❌ Wrong:

class UserProfileWidget extends StatelessWidget {
  final String userId;

  UserProfileWidget(this.userId);

  Future<User> fetchUserData(String id) async {
    // Networking logic
  }

  void navigateToEdit(BuildContext context) {
    // Navigation logic
  }

  @override
  Widget build(BuildContext context) {
    // UI code + network call + navigation
  }
}
Enter fullscreen mode Exit fullscreen mode

Problem:
This widget is doing too much:

  • UI rendering
  • Fetching user data
  • Handling navigation

It has multiple reasons to change:

  • UI design changes
  • API structure changes
  • Route logic changes

βœ… Right:
Split into smaller classes:

// 1. Data Layer
class UserService {
  Future<User> fetchUserData(String id) async {
    // Only responsible for fetching user data
  }
}

// 2. Navigation Helper
class NavigationHelper {
  void goToEditProfile(BuildContext context) {
    Navigator.pushNamed(context, '/editProfile');
  }
}

// 3. UI Layer
class UserProfileWidget extends StatelessWidget {
  final String userId;
  final UserService userService;
  final NavigationHelper navigator;

  UserProfileWidget({
    required this.userId,
    required this.userService,
    required this.navigator,
  });

  @override
  Widget build(BuildContext context) {
    // Only handles UI logic
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ Key Takeaways:

  • One class = one purpose.
  • Makes code easier to read, test, and maintain.
  • Helps avoid spaghetti code in widgets.
  • Widgets β‰  services β‰  business logic. Separate them!

🧠 Interview One-Liner:

β€œIn Flutter, I follow SRP by ensuring that Widgets only build UI, Services handle data, and Helpers take care of navigation or logic.”


O - Open/Closed Principle (OCP)

πŸ“˜ Definition:

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

🧁 Real-Life Analogy:

Think of a custom cake shop 🍰.
The base cake recipe is fixed.

But customers can add new toppings (fruits, choco chips, nuts) without changing the original cake.

Similarly, in code:
You shouldn’t rewrite the original class every time a new requirement comes.
Instead, you should extend it β€” via inheritance, composition, or abstractions.

πŸ’‘ Flutter-Specific Example:

❌ Wrong:

class PaymentProcessor {
  void process(String type) {
    if (type == 'credit') {
      // Credit card logic
    } else if (type == 'paypal') {
      // PayPal logic
    } else if (type == 'upi') {
      // UPI logic
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Problem:
Each time a new payment method is added, we have to modify the PaymentProcessor.
This violates OCP β€” it's not closed for modification

βœ… Right:
Use abstraction with an interface:

abstract class PaymentMethod {
  void pay(double amount);
}

class CreditCardPayment implements PaymentMethod {
  @override
  void pay(double amount) {
    // Credit card logic
  }
}

class PaypalPayment implements PaymentMethod {
  @override
  void pay(double amount) {
    // PayPal logic
  }
}

class UpiPayment implements PaymentMethod {
  @override
  void pay(double amount) {
    // UPI logic
  }
}

class PaymentProcessor {
  final PaymentMethod method;

  PaymentProcessor(this.method);

  void process(double amount) {
    method.pay(amount);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, if you want to add a new payment method (CryptoPayment), you just implement the interface.
No change to PaymentProcessor. βœ”οΈ

πŸ”‘ Key Takeaways:

  • Design your classes so that you can extend behavior without editing existing code.
  • Favor composition or interfaces over if-else/switch ladders.
  • Makes your code future-proof and scalable.

🧠 Interview One-Liner:

I apply OCP in Flutter by using interfaces or abstract classes, so new features can be added without modifying existing logic β€” reducing bugs and regressions.


❀️ That’s a Wrap for Part 1!
We’ve just scratched the surface of the SOLID journey with the first two principles β€” Single Responsibility and Open/Closed β€” and how they apply beautifully in Flutter.
If you found this helpful, drop a like, share it with your dev circle, and don’t forget to leave your feedback or questions in the comments.

This is just the beginning!
πŸ‘‰ Stay tuned for Part 2, where we dive into:

The power of safe inheritance (Liskov)
Why small interfaces matter (Interface Segregation)

Let’s master clean code, one principle at a time. πŸš€
Be part of the series β€” and let’s build better Flutter apps together!

Top comments (3)

Collapse
 
abi_abi_5fbf3e273d0ccdbbc profile image
abi abi

I would like to suggest to develop a basic feature first approach flutter project with SOLID principle and state the git link in your next post. πŸ’™ Happy fluttering...

Collapse
 
hiteshm_devapp profile image
Hitesh Meghwal

Thanks for the suggestion! πŸ™Œ
Actually, I already build production-level Flutter projects following SOLID principles and clean architecture. In most cases, I focus heavily on:
S – Single Responsibility Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle

These help keep the codebase modular, testable, and easy to maintain β€” especially as the project scales. Looking forward to sharing a sample project soon for others to explore and learn from! πŸ’™

Collapse
 
abi_abi_5fbf3e273d0ccdbbc profile image
abi abi

Super ✨