MVVM with JavaFX
Alexander Casall & Manuel Mauky
@sialcasa @manuel_mauky
2016-09-21
● Custom Software Development
● Dresden, München, Berlin, Hamburg, Leipzig, Görlitz
● 200+ Employees
MODEL VIEW
VIEWMODEL
Model View ViewModel
● Based on Presentation Model Pattern
● 2005 published by Microsoft
● WPF (.NET), JavaScript ([Link]), ...
Model
● Application model
● Independent from UI
● Backend systems etc.
Model
ViewModel
● UI state
● Presentation logic
● Communication with backend
● Preparation of model data
ViewModel
Model
View
● Display data from ViewModel
● Pass user input to ViewModel View
● Update UI state in ViewModel
ViewModel
Model
KEEP
VIEW AND
VIEWMODEL
SYNCHRONIZED?
View
ViewModel
Model
Data Binding and Properties
notifications about changes
(events)
StringProperty = StringProperty
String Binding String
StringProperty a = new SimpleStringProperty();
StringProperty b = new SimpleStringProperty();
[Link](b);
[Link](“Hallo”);
[Link]([Link]()); // “Hallo”
[Link](“World”);
[Link]([Link]()); // “World”
Data Binding in MVVM
Data Binding in MVVM
Model firstName lastName
“Max” “Wielsch”
Data Binding in MVVM
[Link](
“Hallo Max Wielsch”
“Hallo “
+ [Link]() + “ ”
ViewModel + [Link]());
Model firstName lastName
“Max” “Wielsch”
Data Binding in MVVM
[Link]().bind(
[Link]());
View
[Link](
“Hallo Max Wielsch”
“Hallo “
+ [Link]() + “ ”
ViewModel + [Link]());
Model firstName lastName
“Max” “Wielsch”
View Hierarchies
View View
View
ViewModel ViewModel ViewModel
? ?
Benefits
● all presentation logic is located only in the ViewModel
● ViewModel is testable with unit tests
UI
Tests
→ High testability of frontend code
System Tests
→ Test-driven-development in the UI
Integration Tests
Unit Tests
Challenges of MVVM
● Communication between VMs in more complex applications
● Demands discipline to not violate visibility constraints
● More code necessary for layers of indirection
… is an open-source application framework providing necessary components for the
usage of the Model View ViewModel pattern with JavaFX.
[Link]
… is an open-source application framework providing necessary components for the
usage of the Model View ViewModel pattern with JavaFX.
[Link]
Basic Classes for MVVM
Extended FXML Loader
Dependency Injection Support
ResourceBundles
ModelWrapper
Notifications
Commands
Validation
Scopes
How to create a MVVM Component?
Component
View FXML
CodeBehind class
ViewModel
Base Classes / Interfaces
class TodolistViewModel implements ViewModel {
…
}
class TodolistView implements FxmlView<TodolistViewModel> {
@InjectViewModel
private TodolistViewModel viewModel;
}
[Link]:
<?xml version="1.0" encoding="UTF-8"?>
...
<VBox xmlns:fx="[Link]
fx:controller="[Link]">
<children>
...
</children>
</VBox>
But how to load a MVVM component?
URL url = getClass().getResource(“/de/saxsys/[Link]”);
FXMLLoader loader = new FXMLLoader(url);
[Link]();
[Link](); // loaded node
[Link](); // controller class
But how to load a MVVM component?
URL url = getClass().getResource(“/de/saxsys/[Link]”);
FXMLLoader loader = new FXMLLoader(url);
[Link]();
[Link](); // loaded node
[Link](); // controller class
ViewTuple tuple = [Link]([Link]).load();
[Link](); // loaded node (View)
[Link](); // controller class (View)
[Link](); // ViewModel
How to trigger an event in the View?
View
ViewModel
Notifications
public class MyView implements FxmlView<MyViewModel> {
@InjectViewModel
private MyViewModel viewModel;
…
[Link](“messageKey”, (k,v) -> doSomething());
…
}
public class MyViewModel implements ViewModel {
…
publish(“messageKey”);
…
}
How to handle dependencies of components?
class MyViewModel implements ViewModel {
private Service service = new ServiceImpl(); // ?
}
How to handle dependencies of components?
class MyViewModel implements ViewModel {
private Service service = new ServiceImpl(); // ?
@Inject
private Service service;
}
Dependency Injection
● Inversion of Control
● No static dependency to a specific implementation, only to
interfaces
● Use mock implementations for unit tests
● Configure lifecycle of instances
Dependency Injection Support
<dependency> <dependency>
<groupId>[Link]</groupId> <groupId>[Link]</groupId>
<artifactId>mvvmfx-cdi</artifactId> <artifactId>mvvmfx-guice</artifactId>
</dependency> </dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>mvvmfx-easydi</artifactId>
</dependency>
or
[Link](...);
Dependency Injection Support
public class MyFxApp extends Application { … }
public class MyFxApp extends MvvmfxCdiApplication { … }
public class MyFxApp extends MvvmfxGuiceApplication { … }
public class MyFxApp extends MvvmfxEasyDIApplication {…}
SCOPES
Views are hierarchical
Component
Views need to share state
Chuck Norris
Duke
Duke
Duke
Duke
M D Master
Duke Detail
Views are hierarchical
Component
Scope
Define a Scope
public class PersonScope implements Scope {
private ObjectProperty<Person> selectedPerson = new SimpleObjectProperty();
//Getter Setters
}
A component in the hierarchy declares the scope
@ScopeProvider(scopes=[Link]})
public class PersonViewModel implements ViewModel {}
ScopeProvider
Components below can inject the same scope instance
public class PersonsOverviewViewModel implements
ViewModel {
@InjectScope
private PersonScope scope;
}
public class PersonDetailView implements ViewModel {
@InjectScope
private PersonScope scope;
}
Scopes can be used to decouple the communication between
components by using a hierarchical dependency injection
Communication
Scenarios
MODEL
WRAPPER
ModelWrapper
The ModelWrapper optimizes reading and writing of model data.
Negative Example
public class ContactFormViewModel implements ViewModel {
private StringProperty firstname = new SimpleStringProperty();
private StringProperty lastname = new SimpleStringProperty();
private StringProperty emailAddress = new SimpleStringProperty();
private StringProperty phoneNumber = new SimpleStringProperty();
public StringProperty firstnameProperty() {
return firstname;
}
public StringProperty lastnameProperty() {
return lastname;
}
public StringProperty emailAddressProperty() {
return emailAddress;
}
public StringProperty phoneNumberProperty() {
return phoneNumber;
}
}
public class ContactFormViewModel implements ViewModel {
// Properties and Property-Getter …
@Inject
private Repository repository;
private person;
public void showPerson(String id) {
person = [Link](id);
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
}
public void save() {
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link](person);
}
}
public class ContactFormViewModel implements ViewModel {
// Properties and Property-Getter …
@Inject
private Repository repository;
private person;
public void showPerson(String id) {
person = [Link](id);
[Link]([Link]()); copy data from model object
[Link]([Link]()); to properties
[Link]([Link]());
[Link]([Link]());
}
public void save() {
[Link]([Link]());
[Link]([Link]()); put data from properties
[Link]([Link]());
back to model object
[Link]([Link]());
[Link](person);
}
}
public class ContactFormViewModel implements ViewModel {
private ModelWrapper<Person> modelWrapper = new ModelWrapper<>();
@Inject
private Repository repository;
public void showPerson(String id) {
Person person = [Link](id);
[Link](person);
[Link]();
}
public void save() {
[Link]();
[Link]([Link]());
}
public StringProperty firstnameProperty() {
return [Link](Person::getFirstName, Person::setFirstName);
}
public StringProperty lastnameProperty() {
return [Link](Person::getLastName, Person::setLastName);
}
public StringProperty emailAddressProperty() {
return [Link](Person::getEmailAddress, Person::setEmailAddress);
}
public StringProperty phoneNumberProperty() {
return [Link](Person::getPhoneNumber, Person::setPhoneNumber);
}
}
VALIDATION
Validation
Validation logic Visualization
boolean isPhoneNumberValid(String input) {
return [Link]("\\+?[0-9\\s]{3,20}")
.matcher(input).matches();
}
Validation
ControlsFX ValidationSupport
TextField textField = ...;
ValidationSupport validationSupport = new ValidationSupport();
[Link](textField,
[Link]("Wrong Number", "\\+?[0-9\\s]{3,20}", [Link]));
Validation
// ViewModel
private final Validator phoneValidator = ...
public ValidationStatus phoneValidation() {
return [Link]();
}
// View
ValidationVisualizer validVisualizer= new ControlsFxVisualizer();
[Link]([Link](), textField);
Validation
StringProperty phoneNumber = new SimpleStringProperty();
Predicate<String> predicate = input -> [Link]("\\+?[0-9\\s]{3,20}")
.matcher(input).matches();
Validator phoneValidator = new FunctionBasedValidator<>(
phoneNumber, predicate, [Link]("Not a valid phone number");
Validation
Validator phoneValidator = new FunctionBasedValidator(...);
Validator emailValidator = new ObservableRuleBasedValidator(...);
Validator formValidator = new CompositeValidator();
[Link](phoneValidator, emailValidator);
Lifecycle
Lifecycle
● React when component is added/removed to scene
● add/remove listeners
● Example: Dialog is closed
Lifecycle
class DialogViewModel implements ViewModel, SceneLifecycle {
private NotificationObserver observer = (k,v) -> …;
@Override
public void onViewAdded() {
[Link]("something", observer);
}
@Override
public void onViewRemoved() {
[Link](observer);
}
}
How to Secure the
Architecture
How to verify that the pattern was adhered to?
● Advantages of MVVM only available when all developers adhere to
the pattern
● How to find mistakes and wrong usage of API?
How to verify that the pattern was adhered to?
● Advantages of MVVM only available when all developers adhere to
the pattern
● How to find mistakes and wrong usage of API?
● AspectJ compile time checking (beta)
class MyViewModel implements ViewModel {
public void initValidation(Label usernameLabel) {
[Link](label,
Validator
.createRegexValidator("Error", "...", [Link]);
}
}
[Link] used in
class MyViewModel implements ViewModel { ViewModel
public void initValidation(Label usernameLabel) {
[Link](label,
Validator
.createRegexValidator("Error", "...", [Link]);
}
}
> mvn aspectj:compile
> mvn aspectj:compile
[WARNING] Methods taking UI elements as arguments is invoked within the ViewModel
layer
/.../[Link]
[Link](label,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Link]
Source Code, Wiki, Tutorials: [Link]
Feedback, Bug Reports, Feature Requests welcome :-)
Q&A
Alexander Casall
[Link]@[Link]
@sialcasa
Manuel Mauky
[Link]@[Link]
[Link]
@manuel_mauky