FXML Patterns / best practices

Greg Brown greg.x.brown at oracle.com
Thu Feb 23 07:32:31 PST 2012


The mail server appears to have stripped the attachments. You can find them here:

http://dl.dropbox.com/u/49180229/src.zip
http://dl.dropbox.com/u/49180229/presenter_example.png

On Feb 23, 2012, at 10:29 AM, Greg Brown wrote:

> Here's a working example that demonstrates one way this could be implemented using FXML. It won't work on 2.1 since it uses bi-directional binding and the controller enhancements I have proposed, but you should be able to get a good idea for how it works from the source code.
> 
> The attached archive includes the following files:
> 
> - PersonModel.java - person model (complete implementation of your example PersonModel class)
> - person_view.fxml - markup for person view
> - PersonViewController.java - controller for person view
> - PersonPresenter.java - presenter implementation that uses PersonModel and person_view.fxml
> - Gender.java - enum definition for gender
> - PresenterExample.java - sample launcher application
> 
> A screen shot of the running application is also attached.
> 
> Note that everything works as expected with one exception - since ChoiceBox does not define a "selectedItem" property, it was not possible to declaratively create the binding to the "controller.model.gender" property. Hopefully this will be resolved in a future release.
> 
> Let me know if you have any questions.
> 
> Greg
> 
> 
> 
> 
> 
> On Feb 23, 2012, at 3:05 AM, Daniel Zwolenski wrote:
> 
>> I'm spawning off a new thread for this, so Greg's discussion on the
>> Controller base class can continue uninterrupted.
>> 
>> I just wrote a huge email outlining all the pros/cons of clean MVP and
>> how/where I think FXML is causing me to compromise, etc. It was too long
>> and too complicated, so back to basics. Below I define a very pure MVP
>> implementation (in psuedo code) of a Person Component using pure Java. I'll
>> avoid defining interfaces at this point for ease, but ideally I would do
>> this as well (so I can use mock objects when testing). I've used a
>> standard, common, popular pattern that I believe a lot of people would like
>> to use if they could.
>> 
>> What I'd like to know is how people intend to implement something like the
>> below using FXML. I'm looking for either the closest approximation of the
>> below using FXML, or if you think you have a better pattern, then how you
>> would implement this. Based on that I'd then like to look at what
>> advantages/disadvantages your proposed design has over the 'pure' MVP
>> approach below (or tell me what you'd do differently if you don't think the
>> below is pure MVP!).
>> 
>> The key benefits that I see from the below architecture are:
>> 
>> 1. *Seperation of concerns*: the relevant implementation details are very
>> well contained within each of the correct elements, view in View,
>> presentation logic in Presenter, model stuff in Model.
>> 2. *Unit Testability*: the well-defined units are easily decoupled from
>> each other and can be tested in isolation allowing for clean unit tests
>> 3. *Reusability*: each element of the MVP bundle is reusable, so you could
>> attach a different View (e.g. a mobile specific one) to the Presenter, etc
>> 
>> Can we retain these benefits in an FXML-based architecture, or do we have
>> to give them up?
>> 
>> 
>> *Model*
>> 
>> class PersonModel {
>> 
>>   private LongProperty personId;
>>   private TextProperty firstName;
>>   private TextProperty lastName;
>>   private ObjectProperty<Gender> gender;
>> 
>>   ... constructor, getters, setters ...
>> }
>> 
>> *View*
>> 
>> class PersonView extends VBox {
>> 
>>   @Inject private PersonModel model;
>>   @Inject private PersonPresenter presenter;
>> 
>>   private TextField firstNameField;
>>   private TextField lastNameField;
>>   private ChoiceBox genderField;
>>   private Button deleteButton;
>>   private Button saveButton;
>> 
>>   @PostConstruct void init() {
>> 
>>       firstNameField = new TextField();
>> 
>> firstNameField.textProperty().bindBidirectional(model.firstNameProperty());
>>       getChildren().add(firstNameField);
>> 
>>       lastNameField = new TextField();
>> 
>> lastNameField.textProperty().bindBidirectional(model.lastNameProperty());
>>       getChildren().add(lastNameField);
>> 
>>       genderField= new ChoiceBox(Gender.values());
>> 
>> genderField.selectedValueProperty().bindBidirectional(model.genderField());
>>       getChildren().add(genderIcon);
>> 
>>       deleteButton= new Button("Delete");
>>       deleteButton.setOnAction(new EventHandler<ActionEvent>() {
>>           public void handle(ActionEvent event) {
>>               presenter.delete();
>>           }
>>       });
>>       getChildren().add(saveButton);
>> 
>>       saveButton= new Button("Save");
>>       saveButton.setOnAction(new EventHandler<ActionEvent>() {
>>           public void handle(ActionEvent event) {
>>               presenter.save();
>>           }
>>       });
>>       getChildren().add(saveButton);
>>   }
>> }
>> 
>> *Presenter*
>> 
>> class PersonPresenter {
>> 
>>   @Inject private PersonModel model;
>>   @Inject private PersonView view;
>>   @Inject private PersonService personService; // e.g. an RMI-like
>> handler onto a server
>> 
>>   // this is the external method called by whatever wants to show a person
>>   void showPerson(final long personId) {
>>       new Thread(new Task<PersonDTO>() {
>>           PersonDTO call() {
>>               return personService.getPersonDetails(personId);
>>           }
>> 
>>           void onSuccess(PersonDTO details) {
>>               model.setPersonId(details.getPersonId());
>>               model.setFirstName(details.getFirstName());
>>               model.setLastName(details.getLastName());
>>               model.setGender(details.getGender());
>>           }
>>       }).run();
>>   }
>> 
>>   void delete() {
>>       final long personId = model.getPersonId();
>>       new Thread(new Task<Void>() {
>>           void call() {
>>               personService.deletePerson(personId);
>>               return null;
>>           }
>> 
>>           void onSuccess(Void result) {
>>               // probably navigate away from the current view
>>           }
>>       }).run();
>>   }
>> 
>>   void save() {
>>       final PersonDTO details = new PersonDTO(
>>               model.getPersonId(),
>>               model.getFirstName(),
>>               model.getLastName()
>>               model.getGender()
>>       );
>> 
>>       new Thread(new Task<PersonDTO>() {
>>           void call() {
>>               return personService.updatePerson(details);
>>           }
>> 
>>           void onSuccess(Void result) {
>>               // probably show a nice confirmation message
>>           }
>>       }).run();
>>   }
>> }
>> 
>> 
>> The above assumes the existence of some sort of factory method that wires
>> everything up. This would ideally be Spring or Guice but could just as
>> easily be done by hand like so:
>> 
>> public PersonPresenter createPersonPresenter() {
>> 
>>   PersonModel model = new PersonModel();
>>   PersonView view = new PersonView();
>>   PersonPresenter presenter = new PersonPresenter();
>> 
>>   view.setModel(model);
>>   view.setPresenter(presenter);
>> 
>>   presenter.setModel(model);
>>   presenter.setView(view);
>>   presenter.setPersonService(getPersonService());
>> 
>>   return presenter;
>> }
> 



More information about the openjfx-dev mailing list