Design patterns in Flutter

Santhosh Adiga U
4 min readMar 3, 2023

--

Flutter is a popular mobile app development framework that uses the Dart programming language. It offers a wide range of tools and libraries that make it easy for developers to create beautiful and functional mobile apps. One of the key features of Flutter is its support for design patterns, which are reusable solutions to common software design problems. In this article, we will explore some of the most popular design patterns in Flutter and provide code examples to illustrate their usage.

Singleton Pattern

The Singleton pattern ensures that a class has only one instance, and provides a global point of access to that instance. This is useful when you need to ensure that there is only one instance of a particular object in your application. Here’s an example of how to implement the Singleton pattern in Flutter:

class MySingleton {
static final MySingleton _singleton = MySingleton._internal();

factory MySingleton() {
return _singleton;
}

MySingleton._internal();

void doSomething() {
print("Doing something");
}
}

In this example, we have created a class called MySingleton with a private constructor that can only be accessed from within the class. We have also created a static instance of the class called _singleton, which is only created once and can be accessed globally through the factory method MySingleton(). Finally, we have defined a method called doSomething() that can be called on the singleton instance.

Observer Pattern

The Observer pattern is used when you want to notify other objects when the state of an object changes. In Flutter, this pattern is commonly used with the Stream and StreamController classes. Here's an example:

import 'dart:async';

class MyObservable {
StreamController<String> _controller = StreamController<String>();

Stream<String> get stream => _controller.stream;

void setValue(String value) {
_controller.add(value);
}
}

class MyObserver {
void listenToStream(Stream<String> stream) {
stream.listen((value) {
print("Value changed: $value");
});
}
}

In this example, we have created a class called MyObservable with a StreamController that can be used to send events to other objects. We have also defined a getter called stream that returns a Stream of events. Finally, we have created a class called MyObserver that listens to the Stream and prints a message whenever a new event is received.

Builder Pattern

The Builder pattern is used when you want to separate the construction of a complex object from its representation. In Flutter, this pattern is commonly used with widgets that have many properties that need to be set. Here’s an example:

class MyWidget extends StatelessWidget {
final String title;
final String subtitle;
final String buttonText;

MyWidget({this.title, this.subtitle, this.buttonText});

@override
Widget build(BuildContext context) {
return Column(
children: [
Text(title),
Text(subtitle),
ElevatedButton(
onPressed: () {},
child: Text(buttonText),
),
],
);
}
}

class MyWidgetBuilder {
String _title;
String _subtitle;
String _buttonText;

MyWidgetBuilder setTitle(String title) {
_title = title;
return this;
}

MyWidgetBuilder setSubtitle(String subtitle) {
_subtitle = subtitle;
return this;
}

MyWidgetBuilder setButtonText(String buttonText) {
_buttonText = buttonText;
return this;
}

MyWidget build() {
return MyWidget(
title: _title,
subtitle: _subtitle,
buttonText: _buttonText,
);
}
}

In this example, we have created a widget called MyWidget that has several properties that can be set when it is created. We have also created a builder class called MyWidgetBuilder that has methods for setting each of the properties. Finally, we have defined a build() method on the builder class that creates a new instance of MyWidget with the properties that have been set.

To use this builder, we can chain the setter methods to set the desired properties and then call the build() method to get an instance of MyWidget:

MyWidget myWidget = MyWidgetBuilder()
.setTitle("Hello")
.setSubtitle("World")
.setButtonText("Press Me")
.build();

This pattern is particularly useful when a widget has many properties that need to be set, as it allows us to keep the code for creating the widget separate from the code for setting its properties. It also makes it easy to reuse the same builder for creating multiple instances of the same widget with different property values.

Strategy Pattern

The Strategy pattern is used when you want to define a family of algorithms, encapsulate each one, and make them interchangeable. In Flutter, this pattern can be used to implement different behaviors for widgets based on user interactions. Here’s an example:

abstract class MyBehavior {
void performBehavior();
}

class MyWidget extends StatefulWidget {
final MyBehavior behavior;

MyWidget({this.behavior});

@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
widget.behavior.performBehavior();
},
child: Text("Press me"),
);
}
}

class MyBehaviorOne implements MyBehavior {
@override
void performBehavior() {
print("Behavior one");
}
}

class MyBehaviorTwo implements MyBehavior {
@override
void performBehavior() {
print("Behavior two");
}
}

In this example, we have created an abstract class called MyBehavior with a method called performBehavior(). We have also created a widget called MyWidget with a property called behavior that can be set to an instance of a class that implements MyBehavior. Finally, we have defined two classes that implement MyBehavior called MyBehaviorOne and MyBehaviorTwo.

When the user presses the button in MyWidget, the performBehavior() method on the selected behavior object is called.

Conclusion

Design patterns can greatly simplify the process of developing mobile apps in Flutter by providing reusable solutions to common problems. The Singleton, Observer, Builder, and Strategy patterns are just a few examples of the many design patterns that can be used in Flutter. By incorporating these patterns into your code, you can improve the maintainability, extensibility, and testability of your app.

--

--

Santhosh Adiga U
Santhosh Adiga U

Written by Santhosh Adiga U

Founder of Anakramy ., dedicated to creating innovative AI-driven cybersecurity solutions.

No responses yet