What's New In JavaFX 1.2 Technology: RSS, Storage, and Charts

   
By Chris Wright and James L. (Jim) Weaver, November 2009  

The first article in this series, What's New In JavaFX 1.2 Technology: New Layouts and Effects, introduced you to new layout classes such as ClipView, Flow, and Stack, and demonstrated how to use these classes within your applications.

Unlike the many articles that concentrate on graphical user interface (GUI) features and application design in JavaFX technology, this article and the next will provide insight into the more technical features such as RSS and Atom tasks, local storage using JavaFX's built-in storage classes, and the use of JavaFX charts.

Contents
 
The RSSTask Class
Adding the User Interface (UI)
Local Storage
Conclusion
For More Information
 

The RSSTask Class

To provide a basic introduction to RSS in JavaFX technology, the example application created in this article, StockReader, will use the RSSTask class to get current prices of stock symbols that the user provides. There will be 3 different versions of the StockReader application as NetBeans Projects, and each will demonstrate new features as the application becomes more robust. When the user closs the application, it will store the stock symbols that are in use by using the built-in Storage classes. This article and the next will cover the use of RSS and Atom tasks in much more detail, as well as the use of JavaFX charts and graphs to visually display the difference in stock prices as they change.

To begin, we first located a source to provide the information that the application needs: current price, time, and date of a given stock symbol through an RSS feed. The source we chose is QuoteRSS.com.

For this first example, the application will take a sequence of three stock symbols -- GOOG, AAPL, and MSFT -- that are provided in the Main file and stored in the model, and run an RSSTask for each. The RSSTasks will then print to the console the title of the item returned, which is simply the title of the RSS item.

Later, the application will take the item.title string and cut out what is needed for the user interface (UI): the stock's price and the time and date of the update. Take a look at the following code from the application's model, which uses the feed from QuoteRSS.com to gather information on a sequence of stock symbols.

public class StockReaderModel {

  // In this case, the FeedTask will be run as an RSSTask.
  var feedTask:FeedTask;

  // The stock symbols will be passed in via the UI and stored here.
  public var symbols:String[];

  // A function that will run a feed for each stock symbol
  public function startFeeds():Void {
    for (s in symbols) {
      println("Starting RSS for: {s}");
      feedTask = RssTask {

        // Note: The stock symbol is being passed into the URL using {s}.
        location: "http://www.quoterss.com/quote.php?symbol={s}&frmt=0&Freq=0"
        interval: 30s

        onException: function(e) {
          println("Exception is: {e}");
          println("There was a feed error with {s}");
        }
        onChannel: function(channel) {
            println("{channel.title}");
        }
        onItem: function(item) {
            println("{item.title}");
        }
      }
      feedTask.start();
    }
  };
       

As shown in the preceding code, the variable symbols is a string sequence that holds the stock symbols for which the user wants to obtain feeds. These symbols will be hard-coded initially, but this article will later show you how to pass them in through UI elements.

The startFeeds() function iterates over the sequence of stock symbols and runs an RSSTask for each. The stock symbol, {s}, is then passed into the location URL. Each RSSTask uses the interval variable of 30s. This causes the RSSTask to run every 30 seconds, so the end user receives frequent updates to any changes in the stock price.

The RSSTask class inherits onException, which is used in this application to print not only the exception that was received but also the stock symbol that caused the exception. This article will expand on more uses of the onException variable later.

The other two variables used in the feed task are onChannel and onItem. For now, both print their object's title to the console to demonstrate the basic workings of the RSSTask. The next example will demonstrate how to cut out the information provided in these strings and use them in UI elements.

In order to pass in the stock symbols and run the RSSTasks, the example contains the following Main file:

import stockreader.model.StockReaderModel;

// This example uses the Singleton pattern.
var model = StockReaderModel.getInstance();

model.symbols = ["GOOG", "AAPL", "MSFT"];
model.startFeeds();
       

Running the Main file provides the console output shown in Figure 1.

 
Figure 1 - StockReader Version 1 Output
 
 

As the application continues to run, the feeds update every 30 seconds and print out their new item.titleS. Without any kind of user interface, however, the application is quite boring and does not have much use. Now, the StockReader will get a UI allowing users to define the stocks that they would like to watch. This article will also demonstrate the use of Java technology to "scrape out" the parts of the title string to be used in the UI.

Table 1 provides an outline of the variables used in the StockReader example application.

Table 1. The RSSTask Variables
Name
Type
Description
interval
Duration
The amount of time before the feed is run again for updates.
location
String
The location of the feed; in this case, it is http://www.quoterss.com/quote.php?symbol={s}&frmt=0&Freq=0
onChannel
function(:Channel):Void
Returns the channel element of the RSS feed.
onException
function(:Exception):Void
If an exception occurred, this function will be executed.
onItem
function(:Item):Void
Returns the current item element of the RSS feed.
onDone
function():Void
Once the task has completed successfully or unsuccessfully, this function will be executed.
 

Adding the User Interface (UI)

As noted in the previous section, the information returned in the feed from QuoteRSS.com is one long string:

QuoteRSS.com: MSFT: 25.16 at 10:34am 9/21/2009
       

That is not very helpful if the application's user interface (UI) needs to use and display specific parts of that string to the end user. This is where combining the Java and JavaFX script technologies comes in very handy.

The following code is a function created to "scrape out" the elements needed for the StockReader application.

public function parseTitleString(inContent:String, symbol:String):String[] {
    def startTitle = "QuoteRSS.com: {symbol}: ";
    def endTitle = "";

    var begPos = 0;
    var endPos = 0;
    var retStr:String;
    var stockElements:String[];

    begPos = inContent.indexOf(startTitle) + startTitle.length();
    if (begPos >= 0) {
      endPos = inContent.indexOf(endTitle, begPos) - 1;
      retStr = inContent.substring(begPos);
    }
    stockElements = retStr.split(" ");
    return stockElements;
  }
       

This code allows you to pass in the item's title -- for example, the long string at the beginning of this section -- then to skip over the URL and stock symbol at the beginning of the string, and to split the remaining elements in between each space.

For instance, passing in the quote for that MSFT update of QuoteRSS.com: MSFT: 25.16 at 10:34am 9/21/2009 would provide the following:

["25.16","at","10:34am","9/21/2009"]
       

Now that the application can break the information down to what it needs, a face-lift of the UI is in order, as shown in Figure 2.

 
Figure 2 - The New StockReader UI
 
 

Each of the white squares in Figure 2 is a StockItemNode. Using the new layout class Tile, which was demonstrated in the previous article, the StockItemNodes are aligned horizontally and vertically in a tablelike format, wrapping to a new row once the nodes reach the width of the Tile.

In this example, the StockItemNode has its own data model, a StockItem, which is very simple:

package stockreader.model;

public class StockItem {

  public var stockSymbol:String;
  public var price:String;
  public var time:String;
  public var date:String;

}
       

At this point, you can modify the RSSTask from the previous section to do something other than print to the console. For each stock symbol in the model, the application will create a StockItem, as seen in the stockItems variable in the following code sample. Instead of running an RSSTask for each symbol, the code is modified to run for each StockItem. The data returned from the RSSTask will be assigned the StockItem variables shown in the previous code sample. Take a look at the model's new RSSTask:

public class StockReaderModel {

  var feedTask:FeedTask;
  public var symbols:String[];
  public var stockItems:StockItem[]= bind for (s in symbols) StockItem {
    stockSymbol: s
  };

    /**
   * A function that will run a feed for each stock symbol,
   * and create a StockItem data model for each.
   */
  public function startFeeds():Void {
    for (si in stockItems) {
      println("symbol is {si.stockSymbol}");
      feedTask = RssTask {

        location: "http://www.quoterss.com/quote.php?symbol={si.stockSymbol}&frmt=0&Freq=0"
        interval: 60m

        onException: function(e) {
          println("Exception is: {e}");
            si.price = "{indexof si + 1}";
            si.time = "Feed Error";
            si.date = "Feed Error";
        }

        onChannel: function(channel) {
            println("{channel.title}");
        }

        onItem: function(item) {
            println("{item.title}");
            si.price = parseTitleString(item.title, si.stockSymbol)[0];
            si.time = parseTitleString(item.title, si.stockSymbol)[2];
            si.date = parseTitleString(item.title, si.stockSymbol)[3];
        }

        onDone: function():Void {
            feedTask.stop();
        }

      }
      feedTask.start();
    }
  };
       

Note that the interval variable in the code sample has been changed to 60m, 60 minutes. You must assign a value to this variable in order for the RSSTask to work properly.

In this case, it is not desirable that the feed tasks run continually, because the stocks themselves are not updated at specific intervals. Therefore, it would make more sense to have the user "refresh" the stocks when desired. Because the interval variable must be set, this application uses a large time interval of 60 minutes and stops the feed, feedTask.stop();, in the RSSTask's onDone event handler.

But the application becomes much more useful if the end user is able to specify which stocks to watch. We added controls to allow the user either to choose from a list of predefined popular stocks or add some stocks by using a text box. When the user adds a stock symbol, that symbol is added to the symbols variable in the model, and the startFeeds() function is called, adding a new StockItemNode to the UI. Figure 3 shows the open dialog box and controls.

 
Figure 3 - Adding a Stock
 
 

Sometimes an error occurs in the RSS feed. As noted in the previous section, there are many uses for the onException variable in the RSSTask. In this application, if the feed for a stock causes an error, we want to make sure that the user sees something, even if it is not the correct data.

Look at the following snippet of code, and you'll notice that the StockItems variables are still assigned something, but the update time and date now show "Feed Error." This way, the user knows that something is wrong.

