8. RxJava in the Swing Environment
8.1. Introduction
Here, we will revisit the Swing application presented in Section 2.
![]() |
To work with RxJava in a Swing environment, we will use the RxSwing library, which adds classes and interfaces to RxJava that are useful in a Swing environment. To do this, the Gradle file for the Swing example is as follows:
![]() |
buildscript {
repositories {
mavenCentral()
}
}
apply plugin: 'java'
jar {
baseName = 'exemples-01'
version = '0.0.1-SNAPSHOT'
}
repositories {
mavenCentral()
}
dependencies {
compile('io.reactivex:rxswing:0.25.0')
compile('io.reactivex:rxjava:1.1.3')
compile('com.fasterxml.jackson.core:jackson-databind:2.7.3')
}
task wrapper(type: Wrapper) {
gradleVersion = '2.9'
}
- line 15: the dependency on RxSwing;
We will use only a single RxSwing-specific object: the scheduler [SwingScheduler.getInstance()], which executes/observes observables on the Swing event loop thread. We will use it exclusively to observe observables running on threads other than the event loop. Let’s review the architecture of the example application:

- the asynchronous service layer provides methods that return observables. We execute these observables in threads other than the event loop thread. This way, the GUI remains responsive. It can react to user input. The most obvious example is allowing the user to click a [Cancel] button to interrupt an asynchronous operation that is taking too long. For this to work, the GUI must be frozen;
- the Swing layer needs to process the results returned by asynchronous operations and use them to update the GUI. However, this can only be done in the event loop thread. To achieve this, these results are observed in the scheduler [SwingScheduler.getInstance()];
Thus, in the GUI event handling code, interaction with the asynchronous layer [rxService] takes the following form:
Observable obs=rxService.doSomething(...).subscribeOn(Schedulers.computation()).observeOn(SwingScheduler.getInstance()) ;
where the scheduler [Schedulers.computation()] can be replaced by another scheduler depending on the use case.
The reader is invited to reread paragraph 2. They now have the knowledge to fully understand it.
8.2. The code structure
The code implements the following architecture:

The IntelliJ IDEA project implementing this architecture is as follows:
![]() |
- the [rxswing.service] package implements the synchronous (IService, Service) and asynchronous (IRxService, RxService) service layers;
- the [rxswing.ui] package implements the Swing interface;
8.3. Running the project
To run the project in IntelliJ IDEA, follow these steps:
![]() |
8.4. The synchronous service

![]() |
The synchronous service layer provides the following [IService] interface:
package dvp.rxswing.service;
public interface IService {
// random numbers in the [a,b] interval
// n numbers are generated with n itself a random number in the interval [minCount, maxCount]
// numbers are generated after a delay of milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
public ServiceResponse getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay);
}
The [ServiceResponse] type of the service response is as follows:
package dvp.rxswing.service;
import java.util.List;
public class ServiceResponse {
// service waiting time
private int delay;
// random numbers
private List<Integer> aleas;
// execution thread
private String executedOn;
// manufacturers
public ServiceResponse() {
// execution thread
executedOn = Thread.currentThread().getName();
}
public ServiceResponse(int delay, List<Integer> aleas) {
// local builder
this();
// other initializations
this.delay = delay;
this.aleas = aleas;
}
// getters and setters
...
}
The [IService] interface is implemented by the following [Service] class:
package dvp.rxswing.service;
import java.util.*;
public class Service implements IService {
@Override
public ServiceResponse getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
// random numbers in the [a,b] interval
// n numbers are generated with n itself a random number in the interval [minCount, maxCount]
// numbers are generated after a delay of milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
// some checks
List<String> messages = new ArrayList<>();
int erreur = 0;
if (a < 0) {
messages.add("Le nombre a de l'intervalle [a,b] de génération doit être supérieur à 0");
erreur |= 2;
}
if (a >= b) {
messages.add("Dans l'intervalle [a,b] de génération, on doit avoir a< b");
erreur |= 4;
}
if (minCount < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du nombre de valeurs générées doit être supérieur à 0");
erreur |= 16;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du nombre de valeurs générées, on doit avoir min<= max");
erreur |= 32;
}
if (minDelay < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du délai d'attente doit être supérieur à 0");
erreur |= 64;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du délai d'attente, on doit avoir min<= max");
erreur |= 128;
}
if (maxDelay > 5000) {
messages.add("L'attente en millisecondes avant la génération des nombres doit être dans l'intervalle [0,5000]");
erreur |= 256;
}
// mistakes?
if (!messages.isEmpty()) {
throw new AleasException(String.join(" [---] ", messages), erreur);
}
// random number generator
Random random = new Random();
// waiting?
int delay = minDelay + random.nextInt(maxDelay - minDelay + 1);
if (delay > 0) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new AleasException(String.format("[%s : %s]", e.getClass().getName(), e.getMessage()), 1024);
}
}
// result generation
int count = minCount + random.nextInt(maxCount - minCount + 1);
List<Integer> nombres = new ArrayList<>();
for (int i = 0; i < count; i++) {
nombres.add(a + random.nextInt(b - a + 1));
}
// return result
return new ServiceResponse(delay,nombres);
}
}
The exception class [AleasException] used by the service is as follows:
package dvp.rxswing.service;
public class AleasException extends RuntimeException {
private static final long serialVersionUID = 1L;
// error code
private int code;
// manufacturers
public AleasException() {
}
public AleasException(String detailMessage, int code) {
super(detailMessage);
this.code = code;
}
public AleasException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public AleasException(String detailMessage, Throwable throwable, int code) {
super(detailMessage, throwable);
this.code = code;
}
// getters and setters
...
}
- line 3: it extends the [RuntimeException] class. It is therefore an unhandled exception;
- line 7: it adds an error code to its parent class (0=no error);
8.5. The asynchronous service

