The art of waiting

  • Post category:Java / javafx / UI

The hour registration application is working nicely, on windows, on android, on OSX, but there is always room for improvement. One of the most obvious issues is that the original applet is a lot quicker when retrieving data from the server. I suspect this has to do with the fact that the applet maintains a web session and that the JavaFX app does an authentication on each request. And yes, of course I could (and probably will) improve that, but it also is an opportunity to try out something else; asynchronous communication. Now, I could pull open a can of threads and try to do this myself, but there have been wheels invented for this. So in this post I’m going to try Java 8’s CompleteableFuture, RxJava and JDeferred.

The app’s communication is very suited to try something like this, because there are 6 separate calls being made to the backend, and some of those calls are depending on each other. The calls are:

dh2fx_fetchData
1. The hours for a given date.
2. Totals per project for the current week.
3. Totals per day for the current week.
4. All projects.
5. The favorite projects for the current user.

For some reason 20 years ago I decided that having multiple sets of favorite projects was a good idea. I have no clue why, but the applet only supports one and so does the JavaFX version. But you still need to first fetch the id’s of all the sets of favorites, and then (using the id of the first and only entry) fetch the associated projects. Therefor there is a 5a (get the favorite sets) and 5b (get the associated projects) call.

More importantly; some calls require the list of projects to be present, so they can show the project name. So the dependencies between the calls are:
– 4 before 1, 2 and 5b,
– 5a before 5b,
– 3 is not depending on anything.

As said; even though the applet does this synchronously, it refreshes pretty quick. The JavaFX app has so much wait time that it becomes annoying. All the libraries we’re going to try use the concept of Promises or Futures (same things, different names) to handle this; Promises or Futures are calls that promise to have a result somewhere in the future.

Note: all the code below has been (slightly) stripped to improve readability and focus on the core of this blog.

Java 8

Java has had Futures since version 1.5, but the minute you try to use them, you get stuck. Even though a Future represents ‘the result of an asynchronous computation‘, it does not do any handling of that call. You can check if the result is present or not, blocking wait for it, but that is all. If you don’t want to block, you need to manually check if the result is available, for example using an class like CompletionService. This would result in code like this:

List<Callable<String>> callables = createCallableList();
for (Callable<String> callable : callables) {
  taskCompletionService.submit(callable);
}
for (int i = 0; i < callables.size(); i++) {
  Future<String> result = taskCompletionService.take();
  System.out.println(result.get());
}

And that is just for waiting on the call to be completed. What you really would like is something like this:

Async.do( () -> callToTheBackendReturningSomeValue() )
  .onError( (e) -> { handleError(e); } )
  .onSuccess( (v) -> { handleResult(v); } )

And as of Java 8 there is the CompletableFuture class, it adds the handling of when the result arrives, but also allows chaining and syncing of CompleteableFutures. The application’s refresh method using CompleteableFutures looks like this:

// TemplateNames
CompletableFuture<String[]> templateNamesFuture = CompletableFuture.supplyAsync( () -> entryService().getTemplateNames() );

// AllProjects
CompletableFuture.supplyAsync( () -> entryService().getAllProjects() )
  .exceptionally(t -> { backgroundActivity.failure(t); return null; } )
  .thenAcceptBothAsync(templateNamesFuture, (Project[] projects, String[] templateNames) -> {

// TemplateProjectNrs
CompletableFuture.supplyAsync( () -> entryService().getTemplateProjectNrs(templateNames[0]) )
  .exceptionally(t -> { backgroundActivity.failure(t); return null; } )
  .thenAcceptAsync( (Long[] favoriteNrs) -> {
    Platform.runLater( () -> {
      refreshProjects(projects, favoriteNrs);
    });
  });

// Hours
CompletableFuture.supplyAsync( () -> entryService().getHours(getCalendarPickerForHessian()))
  .exceptionally(t -> { backgroundActivity.failure(t); return null; } )
  .thenAcceptAsync( (Hour[] hours) -> {
    Platform.runLater( () -> {
      refreshHours(hours);
    });
  });

// ProjectTotals
CompletableFuture.supplyAsync( () -> entryService().getProjectTotals(getCalendarPickerForHessian()) )
  .exceptionally(t -> { backgroundActivity.failure(t); return null; } )
  .thenAcceptAsync( (LinkedHashMap<Integer, Double> projectTotals) -> {
    Platform.runLater( () -> {
      refreshProjectTotals(projectTotals);
    });
  });

});

