Scenario
I am creating a GUI where multiple views reference the same model object.
What I am Accustom to
In Swing, if i want all the views to reference the same model i would pass the model into the constructor.
What I am Currently Doing
In JavaFX, I am passing the model around by having a setter method in the views/controllers (menubars, split panes, tabs, ...), after each view/controller has been loaded. I find this very tacky and cumbersome. Additionally, I find it won't work because in certain situations i need the model to already exist in a controller before some of the controller widgets are initialized.
Lackluster Alternatives
(Note: I am referencing these stackoverflow questions:
Passing parameters to a controller when loading an FXML)
Dependency Injection
Saving the model object as a public static variable
Passing Parameters from Caller to Controller
I want each controller to load itself in its constructor, and I want each custom controller to be automatically injected into its parent controller. For example, the card overview tab loads itself like this:
public CardOverviewTab() { FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("card_overview_tab.fxml")); fxmlLoader.setRoot(content); fxmlLoader.setController(this); try { fxmlLoader.load(); } catch (Exception e) { e.printStackTrace(); } }
And the SingleGameSetup controller has the card overview tab automatically injected into a variable:
public class SingleGameSetupController extends AnchorPane { @FXML private CardOverviewTab cardOverviewTab; // Rest of the class }
And the part of the fxml containing the card overview tab looks like this:
<CardOverviewTab fx:id="cardOverviewTab" />
This way I do not need to worry about manually loading a controller, but I still have the problem of setting the model.
Setting a Controller on the FXMLLoader
Event Bus
Singleton
What I am Looking for
Is there a way to pass the model object around in a less cumbersome way? I am looking for a way that is as simple as passing the model to a constructor, but I do not want to deal with manually loading controllers via the FXMLLoader, or setting the model after the controllers are loaded. Maybe having a class to retrieve the model is the best option, but I am asking just in case there is a better way.
Update
In addition to afterburner.fx, also checkout Gluon Ignite:
Gluon Ignite allows developers to use popular dependency injection frameworks in their JavaFX applications, including inside their FXML controllers. Gluon Ignite creates a common abstraction over several popular dependency injection frameworks (currently Guice, Spring, and Dagger, but we plan at add more as the demand becomes obvious). With full support of JSR-330 Gluon Ignite makes using dependency injection in JavaFX applications trivial.
Injection of model objects into controllers is also via @Inject, similar to afterburner.fx.
Suggested Approach
As you appear to be seeking a dependency injection framework, I think your best option is to use the afterburner.fx framework.
afterburner.fx provides a way injecting model objects into your JavaFX controllers using the standard Java @Inject annotation.
Alternative Dependency Injection Systems
Spring is large and complicated and, unless you need a lot of its other functionality for your application, should not be considered due to its complexity.
Guice is a lot simpler than Spring and a reasonable pick if you need a dependency injection framework with numerous features such as provider classes. But from the sound of it, you don't need all the features that Guice provides as you just want to a way to pass around singleton instances of objects in your application without explicitly looking them up.
So, try out afterburner.fx and see if it fits your needs.
afterburner.fx Sample Code
Here is sample of injecting a model instance (the NotesStore
) into a controller using afterburner.fx. The sample is directly copied from the afterburner.fx documentation.
import com.airhacks.afterburner.views.FXMLView; public class NoteListView extends FXMLView { //usually nothing to do, FXML and CSS are automatically //loaded and instantiated } public class AirpadPresenter implements Initializable { @Inject // injected by afterburner, zero configuration required NotesStore store; @FXML // injected by FXML AnchorPane noteList; @Override public void initialize(URL url, ResourceBundle rb) { //view constructed from FXML NoteListView noteListView = new NoteListView(); //fetching and integrating the view from FXML Parent view = noteListView.getView(); this.noteList.getChildren().add(view); } }
followme.fx is a basic sample application demonstrating how to use afterburner.fx. I did have a few issues getting followme.fx running straight out of the box due to Maven dependency incompatibilities, so I forked it's code and fixed some of the issues which prevented me from using it out of the box.
Answers to addition questions from comments
So from the NoteStore example, are you saying all I have to do is add the afterburner framework dependency and put @Inject on my model variable?
No, you also need to create an associated class that extends FXMLView and instantiate that with a new call (similar to how NotesListView is created in the sample code above). If you are interested in continuing to investigate the afterburner.fx framework, then use the followme.fx project as basis because it provides complete source code for a very simple executable sample using the framework.
I tried google guice and got it to work . . . you'll see in the constructor a game settings object is injected manually.
I don't think you should have to use the Guice injector manually like that. I think you can set a controller factory on an FXMLLoader instance to initiate the injection. This is how the FXMLView in afterburner.fx does it. The exact detail of the injection mechanism used in Guice is going to differ from the afterburner.fx mechanism, but I think the broad concept of setting the controller factory remains similar.
There is a demo of the set controller factory using FXML and Guice in the answer to: Associating FXML and Controller in Guice's Module configuration.
It's a shame there is not a more straightforward way of doing this which does not cause you so many difficulties.
As an inconsequential personal side note, I'm kind of ambivalent on the topic of dependency injection frameworks in general. Sure, they can help, but many times for simple things I'm often OK with a singleton with a getInstance method rather than the more sophisticated framework. Still I do see how in larger projects they can be useful and certainly they are very popular in certain Java frameworks.
I have been researching this problem and I have found that dependency injection along with a singleton (for the model) is my optimal design. I learned that having a setter method for the model in each of my controllers is a form of dependency injection as is passing the model through the constructor. Spring and Guice are frameworks to help that.
I tried using Spring as noted here: http://www.zenjava.com/2011/10/23/better-controller-injection/ but I ran into the problem when the configuration class tried to load a controller, it failed to load the member controllers. This may not have been a Spring issue, but I figured I didn't care enough to spend the time to get it working. Plus, I would have had to go through all of my controller files and edited the way they were created.
Having searched around and doing a lot of reading, I found that this article best expresses what I would like to be able to do: https://forums.oracle.com/thread/2301217?tstart=0. Since the article is referring to suggested improvements, these improvements do not yet exist in JavaFX. But when they do, I will be implementing them. Just for an example, being able to inject a model via the fxml:
<usevar name="model" type="com.mycom.myapp.ModelObject"/>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With