How to create a real-time JavaFX application


At Caplin we spend a lot of time talking about the real-time web and more specifically the growing trend for real time rich internet applications, or RTRIAs.

One of the key advantages of our Caplin Xaqua offering is that it allows our customers to stream live data into a variety of client applications, avoiding technology lock in. For clients who are developing a real-time browser application we provide JavaScript, Silverlight and Flex versions of our StreamLink library. There is, however, one RIA technology missing from that list – JavaFX.

JavaFX is a relatively young product and as yet we haven’t seen much interest in it expressed by our clients, certainly not enough to justify developing a full StreamLink for JavaFX API. However one of the strengths of JavaFX is that it integrates seamlessly with standard Java applications. Since we already provide a mature StreamLink for Java API I decided to see how easy it would be to create a JavaFX RTRIA powered by Caplin Xaqua.

In this blog post I’m going to demonstrate a simple approach to linking up a JavaFX application to Liberator via StreamLink for Java (SL4J) and receiving data. I won’t go into the UI besides the minimum required to get something on the screen – in other words, I’ll be covering the plumbing, not the pretty stuff.

A full code listing is provided at the end.

Setup

Caplin components you will need:

  • Liberator
  • A DataSource to connect to Liberator (the Java DemoSource provided with Liberator is ideal)
  • StreamLink for Java

Most JavaFX developers use NetBeans as their IDE of choice. However I decided to make life difficult for myself by opting for Eclipse, since I’m more familiar with it. The first thing to do is install the JavaFX SDK and Eclipse 3.4 or later – then you can install the JavaFX plugin from within Eclipse. All the instructions are here.

Once Eclipse is set up, create a JavaFX project (I called mine SL4JApp) and add a source package with a new empty JavaFX script inside. You will also need to add a folder called lib and drop the SL4J JAR files into it. The last thing is to go into the project properties and add those jars to the build path – make sure you use the “Add External JARs…” button because “Add JARs…” did not work for me.

You should end up with this directory structure:

DirectoryStructure

Adding the UI

To test that all the relevant classes are available on the build path, let’s open up the empty JavaFX script file (Main.fx) and add in all the imports needed for this project. If the compiler doesn’t complain then you have set things up correctly.

The base element for a JavaFX UI is Stage. It’s easy to add one – the Eclipse plugin provides a Snippets view that you can use to just drag and drop various elements directly into the file. Once you drag in a Stage you should end up with something like this:

package sl4japp;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.Group;
import java.util.Properties;
import java.util.Hashtable;
import java.text.DecimalFormat;
import com.rttp.rtjl.subscriber.RTTPSubscriberAdapter;
import com.rttp.rtjl.subscriber.RTTPSubscriber;
import com.rttp.rtjl.BasicRTJLProvider;
import com.rttp.rtjl.RTTPTargetCreator;

var myStage = Stage
{
    title : "StreamingApp";
    scene: Scene
    {
        width: 200
        height: 100
        content: []
    }
}

Since we’re keeping things simple the UI will just consist of a text element that contains the value of a field, which will change in real time as Liberator sends updates for it. For a bit of visual excitement I would also like the text to change colour (you can tell I’m not a front end developer) – green if the new value is higher than the old value, red if the new value is lower. The field I’m going to use is Tier1Ask, which exists on the Foreign Exchange objects provided by the DemoSource.

We will add some variables to hold the field’s current value and colour, and we also add a Text element to the stage. The content and colour of the Text element are bound to the value of the variables. I’ve also added a formatter to make things look a bit nicer.

def fiveDPFormat : DecimalFormat = new DecimalFormat("0.00000");
var ask : Double = 0;
var askColor : Color = Color.GREEN;

var myStage = Stage
{
    title : "StreamingApp"
    scene: Scene
    {
        width: 200
        height: 100
        content:
        [
        	Text
	        {
	            font: Font
	            {
	                size: 14
	            }
	            x: 10
	            y: 30
	            content: bind fiveDPFormat.format(ask)
	            fill: bind askColor
	        }
    	]
    }
}

You can run that if you like. It will pop up a small window containing the string “0.00000″ in green. Wow!

Connecting to Liberator

Now we will add a bridge class that encapsulates all the work of connecting to Liberator using SL4J. First we need to create some properties in order to pass them into a new instance of the BasicRTJLProvider class. For the purpose of this basic example I’ve hard-coded the properties and I’m loading them in a postinit block, which as far as I can tell is the JavaFX equivalent of a constructor. If we were writing a proper application you would obviously pass those property values in when you create the class, or when you log in.

Talking of logging in, we also add a login() method. This class is now ready to be used:

public class SL4JSubscriber
{
    var provider : BasicRTJLProvider;
    var props 	 : Properties;

	postinit
	{
        props = new Properties();
	    props.setProperty("rttp.login.name", "admin");
	    props.setProperty("rttp.login.password", "password");
	    props.setProperty("rttp.http.enable", "yes");
	    props.setProperty("rttp.http.host", "myhost.mydomain.com");
	    props.setProperty("rttp.http.port", "8080");
	}

	function login()
	{
	    if(provider == null)
	    {
		     provider = new BasicRTJLProvider(props);
		     provider.getRTTPInterfaces().getRTTPReadWrite();
	    }
	}
}

In order to make something happen we will add a run function outside the class, which is the JavaFX equivalent of main. In that method we will create a new instance of the bridge class and log in.

function run(__ARGS__ : String[])
{
	var subscriber = new SL4JSubscriber();
	subscriber.login();
}

If you run the program then the the 0.00000 we saw earlier will be unchanged, but crucially you should see some SL4J log output in the Eclipse console window that indicates you have logged in successfully:

Console

Requesting Data

Now we need to extend the bridge class so that it can subscribe to objects. SL4J provides an interface RttpSubscriber that clients are required to implement in order to receive callbacks when you receive data. It is quite a big interface and we’re not interested in most of the methods, but luckily an RttpSubscriberAdapter class is also provided which stubs out all of the methods in the interface.

We can make our bridge class extend RTTPSubscriberAdapter and just override the method we’re interested in, adding code that will set the value of the ask variable and the colour. Since the content of the Text UI element is bound to that variable, this should be all we need to get updates on to the screen.

public class SL4JSubscriber extends RTTPSubscriberAdapter
{
    var provider : BasicRTJLProvider;
    var props 	 : Properties;

	postinit
	{
	    props = new Properties();
	    props.setProperty("rttp.login.name", "admin");
	    props.setProperty("rttp.login.password", "admin");
	    props.setProperty("rttp.http.enable", "yes");
	    props.setProperty("rttp.http.host", "integrationlinux1");
	    props.setProperty("rttp.http.port", "50180");
	}

	function login()
	{
	    if(provider == null)
	    {
		     provider = new BasicRTJLProvider(props);
		     provider.getRTTPInterfaces().getRTTPReadWrite();
	    }
	}

	override function recordUpdated(name, values:Hashtable, cached)
	{
		FX.deferAction(function(): Void
		{
		    var newVal = Double.parseDouble(values.get("Tier1Ask").toString());

			if(newVal >= ask) {
				askColor = Color.GREEN; }
			else {
				askColor = Color.RED; }

			ask = newVal;
		});
	}
}

This is a good example of the seamless interoperability of JavaFX and Java – the RTTPSubscriberAdapter class is a compiled Java class within a library JAR, but our JavaFX class can extend it without any problems.

The FX.deferAction is important because the recordUpdated() method is called by one of the SL4J threads. If you try to update the UI on this thread then your application will lock up. FX.deferAction just defers the update for when the current event is terminated and the JavaFX UI thread regains control. It’s a lot like SwingUtilities.invokeLater().

Lastly, we add a request() method that will create a subscription to an object and request it from Liberator.

public class SL4JSubscriber extends RTTPSubscriberAdapter
{
    var provider : BasicRTJLProvider;
    var props 	 : Properties;

	postinit
	{
	    props = new Properties();
	    props.setProperty("rttp.login.name", "admin");
	    props.setProperty("rttp.login.password", "admin");
	    props.setProperty("rttp.http.enable", "yes");
	    props.setProperty("rttp.http.host", "integrationlinux1");
	    props.setProperty("rttp.http.port", "50180");
	}

	function login()
	{
	    if(provider == null)
	    {
		     provider = new BasicRTJLProvider(props);
		     provider.getRTTPInterfaces().getRTTPReadWrite();
	    }
	}

	function request(name : String)
	{
	    var target = RTTPTargetCreator.get(name);
	   	provider.setRTTPSubscriber(this);
	    provider.getRTTPInterfaces().getRTTPReadWrite().request(target);
	}

	override function recordUpdated(name, values:Hashtable, cached)
	{
		FX.deferAction(function(): Void
		{
		    var newVal = Double.parseDouble(values.get("Tier1Ask").toString());

			if(newVal >= ask) {
				askColor = Color.GREEN; }
			else {
				askColor = Color.RED; }

			ask = newVal;
		});
	}
}

Let’s add a line to the main method to make it use the request functionality:

function run(__ARGS__ : String[])
{
	var subscriber = new SL4JSubscriber();
	subscriber.login();
	subscriber.request("/FX/EURGBP");
}

That’s it – make sure that the Liberator and DataSource are running and that you’re using a DataSource that provides the /FX/EURGBP object with a field called Tier1Ask. When you run the application you will see real time streaming updates, like this:

streaming

What Now

This is obviously just a quick example and there is plenty of room for improvement. The architecture has a separation of concerns problem because the bridge class, which should just encapsulate connectivity to Liberator, also updates the UI in an application-specific way. It would make more sense to register a JavaFX update listener on the bridge class.

I’d like to spend more time on the UI – it should be easy to use the JavaFX charting API to create a line chart of FX prices that updates in real time, similar to this example. You could also display your live updates in a grid.

If you’re thinking about creating an RTRIA with a JavaFX front end and want to power it with Caplin technology, why not let us know?

Full Code Listing

package sl4japp;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.Group;
import java.util.Properties;
import java.util.Hashtable;
import java.text.DecimalFormat;
import com.rttp.rtjl.subscriber.RTTPSubscriberAdapter;
import com.rttp.rtjl.subscriber.RTTPSubscriber;
import com.rttp.rtjl.BasicRTJLProvider;
import com.rttp.rtjl.RTTPTargetCreator;

def fiveDPFormat : DecimalFormat = new DecimalFormat("0.00000");
var ask : Double = 0;
var askColor : Color = Color.GREEN;

var myStage = Stage
{
    title : "StreamingApp"
    scene: Scene
    {
        width: 200
        height: 100
        content:
        [
        	Text
	        {
	            font: Font
	            {
	                size: 14
	            }
	            x: 10
	            y: 30
	            content: bind fiveDPFormat.format(ask)
	            fill: bind askColor
	        }
    	]
    }
}

function run(__ARGS__ : String[])
{
	var subscriber = new SL4JSubscriber();
	subscriber.login();
	subscriber.request("/FX/EURGBP");
}

public class SL4JSubscriber extends RTTPSubscriberAdapter
{
    var provider : BasicRTJLProvider;
    var props 	 : Properties;

	postinit
	{
	    props = new Properties();
	    props.setProperty("rttp.login.name", "admin");
	    props.setProperty("rttp.login.password", "admin");
	    props.setProperty("rttp.http.enable", "yes");
	    props.setProperty("rttp.http.host", "myliberatorhost.mydomain.com");
	    props.setProperty("rttp.http.port", "8080");
	}

	function login()
	{
	    if(provider == null)
	    {
		     provider = new BasicRTJLProvider(props);
		     provider.getRTTPInterfaces().getRTTPReadWrite();
	    }
	}

	function request(name : String)
	{
	    var target = RTTPTargetCreator.get(name);
	   	provider.setRTTPSubscriber(this);
	    provider.getRTTPInterfaces().getRTTPReadWrite().request(target);
	}

	override function recordUpdated(name, values:Hashtable, cached)
	{
		FX.deferAction(function(): Void
		{
		    var newVal = Double.parseDouble(values.get("Tier1Ask").toString());

			if(newVal >= ask) {
				askColor = Color.GREEN; }
			else {
				askColor = Color.RED; }

			ask = newVal;
		});
	}
}
Related Posts with Thumbnails

One Comment

Leave a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Anti-spam image