Question #79MediumFlutter Basics

What are all the principle in flutter and explain (example like solid principle ) ?

#flutter

Answer

Design Principles in Flutter

Flutter development follows several key design principles that help create maintainable, scalable, and efficient applications. Here are the most important principles:

1. SOLID Principles

SOLID is an acronym for five design principles that make software more maintainable and flexible:

S - Single Responsibility Principle (SRP)

A class should have only one reason to change. Each class should have a single, well-defined responsibility.

dart
// ❌ Bad: Multiple responsibilities
class UserManager {
  void saveUser(User user) { /* saves to database */ }
  void validateEmail(String email) { /* validates email */ }
  void sendNotification(String message) { /* sends notification */ }
}

// ✅ Good: Single responsibility per class
class UserRepository {
  void saveUser(User user) { /* saves to database */ }
}

class EmailValidator {
  bool validateEmail(String email) { /* validates email */ }
}

class NotificationService {
  void sendNotification(String message) { /* sends notification */ }
}

O - Open/Closed Principle (OCP)

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

dart
// ✅ Good: Using abstraction for extension
abstract class PaymentMethod {
  void processPayment(double amount);
}

class CreditCardPayment implements PaymentMethod {
  
  void processPayment(double amount) {
    print('Processing credit card payment: \$amount');
  }
}

class PayPalPayment implements PaymentMethod {
  
  void processPayment(double amount) {
    print('Processing PayPal payment: \$amount');
  }
}

// New payment method can be added without modifying existing code
class CryptoPayment implements PaymentMethod {
  
  void processPayment(double amount) {
    print('Processing crypto payment: \$amount');
  }
}

L - Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.

dart
// ✅ Good: Subtypes can replace base type
abstract class Bird {
  void move();
}

class Sparrow extends Bird {
  
  void move() => print('Flying');
}

class Penguin extends Bird {
  
  void move() => print('Swimming');
}

void moveBird(Bird bird) {
  bird.move(); // Works with any Bird subtype
}

I - Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use.

dart
// ❌ Bad: Fat interface
abstract class Worker {
  void work();
  void eat();
  void sleep();
}

// ✅ Good: Segregated interfaces
abstract class Workable {
  void work();
}

abstract class Eatable {
  void eat();
}

abstract class Sleepable {
  void sleep();
}

class Human implements Workable, Eatable, Sleepable {
  
  void work() => print('Working');

  
  void eat() => print('Eating');

  
  void sleep() => print('Sleeping');
}

class Robot implements Workable {
  
  void work() => print('Working');
  // Robot doesn't need eat() or sleep()
}

D - Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

dart
// ✅ Good: Depending on abstraction
abstract class DatabaseInterface {
  Future<void> save(String data);
  Future<String> fetch(String id);
}

class FirebaseDatabase implements DatabaseInterface {
  
  Future<void> save(String data) async {
    // Firebase implementation
  }

  
  Future<String> fetch(String id) async {
    // Firebase implementation
    return 'data';
  }
}

class SQLiteDatabase implements DatabaseInterface {
  
  Future<void> save(String data) async {
    // SQLite implementation
  }

  
  Future<String> fetch(String id) async {
    // SQLite implementation
    return 'data';
  }
}

class UserService {
  final DatabaseInterface database;

  UserService(this.database); // Depends on abstraction

  Future<void> saveUser(String userData) {
    return database.save(userData);
  }
}

2. DRY (Don't Repeat Yourself)

Avoid code duplication by extracting common functionality into reusable components.

dart
// ❌ Bad: Repeated code
Widget buildButton1() {
  return ElevatedButton(
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.blue,
      padding: EdgeInsets.all(16),
    ),
    onPressed: () {},
    child: Text('Button 1'),
  );
}

Widget buildButton2() {
  return ElevatedButton(
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.blue,
      padding: EdgeInsets.all(16),
    ),
    onPressed: () {},
    child: Text('Button 2'),
  );
}

// ✅ Good: Reusable component
Widget buildCustomButton(String text, VoidCallback onPressed) {
  return ElevatedButton(
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.blue,
      padding: EdgeInsets.all(16),
    ),
    onPressed: onPressed,
    child: Text(text),
  );
}

3. KISS (Keep It Simple, Stupid)

Keep code simple and straightforward. Avoid unnecessary complexity.

dart
// ❌ Bad: Over-complicated
String formatName(String? firstName, String? lastName) {
  if (firstName != null && firstName.isNotEmpty) {
    if (lastName != null && lastName.isNotEmpty) {
      return firstName + ' ' + lastName;
    } else {
      return firstName;
    }
  } else if (lastName != null && lastName.isNotEmpty) {
    return lastName;
  } else {
    return 'Unknown';
  }
}

// ✅ Good: Simple and clear
String formatName(String? firstName, String? lastName) {
  final parts = [firstName, lastName].where((s) => s?.isNotEmpty ?? false);
  return parts.isEmpty ? 'Unknown' : parts.join(' ');
}

4. YAGNI (You Aren't Gonna Need It)

Don't implement features until they're actually needed.

dart
// ❌ Bad: Implementing unused features
class User {
  String name;
  String email;
  String? phoneNumber;
  String? address;
  String? socialSecurityNumber; // Not needed yet
  DateTime? lastLoginTime; // Not needed yet
  List<String>? favoriteColors; // Not needed yet

  User({required this.name, required this.email});
}

// ✅ Good: Only implement what's needed now
class User {
  String name;
  String email;

  User({required this.name, required this.email});
}

5. Composition Over Inheritance

Favor composing objects over inheriting from classes.

dart
// ❌ Bad: Deep inheritance hierarchy
class Animal {
  void eat() => print('Eating');
}

class Mammal extends Animal {
  void breathe() => print('Breathing');
}

class Dog extends Mammal {
  void bark() => print('Barking');
}

// ✅ Good: Using composition
class Eatable {
  void eat() => print('Eating');
}

class Breathable {
  void breathe() => print('Breathing');
}

class Barkable {
  void bark() => print('Barking');
}

class Dog {
  final Eatable _eatable = Eatable();
  final Breathable _breathable = Breathable();
  final Barkable _barkable = Barkable();

  void eat() => _eatable.eat();
  void breathe() => _breathable.breathe();
  void bark() => _barkable.bark();
}

Principle Comparison Table

PrinciplePurposeBenefit
Single ResponsibilityOne class, one purposeEasier maintenance and testing
Open/ClosedExtend without modifyingReduces bugs in existing code
Liskov SubstitutionSubtypes are interchangeableReliable polymorphism
Interface SegregationSmall, focused interfacesNo forced unused methods
Dependency InversionDepend on abstractionsFlexible, testable code
DRYNo code duplicationEasier updates and maintenance
KISSKeep it simpleBetter readability
YAGNIDon't over-engineerFaster development
Composition Over InheritanceFlexible object compositionBetter code reuse

Best Practices Summary

  • Apply SOLID principles for maintainable architecture
  • Use composition to create flexible components
  • Keep code simple and avoid premature optimization
  • Extract reusable widgets and utilities
  • Write unit tests to verify principles are followed
  • Refactor regularly to maintain code quality

Important: These principles are guidelines, not strict rules. Apply them thoughtfully based on your specific needs and context.

Documentation: Effective Dart: Design