Sample application using JavaFX 2.0 beta and the after thoughts

This sample uses- TabPane, GridView and Bindings in javafx 2.0.

I had quite sometime back played around with JavaFX and had good and bad experiences using the language. With the JavaFX 2.0 beta being released I thought of giving it a try. Here I developed a simple Geocoding application which will take the address and provide the latitude-longitude values for that location- using Google Geocoding API.

I used Groovy for the parsing of the json as the latest version-1.8 provides a really neat json parsing support.


import groovy.json.*

class GeocodingParser {
  static def GEOCODE_JSON_URL = "http://maps.googleapis.com/maps/api/geocode/json"
  static def GEOCODE_XML_URL = "http://maps.googleapis.com/maps/api/geocode/xml"

  static def getGeocodeForAddress(address){
    def queryBuilder = []
    queryBuilder << "address=${URLEncoder.encode(address)}"
    queryBuilder << "sensor=false"
    def queryString = queryBuilder.join("&")
    def requestUrl = GEOCODE_JSON_URL+"?${queryString}"
    def payload = new URL(requestUrl).text
    def jsonSlurper = new JsonSlurper()
    def doc = jsonSlurper.parseText(payload)
    def geocode = new Geocode()
    geocode.latitude = doc.results.geometry.location.lat.join("")
    geocode.longitude = doc.results.geometry.location.lng.join("")
    geocode.locationType = doc.results.geometry.location_type.join("")
    return geocode
  }
}

class Geocode {
  def String latitude
  def String longitude
  def String locationType
  def String toString(){
    return "Latitude: ${latitude}, Longitude:${longitude} and Location type: ${locationType}"
  }
}

You can see that the json parsing using JsonSlurper is so succinct. The groovy parser returns the latitude, longitude and location type (these are the values of interest for our application) values in the Geocode wrapper class- which is again a Grooy bean. Now lets look at the JavaFX code which actually is the crux of this post-


public class NewFXMain extends Application {

 /**
 * @param args the command line arguments
 */
 public static void main(String[] args) {
   Application.launch(NewFXMain.class, args);
 }

 @Override
 public void start(Stage primaryStage) {
   primaryStage.setTitle("Geocoder");
   TabPane mainTabPane = new TabPane();
   Tab geoTab = new Tab("Geocoding");
   geoTab.setClosable(false);
   mainTabPane.getTabs().add(geoTab);

   final GridPane geoGrid = new GridPane();
   geoGrid.setHgap(10);
   geoGrid.setVgap(10);
   geoGrid.setPadding(new Insets(0, 20, 0, 10));

   Label mainGeoLabel = new Label("Geocoding");

   final TextBox geoAddressTextBox = new TextBox(15);

   Button geoCodeButton = new Button("Geocode");
   final TextBox latitudeValTextBox = new TextBox();
   latitudeValTextBox.setEditable(false);
   final TextBox longitudeValTextBox = new TextBox();
   longitudeValTextBox.setEditable(false);
   final TextBox locationTypeValTextBox = new TextBox();
   locationTypeValTextBox.setEditable(false);

   final StringProperty latitudeProperty = new StringProperty();
   latitudeProperty.addListener(new ChangeListener<String>() {

     @Override
     public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
       latitudeValTextBox.setText(newValue);
     }
   });

   final StringProperty longitudeProperty = new StringProperty();
   longitudeProperty.addListener(new ChangeListener<String>() {

     @Override
     public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
       longitudeValTextBox.setText(newValue);
     }
   });

   final StringProperty locationTypeProperty = new StringProperty();
   locationTypeProperty.addListener(new ChangeListener<String>() {

     @Override
     public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
       locationTypeValTextBox.setText(newValue);
     }
   });

   geoCodeButton.setOnAction(new EventHandler<ActionEvent>(){

     @Override
     public void handle(ActionEvent event) {
       String address = geoAddressTextBox.getText();
       if(address == null){
       }else{
         Geocode parsedCode = (Geocode)GeocodingParser.getGeocodeForAddress(address);
         latitudeProperty.set(parsedCode.getLatitude());
         longitudeProperty.set(parsedCode.getLongitude());
         locationTypeProperty.set(parsedCode.getLocationType());
      }
    }
   });

   geoGrid.add(mainGeoLabel, 4, 1);
   geoGrid.add(new Label("Address"), 2, 3);
   geoGrid.add(geoAddressTextBox, 3, 3,3,1);
   geoGrid.add(new Label("Latitude"), 2,7);

   geoGrid.add(new Label("Longitude"),2,8);

   geoGrid.add(new Label("Location Type"),2,9);
   geoGrid.add(latitudeValTextBox,3,7,2,1);
   geoGrid.add(longitudeValTextBox,3,8,2,1);
   geoGrid.add(locationTypeValTextBox,3,9,2,1);
   geoGrid.add(geoCodeButton, 4, 5);
   geoTab.setContent(geoGrid);
   Scene scene = new Scene(mainTabPane);
   primaryStage.setScene(scene);
   primaryStage.setVisible(true);
   primaryStage.setResizable(false);
 }
}

