Flutter Strategy pattern with Dependency Injection (Get It)

June Ligan
4 min readAug 19, 2021

This is how I create/setup a strategy design pattern in the flutter.

Benefits?

In my opinion, we can write a readable, robust, extensible and maintainable code.

Photo by koko rahmadie from Pexels

So what is Strategy pattern?

Honestly, I can’t explain it better than the other other blogs, I’ll just paste my references at the end of this article :), so let’s start

When to use the strategy pattern?

Usually, we will use it if we have multiple algorithms and we want to control on what behavior should go out. and especially if you have a lot of “if statements”.

example: 
- Exception
- Socket Timeout
- Null Pointer
- FormatException

Problem: create a function to navigate/display given widget/snackbar view for the given exception

displayViewOf(Exception exception) { 
if (exception is SocketTimeoutException) {
// display view/snackbar
} else if (exception is FormatException) {
// display view/snackbar
} else {
// display view/snackbar
}
}

So, let’s try to apply the design pattern here.
1st example will be the enum strategy pattern, this is common in Java but this is somewhat an anti pattern with S.O.L.I.D especially with the “open closed principle” because every time you will add a new strategy, you will update enum class. anyways, let’s just go ahead and see it for ourselves what are the pros and cons of this design.

In java enum:

public enum TestException {
SOCKET_TIMEOUT {
@override
public void displayView() {
// display view/snackbar
}
},
NULL_POINTER {
@override
public void displayView() {
// display view/snackbar
}
},
FORMAT {
@override
public void displayView() {
// display view/snackbar
}
},
public void displayView();
}

In flutter (1st option):
NOTE: I’ve used GetIt(aka locator) plugin to interact the single service instance of the app and also, I’m using build runner to create auto generated classes based from the annotation.
References:
1.) https://medium.com/filledstacks/dependency-injection-in-flutter-2225d3081f61
2.) and https://dart.dev/tools/build_runner👏

Created an Interface class and its concrete classes

abstract class MyStrategy{ 
test(Exception exception);
displayError();
}
@Singleton()
class SocketTimeoutErrorService implements MyStrategy{
test(Exception exception){...}
displayError() {//display implementation}
}
@Singleton()
class DefaultErrorService implements MyStrategy{
test(Exception exception){...}
displayError() {//display implementation}
}
@Singleton()
class FormatErrorService implements MyStrategy{
test(Exception exception){...}
displayError() {//display implementation}
}

Then the enum

enum TestException {
socketTimeout, nullPointer, format
}
extension TestExceptionExtension on TestException {static const exceptions = {
TestException.socketTimeout:
()=>locator<SocketTimeoutErrorService>(),
TestException.nullPointer:
()=>locator<DefaultErrorService>(),
TestException.format:
()=>locator<FormatErrorService>()
};
void displayErrorView(error) {
this.values.firstWhere((element)=>element.test(),
orElse: ()=> locator<DefaultErrorService>()
).displayError();
}
}
void main() {
runZonedGuarded(() async {
....

setupLocator();
....
....
runApp(MyApp());
}, (dynamic error, StackTrace stackTrace) {
// Send report
TestExceptionExtension.displayErrorView(error);
});
}

pros:

- easy to identify what are the available strategy for the exceptions and immediately check their implementations

cons

- we always update the enum class file every time we add a new one
- conflicts with the open-closed principle of S.O.L.I.D principles

but it depends on your preferences, it’s your call :)

In flutter (1st option):
NOTE: I’m still using GetIt :)

Added a new method and implement the constructor to connect to the controller class file.

abstract class MyStrategy { 
test(Exception exception);
displayError();
registerStrategy(MyStrategy);
}
@Singleton()
class SocketTimeoutErrorService implements MyStrategy{
late final MyErrorHandler = _handler;
SocketTimeoutErrorService() {
_handler = locator<MyErrorHandler>();
_handler.registerStrategy(this);
}
test(Exception exception){...}
displayError() {//display implementation}
registerStrategy(... strategy){//display implementation}
}
@Singleton()
class DefaultErrorService implements MyStrategy{
late final MyErrorHandler = _handler;
DefaultErrorService() {
_handler = locator<MyErrorHandler>();
_handler.registerStrategy(this);
}
test(Exception exception){...}
displayError() {//display implementation}
registerStrategy(... strategy){//display implementation}
}
@Singleton()
class FormatErrorService implements MyStrategy{
late final MyErrorHandler = _handler;
FormatErrorService() {
_handler = locator<MyErrorHandler>();
_handler.registerStrategy(this);
}
test(Exception exception){...}
displayError() {//display implementation}
registerStrategy(... strategy){}
}

Let’s check the MyErrorHandler

@Singleton()
class MyErrorHandler {
final List<MyStrategy> strategies = [];

void displayErrorView(dynamic exception, StackTrace stackTrace) {
strategies
.firstWhere((element) => element.test(exception),
orElse: () => locator<DefaultErrorService>())
.displayError();
}

void registerStrategy(MyStrategy strategy) {
strategies.add(strategy);
}
}
late MyErrorHandler handler;
// "late" means it will initialize at the later time
void main() {
runZonedGuarded(() async {
....

setupLocator();
handler = locator<MyErrorHandler>();
....
....
runApp(MyApp());
}, (dynamic error, StackTrace stackTrace) {
// Send report
handler.displayErrorView(error);
});
}

and yeah, I think this is it! 🙌high ten! 😄

pros:

- if we want to add a new class, we'll just have to implement the same pattern to the existing ones without altering the base codes

cons

- maybe it is too much/overkill for an easy design
- not really sure, but in my experience, I have a hard time tracing out the code especially if it's a more complex design

These options are just guide to your development, I hope this article helps you for your development journey with flutter. Let’s help the dev world by sharing our ideas and experiences. 😄

Sorry for the long post, if you have any questions or clarifications, please ask and I’ll try my best to answer it, comments/suggestions are also welcome.

My References:

--

--