FXML Patterns / best practices

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


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

-------------- next part --------------


-------------- next part --------------


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