I have used bindings to bind the component displaying the latitude, longitude and location type values with the properties for the same.For example- The code below shows how the latitude value was bound to the control which will display the value.


final TextBox latitudeValTextBox = new TextBox();

latitudeValTextBox.setEditable(false);

This control (textbox) holds the latitude value obtained after passing the json response sent by the Geocoding API.

final StringProperty latitudeProperty = new StringProperty();
latitudeProperty.addListener(new ChangeListener<String>() {
   @Override
   public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
     latitudeValTextBox.setText(newValue);
   }
});

Now we create a StringProperty to hold the value for the latitude and we attach a change listener to this property such that when ever the value gets updated in the property we update the textbox with the new value. So, what exactly is changing the value of this property? We add a button which invokes the groovy parser and gets the latitude, longitude and location type values in a wrapper class.

geoCodeButton.setOnAction(new EventHandler<ActionEvent>(){
   @Override
   public void handle(ActionEvent event) {
     String address = geoAddressTextBox.getText();
     //Missing null handling to shorten the code in display here.
     Geocode parsedCode = (Geocode)GeocodingParser.getGeocodeForAddress(address);
     latitudeProperty.set(parsedCode.getLatitude());
    //Updating the other properties as well
   }
});

In the above action listener we get the parsed values and then update the properties with the corresponding values. This updation in turn triggers the method in the corresponding change listeners.

Now coming to the layout for tha controls- I used a GridBox layout and this was really flexible as it allowed me to place the components in a neat order.
I will share the link once I get the code to the repo. Here are the screenshots


 

Here are a few After thoughts-

  • JavaFX 2.0 has been changed to make more Java programmer friendly
  • JavaFX 2.0 more verbose than the JavaFX script- for example: looking at the way bindings has to be done.
  • Lack of tooling support- Creating a GUI is difficult.
  • Java programmers need not learn a new language all together, they wold feel at home with JavaFX APIs
  • Interoperability with other JVM languages like Groovy, Scala.
  • Lot of new controls, APIs added in the JavaFX 2.0.
  • Lack of multi-platform support.

PS: I am working on adding Reverse Geocoding- giving the address for the provided latitude and longitude values.

Update: The source code can be found here.

Update:

Updating a few things after Suggestions from Jonathan here.

Jonathan had suggested that the bindings can be made more shorter and more JavaFX way and I did realize that the sample I had written above was a bit more verbose than it should have been.The following is the updated code (only binding)- showing it for one of the fields- Latitude.


final TextBox latitudeValTextBox = new TextBox();
latitudeValTextBox.setEditable(false);

final StringProperty latitudeProperty = new StringProperty("");
latitudeValTextBox.textProperty().bindBidirectional(latitudeProperty);
 

This has been shortened from-


final StringProperty latitudeProperty = new StringProperty();
latitudeProperty.addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
latitudeValTextBox.setText(newValue);
}
});

Looks more succinct and more importantly straight forward to understand.

6 thoughts on “Sample application using JavaFX 2.0 beta and the after thoughts”

  1. i am getting null pointer exception at line

    Address: jaya
    java.lang.NullPointerException
    at geofx.NewFXMain$4.handle(NewFXMain.java:103)
    at geofx.NewFXMain$4.handle(NewFXMain.java:93)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:59)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:161)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at javafx.event.Event.fireEventImpl(Event.java:185)
    at javafx.event.Event.fireEvent(Event.java:170)
    at javafx.scene.Node.fireEvent(Node.java:5514)
    at javafx.scene.control.Button.fire(Button.java:138)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:164)
    at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:269)
    at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:262)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:55)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:161)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at javafx.scene.Node$19.dispatchEvent(Node.java:5409)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at javafx.event.Event.fireEventImpl(Event.java:185)
    at javafx.event.Event.fireEvent(Event.java:175)
    at javafx.scene.Node.fireEvent(Node.java:5514)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:2087)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:1934)
    at javafx.scene.Scene$MouseHandler.access$1000(Scene.java:1906)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1007)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:1553)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:122)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:254)
    at com.sun.glass.ui.View.notifyMouse(View.java:511)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.access$100(WinApplication.java:29)
    at com.sun.glass.ui.win.WinApplication$2.run(WinApplication.java:51)
    at java.lang.Thread.run(Thread.java:619)

    Reply
    • Few things i wanted to clarify-
      How are you running this application? via Netbeans?
      Also did you try giving a different address?

      Reply
    • May be you can post it on JavaFX official forums, Also you can post your attempt there and someone would be able to help you.
      Or you can even post in Javaranch JavaFX forum.

      Reply
  2. Pingback: JavaPins

Leave a Reply

Discover more from Experiences Unlimited

Subscribe now to keep reading and get access to the full archive.

Continue reading