// DayTotals
CompletableFuture.supplyAsync( () -> entryService().getDayTotals(getCalendarPickerForHessian()))
  .exceptionally(t -> { backgroundActivity.failure(t); return null; } )
  .thenAcceptAsync( (SortedMap<String, Double> dayTotals) -> {
    Platform.runLater( () -> {
      refreshDayTotals(dayTotals);
    });
  });

The code is fairly straight forward; call, on-error handling, and on-result handling. Unfortunately the CompletetableFuture class is part of Java 8 and Android (being Java 6) does not support them. Luckily there exists a drop-in backport called streamsupport (you need to include the streamsupport-cfuture dependency).

RxJava

Another implementation uses the generic ‘reactive programming’ library RxJava. RxJava implements the ReactiveX standard and uses the concept of an Observable, which basically is something that over time may output one or more values. A Future of course does exactly that; output a single value after a few (milli)seconds. So you can write the refresh method with RxJava, which would look like this:


// TemplateNames &amp; AllProjects
Observable.zip( Observable.fromCallable( () -> entryService().getTemplateNames() )
              , Observable.fromCallable( () -> entryService().getAllProjects() )
    , (String[] templateNames, Project[] projects) -> {

    // TemplateProjectNrs
    Observable.fromCallable( () -> entryService().getTemplateProjectNrs(templateNames[0]) )
      .subscribeOn(Schedulers.io()) // execute the HTTP call on the io thread
      .observeOn(JavaFxScheduler.platform()) // but switch to the JavaFX thread to handle the result
      .doOnError( e -> { backgroundActivity.failure(e); } )
      .subscribe( (Long[] favoriteNrs) -> {
        refreshProjects(projects, favoriteNrs);
      });

    // Hours
    Observable.fromCallable( () -> entryService().getHours(getCalendarPickerForHessian()))
      .subscribeOn(Schedulers.io()) // execute the HTTP call on the io thread
      .observeOn(JavaFxScheduler.platform()) // but switch to the JavaFX thread to handle the result
      .doOnError( e -> { backgroundActivity.failure(e); } )
      .subscribe( (Hour[] hours) -> {
        refreshHours(hours);
      });

    // ProjectTotals
    Observable.fromCallable( () -> entryService().getProjectTotals(getCalendarPickerForHessian()) )
      .subscribeOn(Schedulers.io()) // execute the HTTP call on the io thread
      .observeOn(JavaFxScheduler.platform()) // but switch to the JavaFX thread to handle the result
      .doOnError( e -> { backgroundActivity.failure(e); } )
      .subscribe( (LinkedHashMap<Integer, Double> projectTotals) -> {
        refreshProjectTotals(projectTotals);
      });

    return ""; // dummy object must be returned
  })
  .subscribeOn(Schedulers.io()) // execute the HTTP call on the io thread, no need to switch to JavaFX because there are follow up observables
  .doOnError(e -> { backgroundActivity.failure(e); } )
  .subscribe();

  // DayTotals
  Observable.fromCallable( () -> entryService().getDayTotals(getCalendarPickerForHessian()) )
    .subscribeOn(Schedulers.io()) // execute the HTTP call on the io thread
    .observeOn(JavaFxScheduler.platform()) // but switch to the JavaFX thread to handle the result
    .doOnError( e -> { backgroundActivity.failure(e); })
    .subscribe( (SortedMap<String, Double> dayTotals) -> {
      refreshDayTotals(dayTotals);
    } );

