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)