I'm writing an application in Swing and I'd like to make use of Model–view–presenter pattern. I found a sample application using this pattern and then I alter it to make use of Guice to do dependency injection. I was able to make it work with albeit with one problematic piece of code. Let me first show my code and later on the piece of code I find problematic.
Application starting point:
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class Main {
private final View view;
public static void main(final String[] args) {
final Main main = Guice.createInjector().getInstance(Main.class);
SwingUtilities.invokeLater(main.view::createUI);
}
}
Model:
public class Model {
public String getPassword() {
return "password";
}
}
Presenter:
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class Presenter {
private final View view;
private final Model model;
public void login(final String password) {
final String result = model.getPassword().equals(password)
? "Correct password" : "Incorrect password";
view.updateStatusLabel(result);
}
}
Until this point everything seems to be fine.
The problem is in the View constructor. I manually create instance of Presenter using this (due to some coupling involved in MVP pattern) instead of letting Guice does it job.
EDIT
How do I overcome this problem? As per comments,I find this code incorrect and I'd like to rewrite it so that the view could be injected into the presenter via dependency injection.
Guice has this somewhat covered how to do this in its documentation, however, I find it really difficult to understand the docs.
public class View {
private final Presenter presenter;
private JLabel statusLabel;
private JTextField inputField;
public View() {
// manually adding this, should be DI
this.presenter = new Presenter(this, new Model());
}
public void createUI() {
// removed
}
//called by the presenter to update the status label
public void updateStatusLabel(final String text) {
statusLabel.setText(text);
}
}
You are indeed manually instantiating the
Presenterin theViewand you would ideally want this to be done by Guice through dependency injection. That is a circular dependency issue becausePresenterdepends onViewand...Viewdepends onPresenter.The simplest solution is to use a method called 'injecting providers'. In Guice, providers are simple interfaces that provide instances of a type
T. You can leverage this to decouple the view and presenter creation logic.View: (using provider binding)Presenter:Then you will need to bind these dependencies in Guice's module:
And then finally, your
mainclass should use this module:That setup will make sure that
Presenteris injected intoViewlazily (on the first call ofgetPresenter()), breaking the circular dependency.It uses Just-In-Time Bindings: These are the bindings that you have not explicitly defined in your module, but Guice can still inject them because it knows how to build them.
When you are injecting
ViewintoMainandPresenterintoViewusing aProvider, you are not creating explicit bindings for them in theApplicationModule. But Guice can still make these injections because it can buildViewandPresenterusing their injectable constructors (i.e., constructors that are annotated with@Inject), effectively performing just-in-time bindings.