Java_MVVM_with_Swing_and_RxJava_Examples icon indicating copy to clipboard operation
Java_MVVM_with_Swing_and_RxJava_Examples copied to clipboard

Explorative Java Swing GUI example code from 2016 with an implementation of MVVM (Model View ViewModel) using RxJava and RxSwing. DON'T use it in production, there are some open issues here!

= Java MVVM with Swing, RxJava and RxSwing examples Peti Koch :imagesdir: ./docs :project-name: Java_MVVM_with_Swing_and_RxJava_Examples :github-branch: master :github-user: Petikoch :bintray-user: petikoch

image:http://img.shields.io/badge/license-ASF2-blue.svg["Apache License 2", link="http://www.apache.org/licenses/LICENSE-2.0.txt"] image:https://travis-ci.org/{github-user}/{project-name}.svg?branch={github-branch}["Build Status", link="https://travis-ci.org/{github-user}/{project-name}"]

== Introduction

There are a lot of different architectures for the GUI: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller[Model View Controller], https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter[Model View Presenter], https://en.wikipedia.org/wiki/Model_View_ViewModel[Model View ViewModel], http://martinfowler.com/eaaDev/PresentationModel.html[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.

=== The idea

One day, I had a deep lock at https://en.wikipedia.org/wiki/Model_View_ViewModel[MVVM] and came up with the idea of

  • using https://github.com/ReactiveX/RxJava[RxJava]'s http://reactivex.io/documentation/subject.html[Subject] objects as "listenable" value objects for the ViewModel
  • https://github.com/ReactiveX/RxSwing[RxSwing] to connect the View widgets (JButton, JTextField, ...) to the ViewModel-Subjects as "data binding"
  • https://github.com/ReactiveX/RxJava[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 http://reactivex.io/documentation/scheduler.html[Scheduler] and the RxSwing https://github.com/ReactiveX/RxSwing/blob/0.x/src/main/java/rx/schedulers/SwingScheduler.java[SwingScheduler] to do the threading

image::Java_MVVM_RxJava_basic_idea.png[]

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.

== Implementing MVVM using RxJava and RxSwing

First, let's look at the "data flow" in MVVM:

image::MVVMPattern.png[]

  • 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:

image::MVVM_dependencies.png[]

  • 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

==== Basic ideas behind the implementation of the examples here

  • 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 https://github.com/canoo/open-dolphin/blob/master/subprojects/shared/src/main/groovy/org/opendolphin/binding/Binder.groovy[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 http://reactivex.io/documentation/subject.html[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_ or vm2v_ 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.

== Examples

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: +

Run the main class of each example in your favourite IDE

or

Use the gradle-wrapper on your favourite console

./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.

=== Example 1: Hello World (from the Model)

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example1[]

  • 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 the SwingScheduler

image::example1.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example1[]

=== Example 2: Form submit

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example2[]

  • 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 the Schedulers.io() scheduler

image::example2.png[]

This example is the implementation of the initial idea:

image::Java_MVVM_RxJava_basic_idea.png[]

Screencast with live coding: + https://www.youtube.com/watch?v=wjZ6xJkWD-U

image::example2_introduction_screencast.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example2[]

=== Example 3: Form submit with Submit Button enabling

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example3[]

  • Same as Example 2
  • But: Submit button is only enabled, if both textfields contain a value

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example3[]

=== Example 4: Form submit with form disabling / reenabling

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example4[]

  • Same as Example 3
  • But: The form is completely disabled during the submit processing time

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example4[]

=== Example 5: Form submit with cancellation and classic "blocking" Model API

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example5[]

  • Same as Example 4
  • You can cancel the submit processing
  • The Model has a classic "blocking" API

image::example5.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example5[]

=== Example 5a: Form submit with cancellation and "non-blocking" Model API

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example5a[]

  • Same as Example 5
  • The Model has a "non-blocking" API, the ViewModel gets simpler

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example5a[]

=== Example 6: Form submit with combining two asynchronous backend actions

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example6[]

  • 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

image::example6.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example6[]

=== Example 6a: Form submit with exceptions in Model (backend) calls

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example6a[]

  • 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(..).

image::example6a.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example6a[]

=== Example 7: Log table with LogRow's pushed up from the Model

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example7[]

  • The Model publishes LogRow s (using a computational thread)
  • These are added in the View as rows of a JTable (using GUI thread)

image::example7.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example7[]

=== Example 7a: Log table with LogRow's pushed up from the Model and exception handling

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example7a[]

  • 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

image::example7a.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example7a[]

=== Example 8: Log table with LogRow's pushed up from the Model and dealing with backpressure

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example8[]

  • 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 the LogRow s ** The example uses dropping

image::example8.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example8[]

=== Example 9: Structural changes at runtime in the View (and GUI composition)

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example9[]

  • 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

image::example9.png[]

image::example9_2.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example9[]

=== Example 10: Composition of GUI's and communication from outer ViewModel's to inner ViewModel's

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example10[]

  • View / ViewModel composition ** How can on "outer" component communicate with "inner" components? *** How to propagate the "edit" state to the inner components?

image::example10.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example10[]

=== Example 11: Composition of GUI's and communication from inner ViewModel's to outer ViewModel's (dirty flag, Save Button)

link:./src/main/java/ch/petikoch/examples/mvvm_rxjava/example11[]

  • 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

image::example11.png[]

Tests:

link:./src/test/groovy/ch/petikoch/examples/mvvm_rxjava/example11[]

== Requirements

  • Java 8 or later

== Feedback

Please use GitHub issues and pull requests for feedback or contributions.

== What's next?

These examples do only answer some of the inital questions. It's work in progress. Feel free to get in touch with me, give feedback, contribute some more examples... :-)

Best regards,

image::Signature.jpg[]