There are a lot of different architectures for the GUI: Model View Controller, Model View Presenter, Model View ViewModel, PresentationModel, …
All of them try more or less
-
to decouple the "different things" (widgets, forms, business logic, …) from each other
-
make the "different things" easily testable (unit-testable)
-
try to separate presentation from behaviour
-
make the View easily "exchangeable" (e.g. replace Swing with JavaFX)
-
concentrate the business logic into e.g. the "Model", which "lives longer" (in years) than the often changing GUI technologies
-
…
When you develop a Java Swing GUI in practice, you face the following additional challenges
-
how to enforce the proper threading (View elements like JPanel, JButton, … should be only accessed by AWT-EventDispatchThread)?
-
how to keep the View "responsive" by kicking off background actions (→ SwingWorker, new Thread(…), ExecutorService, …)?
-
how to combine the results from multiple asynchronous actions (multiple SwingWorker’s, Thread’s, Future’s, …) into one result for the View?
-
how to implement Undo/Redo, Validation, Exception-Handling, Timeouts, …?
-
how to enforce acyclic relationships between "stuff" to get good (isolated) testability?
-
how to deal with backpressure when e.g. the backend is too fast for the frontend and the GUI thread can’t keep up?
-
how to connect an ActionListener on e.g. a "Cancel" button with a just started SwingWorker and disconnect it after the SwingWorker finished?
-
…
So, which GUI architecture should you choose? What are the benefits and drawbacks in general? And for your current project? What libraries are available to make "quick progress" and not reinvent the wheel?
These are a lot of questions which are IMO not easily answered for a Java Swing GUI.
One day, I had a deep lock at MVVM and came up with the idea of
-
using RxJava's Subject objects as "listenable" value objects for the ViewModel
-
RxSwing to connect the View widgets (JButton, JTextField, …) to the ViewModel-Subjects as "data binding"
-
RxJava to react on changes of the ViewModel-Subjects and interact with the Model (backend) → fluent API for "flows" (aka streams, pipeline)
-
use RxJava’s Scheduler and the RxSwing SwingScheduler to do the threading
I started with some small examples for different aspects. The examples were really nice IMO and I’d like to share it with you through this github repo.
First, let’s look at the "data flow" in MVVM:
-
The View interacts with the ViewModel through DataBinding (this means in practise you don’t see any method calls, listeners etc in your sourcecode, you just see "binding" code)
-
The View doesn’t interact with the Model
-
The ViewModel interacts with the Model through ordinary Model API call’s
-
The Model API has some kind of callbacks to push data up to the ViewModel (dashed arrow)
Second, let’s look at the dependencies:
-
The View "sees" the ViewModel
-
The View "doesn’t see" the Model
-
The ViewModel "sees" the Model
-
The Model "doesn’t see" the ViewModel and "doesn’t see" the View
-
We have acyclic dependencies
-
Threading
-
All the action between View and ViewModel happens on the
SwingScheduler
-
All the action between ViewModel and Model happens "async" on
Schedulers.io()
(= cached thread pool)
-
-
View
-
DataBinding
-
We add a little "Binder" fluent API a la open-dolphin’s Binder on top of RxJava
-
-
There is no other code than a) DataBinding code and b) plain GUI structure code in the View (= passive View)
-
"Large" View’s are composed by combining multiple "small" View’s
-
-
ViewModel
-
Per View exits a ViewModel
-
The ViewModel represents the structure of the View 1:1
-
-
We use Rx Subject's as "fields" in the ViewModel
-
Each subject "field" in the ViewModel corresponds to one widget in the View
-
We use a little prefix for the "field" name like
v2vm_
orvm2v_
to indicate the flow direction -
We use the
BehaviorSubject
class for Subject instances, which is a stateful Subject to have the "current" state, an initial state and easy testability
-
-
The ViewModel handles all the "complicated" interaction stuff between View and Model (threading, exception handling, flows, …)
-
-
Model
-
The Model doesn’t care about it’s presentation and just offers an API
-
The Model is therefore fully focused on business logic and data
-
As you can see, there is no kind of "framework" described here to implement MVVM. Instead, it’s just the combination of standard JDK classes with the RxJava and RxSwing libraries, together with some additional fluent API code for "nice" DataBinding.
The examples start simple and get more and more complicated, adding additional aspects and features.
There is not a "full example" which shows all aspects at the moment, since this is just some code to figure out how to build MVVM using RxJava and RxSwing. Every example shows just one or more aspects.
Running the examples:
./gradlew run -Pexample=<example>, e.g.: ./gradlew run -Pexample=7a
Scope:
The current examples are all "everything in one process" examples: View, ViewModel and Model run in one process in the same JVM.
Upcoming examples might include JavaFx, Android, Web and of course some kind of remoting to split "things" across multiple processes.
-
The Model pushes "hello world’s" thru an Observable to the ViewModel (using a computational thread)
-
A JLabel in the View is bound to the
vm2v_info
field of the ViewModel -
The
RxViewModel2SwingViewBinder
code does the switch to theSwingScheduler
Tests:
-
A simple form submit of two textfields
-
The ViewModel combines the two textfield values into one DTO and calls the Model API on a IO-Thread
-
The
RxModelInvoker
code does the switch to theSchedulers.io()
scheduler
This example is the implementation of the initial idea:
Screencast with live coding:
https://www.youtube.com/watch?v=wjZ6xJkWD-U
Tests:
-
Same as Example 2
-
But: Submit button is only enabled, if both textfields contain a value
Tests:
-
Same as Example 3
-
But: The form is completely disabled during the submit processing time
Tests:
-
Same as Example 4
-
You can cancel the submit processing
-
The Model has a classic "blocking" API
Tests:
-
Same as Example 5
-
The Model has a "non-blocking" API, the ViewModel gets simpler
Tests:
-
Same as Example 5a
-
But with two Model API calls running in two different threads
-
Waiting for both of them
-
Cancellation for both of them
Tests:
-
Same as Example 6
-
But like in real world, there are sometimes exceptions during e.g. Model (backend) method calls
-
How to handle them?
This is of course a challenging task:
How to combine results from asynchronous tasks in general?
How to handle exceptions in those?
This is a typical problem which solves RxJava easily: It offers all the necessary API’s.
See e.g. https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators or of course the onError
in rx.Observable#subscribe(..)
.
Tests:
-
The Model publishes
LogRow
s (using a computational thread) -
These are added in the View as rows of a
JTable
(using GUI thread)
Tests:
-
Same as example 7
-
But: The Model’s log Observable sometimes fail
-
We add some exception handling into the ViewModel, to keep the View alive
Tests:
-
The Model uses a threadpool to create plenty of
LogRow
s (using as many threads as there are CPU cores) -
Since the View runs on a single thread, it can’t keep up with the pace
-
"Fast producer, slow consumer" kind of problem
-
-
We need to think about backpressure
-
We could slow down the
LogRow
production (blocking backpressure) -
Or we could drop the
LogRow
s which are "too much", keep up "full speed" and show only some of theLogRow
s -
The example uses dropping
-
Tests:
-
So far the views were very static
-
Now we have structural changes at runtime, think of a wizard with it’s steps
-
Parts of the views remain static (header, footer)
-
Therefore we need some kind of View composition
Tests:
-
View / ViewModel composition
-
How can on "outer" component communicate with "inner" components?
-
How to propagate the "edit" state to the inner components?
-
-
Tests:
Example 11: Composition of GUI’s and communication from inner ViewModel’s to outer ViewModel’s (dirty flag, Save Button)
-
View / ViewModel composition
-
How can on "inner" component communicate with an "outer" component?
-
"Dirty" flag of "inner" component enables the "Save" button of "outer" component
-
-
Tests: