Skip to content

Latest commit

 

History

History
351 lines (219 loc) · 13.3 KB

File metadata and controls

351 lines (219 loc) · 13.3 KB

Java MVVM with Swing, RxJava and RxSwing examples

Apache License 2 Build Status

Introduction

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.

The idea

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

Java MVVM RxJava basic idea

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:

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

MVVM 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

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

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

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

  • 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

example1

Tests:

Example 2: Form submit

  • 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

example2

This example is the implementation of the initial idea:

Java MVVM RxJava basic idea

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

example2 introduction screencast

Tests:

Example 3: Form submit with Submit Button enabling

  • Same as Example 2

  • But: Submit button is only enabled, if both textfields contain a value

Tests:

Example 4: Form submit with form disabling / reenabling

  • Same as Example 3

  • But: The form is completely disabled during the submit processing time

Tests:

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

  • Same as Example 4

  • You can cancel the submit processing

  • The Model has a classic "blocking" API

example5

Tests:

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

  • Same as Example 5

  • The Model has a "non-blocking" API, the ViewModel gets simpler

Tests:

Example 6: Form submit with combining two asynchronous backend actions

  • 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

example6

Tests:

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

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

example6a

Tests:

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

  • The Model publishes LogRow s (using a computational thread)

  • These are added in the View as rows of a JTable (using GUI thread)

example7

Tests:

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

  • 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

example7a

Tests:

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

  • 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

example8

Tests:

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

  • 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

example9
example9 2

Tests:

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

  • View / ViewModel composition

    • How can on "outer" component communicate with "inner" components?

      • How to propagate the "edit" state to the inner components?

example10

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

example11

Tests:

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,

Signature