top of page
  • Writer's pictureDon Peter

Introduction to SOLID principles in Flutter


Introduction to SOLID principles in Flutter

SOLID principles are a set of design principles that help developers create more maintainable and scalable code. These principles were introduced by Robert C. Martin, also known as "Uncle Bob".


In this blog post, we will discuss how to implement SOLID principles in the development of Flutter apps.


1. S - Single Responsibility Principle (SRP)


The Single Responsibility Principle states that a class should have only one reason to change. This means that a class should have only one responsibility or job. In the context of Flutter app development, this principle can be implemented by creating small and focused classes that handle specific tasks.


Suppose you have a screen that displays a list of products. When the user taps on a product, the app should navigate to a detail screen that shows more information about the selected product. To apply the SRP to this scenario, you can create two classes: one for handling the list of products and another for displaying the details of a single product.


ProductList class: This class is responsible for fetching the list of products from a backend API and displaying them on the screen.



class ProductList extends StatefulWidget {
  @override
  _ProductListState createState() => _ProductListState();
}

class _ProductListState extends State<ProductList> {
  List<Product> _products = [];

  @override
  void initState() {
    super.initState();
    _fetchProducts();
  }

  void _fetchProducts() async {
    final products = await ProductService().getProducts();
    setState(() {
      _products = products;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Product List'),
      ),
      body: ListView.builder(
       ....
       ....
      
      ),
    );
  }
}

ProductDetail class: This class is responsible for displaying the details of a single product.


class ProductDetail extends StatelessWidget {
  final Product product;

  const ProductDetail({required this.product});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(product.imageUrl),
          SizedBox(height: 16),
          Text(product.name),
          SizedBox(height: 16),
          Text(product.description),
          SizedBox(height: 16),
          Text('Price: ${product.price}'),
        ],
      ),
    );
  }
}

By separating the responsibilities of displaying the list of products and displaying the details of a single product into two separate classes, you make your code more maintainable and easier to extend. If you need to make changes to how the list is displayed or how the details are shown, you can do so without affecting the other part of the code.


2. O - Open/Closed Principle (OCP)


The Open/Closed Principle states that classes should be open for extension but closed for modification. This means that a class should be easily extendable without modifying its existing code. In the context of Flutter app development, this principle can be implemented by using interfaces and abstract classes. By using interfaces and abstract classes, you can create a contract for the class, which can be extended by other classes without modifying the existing code.


Suppose you have an app that displays a list of items. The app needs to be able to sort the items based on different criteria, such as alphabetical order or price. To apply the OCP to this scenario, you can create an abstract class that defines the behavior of a sorting algorithm, and then create concrete classes that implement specific sorting algorithms.


abstract class ItemSorter {
  List<Item> sort(List<Item> items);
}

class AlphabeticalSorter implements ItemSorter {
  @override
  List<Item> sort(List<Item> items) {
    items.sort((a, b) => a.name.compareTo(b.name));
    return items;
  }
}

class PriceSorter implements ItemSorter {
  @override
  List<Item> sort(List<Item> items) {
    items.sort((a, b) => a.price.compareTo(b.price));
    return items;
  }
}

In this example, the ItemSorter abstract class defines the behavior of a sorting algorithm. The AlphabeticalSorter and PriceSorter classes implement specific sorting algorithms by overriding the sort method.


3. L - Liskov Substitution Principle (LSP)


The Liskov Substitution Principle states that a subclass should be able to replace its superclass without causing any problems. This means that the subclass should behave in the same way as the superclass. In the context of Flutter app development, this principle can be implemented by creating subclasses that adhere to the same interface as the superclass. By doing this, you can ensure that the subclasses can be used interchangeably with the superclass without any issues.


4. I - Interface Segregation Principle (ISP)


The Interface Segregation Principle states that a class should not be forced to depend on interfaces that it does not use. This means that a class should only depend on the interfaces that it needs to perform its tasks. In the context of Flutter app development, this principle can be implemented by creating small and focused interfaces that handle specific tasks. By doing this, you can reduce the dependencies of the class and make it easier to maintain.


Suppose you have an app that displays a list of articles. Each article can be shared with different social media platforms, such as Facebook, Twitter, or LinkedIn. To apply the Interface Segregation Principle, you can create an interface for each social media platform that only includes the methods that are relevant to that platform.



abstract class SocialMediaSharing {
  void shareOnFacebook(Article article);
  void shareOnTwitter(Article article);
  void shareOnLinkedIn(Article article);
}

class FacebookSharing implements SocialMediaSharing {
  @override
  void shareOnFacebook(Article article) {
    // Implementation for sharing on Facebook
  }

  @override
  void shareOnTwitter(Article article) {
    throw UnimplementedError();
  }

  @override
  void shareOnLinkedIn(Article article) {
    throw UnimplementedError();
  }
}

class TwitterSharing implements SocialMediaSharing {
  @override
  void shareOnFacebook(Article article) {
    throw UnimplementedError();
  }

  @override
  void shareOnTwitter(Article article) {
    // Implementation for sharing on Twitter
  }

  @override
  void shareOnLinkedIn(Article article) {
    throw UnimplementedError();
  }
}

class LinkedInSharing implements SocialMediaSharing {
  @override
  void shareOnFacebook(Article article) {
    throw UnimplementedError();
  }

  @override
  void shareOnTwitter(Article article) {
    throw UnimplementedError();
  }

  @override
  void shareOnLinkedIn(Article article) {
    // Implementation for sharing on LinkedIn
  }
}

In this example, the SocialMediaSharing interface defines the methods for sharing an article on different social media platforms. However, not all platforms may support all methods. Therefore, each concrete class only implements the methods that are relevant to that platform.

This approach allows you to create more specialized classes for each platform, without cluttering their interfaces with methods that are not relevant to them. This makes the code easier to maintain and less prone to errors.


5. D - Dependency Inversion Principle (DIP)


The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. This means that the code should be designed in a way that high-level modules can use low-level modules without depending on their implementation. In the context of Flutter app development, this principle can be implemented by using dependency injection. By using dependency injection, you can decouple the code and make it easier to test and maintain.


Conclusion


In conclusion, implementing SOLID principles in the development of Flutter apps can lead to more maintainable and scalable code. By using the Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle, you can create code that is easier to test, maintain, and extend.

Blog for Mobile App Developers, Testers and App Owners

 

This blog is from Finotes Team. Finotes is a lightweight mobile APM and bug detection tool for iOS and Android apps.

In this blog we talk about iOS and Android app development technologies, languages and frameworks like Java, Kotlin, Swift, Objective-C, Dart and Flutter that are used to build mobile apps. Read articles from Finotes team about good programming and software engineering practices, testing and QA practices, performance issues and bugs, concepts and techniques. 

bottom of page