* Remove unused member which was also causing a false positive sonar issue. Fixes sonar issue https://sonarcloud.io/project/issues?open=AY3gHwu5DIZTZkppqVEG&id=iluwatar_java-design-patterns * Fixes sonar issue https://sonarcloud.io/project/issues?open=AXK0OzDA-CiGJS70dLki&id=iluwatar_java-design-patterns related to "Refactor the code of the lambda to not have multiple invocations throwing the same checked exception." Also, updated the code to use Instant and Duration to deal with time instead of int. Added the awaitility library to perform assertions in test which is more reliable than using Thread.sleep directly to wait for events to happen. * checkstyle fix * Add sneaky throws to fix sonar lint issue. This is fine as the newFile method is not being tested but instead the new SimpleFileWriter(...) is. * The first booking needs to happen outside the assertions. Fixed other warnings * Use records to pass around related objects instead of using a large number of individual params, which sonar did not like. * Checkstyle fixes * checkstyle fixes * Remove complexity to keep sonar happy. * Split into different methods to reduce complexity. Could be broken down even further but currently Sonar is happy. * Move files to correct package * Add valid assertions to tests * rename constants to avoid confusion * Sonar warning related to cognitive complexity can be suppressed as the methods are quite generic and not functional enough to be separated out. * Use constants to keep Sonar happy * Use correct constant naming conventions * Use correct constant naming conventions * Use lombok to define noargsconstructor * Use a single method to do the logging * Remove unused constructor and redundant method * Use a reusable method for logging
title, category, language, tags
| title | category | language | tags | ||
|---|---|---|---|---|---|
| Model-View-Intent | Architectural | en |
|
Intent
MVI is a derivation of the original MVC architectural pattern. Instead of working with a proactive controller MVI works with the reactive component called intent: it's a component which translates user input events into model updates.
Explanation
MVI is a Reactive Architecture Pattern which is short for Model -View-Intent. It introduces two new concepts: the intent and the state. UI might have different states — Loading State, Fetch Data State, Error State, and user events are submitted in the form of an Intent.
Class diagram
Programmatic Example
CalculatorAction defines our Intent in MVI for user interactions. It has to be an interface instead of enum, so that we can pass parameters to certain children.
public interface CalculatorAction {
/**
* Makes identifying action trivial.
*
* @return subclass tag.
* */
String tag();
}
CalculatorModel defines the state of our view or in out case, variable and output of the calculator.
@Data
public class CalculatorModel {
/**
* Current calculator variable used for operations.
**/
final Double variable;
/**
* Current calculator output -> is affected by operations.
**/
final Double output;
}
CalculatorView will serve as a mock view which will expose potential user actions and display calculator state -> output and current variable
@Slf4j
public class CalculatorView {
/**
* View model param handling the operations.
* */
private final CalculatorViewModel viewModel = new CalculatorViewModel();
/**
* Display current view model output with logger.
* */
void displayTotal() {
LOGGER.info(
"Total value = {}",
viewModel.getCalculatorModel().getOutput().toString()
);
}
/**
* Handle addition action.
* */
void add() {
viewModel.handleAction(new AdditionCalculatorAction());
}
/**
* Handle subtraction action.
* */
void subtract() {
viewModel.handleAction(new SubtractionCalculatorAction());
}
/**
* Handle multiplication action.
* */
void multiply() {
viewModel.handleAction(new MultiplicationCalculatorAction());
}
/**
* Handle division action.
* */
void divide() {
viewModel.handleAction(new DivisionCalculatorAction());
}
/**
* Handle setting new variable action.
*
* @param value -> new calculator variable.
* */
void setVariable(final Double value) {
viewModel.handleAction(new SetVariableCalculatorAction(value));
}
}
Finally, ViewModel handles the exposed events with the handleAction(event) method, which delegates the specific handling to private methods. Initially calculator output and variable are equal to 0.
public final class CalculatorViewModel {
/**
* Current calculator model (can be changed).
*/
private CalculatorModel model =
new CalculatorModel(0.0, 0.0);
/**
* Handle calculator action.
*
* @param action -> transforms calculator model.
*/
void handleAction(final CalculatorAction action) {
switch (action.tag()) {
case AdditionCalculatorAction.TAG -> add();
case SubtractionCalculatorAction.TAG -> subtract();
case MultiplicationCalculatorAction.TAG -> multiply();
case DivisionCalculatorAction.TAG -> divide();
case SetVariableCalculatorAction.TAG -> {
SetVariableCalculatorAction setVariableAction =
(SetVariableCalculatorAction) action;
setVariable(setVariableAction.getVariable());
}
default -> {
}
}
}
/**
* Getter.
*
* @return current calculator model.
*/
public CalculatorModel getCalculatorModel() {
return model;
}
/**
* Set new calculator model variable.
*
* @param variable -> value of new calculator model variable.
*/
private void setVariable(final Double variable) {
model = new CalculatorModel(
variable,
model.getOutput()
);
}
/**
* Add variable to model output.
*/
private void add() {
model = new CalculatorModel(
model.getVariable(),
model.getOutput() + model.getVariable()
);
}
/**
* Subtract variable from model output.
*/
private void subtract() {
model = new CalculatorModel(
model.getVariable(),
model.getOutput() - model.getVariable()
);
}
/**
* Multiply model output by variable.
*/
private void multiply() {
model = new CalculatorModel(
model.getVariable(),
model.getOutput() * model.getVariable()
);
}
/**
* Divide model output by variable.
*/
private void divide() {
model = new CalculatorModel(
model.getVariable(),
model.getOutput() / model.getVariable()
);
}
}
Applicability
Use the Model-View-Intent pattern when
- You want to clearly separate the domain data from its user interface representation
- You want to minimise the public api of the view model
Known uses
A popular architecture pattern in android. The small public api is particularly powerful with the new Android Compose UI, as you can pass a single method (viewModel::handleEvent) to all Composables(parts of UI) as a callback for user input event.
Consequences
Pros:
- Encapsulation
- Separation of concerns
- Clear list of all possible user events
Cons:
- More boilerplate code compared to alternatives (especially in Java)
Related patterns
MVC:
