Close
Back

Reactive functional UI development with Vaadin

Introduction

Event-based callback methods provide a clear and asynchronous way to catch interactions and attach logic to them, executed whenever a suitable event enters the system. This is especially useful in user interface APIs where event callbacks make it straightforward to respond to user interaction.

This approach has its drawbacks, however. The event handlers are separate methods from each other, possibly even living in different classes or files. Unless the developer is careful, program logic that is implemented by wrangling can be hard to follow. It’s tedious and error prone to implement, too. It can even lead to the so-called “callback hell” of deeply nested callback structures.

Functional reactive programming (FRP) is technique that can help.

What is functional reactive programming?

FRP is a way to take the modelling of program logic to a higher level of abstraction by representing values over time as a first class concept. In this document, we’ll call these time-varying values observables. They can be thought as “push” counterparts to iterables, which are “pull”.

Observables are the basic building block of FRP. They are inherently composable, which means that they can be combined into further new observables, and that they support functional operations like filtering, mapping and so on. These features allow the developer to work with observables without resorting to loops.

These crucial properties give a rise to an elegant way to express program logic as combinations of observables. Program results can then be generated by connecting the outputs to the observables created by the program. This lets the developer to concentrate on what the program logic should do, instead of how it should do it.

Sounds pretty abstract. Are there any practical uses?

Yes! In fact, there is already an FRP-like tool that you probably very familiar with: the spreadsheet.

A cell in a spreadsheet can be thought of as a value that changes over time, and this is the essence of what an observable is. What’s more, the formula in a cell is the embodiment of a composition operation that uses the values of other cells (observables) as input and generates a new value based on the current values of those other cells.

This demonstrates the fundamental difference to the event-based approach: You don’t add event listeners to every cell that might change and write logic to update the values of one or more other cells. Instead, you just add formulas to the target cells, and let the system worry about propagating the changes – “what” instead of “how”.

It should also be noted that the formulas do not care if their input cell values are given by the user or generated by another formula. This property makes a spreadsheet extremely composable.

Rx - Reactive Extensions

There exist several implementations and variants of the concept for various platforms. One of the major ones is Rx, a Microsoft Open Technologies project that implements observables on the .NET platform. RxJava is a Java implementation of the Rx observables by Netflix. You can find out more about how and why Netflix uses the reactive approach in this blog post.

Vaadin + FRP = RxVaadin

RxVaadin is a thin and currently very incomplete wrapper around Vaadin that uses RxJava for two things:

  • Convert a Vaadin ValueChangeNotifier into an Observable.
  • Connect an Observable into a Vaadin Property so that every time the Observable emits a value it’s set into the Property.

I wrote a simple application to demonstrate its capabilities. The demo calculates a fictitious score for a software development team. It takes in values for a few properties of the team, and comes up with a numeric score and a traffic light indicator based on the inputs. The user interface looks like this:

RxVaadin demo screenshot
Thanks to Marlon Richert for help with the look and feel.

The user interface logic is quite simple. First the inputs are validated. They are valid if the user has given values for test coverage, team size and project manager technicality. Moreover, the value in the team size text field must be an integer.

  • If the inputs are not valid:
    • The color of the traffic light indicator is grey.
    • No score is displayed below the indicator.
    • An instructive text is shown below the input components. It should list the names of the inputs that contain an invalid value.
  • If the inputs are valid:
    • A traffic light color is displayed in the analysis panel. The indicator color for a valid input is based on the team score (see below), and it may be green, yellow or red.
    • A numeric score is displayed below the traffic light indicator. The score is calculated based on the inputs, and is between 0-100.
    • No instructive text is shown under the inputs.

Setting up the user interface is basic Vaadin code that just creates the components and places them into the view. It is not very interesting, so let’s concentrate on the presenter.

Handling inputs

The presenter is responsible for converting the input components into Observables that provide the values given by the user.

// Get the value streams corresponding to the events
// generated by the input components. We use valuesWithDefault
// so that each Observable will include the given default
// value immediately.
Observable<String> coverages =
    RxVaadin.valuesWithDefault(ui.ogTestCoverage, null);

Observable<Set<String>> agilities = 
    RxVaadin.valuesWithDefault(ui.lsAgility, null);

Observable<String> teamSizeStrings = 
    RxVaadin.valuesWithDefault(ui.tfTeamSize, null);

Observable<String> PMTechnicalities = 
    RxVaadin.valuesWithDefault(ui.cbPMTechnicality, null);

Observable<Boolean> vaadinUses =
    RxVaadin.valuesWithDefault(ui.chkVaadin, false);

As you can see, it’s easy to get the value stream from a Vaadin component as an Observable. Generics help us nicely here as well, so that the values correspond to the type the UI component is providing.

Next we will let our business logic combine the individual input Observables into two further Observables that represent valid scores and invalid input names, respectively.

// Get a stream of scores that are calculated for each valid
// input combination
Observable<Double> scores = DevScoreLogic.scores(coverages,
    agilities, teamSizeStrings, PMTechnicalities, vaadinUses);

// Get a stream of lists that contain the names of inputs 
// that have an invalid or missing value
Observable<List<String>> faults = DevScoreLogic.faults(coverages,
    agilities, teamSizeStrings, PMTechnicalities, vaadinUses);

Again, quite straightforward.

The last task for the presenter is to hook up scores and faults into our output components. RxVaadin provides a simple way to do this. For example, here is the code to drive the score label.

private static void setupScoreLabelLogic(Label lblScore,
    Observable<Double> scores, Observable<List<String>> faults) {
    // Map the scores to strings through a simple formatter
    Observable<String> scoreStrings = scores.map(score -> {
        return String.format("%.2f", score);
    });

    // Hook the score label to the score string stream
    RxVaadin.follow(lblScore, scoreStrings);

    // Make a fault clear the score label
    RxVaadin.clearBy(lblScore, faults);
}

First we convert scores into a stream of formatted String representations of the numeric scores. A simple Java 8 lambda and the map operation in Observable makes short work of this. Then we just use RxVaadin to connect the score strings to the score label. Furthermore, we’ll also make every value coming from faults clear the score label, like the spec said.

Three statements (four if you count the one-line lambda), and virtually no room for bugs - not bad! The methods that set up the traffic light indicator and fault label are similarly simple.

That is the entirety of the presenter layer for the application. Let’s look at the business logic next.

Business logic

Observables completely decouple our business logic from the UI code, and the public API consists of only two functions. The first converts the input Observables into a new Observable that contains numeric scores represented by the valid inputs. It does the following:

  1. Combine the latest values of the five input Observables into a single Observable that provides instances of Input, which is an internal class representing a set of the input values.
  2. Filter this Observable to contain only valid inputs
  3. Return a new Observable that is created by mapping the Input instances into numeric scores

Using first class methods available in Java 8 makes the implementation straightforward:

public static Observable<Double> scores(Observable<String> coverages,
    Observable<Set<String>> agilities, Observable<String> teamSizeStrings,
    Observable<String> pMTechnicalities, Observable<Boolean> vaadinUses) {

    Observable<Inputs> inputs = Observable.combineLatest(coverages,
        agilities, teamSizeStrings, pMTechnicalities, vaadinUses,
        DevScoreLogic::combineInputs);

    return inputs.filter(DevScoreLogic::areInputsValid).map(
        DevScoreLogic::inputsToScore);
}

The function that gets the faulty field names is equally simple.

Advantages: stateless, loopless and extremely lazy

It should be noted that all Observers presented so far are completely lazy. None of them actually process anything before the first input combination enters the system. The code above just sets up the Observers and they will start doing their thing when the UI provides them with values. No imperative supervision code is needed.

Moreover, both the presenter and business logic APIs are static. There is no superfluous state that the developer needs to manage.

And third, there are exactly zero loops in the whole program. Iterating collections using loops is tedious and error prone, and FRP allows us to do away with them completely.

What’s next?

RxVaadin is available as an add-on in the Vaadin Directory. I recommend Maven or Ivy to get it with.

There is a discussion thread in the Vaadin forums, and you can check out the project in Github, if you want to play with it. Pull requests are welcome.

Note that RxVaadin is one of my weekend projects and not an official product of Vaadin Ltd.

Henri MuurimaaHenri Muurimaa runs the Vaadin services as his day job, and by night he hacks away with Scaladin, Vaadin, GWT and other related technologies. You can follow him on Twitter - @henrimuurimaa

Comments
Trackback URL: