DEV Community

Cover image for πŸ’‘ Understanding SOLID Principles in Flutter – Part 2 (L & I)
Hitesh Meghwal
Hitesh Meghwal

Posted on

πŸ’‘ Understanding SOLID Principles in Flutter – Part 2 (L & I)

Welcome back to the second part of our journey into mastering the SOLID principles in Flutter. In the previous part, we explored:

βœ… S – Single Responsibility Principle
βœ… O – Open/Closed Principle

Today, we’re diving into two more important concepts that’ll help make your code more safe, scalable, and focused:

L - Liskov Substitution Principle (LSP)

πŸ“˜ Definition:

Subtypes must be substitutable for their base types without altering the correctness of the program.

🏎️ Real-Life Analogy:

You rent a sports car, and later they replace it with a sedan.
You expect:

  • It should still drive πŸš—
  • Use the same controls πŸ•ΉοΈ
  • Not break down when you turn the key πŸ”‘

That’s LSP β€” if something claims to be a type, it should behave like that type without surprises.

πŸ’‘ Flutter-Specific Example:

❌ Wrong: Violating LSP

class Bird {
  void fly() {
    print("Flying...");
  }
}

class Ostrich extends Bird {
  @override
  void fly() {
    throw Exception("Ostriches can't fly!");
  }
}
Enter fullscreen mode Exit fullscreen mode

Problem:

  • Ostrich is a Bird, but replacing a Bird with an Ostrich breaks the app!
  • It violates expectations of the base class.

βœ… Right: Respect LSP via abstraction

abstract class Bird {}

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

class Sparrow extends FlyingBird {
  @override
  void fly() {
    print("Flying high!");
  }
}

class Ostrich extends Bird {
  // Doesn’t extend FlyingBird, so no broken promises
}
Enter fullscreen mode Exit fullscreen mode

Now:

  • Code expecting a FlyingBird will only get actual flying birds.
  • Ostrich won’t be misused in flying-related logic.

πŸ“¦ Flutter Real Case – Widget Inheritance

Avoid this πŸ‘‡

class CustomButton extends ElevatedButton {
  // Trying to override core ElevatedButton behavior
  // Can break consumers expecting regular ElevatedButton
}
Enter fullscreen mode Exit fullscreen mode

Instead: βœ…

class CustomButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;

  const CustomButton({required this.label, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Why? You’re creating new behavior without violating the contract of built-in widgets.

πŸ”‘ Key Takeaways:

  • Don’t override methods in a way that breaks expectations.
  • Subclass only when the child truly behaves like the parent.
  • If behavior differs, prefer composition or separation, not inheritance.

🧠 Interview One-Liner:

β€œLSP means I can swap any child class with its parent and the app still works correctly. In Flutter, I use it by avoiding inappropriate overrides and designing my inheritance carefully.”


I - Interface Segregation Principle (ISP)

πŸ“˜ Definition:

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

🍱 Real-Life Analogy:

Imagine a food delivery app asking you to:

  • Select your food βœ…
  • Enter address βœ…
  • Rate the driver βœ…
  • But also... provide restaurant inventory info? ❌

Why should you, the end-user, be responsible for restaurant tasks?

Similarly, in code:

  • Don’t force a class to implement methods it doesn’t need.

πŸ’‘ Flutter-Specific Example:

❌ Wrong:

abstract class SocialFeatures {
  void post();
  void like();
  void comment();
  void blockUser();
}

class ReadOnlyUser implements SocialFeatures {
  @override
  void post() {
    throw UnimplementedError();
  }

  @override
  void like() {
    throw UnimplementedError();
  }

  @override
  void comment() {
    throw UnimplementedError();
  }

  @override
  void blockUser() {
    print("User blocked");
  }
}
Enter fullscreen mode Exit fullscreen mode

Problem:
ReadOnlyUser is forced to implement methods it doesn’t need.

βœ… Right:
Break interfaces into smaller ones:

abstract class CanBlock {
  void blockUser();
}

abstract class CanPost {
  void post();
  void like();
  void comment();
}

class ReadOnlyUser implements CanBlock {
  @override
  void blockUser() {
    print("User blocked");
  }
}

class ActiveUser implements CanPost, CanBlock {
  @override
  void post() => print("Posted");

  @override
  void like() => print("Liked");

  @override
  void comment() => print("Commented");

  @override
  void blockUser() => print("Blocked");
}
Enter fullscreen mode Exit fullscreen mode

Now:
Each class only implements what it needs, and nothing else.

πŸ“¦ Flutter Example – Bad Form Validation

abstract class FormValidator {
  String? validateEmail();
  String? validatePhone();
  String? validatePassword();
}
Enter fullscreen mode Exit fullscreen mode

If your form only has email, you're still forced to implement all three.

βœ… Split Validators:

abstract class EmailValidator {
  String? validateEmail();
}

abstract class PasswordValidator {
  String? validatePassword();
}

class LoginValidator implements EmailValidator, PasswordValidator {
  @override
  String? validateEmail() => null;

  @override
  String? validatePassword() => null;
}
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ Key Takeaways:

  • Prefer many small, specific interfaces over one large interface.
  • Avoid forcing unrelated responsibilities onto a class.
  • Makes code cleaner and more flexible.

🧠 Interview One-Liner:

β€œInterface Segregation helps me design focused contracts. In Flutter, I split large interfaces into smaller validators or roles so that classes only take what they need β€” nothing extra.”


βœ… That wraps up Part 2 of the series!
Today, we explored:

🟩 Liskov Substitution Principle β€” Why subclasses should behave like their parents
🟦 Interface Segregation Principle β€” How to keep interfaces lean and focused

With real-life analogies and clean Flutter examples, you now know how to avoid broken inheritance and bloated interfaces β€” two of the most common clean-code traps in app development.

πŸ’¬ If this helped, I’d love to hear your feedback, thoughts, or questions in the comments.
πŸ‘‰ Stay tuned for Part 3, where we’ll wrap up the series with the final (and powerful) principle:

🟫 D – Dependency Inversion Principle

Let’s finish strong and make our Flutter code truly solid. πŸ”₯

Top comments (0)