        onException: function(e) {
          println("Exception is: {e}");
            si.price = "{indexof si + 1}";
            si.time = "Feed Error";
            si.date = "Feed Error";
        }
       

The StockReader application now has a UI that allows the user to add and delete stocks, and to update the stocks using the refresh button.

The next step is very important in this kind of application: local storage.

Local Storage

The ability to store a user's data is an incredibly useful feature and, in many cases, a requirement. In this article's example application, users would get frustrated if they had to add the same stocks every time they ran the program. Therefore, locally storing the user's stocks and loading them on startup would make the application both easier to use and more effective.

First, take a look at the saveProperties() function in the following code snippet:

  var entry:Storage;

  public function saveProperties():Void {
    println("Storage.list():{Storage.list()}");
    entry = Storage {
      source: "stockreader.properties"
    };
    var resource:Resource = entry.resource;
    var properties:Properties = new Properties();
    def symbolsTemp = for (symbol in symbols) "{symbol},";
    println("symbolsTemp looks like this: {symbolsTemp}");
    properties.put("symbolsTemp", "{symbolsTemp}");
    try {
      var outputStream:OutputStream = resource.openOutputStream(true);
      properties.store(outputStream);
      outputStream.close();
      println("properties written");
    }
    catch (ioe:IOException) {
      println("IOException in saveProperties:{ioe}");
    }
  };
       

The Storage class contains two important variables that are used in the previous snippet: source and resource.

The source variable is simply the path to the resource. In this example, it is a filename: stockreader.properties.

The source can also be the absolute path to the resource, starting with /. A Resource is what will be stored on the platform, similar to a file. In the previous example, the stock symbols will be the resource.

A very important note is that the symbols used in the program are in a String array. When this sequence of strings is stored, it becomes a single string. For example, ["AAPL","MSFT","GOOG"] becomes "AAPLMSFTGOOG".

In preparation for this, the constant symbolsTemp is defined, creating a comma-separated list of symbols: "AAPL,MSFT,GOOG". This will make the stored symbols easy to split apart when loaded, and it will ensure that there are no extra spaces or characters when the symbols are stored.

Table 2 and Table 3 outline the variables and functions of the Storage and Resource classes that are used in the StockReader example application.

Table 2. The Storage Variables
Name
Type
Description
resource
Resource
The resource to be managed
source
String
The path (or absolute path) to the resource
 
Table 3. The Resource Variables and Functions
Name
Type
Description
name
String
The name of the resource
openInputStream():InputStream
 
Opens an InputStream from the resource
openOutputStream():OutputStream
 
Opens an OutputStream to the resource
 

The Properties class is defined by the JavaFX 1.2 API as a "utility class for accessing and storing name/value pairs." The example creates a new instance of the Properties class and creates a name/value pair for symbolsTemp.

The put() function is passed a key:String, which is a name for the value to be stored, and a value:String, which in this case is symbolsTemp.

Now that things are set up, it's time to try storing the data using the resource's openOutputStream(overwrite:Boolean):OutputStream function, which defines whether to overwrite existing data and returns an OutputStream.

Use this line of code:

      var outputStream:OutputStream = resource.openOutputStream(true);
       

The code of ..., provides a reference to the resource's OutputStream. Finally, the application calls properties.store(outputStream), which stores the symbolsTemp resource that we put away in the last paragraph, then closes the outputStream. Remember always to close any and all OutputStreams that you opened.

Table 4 provides a list of useful variables and functions for the Properties class.

Table 4. The Properties Functions
Name
Description
get(key:String):String
Retrieves the value of a name/value pair with the specified name (key)
load(inputstream:InputStream):Void
Load the name/value pairs from the passed input stream
put(key:String, value:String):Void
Creates the specified name/value pair, or if the pair already exists, updates the value for the name/value pair to the value specified
store(outputstream:OutputStream):Void
Stores the properties using the specified output stream
 

Obviously, we have to trigger the saveProperties() function at some point. The following two code samples demonstrate saving properties and exiting by using the close() and FX.addShutdownAction() functions.

  closeButton = Button {
    text: "Exit Program"
    action: function():Void {
      model.saveProperties();
      stage.close();
    }
  }

  FX.addShutdownAction(function():Void {
    if (Alert.question("Save your stocks for next time?")) {
      model.saveProperties();
    }
  })
       

Now that the symbols have been locally stored, they need to be loaded the next time that the user runs the application. For this, a loadProperties() function was created:

  public function loadProperties():Void {
    println("Storage.list():{Storage.list()}");
    entry = Storage {
      source: "stockreader.properties"
    };
    var resource:Resource = entry.resource;
    var properties:Properties = new Properties();
    try {
      var inputStream:InputStream = resource.openInputStream();
      properties.load(inputStream);
      inputStream.close();
      def symbolsTemp = properties.get("symbolsTemp");
      if (symbolsTemp != null and symbolsTemp.trim() != "") {
        symbols = symbolsTemp.split(",");
      }
      else {
        symbols = [];
      }
    }
    catch (ioe:IOException) {
      println("IOException in loadProperties:{ioe}");
    }
  };
       

Notice that the beginning of this function is identical to the saveProperties() function. The application still needs a reference to the existing Storage, "stockreader.properties", the resource, and an instance of the Properties class in order to create an InputStream for loading the stored data.

This time, the application will create a variable of type InputStream and assign it the resource's openInputStream() function, which returns an InputStream. It then calls properties.load(inputStream), which loads the name/value pair discussed earlier.

Now that we have the comma-separated list of symbols, symbolsTemp, it's time to split them apart and assign them to the model's symbol variable, which is used by the RSSTasks.

Another important feature of data-storing applications is the ability to clear the user's data. The Storage class provides two functions for clearing the application's stored data: clear() and clearAll(). As their names suggest, clear() deletes a single resource from Storage, and clearAll() deletes all stored items or files.

Following is a clearProperties()function that is called when the user clicks a "Clear Cache" button in the UI, which is shown in Figure 4.

 
Figure 4 - The Clear Cache Button
 
 
public function clearProperties():Void {
    if (entry != null) {
      entry.clearAll();
      delete symbols;
      println("Properties cleared");
    }
  };
       

Finally, the following code sample, located in the Main file, shows how the application invokes the loadProperties() function.

function run():Void {
  model.loadProperties();
  if (sizeof model.symbols > 0) {
    model.startFeeds();
  }
  stage = Stage {
    ...
    ...
      // ... EXTRA CODE REMOVED
    ...
    ...
  FX.addShutdownAction(function():Void {
    if (Alert.question("Save your stocks for next time?")) {
      model.saveProperties();
    }
  })
}
       

First, the application loads the stored data. Then, as long as an empty string was not returned and model.symbols actually contains a symbol, the RSSTasks are started and the UI is created, including StockItemNodes for the stored symbols.

The ability to store data locally can benefit the end user in ways other than storing stock symbols. For instance, some applications are designed so that a window opens to the size that it was when the user last closed the application. Storage makes this incredibly simple, as demonstrated in the book Pro JavaFX Platform.

Conclusion

Not only does JavaFX make it easy to invoke RSS feeds, the binding power of the platform easily allows you to update the UI with new information from feeds. Storing local data, an essential feature for most applications, is simple to integrate into your JavaFX code.

The next article in this series will add another UI element to the StockReader example: JavaFX Charts.

For More Information

Rate This Article

 
 

Discussion

We welcome your participation in our community. Please keep your comments civil and on point. You can optionally provide your email address to be notified of replies—your information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.