Giter Club home page Giter Club logo

Comments (1)

ShadyBoukhary avatar ShadyBoukhary commented on May 18, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.