bekwam courses

Tracking Connection Status With JavaFX Binding

August 27, 2016

This article describes how to track the back-end connection status of an application using JavaFX Binding. JavaFX Binding is a powerful programming construct that helps developers better control UI states and modes. This control comes from reducing the coupling between a group of related UI changes. For example, there is no single Listener that update all the components in the UI. Individual elements register themselves for the bound changes.

See the following video of an application which begins in a Connected state. Unplugging the wired network cable on the computer then puts the app in a Not Connected state. The cable is reconnected and the app returns to the Connected State.

The user interface has a pair of components in the lower-right of the screen: a Label and an ImageView. Both will be updated when the connection status of the application changes. When the application is not connected, the Label will display “Not Connected” and the ImageView will be a gray icon. When the application is connected, the Label will display “Connected” and the ImageView will be a green icon.

Declarations

The example beings with a Core Java enum called TrayAppStatusType. This enum has three values: DEAD, CONNECTING, and ALIVE. Some presentation (“Connected”, “Not Connected”) was added to make the JavaFX Binding application simpler. The enum could also return a resources key for internationalized applications.

public enum TrayAppStatusType {

  ALIVE("Connected"),
  CONNECTING("Connecting"),
  DEAD("Not Connected");

  private final String displayName;

  TrayAppStatusType(String displayName) {
    this.displayName = displayName;
  }

  @Override
  public String toString() {
    return displayName;
  }
}

The program state is tracked with a variable “trayAppStatus”. Rather than using a plain Core Java type, the type is wrapped in an FX ObjectProperty. This supports Binding expressions. The two FX elements are also presented as variables as is a Map containing the Image icons. Notice that the Label and ImageView are initialized with empty values.

Map<String, Image> appIconSet = new LinkedHashMap< >();

ObjectProperty<TrayAppStatusType> trayAppStatus = new SimpleObjectProperty< >(TrayAppStatusType.DEAD);

ImageView imageView = new ImageView();
Label statusLabel = new Label("");

Binding

The Binding expression for statusLabel is based on the enum toString() method and imageView uses the JavaFX Binding class “When”. "When" works like an if / else statement and can be expanded for additional enum values. This is the expression for the statusLabel.

statusLabel.textProperty().bind( trayAppStatus.asString() );

When the state is DEAD, “Not Connected” is displayed. When the state is ALIVE, “Connected” is displayed. Otherwise, “Connecting” is displayed. This is formed from the toString() method of the enum.

This is the code for the imageView Binding expression.

imageView.imageProperty().bind(
  new When(
    trayAppStatus.isEqualTo(TrayAppStatusType.DEAD) ).
    then( appIconSet.get("gray") ).
    otherwise(
      new When(
trayAppStatus.isEqualTo(TrayAppStatusType.ALIVE)).
      then( appIconSet.get("green") ).
      otherwise( appIconSet.get("yellow")
)

Pulling Images from a preloaded Map, a gray icon is shown when the state is DEAD. When the state is ALIVE, a green icon is shown. Otherwise, a YELLOW icon is shown.

The appIconSet Map is created with the following methods.

private void loadAppIconSet() {
  appIconSet.put("red", createFXImage("/images/bkcourse_trayapp_indicator_red_16x16.png", "red tray icon"));
  appIconSet.put("yellow", createFXImage("/images/bkcourse_trayapp_indicator_yellow_16x16.png", "yellow tray " + "icon"));
  appIconSet.put("green", createFXImage("/images/bkcourse_trayapp_indicator_green_16x16.png", "green tray icon"));
  appIconSet.put("gray", createFXImage("/images/bkcourse_trayapp_indicator_gray_16x16.png", "gray tray icon"));
}

protected static Image createFXImage(String path, String description) {
  return new Image( path );
}

State Change

The app's state change is driven from a polling of a web resource. Every 10 seconds, an HTTP GET is issued to localhost. If the response is good, the app is in the ALIVE state. If the response is bad – say from a 404 – the app is in the DEAD state. At this time, there is not CONNECTING condition, but one will be added that tracks a number of failed responses before transitioning to DEAD.

private static boolean isAlive() {
  try {
    Request.Get("http://www.bekwam.com").execute().returnContent();
  } catch(Exception exc) {
    return false;
  }
  return true;
}

Polling

The polling is performed using the JavaFX Concurrency API class “ScheduledService”. The call() method issues the time-consuming web server request on a separate Thread. When finished, the succeeded() method is called. The getValue() result drives a change in program state. It is important to note that succeeded() is executed on the JavaFX Thread. Without this protection, the state change would trigger an off-FX Thread JavaFX UI update and an exception would be thrown.

ScheduledService<Boolean> serverCheck = new ScheduledService<Boolean>() {
  @Override
  protected Task<Boolean> createTask() {
    Task<Boolean> aliveTask = new Task<Boolean>() {
    @Override
    protected Boolean call() throws Exception {
      return isAlive();
    }
    @Override
    protected void succeeded() {
    if( getValue() ) { // alive
      trayAppStatus.set(TrayAppStatusType.ALIVE);
    } else {
      trayAppStatus.set(TrayAppStatusType.DEAD);
    }
    return aliveTask;
  }
};
serverCheck.setPeriod(Duration.seconds(10));
serverCheck.start();

This is an alternate syntax using Core Java. The ObjectProperty set() call is wrapped in a Platform.runLater() to make sure that all UI updates occur on the FX Thread.

Timer t = new Timer("heartbeat-timer");
t.schedule(new TimerTask() {
  @Override
  public void run() {
    boolean alive = isAlive();
    if (alive) {
      Platform.runLater(() -> trayAppStatus.set(TrayAppStatusType.ALIVE));
    } else {
      Platform.runLater(() -> trayAppStatus.set(TrayAppStatusType.DEAD) );
    }
   }
}, 0, 10000);

This example showed a polling process updating a state variable. The UI pinned Label and ImageView changes to the state variable. JavaFX Binding allowed the individual components – the Label and the ImageView – to associate individual behaviors with the TrayAppStatusType property. In the case of the Label, Core Java techniques were applied to an enum that made this a one-liner.

Beyond using fewer lines of code, JavaFX Binding holds up well in larger applications. A well-defined enum provides a solid documentation of application state and the enum property can be exposed for other code modules. Other classes can bind their UI elements to the enum. Listeners can still be used, but the rich set of JavaFX Binding statements can handle quite a few cases in a more consistent manner.


Headshot of Carl Walker

By Carl Walker

President and Principal Consultant of Bekwam, Inc