Discover How Flutter is Built with Dart Using the Power of S.O.L.I.D Principles!


Flutter is a cross-platform mobile application development framework built with the Dart programming language. Dart is a client-optimized language for fast apps on multiple platforms, and it is used to build Flutter applications.
The architecture of Flutter is based on the SOLID principles. SOLID stands for Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. These principles help developers to write clean, maintainable, and scalable code.
Let us look closer at how Flutter is built with Dart using SOLID principles.
1. Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one reason to change. In other words, each class should have only one responsibility. This principle helps to keep the code organized and easy to maintain.
Image from a blog.
In the image, the person may become overwhelmed and stressed out due to the sheer number of responsibilities they have taken on. They may find it difficult to perform each task effectively and efficiently, leading to lower productivity, reduced quality of work, and decreased satisfaction. In this case, it may be better to delegate or outsource some of the responsibilities to others to ensure that each task receives the proper attention and care it deserves.
In Flutter, each widget is responsible for a single part of the UI. For example, the Text widget is responsible for displaying text on the screen. Similarly, the Image widget is responsible for displaying images. By following the SRP, the codebase is easy to understand and modify.
Flutter has a lot of classes that follow the SRP, such as TextEditingController
and Navigator
. For example, the TextEditingController
the class has only one responsibility - to manage the text input field's value. Here's an example of how TextEditingController
it is used:
final myController = TextEditingController();
TextField(
controller: myController,
)
Here, myController
is responsible for managing the text input field's value, while the TextField
widget is responsible for rendering the text input field.
2. Open-Closed Principle (OCP)
The Open-Closed Principle states that a class should be open for extension but closed for modification. This means that we should be able to add new features to a class without changing its existing code.
Image from a blog
Chrome provides a well-defined interface and API for developers to create plugins or extensions that can be easily added to the browser without modifying the existing code. This allows users to customize and extend the functionality of the browser without compromising its stability or security.
Flutter follows this principle by using inheritance and composition to build widgets. Widgets are the building blocks of Flutter applications. They can be combined to create complex UIs. By extending or composing existing widgets, developers can add new features without modifying the existing code.
Flutter uses the OCP in various ways, such as through the use of mixins and interfaces. For example, the RenderObjectWidget
class is an interface that allows widgets to define their own rendering logic. Here's an example of how the RenderObjectWidget
interface is used in the Padding
widget:
class Padding extends SingleChildRenderObjectWidget {
// ...
@override
RenderPadding createRenderObject(BuildContext context) {
return RenderPadding(
padding: padding,
textDirection: Directionality.of(context),
child: null,
);
}
// ...
}
In this example, Padding
is a widget that adds padding to its child widget. It extends SingleChildRenderObjectWidget
, which is a mixin that provides the rendering logic for widgets that have a single child. The createRenderObject
method is overridden to create an RenderPadding
object, which is responsible for rendering the widget with the given padding.
3. Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that a derived class should be able to substitute its base class without affecting the correctness of the program. This means that we should be able to use a subclass in place of its superclass without any issues.
Image from a blog
After feeding your neighbor’s pet duck expired bread, it unfortunately passed away. As a solution, you chose to substitute it with a toy duck, although your neighbor may not be pleased with this outcome. By replacing the live duck with a toy duck, you have violated the Liskov Substitution Principle, as the two objects are not interchangeable.
Flutter follows this principle by using a widget tree to build UIs. A widget can be replaced with its subclass without affecting the behavior of the UI. This makes it easy to customize the UI without breaking the app.
Flutter uses the LSP to ensure that widgets can be easily replaced with their subclasses without affecting the overall behavior of the application. For example, the StatefulWidget
class is a base class for widgets that have mutable state. Here's an example of how StatefulWidget
is used:
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
// ...
@override
Widget build(BuildContext context) {
return Text('Hello, world!');
}
// ...
}
In this example, MyStatefulWidget
is a widget that has mutable state. It extends the StatefulWidget
class and overrides the createState
method to create an instance of _MyStatefulWidgetState
, which is a subclass of State
. The build
method is overridden to render the widget.
4. Interface Segregation Principle (ISP)
The Interface Segregation Principle states that a class should not be forced to implement interfaces that it does not need. This means that we should create small, focused interfaces that are easy to implement.
The Interface Segregation Principle can also be applied to the design of everyday objects such as dustbins. A common issue with dustbins is that they are often designed with a single opening or interface for disposing of all types of waste, whether it is recyclable or non-recyclable, wet or dry.
Flutter follows this principle by providing small, focused interfaces for each widget. For example, the Text widget only has a few properties and methods that are specific to text rendering. This makes it easy to implement and customize the widget.
Flutter uses the ISP to define small, focused interfaces for various features, such as rendering and gesture recognition. For example, the GestureRecognizer
class is an interface that defines the methods that must be implemented by gesture recognizer classes. Here's an example of how GestureRecognizer
is used:
class MyGestureRecognizer extends OneSequenceGestureRecognizer {
// ...
@override
void handleEvent(PointerEvent event) {
// ...
}
@override
void acceptGesture(int pointer) {
// ...
}
// ...
}
In this example, MyGestureRecognizer
is a gesture recognizer that extends OneSequenceGestureRecognizer
, which is a base class for gesture recognizers that only recognize one sequence of events. MyGestureRecognizer
implements the handleEvent
and acceptGesture
methods of the GestureRecognizer
interface to handle gesture events.
5. 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 we should use interfaces or abstract classes to define dependencies.
For example, the inverter could be designed to be compatible with different types of solar panels, allowing for easy upgrades or replacements in the future. Additionally, the inverter could be designed to incorporate smart features, such as remote monitoring and control, to improve the efficiency and reliability of the system.
Flutter follows this principle by using dependency injection to manage dependencies. Each widget is defined as an abstract class, and the implementation is injected at runtime. This makes it easy to change the implementation of a widget without modifying the code.
In Flutter, the InheritedWidget
class is a good example of DIP. InheritedWidget
is an abstract class that defines the interface for all widgets that can be inherited by child widgets in a widget tree.
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
static InheritedWidget? of(BuildContext context) {
final element = context.dependOnInheritedWidgetOfExactType<InheritedWidgetElement>();
return element?.widget;
}
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
InheritedWidget
is a high-level module that provides a way for child widgets to inherit data from their parent widgets. It doesn't depend on any low-level modules. Instead, it defines an abstraction that low-level modules can implement to provide the data that child widgets can inherit.
InheritedWidget
is used throughout the Flutter framework to provide inherited data to child widgets. For example, the Theme
widget is an implementation of InheritedWidget
that provides a way for child widgets to inherit the current theme of the app.
class Theme extends InheritedWidget {
const Theme({
Key? key,
required this.data,
required Widget child,
}) : assert(child != null),
assert(data != null),
super(key: key, child: child);
final ThemeData data;
@override
bool updateShouldNotify(covariant Theme oldWidget) => data != oldWidget.data;
}
Theme
is a low-level module that depends on InheritedWidget
. It provides the data that child widgets can inherit, but it doesn't depend on any specific child widgets. This means that the implementation of Theme
can change without affecting any of the child widgets that depend on it.
By following the Dependency Inversion Principle, Flutter is able to create a flexible and extensible framework that allows developers to easily create and reuse widgets that depend on a wide range of low-level modules.
Inconclusion, discovering how Flutter is built with Dart using the power of S.O.L.I.D principles can provide a deeper understanding of the framework’s capabilities. By following these principles, Flutter offers a flexible and maintainable codebase that makes it an excellent choice for building cross-platform mobile applications. With its impressive speed and aesthetic appeal, Flutter is changing the game in mobile app development. The future looks bright for this innovative technology, and those who embrace it are poised for success in the fast-paced world of mobile development.
Subscribe to my newsletter
Read articles from NonStop io Technologies directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

NonStop io Technologies
NonStop io Technologies
Product Development as an Expertise Since 2015 Founded in August 2015, we are a USA-based Bespoke Engineering Studio providing Product Development as an Expertise. With 80+ satisfied clients worldwide, we serve startups and enterprises across San Francisco, Seattle, New York, London, Pune, Bangalore, Tokyo and other prominent technology hubs.