But being a generic library does not make it very readable. Especially the terms observe and subscribe are abstract, and their difference remains vague to everyone not deeply involved in the ReactiveX domain. Also, RxJava is a streaming library, which is way more complex than simply handling single asynchronous calls, and that does not make things simpler either.

Note: yes, it would have been possible to move boilerplate code to a special function, but that masks away the library too much in the context of this post.

JDeferred

JDeferred is inspired by JQuery, and is specifically written for asynchronous calls, and thus it has in this context more suitably named methods like when, done and fail:

// TemplateNames &amp; AllProjects
deferredManager
  .when( () -> entryService().getTemplateNames()
       , () -> entryService().getAllProjects() )
  .fail( oneReject -> { backgroundActivity.failure(); } )
  .done( (MultipleResults multipleResults) -> {
    String[] templateNames = (String[])multipleResults.get(0).getResult();
    Project[] projects = (Project[])multipleResults.get(1).getResult();

    // TemplateProjectNrs
    deferredManager
      .when( () -> entryService().getTemplateProjectNrs(templateNames[0]) )
      .fail( e -> { backgroundActivity.failure(e); } )
      .done( (Long[] favoriteNrs) -> {
        Platform.runLater( () -> {
          refreshProjects(projects, favoriteNrs);
        });
      });

    // Hours
    deferredManager
      .when( () -> entryService().getHours(getCalendarPickerForHessian()))
      .fail( e -> { backgroundActivity.failure(e); } )
      .done( (Hour[] hours) -> {
        Platform.runLater( () -> {
          refreshHours(hours);
        });
      });

    // ProjectTotals
    deferredManager
      .when( () -> entryService().getProjectTotals(getCalendarPickerForHessian()) )
      .fail( e -> { backgroundActivity.failure(e); } )
      .done( (LinkedHashMap<Integer, Double> projectTotals) -> {
        Platform.runLater( () -> {
          refreshProjectTotals(projectTotals);
        });
      });
  }); 

  // DayTotals
  deferredManager
    .when( () -> entryService().getDayTotals(getCalendarPickerForHessian()) )
    .fail( (e) -> { backgroundActivity.failure(e); })
    .done( (SortedMap<String, Double> dayTotals) -> {
      Platform.runLater( () -> {
        refreshDayTotals(dayTotals);
      });
    });

I find JDeferred much more readable than both previous implementations, but it too has some quirks; first the question why the first fail method (for TemplateNames & AllProjects) does not provide easy access to the exception(s) causing the fail (the other fail methods do). And secondly the usage of MultipleResults, which is not type safe and requires casting to get to the result values. The other frameworks show that it is very possible to do this type safe.

Summary

All three libraries do what they are supposed to, and each has his pro’s and con’s:

  • CompleteableFuture is a Java standard, it has bit strange method names in light of readability, but more importantly it needs a backport library to make it work on Android.
  • RxJava is a universal standard, but because it is a Swiss army knife intended for streaming, the code is not very readable in this specific context.
  • JDeferred seems tailored for this situation, has a readable but unfortunately not very type safe API.

To be frank; given that RxJava and JDeferred existed, I do not really understand why Java implemented CompleteableFuture. If you don’t want a platform to bloat…

Anyhow, for the hour app I think I’m going to sticking to CompleteableFuture, because of the balance between readability and type safety. That said, I am wondering if it would be worthwhile to have a small wrapper API on top of RxJava purely for making asynchronous communication more readable, in style of JDeferred, and profit from the better thread handling. Something like:

ASync.when( () -> entryService().getDayTotals(getCalendarPickerForHessian()) )
    .whenOn(Schedulers.io())
    .doneAndFailOn(JavaFxScheduler.platform())
   &nbsp;.fail( e -> { backgroundActivity.failure(e); })
    .done( (SortedMap<String, Double> dayTotals) -> {
      refreshDayTotals(dayTotals);
    });

What do you think?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.