Skip to content

Commit fb86ca1

Browse files
authored
feature: iluwatar#2151 Feature/mvi pattern (iluwatar#2177)
* iluwatar#2151 add module and pom.xml * iluwatar#2151 add Calculator Actions * iluwatar#2151 add remaining mvi pattern classes (actions already implemented) * iluwatar#2151 add Main and unit tests * add README.md and class diagrams * iluwatar#2151 add module and pom.xml * iluwatar#2151 add Calculator Actions * iluwatar#2151 add remaining mvi pattern classes (actions already implemented) * iluwatar#2151 add Main and unit tests * add README.md and class diagrams * fixes for lint errors * iluwatar#2151 add module and pom.xml * iluwatar#2151 add Calculator Actions * iluwatar#2151 add remaining mvi pattern classes (actions already implemented) * iluwatar#2151 add Main and unit tests * add README.md and class diagrams * fixes for lint errors * use Lombok @DaTa decorator and decouple View from ViewModel * add comments and documentation * fix checkstyle, the smart switch syntax was breaking checkstyle, so I had to change it back
1 parent 6b0a90e commit fb86ca1

19 files changed

+943
-0
lines changed

model-view-intent/README.md

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
---
2+
title: Model-View-Controller
3+
category: Architectural
4+
language: en
5+
tags:
6+
- Decoupling
7+
- Encapsulation
8+
---
9+
10+
## Intent
11+
MVI is a derivation of the original MVC architectural pattern. Instead of working with a
12+
proactive controller MVI works with the reactive component called intent: it's a component
13+
which translates user input events into model updates.
14+
15+
## Explanation
16+
17+
> MVI is a Reactive Architecture Pattern which is short for Model -View-Intent.
18+
It introduces two new concepts: the intent and the state.
19+
UI might have different states — Loading State, Fetch Data State, Error State,
20+
and user events are submitted in the form of an Intent.
21+
22+
* [Stateful Android Apps With MVI (MODEL — VIEW — INTENT)](https://medium.com/huawei-developers/stateful-android-apps-with-mvi-architecture-model-view-intent-d106b09bd967)
23+
24+
## Class diagram
25+
![alt text](./etc/model-view-intent.png "Model-View-Intent")
26+
27+
**Programmatic Example**
28+
29+
CalculatorAction defines our Intent in MVI for user interactions. It has to be an interface
30+
instead of enum, so that we can pass parameters to certain children.
31+
```java
32+
public interface CalculatorAction {
33+
34+
/**
35+
* Makes identifying action trivial.
36+
*
37+
* @return subclass tag.
38+
* */
39+
String tag();
40+
}
41+
```
42+
43+
CalculatorModel defines the state of our view or in out case, variable and output of the calculator.
44+
```java
45+
@Data
46+
public class CalculatorModel {
47+
48+
/**
49+
* Current calculator variable used for operations.
50+
**/
51+
final Double variable;
52+
53+
/**
54+
* Current calculator output -> is affected by operations.
55+
**/
56+
final Double output;
57+
}
58+
```
59+
60+
61+
CalculatorView will serve as a mock view which will expose potential user actions and
62+
display calculator state -> output and current variable
63+
```java
64+
@Slf4j
65+
public class CalculatorView {
66+
67+
/**
68+
* View model param handling the operations.
69+
* */
70+
private final CalculatorViewModel viewModel = new CalculatorViewModel();
71+
72+
/**
73+
* Display current view model output with logger.
74+
* */
75+
void displayTotal() {
76+
LOGGER.info(
77+
"Total value = {}",
78+
viewModel.getCalculatorModel().getOutput().toString()
79+
);
80+
}
81+
82+
/**
83+
* Handle addition action.
84+
* */
85+
void add() {
86+
viewModel.handleAction(new AdditionCalculatorAction());
87+
}
88+
89+
/**
90+
* Handle subtraction action.
91+
* */
92+
void subtract() {
93+
viewModel.handleAction(new SubtractionCalculatorAction());
94+
}
95+
96+
/**
97+
* Handle multiplication action.
98+
* */
99+
void multiply() {
100+
viewModel.handleAction(new MultiplicationCalculatorAction());
101+
}
102+
103+
/**
104+
* Handle division action.
105+
* */
106+
void divide() {
107+
viewModel.handleAction(new DivisionCalculatorAction());
108+
}
109+
110+
/**
111+
* Handle setting new variable action.
112+
*
113+
* @param value -> new calculator variable.
114+
* */
115+
void setVariable(final Double value) {
116+
viewModel.handleAction(new SetVariableCalculatorAction(value));
117+
}
118+
}
119+
```
120+
121+
Finally, ViewModel handles the exposed events with the handleAction(event) method, which delegates
122+
the specific handling to private methods. Initially calculator output and variable are equal to 0.
123+
```java
124+
public final class CalculatorViewModel {
125+
126+
/**
127+
* Current calculator model (can be changed).
128+
*/
129+
private CalculatorModel model =
130+
new CalculatorModel(0.0, 0.0);
131+
132+
/**
133+
* Handle calculator action.
134+
*
135+
* @param action -> transforms calculator model.
136+
*/
137+
void handleAction(final CalculatorAction action) {
138+
switch (action.tag()) {
139+
case AdditionCalculatorAction.TAG -> add();
140+
case SubtractionCalculatorAction.TAG -> subtract();
141+
case MultiplicationCalculatorAction.TAG -> multiply();
142+
case DivisionCalculatorAction.TAG -> divide();
143+
case SetVariableCalculatorAction.TAG -> {
144+
SetVariableCalculatorAction setVariableAction =
145+
(SetVariableCalculatorAction) action;
146+
setVariable(setVariableAction.getVariable());
147+
}
148+
default -> {
149+
}
150+
}
151+
}
152+
153+
/**
154+
* Getter.
155+
*
156+
* @return current calculator model.
157+
*/
158+
public CalculatorModel getCalculatorModel() {
159+
return model;
160+
}
161+
162+
/**
163+
* Set new calculator model variable.
164+
*
165+
* @param variable -> value of new calculator model variable.
166+
*/
167+
private void setVariable(final Double variable) {
168+
model = new CalculatorModel(
169+
variable,
170+
model.getOutput()
171+
);
172+
}
173+
174+
/**
175+
* Add variable to model output.
176+
*/
177+
private void add() {
178+
model = new CalculatorModel(
179+
model.getVariable(),
180+
model.getOutput() + model.getVariable()
181+
);
182+
}
183+
184+
/**
185+
* Subtract variable from model output.
186+
*/
187+
private void subtract() {
188+
model = new CalculatorModel(
189+
model.getVariable(),
190+
model.getOutput() - model.getVariable()
191+
);
192+
}
193+
194+
/**
195+
* Multiply model output by variable.
196+
*/
197+
private void multiply() {
198+
model = new CalculatorModel(
199+
model.getVariable(),
200+
model.getOutput() * model.getVariable()
201+
);
202+
}
203+
204+
/**
205+
* Divide model output by variable.
206+
*/
207+
private void divide() {
208+
model = new CalculatorModel(
209+
model.getVariable(),
210+
model.getOutput() / model.getVariable()
211+
);
212+
}
213+
}
214+
```
215+
216+
## Applicability
217+
Use the Model-View-Intent pattern when
218+
219+
* You want to clearly separate the domain data from its user interface representation
220+
* You want to minimise the public api of the view model
221+
222+
## Known uses
223+
A popular architecture pattern in android. The small public api is particularly powerful
224+
with the new Android Compose UI, as you can pass a single method (viewModel::handleEvent)
225+
to all Composables(parts of UI) as a callback for user input event.
226+
227+
## Consequences
228+
Pros:
229+
* Encapsulation
230+
* Separation of concerns
231+
* Clear list of all possible user events
232+
233+
Cons:
234+
* More boilerplate code compared to alternatives (especially in Java)
235+
236+
## Related patterns
237+
MVC:
238+
* [Trygve Reenskaug - Model-view-controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
239+
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
240+
241+
## Credits
242+
243+
* [Model View Intent: a new Android Architecture Pattern](https://apiumacademy.com/blog/model-view-intent-pattern/)
244+
* [MVI Architecture for Android Tutorial](https://www.kodeco.com/817602-mvi-architecture-for-android-tutorial-getting-started)
245+
87.2 KB
Loading
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@startuml
2+
package com.iluwatar.model.view.intent.actions {
3+
class AdditionCalculatorAction {
4+
+ TAG : String {static}
5+
+ AdditionCalculatorAction()
6+
+ tag() : String
7+
}
8+
interface CalculatorAction {
9+
+ tag() : String {abstract}
10+
}
11+
class DivisionCalculatorAction {
12+
+ TAG : String {static}
13+
+ DivisionCalculatorAction()
14+
+ tag() : String
15+
}
16+
class MultiplicationCalculatorAction {
17+
+ TAG : String {static}
18+
+ MultiplicationCalculatorAction()
19+
+ tag() : String
20+
}
21+
class SetVariableCalculatorAction {
22+
+ TAG : String {static}
23+
+ variable : Double
24+
+ SetVariableCalculatorAction(variable : Double)
25+
+ tag() : String
26+
}
27+
class SubtractionCalculatorAction {
28+
+ TAG : String {static}
29+
+ SubtractionCalculatorAction()
30+
+ tag() : String
31+
}
32+
}
33+
package com.iluwatar.model.view.intent {
34+
class App {
35+
+ App()
36+
+ main(args : String[]) {static}
37+
}
38+
class CalculatorModel {
39+
+ output : Double
40+
+ variable : Double
41+
+ CalculatorModel(output : Double, variable : Double)
42+
+ copy(output : Double, variable : Double) : CalculatorModel
43+
}
44+
class CalculatorView {
45+
- LOGGER : Logger {static}
46+
~ viewModel : CalculatorViewModel
47+
+ CalculatorView()
48+
~ add()
49+
~ displayTotal()
50+
~ divide()
51+
~ multiply()
52+
~ setVariable(value : Double)
53+
~ subtract()
54+
}
55+
class CalculatorViewModel {
56+
- model : CalculatorModel
57+
+ CalculatorViewModel()
58+
- add()
59+
- divide()
60+
+ getCalculatorModel() : CalculatorModel
61+
~ handleAction(action : CalculatorAction)
62+
- multiply()
63+
- setVariable(variable : Double)
64+
- subtract()
65+
}
66+
}
67+
CalculatorView --> "-viewModel" CalculatorViewModel
68+
CalculatorViewModel --> "-model" CalculatorModel
69+
AdditionCalculatorAction ..|> CalculatorAction
70+
DivisionCalculatorAction ..|> CalculatorAction
71+
MultiplicationCalculatorAction ..|> CalculatorAction
72+
SetVariableCalculatorAction ..|> CalculatorAction
73+
SubtractionCalculatorAction ..|> CalculatorAction
74+
@enduml

model-view-intent/pom.xml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
29+
<modelVersion>4.0.0</modelVersion>
30+
<parent>
31+
<groupId>com.iluwatar</groupId>
32+
<artifactId>java-design-patterns</artifactId>
33+
<version>1.26.0-SNAPSHOT</version>
34+
</parent>
35+
<artifactId>model-view-intent</artifactId>
36+
<dependencies>
37+
<dependency>
38+
<groupId>org.junit.jupiter</groupId>
39+
<artifactId>junit-jupiter-engine</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.mockito</groupId>
44+
<artifactId>mockito-core</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
<build>
49+
<plugins>
50+
<plugin>
51+
<groupId>org.apache.maven.plugins</groupId>
52+
<artifactId>maven-assembly-plugin</artifactId>
53+
<executions>
54+
<execution>
55+
<configuration>
56+
<archive>
57+
<manifest>
58+
<mainClass>com.iluwatar.model.view.controller.App</mainClass>
59+
</manifest>
60+
</archive>
61+
</configuration>
62+
</execution>
63+
</executions>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
</project>

0 commit comments

Comments
 (0)