![]() |
The asynchronous service layer provides the following [IRxService] interface:
package dvp.rxswing.service;
import dvp.rxswing.ui.UiResponse;
import rx.Observable;
public interface IRxService {
// random numbers in the [a,b] interval
// n numbers are generated with n itself a random number in the interval [minCount, maxCount]
// numbers are generated after a delay of milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
public Observable<UiResponse> getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay, UiResponse uiResponse);
}
- line 11: the [getAleas] method of the service now returns an observable;
The [getAleas] method returns a response of type [UiResponse] intended for the [Ui] layer. This type is as follows:
package dvp.rxswing.ui;
import dvp.rxswing.service.ServiceResponse;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class UiResponse {
// customer id
private int idClient;
// service response
private ServiceResponse serviceResponse;
// observation thread name
private String observedOn;
// query time
private String requestAt;
// response time
private String responseAt;
// manufacturers
public UiResponse() {
// observation thread
observedOn = Thread.currentThread().getName();
// query time
requestAt = getTimeStamp();
}
// private methods
private String getTimeStamp() {
return new SimpleDateFormat("hh:mm:ss:SSS").format(Calendar.getInstance().getTime());
}
// getters and setters
...
}
- The random numbers are in the field on line 13;
- the other fields are there to specify the execution and observation threads of the asynchronous service's observable, as well as the timestamps of the request made to the service and the response received;
The asynchronous interface is implemented by the following [RxService] class:
package dvp.rxswing.service;
import dvp.rxswing.ui.UiResponse;
import rx.Observable;
public class RxService implements IRxService {
// synchronous service
private IService service;
// manufacturer
public RxService(IService service) {
this.service = service;
}
@Override
public Observable<UiResponse> getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay, UiResponse uiResponse) {
// we create an observable emitting the value rendered by the synchronous service
return Observable.create(subscriber -> {
try {
// synchronous call
uiResponse.setServiceResponse(service.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
// the result is passed on to the observer
subscriber.onNext(uiResponse);
} catch (Exception e) {
// we pass the error to the observer
subscriber.onError(e);
} finally {
// the observer is informed that the emissions are finished
subscriber.onCompleted();
}
});
}
}
- lines 12–14: the [RxService] class of the asynchronous service is constructed from an instance of the synchronous interface [IService];
- lines 20–33: construction of the observable, the result of the [getAleas] method;
- line 22: the synchronous method [service.getAleas] is called. Its result of type [ServiceResponse] is included in the object of type [UiResponse] to be provided to the [swing] layer. This object was initially passed in the method’s call parameters (last parameter, line 17);
- line 24: the [UiResponse] is sent to the observer (the [swing] layer). The [UiResponse] object contains not only the information generated by the synchronous service on line 22, but also other information generated by the method calling the [getAleas] method on line 17. This is why the calling method passed the [UiResponse] object as a parameter to the [getAleas] method (last parameter, line 17);
- line 30: we don’t forget to signal the end of the emissions. Here we have an observable emitting only one value: the one returned by the synchronous service;
- line 27: we notify the observer of any errors;
8.6. The graphical user interface

