[go: up one dir, main page]

0% found this document useful (0 votes)
122 views67 pages

MVVMFX - JavaOne 2016

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 67

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 (knockout.js), ...
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();

a.bindBidirectional(b);

a.setValue(“Hallo”);
System.out.println(b.getValue()); // “Hallo”
b.setValue(“World”);
System.out.println(a.getValue()); // “World”
Data Binding in MVVM
Data Binding in MVVM

Model firstName lastName

“Max” “Wielsch”
Data Binding in MVVM

welcomeMessageProperty.set(
“Hallo Max Wielsch”
“Hallo “
+ user.getFirstName() + “ ”
ViewModel + user.getLastName());

Model firstName lastName

“Max” “Wielsch”
Data Binding in MVVM
welcomeLabel.textProperty().bind(
viewModel.welcomeMessageProperty());
View

welcomeMessageProperty.set(
“Hallo Max Wielsch”
“Hallo “
+ user.getFirstName() + “ ”
ViewModel + user.getLastName());

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.

https://github.com/sialcasa/mvvmFX
… is an open-source application framework providing necessary components for the
usage of the Model View ViewModel pattern with JavaFX.

https://github.com/sialcasa/mvvmFX
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;
}
TodolistView.fxml:

<?xml version="1.0" encoding="UTF-8"?>

...
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="de.saxsys.mvvmfx.examples.todo.TodolistView">
<children>
...
</children>
</VBox>
But how to load a MVVM component?
URL url = getClass().getResource(“/de/saxsys/MyView.fxml”);
FXMLLoader loader = new FXMLLoader(url);
loader.load();
loader.getRoot(); // loaded node
loader.getController(); // controller class
But how to load a MVVM component?
URL url = getClass().getResource(“/de/saxsys/MyView.fxml”);
FXMLLoader loader = new FXMLLoader(url);
loader.load();
loader.getRoot(); // loaded node
loader.getController(); // controller class

ViewTuple tuple = FluentViewLoader.fxmlView(MyView.class).load();


tuple.getView(); // loaded node (View)
tuple.getCodeBehind(); // controller class (View)
tuple.getViewModel(); // ViewModel
How to trigger an event in the View?

View

ViewModel
Notifications
public class MyView implements FxmlView<MyViewModel> {
@InjectViewModel
private MyViewModel viewModel;

viewModel.subscribe(“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>de.saxsys</groupId> <groupId>de.saxsys</groupId>
<artifactId>mvvmfx-cdi</artifactId> <artifactId>mvvmfx-guice</artifactId>
</dependency> </dependency>

<dependency>
<groupId>de.saxsys</groupId>
<artifactId>mvvmfx-easydi</artifactId>
</dependency>

or

MvvmFX.setCustomDependencyInjector(...);
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=PersonScope.class})
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 = repository.find(id);

firstname.setValue(person.getFirstName());
lastname.setValue(person.getLastName());
emailAddress.setValue(person.getEmailAddress());
phoneNumber.setValue(person.getPhoneNumber());
}
public void save() {
person.setFirstName(firstname.get());
person.setLastName(lastname.get());
person.setEmailAddress(emailAddress.get());
person.setPhoneNumber(phoneNumber.get());

repository.persist(person);
}
}
public class ContactFormViewModel implements ViewModel {
// Properties and Property-Getter …

@Inject
private Repository repository;
private person;

public void showPerson(String id) {


person = repository.find(id);

firstname.setValue(person.getFirstName()); copy data from model object


lastname.setValue(person.getLastName()); to properties
emailAddress.setValue(person.getEmailAddress());
phoneNumber.setValue(person.getPhoneNumber());
}
public void save() {
person.setFirstName(firstname.get());
person.setLastName(lastname.get()); put data from properties
person.setEmailAddress(emailAddress.get());
back to model object
person.setPhoneNumber(phoneNumber.get());

repository.persist(person);
}
}
public class ContactFormViewModel implements ViewModel {

private ModelWrapper<Person> modelWrapper = new ModelWrapper<>();


@Inject
private Repository repository;

public void showPerson(String id) {


Person person = repository.find(id);
modelWrapper.set(person);
modelWrapper.reload();
}
public void save() {
modelWrapper.commit();
repository.persist(modelWrapper.get());
}

public StringProperty firstnameProperty() {


return modelWrapper.field(Person::getFirstName, Person::setFirstName);
}
public StringProperty lastnameProperty() {
return modelWrapper.field(Person::getLastName, Person::setLastName);
}
public StringProperty emailAddressProperty() {
return modelWrapper.field(Person::getEmailAddress, Person::setEmailAddress);
}
public StringProperty phoneNumberProperty() {
return modelWrapper.field(Person::getPhoneNumber, Person::setPhoneNumber);
}
}
VALIDATION
Validation
Validation logic Visualization
boolean isPhoneNumberValid(String input) {
return Pattern.compile("\\+?[0-9\\s]{3,20}")
.matcher(input).matches();
}
Validation
ControlsFX ValidationSupport
TextField textField = ...;

ValidationSupport validationSupport = new ValidationSupport();

validationSupport.registerValidator(textField,
Validator.createRegexValidator("Wrong Number", "\\+?[0-9\\s]{3,20}", Severity.ERROR));
Validation
// ViewModel

private final Validator phoneValidator = ...

public ValidationStatus phoneValidation() {


return phoneValidator.getValidationStatus();
}

// View

ValidationVisualizer validVisualizer= new ControlsFxVisualizer();


validVisualizer.initVisualization(viewModel.phoneValidation(), textField);
Validation
StringProperty phoneNumber = new SimpleStringProperty();

Predicate<String> predicate = input -> Pattern.compile("\\+?[0-9\\s]{3,20}")


.matcher(input).matches();

Validator phoneValidator = new FunctionBasedValidator<>(


phoneNumber, predicate, ValidationMessage.error("Not a valid phone number");
Validation
Validator phoneValidator = new FunctionBasedValidator(...);
Validator emailValidator = new ObservableRuleBasedValidator(...);

Validator formValidator = new CompositeValidator();


formValidator.addValidators(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() {
notificationCenter.subscribe("something", observer);
}

@Override
public void onViewRemoved() {
notificationCenter.unsubscribe(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) {


validationSupport.registerValidator(label,
Validator
.createRegexValidator("Error", "...", Severity.ERROR);
}
}
javafx.controls.Label used in
class MyViewModel implements ViewModel { ViewModel

public void initValidation(Label usernameLabel) {


validationSupport.registerValidator(label,
Validator
.createRegexValidator("Error", "...", Severity.ERROR);
}
}
> mvn aspectj:compile
> mvn aspectj:compile

[WARNING] Methods taking UI elements as arguments is invoked within the ViewModel


layer

/.../PersonsViewModel.java:34

validationSupport.registerValidator(label,

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
www.mvvmfx.de

Source Code, Wiki, Tutorials: https://github.com/sialcasa/mvvmFX

Feedback, Bug Reports, Feature Requests welcome :-)


Q&A
Alexander Casall

alexander.casall@saxsys.de
@sialcasa

Manuel Mauky

manuel.mauky@saxsys.de
http://lestard.eu
@manuel_mauky

You might also like