Comments (1)
@nielsezeka thanks for asking!
To begin, you're right about Uncle Bob's original design i.e. the Controller communicating directly with the Usecase.
It begins in the controller, moves through the use case, and then winds up executing in the presenter.
From his blog.
Allow me to preface this by saying that Uncle Bob's architectural design is very abstract and can be adapted and tuned depending on the language and the platform. He says that the picture is schematic and you do not necessarily have to abide by its every detail as long as The Dependency Rule always applies.
Now, let's get to why I chose this implementation and what the advantages/disadvantages are.
Having the Controller communicate directly with the Usecase would introduce extra unnecessary overhead, as the Controller now has to worry about the Usecase's lifecycle. That might be trivial with 1 Usecase, but as some Controller would use more and more Usecases, the overhead becomes too large with no concrete advantage.
If you noticed in the repository, the Presenter is responsible for disposing of all the Usecases. Let's say you are implementing a page that needs 3 different Usecases. Executing them from the Presenter means the Controller does not have to worry about instantiating, executing, nor disposing the Usecases. It only cares about retrieving the data it requires and should only be concerned with handling events and and whatnot.
The main appeal of the architecture is scalability and separation of concerns. By treating the Presenter as a container of all needed usecases for a page and preparer of data, the Controller can remain somewhat short.
One disadvantage of implementing it this way would be one extra function call to the Presenter and having to provide event handlers for the Presenter by setting them in initListeners()
. However, I think it is worth it since those handlers are typically only concerned with things directly related to data required by the View rather than sorting, filtering, etc..
The main difference between this implementation and Uncle Bob's description is that the Presenter handles the lifecycle of the Usecase rather than the Controller in order to shorten the Controller and maintain its single responsibility.
Here is an example of a Presenter handling multiple Usecases:
import 'package:hnh/domain/entities/event.dart';
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';
import 'package:hnh/domain/usecases/register_for_event_usecase.dart';
import 'package:hnh/domain/usecases/get_user_events_usecase.dart';
import 'package:hnh/domain/usecases/unregister_event_usecase.dart';
import 'package:meta/meta.dart';
class EventPresenter extends Presenter {
Function registerOnComplete;
Function registerOnError;
Function unRegisterOnComplete;
Function unRegisterOnError;
Function isRegisteredOnNext;
Function isRegisteredOnComplete;
Function isRegisteredOnError;
RegisterEventUseCase _registerEventUseCase;
GetUserEventsUseCase _getUserEventsUseCase;
UnRegisterEventUseCase _unRegisterEventUseCase;
String _eventId;
EventPresenter(eventRepo) {
_registerEventUseCase = RegisterEventUseCase(eventRepo);
_getUserEventsUseCase = GetUserEventsUseCase(eventRepo);
_unRegisterEventUseCase = UnRegisterEventUseCase(eventRepo);
}
void dispose() {
_registerEventUseCase.dispose();
_getUserEventsUseCase.dispose();
_unRegisterEventUseCase.dispose();
}
void registerForEvent({@required String uid, @required String eventId}) {
_registerEventUseCase.execute(_RegisterEventObserver(this), RegisterEventUseCaseParams(uid, eventId));
}
void unRegisterFromEvent({@required String uid, @required String eventId}) {
_unRegisterEventUseCase.execute(_UnRegisterEventObserver(this), UnRegisterEventUseCaseParams(uid, eventId));
}
void isRegistered({@required String eventId, @required String uid}) {
_eventId = eventId;
_getUserEventsUseCase.execute(_GetUserEventsObserver(this), GetUserEventsUseCaseParams(uid));
}
}
class _RegisterEventObserver implements Observer<void> {
EventPresenter _homePresenter;
_RegisterEventObserver(this._homePresenter);
void onNext(_) {}
void onComplete() {
assert(_homePresenter.registerOnComplete != null);
_homePresenter.registerOnComplete();
}
void onError(e) {
assert(_homePresenter.registerOnError != null);
_homePresenter.registerOnError(e);
}
}
class _UnRegisterEventObserver implements Observer<void> {
EventPresenter _homePresenter;
_UnRegisterEventObserver(this._homePresenter);
void onNext(_) {}
void onComplete() {
assert(_homePresenter.unRegisterOnComplete != null);
_homePresenter.unRegisterOnComplete();
}
void onError(e) {
assert(_homePresenter.unRegisterOnError != null);
_homePresenter.unRegisterOnError(e);
}
}
class _GetUserEventsObserver implements Observer<List<Event>> {
EventPresenter _eventPresenter;
_GetUserEventsObserver(this._eventPresenter);
void onNext(events) {
assert(_eventPresenter.isRegisteredOnNext != null);
List<Event> matchingEvents = events.where((event) => event.id ==_eventPresenter._eventId).toList();
_eventPresenter.isRegisteredOnNext(matchingEvents.length == 1 ? true : false);
}
void onComplete() {
assert(_eventPresenter.isRegisteredOnComplete != null);
_eventPresenter.isRegisteredOnComplete();
}
void onError(e) {
assert(_eventPresenter.isRegisteredOnError != null);
_eventPresenter.isRegisteredOnError(e);
}
}
Notice how the Presenter also does some filtering before sending the data back to the Controller. It also disposes of all the Usecases when the Controller calls presenter.dispose();
This makes it very easy for the Controller to focus on handling events of the View rather than worrying about Usecases. It also decreases the amount of code required inside the Controller (which in other architectures tend to grow uncontrollably).
If the Controller were to communicate directly with the Usecases, it would have to instantiate the 3 Usecases, implement 3 Observers, and dispose of every Usecase. Not only does it grow dramatically, but it also renders the Presenter virtually useless. This isn't true for all frameworks and languages, but it is the case for Flutter and Dart.
In the Controller
@override
void dispose() {
_eventPresenter.dispose();
super.dispose();
}
}
There are some limitations with Dart that make the Presenter larger than I would like. Dart does not yet support anonymous class nor inner classes (May 2019). Once it does, the Presenter can also be dramatically shortened, which is when the decision of having the Presenter handle the lifecycle of Usecases will be even more advantageous.
To summarize,
Advantages | Disadvantages |
---|---|
Shorter Controller | A little more setup code and extra call |
Centralized location for Usecases | |
Proper handling of Usecases' lifecycle | |
Better separation of concerns |
I hope my answer was helpful. Let me know if I need to clarify anything.
Thanks for your feedback,
Shady Boukhary
from flutter_clean_architecture.
Related Issues (20)
- Cant add AppBar via ControlledWidgetBuilder.builder HOT 2
- One Controller for multiple pages HOT 1
- RefreshUI and setState HOT 1
- Question: Same controller in multiple widgets HOT 9
- Error when migrating from flutter_clean_architecture ^3.0.2 to ^5.0.0 HOT 1
- [Question] About initViewState() migration HOT 1
- [Question] How do I write test for the controller or presenter class? HOT 5
- If try add block isolate I have error HOT 1
- refreshUI() doesn't work with Shared Controller. HOT 1
- Can u update to support flutter_bloc >=7.2.0 HOT 2
- Unable to override `initState` on ViewState HOT 3
- How to avoid rebuild in components.
- Migrate to support 2.10.3 HOT 1
- Update to flutter 3.0 HOT 4
- integrating with Riverpod HOT 2
- I tried to make a 100% test coverage in example app but stuck in 98.3 HOT 1
- Getting this issues while app is build on ios ?
- Getting this error on ios build ?
- Add support to Dart 3 HOT 1
- Error: 'View' is imported from both
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from flutter_clean_architecture.