![]() |
- The graphical user interface was built using the [NetBeans] IDE, which has a good graphical editor. This editor generated the [AbstractJFrameAleas.form] file, which can only be used by this IDE;
- The [AbstractJFrameAleas] class was also generated by the NetBeans graphical editor. It was then refactored as follows: the GUI events we wanted to handle are processed in the [AbstractJFrameAleas] class via abstract methods implemented in the child class [JFrameAleasEvents]. Ultimately,
- the abstract class [AbstractJFrameAleas] is responsible for building and displaying the GUI;
- the child class [JFrameAleasEvents] handles its events;
The GUI components of the [Request] tab are as follows:
![]() |
No. | type | name | role |
1 | JTabbedPane | jTabbedPane1 | A tabbed container. Contains two tabs (JPanel): [jPanelRequest] for the request and [jPanelResponse] for the response; |
2 | JTextField | jTextFieldNbValues | the number of requests to be made to the random number service. In the case of the asynchronous service running on the scheduler [Schedulers.io], these requests will share a processor; |
3 | JTextField | jTextFieldA | endpoint a of the interval [a,b] |
4 | JTextField | jTextFieldB | endpoint b of the interval [a,b] |
5 | JTextField | jTextFieldMinCount | minCount endpoint of the interval [minCount, maxCount] |
6 | JTextField | jTextFieldMaxCount | maxCount bound of the interval [minCount, maxCount] |
7 | JTextField | jTextFieldMinDelay | minDelay bound of the interval [minDelay, maxDelay] |
8 | JTextField | jTextFieldMaxDelay | maxDelay bound of the interval [minDelay, maxDelay] |
9 | JCheckBox | jCheckBoxRxSwing | If the checkbox is checked, requests are made to the asynchronous interface. Otherwise, they are made to the synchronous interface |
10 | JComboBox | jComboBoxSchedulers | For asynchronous requests, they will be executed using the scheduler selected here |
11 | JButton | jButtonGenerate | launches the execution of requests to the synchronous or asynchronous service |
The GUI components of the [Response] tab are as follows:
![]() |
No. | type | name | role |
1 | JLabel | jLabelDuration | the total execution time in milliseconds of the requests |
2 | JLabel | jLabelNbResponses | the total number of observed responses (may differ from the number of requests, as each request may return multiple values to be observed) |
3 | JList | jListNumbers | display of observed (received) values |
4 | JButton | jButtonCancel | cancels requests currently being executed |
8.7. Instantiation of the graphical user interface
![]() |
The [JFrameAleasEvents] class handles GUI events, including clicks on the [Generate] button. It is an executable class that runs in the following context:
public class JFrameAleasEvents extends AbstractJFrameAleas {
private static final long serialVersionUID = 1L;
// synchronous generation service
private IService service;
// asynchronous generation service
private IRxService rxService;
// seizures
private int nbRequests;
private int a;
private int b;
private int minDelay;
private int maxDelay;
private int minCount;
private int maxCount;
// error messages
private final String jLabelNbValuesErrorText = "Tapez un nombre entier >=1";
private final String jLabelCountErrorText = "minCount doit être >=0 et maxCount>=minCount ";
private final String jLabelDelayErrorText = "minDelay doit être >=0 et maxDelay>=minDelay et maxDelay<=5000";
private final String jLabelIntervalErrorText = "a doit être >=0 et b>=a ";
// subscriptions to observables
protected List<Subscription> subscriptions = new ArrayList<Subscription>();
// start-end of execution
private long debut;
// mapper jSON
private ObjectMapper jsonMapper;
// answer model
private DefaultListModel<String> model;
// manufacturer
public JFrameAleasEvents() {
// parent
super();
// local
initJFrame();
// services
service = new Service();
rxService = new RxService(service);
// mapper jSON
jsonMapper = new ObjectMapper();
}
private void initJFrame() {
// hide error messages
jLabelCountError.setText("");
jLabelDelayError.setText("");
jLabelIntervalError.setText("");
jLabelNbValuesError.setText("");
// hide texts by default
jTextFieldA.setText("100");
jTextFieldB.setText("200");
jTextFieldMinCount.setText("5");
jTextFieldMaxCount.setText("10");
jTextFieldMinDelay.setText("100");
jTextFieldMaxDelay.setText("500");
jTextFieldNbValeurs.setText("10");
jLabelDuree.setText("");
// answer model
model = new DefaultListModel<>();
jListNumbers.setModel(model);
// number of hearts
System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
}
public static void main(String args[]) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException
| IllegalAccessException e) {
System.out.println(e);
System.exit(0);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(() -> {
new JFrameAleasEvents().setVisible(true);
});
}
- line 1: the [JFrameAleasEvents] class extends the [AbstractJFrameAleas] class, which itself extends the Swing [JFrame] class. The [JFrameAleasEvents] class is therefore a Swing window;
- lines 68–75: the [main] method that will be executed;
- line 70: sets the look and feel of the GUI;
- line 79: the constructor of the [JFrameAleasEvents] class is called: the GUI will be constructed and initialized. Once this is done, it is made visible;
- lines 34–44: the constructor;
- line 36: the call to the parent constructor will initialize the GUI. At this point, it looks exactly as the developer designed it. It is not yet visible;
- line 38: certain components of the GUI are initialized;
- line 40: instantiation of the synchronous service;
- line 41: instantiation of the asynchronous service;
8.8. Execution of synchronous requests
Clicking the [Generate] button triggers the execution of the following [doGenerate] method:
@Override
protected void doGenerate() {
// saisies valides ?
if (!isPageValid()) {
return;
}
// rx ou pas ?
if (jCheckBoxRxSwing.isSelected()) {
// requêtes asynchrones
doGenerateWithRxService();
} else {
// requêtes synchrones
doGenerateWithService();
}
}
- lines 4–6: we verify that the user’s input is valid. We won’t comment on the [isPageValid] method. It’s basic;
- line 8: we check the state of the RxSwing checkbox;
- line 13: we execute the requests synchronously;
The [doGenerateWithService] method is as follows:
// synchronous generation
private void doGenerateWithService() {
// start waiting
beginWaiting();
try {
for (int i = 0; i < nbRequests; i++) {
// response preparation
UiResponse uiResponse = new UiResponse();
// customer no
uiResponse.setIdClient(i);
// synchronous call
uiResponse.setServiceResponse(service.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
// response time
uiResponse.setResponseAt();
// update the JList model with the responses received
model.add(0, jsonMapper.writeValueAsString(uiResponse));
// update number of responses
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
}
} catch (JsonProcessingException | RuntimeException e) {
JOptionPane.showMessageDialog(this, getInfoForThrowable("L'erreur suivante s'est produite", e), "Informations",
JOptionPane.PLAIN_MESSAGE);
}
// end waiting
endWaiting();
}
- line 12: synchronous call to the random number generation service;
- the [doGenerateWithService] method is executed entirely within the Swing event loop thread. Until the method is finished, the GUI does not process any new events. It is frozen. Thus, for example, the GUI updates in lines 16 and 18 will never be seen. They will only be visible with their final values, and this will occur at the end of the execution of all requests;
The [beginWaiting] method (line 4) is as follows:
private void beginWaiting() {
// buttons
jButtonGenerate.setVisible(false);
jButtonCancel.setVisible(true);
// wait slider
jTabbedPane1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
jButtonCancel.setCursor(Cursor.getDefaultCursor());
// raz answers
model.clear();
// rx subscriptions
subscriptions.clear();
// the response view is displayed
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuree.setText("");
// start of execution
debut = new Date().getTime();
}
- line 3: the [Generate] button is hidden. This creates an event that can also only be executed once all requests have finished running. We never see it hidden, however, because the [endWaiting] method on line 25 of the [doGenerateWithService] method displays it again;
- line 13: we select the [Response] tab to see the responses arrive. Again, this event will only be executed after all requests have finished, at which point we will see all the responses at once, whereas we wanted to see them arrive one after another;
The synchronous interface clearly has shortcomings. These are overcome by the asynchronous interface.
8.9. Executing Asynchronous Requests
The code for executing asynchronous requests is as follows:
private void doGenerateWithRxService() {
// début attente
beginWaiting();
// on va obtenir les nombres aléatoires sous la forme d'un observable
Observable<UiResponse> observable = Observable.empty();
// Schéduler d'exécution des différents observables
Scheduler[] schedulers = { Schedulers.io(), Schedulers.computation(), Schedulers.newThread(),
Schedulers.trampoline(), Schedulers.immediate() };
Scheduler scheduler = schedulers[jComboBoxSchedulers.getSelectedIndex()];
// configuration des observables
for (int i = 0; i < nbRequests; i++) {
// préparation réponse
UiResponse uiResponse = new UiResponse();
uiResponse.setIdClient(i);
// l'observable est configuré pour s'exécuter sur le schéduler choisi par l'utilisateur
// puis cumul de l'observable obtenu à l'observable du tout
observable = observable.mergeWith(
rxService.getAleas(a, b, minCount, maxCount, minDelay, maxDelay, uiResponse).subscribeOn(scheduler));
}
// observateur
observable = observable.observeOn(SwingScheduler.getInstance());
// pour l'instant, on a juste fait de la configuration
// aucune requête n'a encore été faite au service synchrone de génération des nombres aléatoires
// on s'abonne à l'observable - c'est ce qui va provoquer l'appel au service synchrone de génération des nombres aléatoires
try {
// il n'y a ici qu'un abonnement - le résultat est une souscription
subscriptions.add(observable.subscribe(
// notification d'émission
uiResponse -> {
// on met à jour l'Ui avec la réponse
// ceci est possible car l'observation a lieu dans le thread de l'Ui
updateUi(uiResponse);
} ,
// notification d'erreur
th -> {
// cas d'erreur - on l'affiche
String message = getInfoForThrowable("L'erreur suivante s'est produite", th);
JOptionPane.showMessageDialog(this, message, "Informations", JOptionPane.PLAIN_MESSAGE);
// annulation requêtes
doCancel();
} ,
// notification [onCompleted]
// fin de l'attente
this::endWaiting));
} catch (Throwable th) {
// cas d'exception + générale - on l'affiche
String message = getInfoForThrowable("L'erreur suivante s'est produite", th);
JOptionPane.showMessageDialog(this, message, "Informations", JOptionPane.PLAIN_MESSAGE);
// on annule les requêtes
doCancel();
}
}
- line 3: the GUI is updated to indicate that a potentially long-running operation is in progress;
- line 5: an empty observable is created. This observable will be observed by the [Swing] layer;
- line 7: the array of possible schedulers;
- line 9: we have given the user the ability to choose the scheduler on which to execute the queries. We retrieve the scheduler of their choice;
- lines 11–19: each request returns an observable whose elements are merged (mergeWith) (line 17) into the observable from line 5;
- lines 13–14: the [UiResponse] object is constructed. Recall that this object is both the input parameter of the [RxService.getAleas] method and its result (lines 17–18);
- Line 14: Each request is identified by a number, referred to here as [idClient]. This is necessary because, in an asynchronous environment, the order in which responses are received may differ from the order in which requests were sent. [idClient] allows us to determine which request the response belongs to;
- Lines 17–18: The asynchronous request is made [rxService.getRandom]. It is executed on the scheduler chosen by the user. Its result, of type Observable<UiResponse>, is combined with the observable from line 5. It is important to note that the method [rxService.getAleas] is executed here and returns an observable. However, this does not mean that random numbers have been generated. In fact, an observable is only executed when subscribed to. This is not yet the case;
- line 21: this is the key instruction: we specify that the elements emitted by the observable on line 5 should be observed on the UI thread. Here, we use a scheduler specific to the RxSwing library;
- lines 25–51: we subscribe to the observable from line 5. Only now will the random numbers be requested from the synchronous service that generates them. The key part is in the instructions on lines 29–33. The rest primarily handles error cases and the [onCompleted] notification from the observable;
- lines 28–44: Remember that we requested to observe the process from line 5 on the UI thread. Therefore, the code in lines 28–44 runs on the UI thread;
- lines 29–33: We handle the observable’s [onNext] notification. We receive a [UiResponse] type emitted by the observed process. This is the result of one of the asynchronous requests. We update the user interface with this response;
- lines 34–41: we handle the observable’s [onError] notification. We display a dialog box showing the error (lines 37–38) and then cancel the requests (line 40);
- lines 42–44: we handle the observable’s [onCompleted] notification. We update the GUI to show that the requested service has completed. Line 44 could also have been written as follows
Here, we chose to use a method reference;
- lines 45–51: certain exceptions do not go through lines 34–41. This happens when too many requests are made. Once a certain limit—which depends on the runtime environment—is exceeded, a [StackOverflowError] occurs, which is handled by lines 45–51;
- Line 27: The subscription produces a [Subscription] type that is added to a list of subscriptions. This list will have only one element here;
Line 32: We update the user interface using the following [updateUi] method:
private void updateUi(UiResponse uiResponse) {
// response time
uiResponse.setResponseAt();
// observation thread
uiResponse.setObservedOn();
// number of responses
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
// running time
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
// add the jSON response string to the JList response template
try {
model.add(0, jsonMapper.writeValueAsString(uiResponse));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Here we see that components of the graphical user interface are updated (lines 7, 9, 12). For this to be possible, you must be in the UI thread (event loop).
The [endWaiting] method is as follows:
private void endWaiting() {
// generate] button visible
jButtonGenerate.setVisible(true);
// hidden [Cancel] button
jButtonCancel.setVisible(false);
// hidden wait cursor
jTabbedPane1.setCursor(Cursor.getDefaultCursor());
// selected answers tab
jTabbedPane1.setSelectedIndex(1);
// duration updated one last time
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
}
The [doCancel] method is called when an error occurs during the execution of asynchronous requests or when the user clicks the [Cancel] button. Its code is as follows:
// les souscriptions aux observables
private List<Subscription> subscriptions = new ArrayList<Subscription>();
....
@Override
protected void doCancel() {
// fin attente
endWaiting();
// dans le cas de souscriptions
if (jCheckBoxRxSwing.isSelected() && subscriptions != null) {
subscriptions.forEach(Subscription::unsubscribe);
//subscriptions.forEach(s -> s.unsubscribe());
}
}
- line 2: [subscriptions] is a list of subscriptions;
- line 11: all subscriptions are canceled;
- line 12: another way of writing line 11. The [forEach] method expects an instance of type Consumer<Subscription> here (see section 4.4);
Let’s return to the code for the [doGenerateWithService] method: it can be broken down into two steps:
- the observable configuration step. This is done in the thread of the caller of the [doGenerateWithService] method, i.e., the UI thread;
- the subscription that will trigger the execution of the observables;
If the observables use one of the schedulers [Schedulers.computation(), Scheduler.io(), Schedulers.newThread()], then they will execute outside the UI thread. These different threads will compete for the machine’s core(s). Since the requests are long-running operations (several hundred milliseconds), the [doGenerateWithService] method executed in the UI thread will finish before the requests have returned their responses. However, this method had been executed on the click event of the [Generate] button. Once this event is processed, the UI thread will be able to move on to processing the following events. There are several of them. Thus, the [beginWaiting] method had set up several:
private void beginWaiting() {
// buttons
jButtonGenerate.setVisible(false);
jButtonCancel.setVisible(true);
// waiting cursor
jTabbedPane1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
jButtonCancel.setCursor(Cursor.getDefaultCursor());
// raz answers
model.clear();
// rx subscriptions
subscriptions.clear();
// the response view is displayed
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuree.setText("");
// start of execution
debut = new Date().getTime();
}
Virtually every line of this code affects the graphical user interface. This update does not occur immediately: events are placed in the event loop queue. Once the click event on the [Generate] button is processed, these events are executed in turn, and the user can see the graphical user interface change:
- the [Response] tab is displayed (line 13) and a loading indicator is associated with it (line 6)
- its [Cancel] button is displayed (line 4) and the user can click on it;
- the JList of responses is cleared (line 9);
- the JLabel for the number of responses displays 0;
- the JLabel for the execution time displays an empty string;
Throughout the execution of the queries, the UI thread has regular access to the processor. It can then process pending events. Among these are those set by the [updateUi] method:
private void updateUi(UiResponse uiResponse) {
// response time
uiResponse.setResponseAt();
// observation thread
uiResponse.setObservedOn();
// number of responses
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
// running time
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
// add the jSON response string to the JList response template
try {
model.add(0, jsonMapper.writeValueAsString(uiResponse));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
When the UI thread is active:
- the JLabel displaying the number of responses is updated (line 7);
- the JLabel for the execution time is updated (line 9);
- the JList of responses is updated via its model (line 12);
This allows the user to see the progress of the query execution. Additionally, they can cancel them via the [Cancel] button. This is the whole point of having asynchronous services in front of the [Swing] layer, and RxJava is the technology of choice for implementing them.
Finally, note that if the user chooses one of the schedulers [Schedulers.immediate(), Schedulers.trampoline()], the observables are then executed on the same thread as the caller, i.e., the UI thread. This results in synchronous behavior.
The results obtained with the different schedulers were shown in sections 2.8.1, 2.8.2, 2.8.3, and 2.8.4.








