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 = 'examples-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 interval [a,b]
// n numbers are generated, where n itself is a random number in the interval [minCount, maxCount]
// the numbers are generated after a delay of delay milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
public ServiceResponse getRandom(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 timeout
private int delay;
// random numbers
private List<Integer> aleas;
// execution thread
private String executedOn;
// constructors
public ServiceResponse() {
// execution thread
executedOn = Thread.currentThread().getName();
}
public ServiceResponse(int delay, List<Integer> aleas) {
// local constructor
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 getRandomNumbers(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
// random numbers in the interval [a,b]
// n numbers are generated, where n itself is a random number in the range [minCount, maxCount]
// the numbers are generated after a delay of delay milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
// some checks
List<String> messages = new ArrayList<>();
int error = 0;
if (a < 0) {
messages.add("The number a from the generation interval [a,b] must be greater than 0");
error |= 2;
}
if (a >= b) {
messages.add("In the generation interval [a,b], a must be less than b");
error |= 4;
}
if (minCount < 0) {
messages.add("The minimum number in the [min, count] interval of generated values must be greater than 0");
error |= 16;
}
if (minCount > maxCount) {
messages.add("In the interval [min, count] of the number of generated values, min must be less than or equal to max");
error |= 32;
}
if (minDelay < 0) {
messages.add("The minimum value in the [min, count] range of the wait time must be greater than 0");
error |= 64;
}
if (minCount > maxCount) {
messages.add("In the interval [min, count] of the timeout, min must be less than or equal to max");
error |= 128;
}
if (maxDelay > 5000) {
messages.add("The wait time in milliseconds before generating numbers must be in the range [0,5000]");
error |= 256;
}
// errors?
if (!messages.isEmpty()) {
throw new AleasException(String.join(" [---] ", messages), error);
}
// random number generator
Random random = new Random();
// wait?
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);
}
}
// generate result
int count = minCount + random.nextInt(maxCount - minCount + 1);
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < count; i++) {
numbers.add(a + random.nextInt(b - a + 1));
}
// return result
return new ServiceResponse(delay, numbers);
}
}
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;
// constructors
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 interval [a,b]
// n numbers are generated, where n itself is a random number in the interval [minCount, maxCount]
// the numbers are generated after a delay of delay milliseconds,
// where [delay] is itself a random number in the interval [minDelay, maxDelay]
public Observable<UiResponse> getRandom(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 {
// client ID
private int clientId;
// service response
private ServiceResponse serviceResponse;
// name of the observation thread
private String observedOn;
// request time
private String requestAt;
// response time
private String responseAt;
// constructors
public UiResponse() {
// observation thread
observedOn = Thread.currentThread().getName();
// request 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;
// constructor
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) {
// create an observable emitting the value returned by the synchronous service
return Observable.create(subscriber -> {
try {
// synchronous call
uiResponse.setServiceResponse(service.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
// Pass the result to the observer
subscriber.onNext(uiResponse);
} catch (Exception e) {
// pass the error to the observer
subscriber.onError(e);
} finally {
// Notify the observer 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;
// the inputs
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 = "Enter an integer >=1";
private final String jLabelCountErrorText = "minCount must be >=0 and maxCount >= minCount";
private final String jLabelDelayErrorText = "minDelay must be >=0, maxDelay must be >=minDelay, and maxDelay must be <=5000";
private final String jLabelIntervalErrorText = "a must be >=0 and b >= a";
// subscriptions to observables
protected List<Subscription> subscriptions = new ArrayList<Subscription>();
// start-end of execution
private long start;
// JSON mapper
private ObjectMapper jsonMapper;
// response model
private DefaultListModel<String> model;
// constructor
public JFrameAleasEvents() {
// parent
super();
// local
initJFrame();
// services
service = new Service();
rxService = new RxService(service);
// JSON mapper
jsonMapper = new ObjectMapper();
}
private void initJFrame() {
// hide error messages
jLabelCountError.setText("");
jLabelDelayError.setText("");
jLabelIntervalError.setText("");
jLabelNbValuesError.setText("");
// Hide the default text
jTextFieldA.setText("100");
jTextFieldB.setText("200");
jTextFieldMinCount.setText("5");
jTextFieldMaxCount.setText("10");
jTextFieldMinDelay.setText("100");
jTextFieldMaxDelay.setText("500");
jTextFieldNbValeurs.setText("10");
jLabelDuration.setText("");
// response model
model = new DefaultListModel<>();
jListNumbers.setModel(model);
// number of cores
System.out.printf("The JVM detected [%s] cores on your 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() {
// Are the entries valid?
if (!isPageValid()) {
return;
}
// rx or not?
if (jCheckBoxRxSwing.isSelected()) {
// asynchronous requests
doGenerateWithRxService();
} else {
// synchronous requests
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++) {
// prepare response
UiResponse uiResponse = new UiResponse();
// client ID
uiResponse.setIdClient(i);
// synchronous call
uiResponse.setServiceResponse(service.getRandom(a, b, minCount, maxCount, minDelay, maxDelay));
// response time
uiResponse.setResponseAt();
// Update the JList model with the received responses
model.add(0, jsonMapper.writeValueAsString(uiResponse));
// update the number of responses
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
}
} catch (JsonProcessingException | RuntimeException e) {
JOptionPane.showMessageDialog(this, getInfoForThrowable("The following error occurred", e), "Information",
JOptionPane.PLAIN_MESSAGE);
}
// end wait
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 cursor
jTabbedPane1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
jButtonCancel.setCursor(Cursor.getDefaultCursor());
// clear responses
model.clear();
// Rx subscriptions
subscriptions.clear();
// display the answers view
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuration.setText("");
// start execution
start = 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() {
// start waiting
beginWaiting();
// we will obtain the random numbers as an observable
Observable<UiResponse> observable = Observable.empty();
// Scheduler for executing the various observables
Scheduler[] schedulers = { Schedulers.io(), Schedulers.computation(), Schedulers.newThread(),
Schedulers.trampoline(), Schedulers.immediate() };
Scheduler scheduler = schedulers[jComboBoxSchedulers.getSelectedIndex()];
// Configure observables
for (int i = 0; i < nbRequests; i++) {
// prepare response
UiResponse uiResponse = new UiResponse();
uiResponse.setClientId(i);
// the observable is configured to run on the scheduler chosen by the user
// then merge the resulting observable with the main observable
observable = observable.mergeWith(
rxService.getAleas(a, b, minCount, maxCount, minDelay, maxDelay, uiResponse).subscribeOn(scheduler));
}
// observer
observable = observable.observeOn(SwingScheduler.getInstance());
// so far, we've just done some configuration
// no requests have been made yet to the synchronous random number generation service
// we subscribe to the observable—this is what will trigger the call to the synchronous random number generation service
try {
// there is only one subscription here—the result is a subscription
subscriptions.add(observable.subscribe(
// emission notification
uiResponse -> {
// update the UI with the response
// this is possible because the observation takes place in the UI thread
updateUi(uiResponse);
},
// error notification
th -> {
// error case - display it
String message = getInfoForThrowable("The following error occurred", th);
JOptionPane.showMessageDialog(this, message, "Information", JOptionPane.PLAIN_MESSAGE);
// cancel requests
doCancel();
},
// [onCompleted] notification
// end of wait
this::endWaiting));
} catch (Throwable th) {
// general exception case - display it
String message = getInfoForThrowable("The following error occurred", th);
JOptionPane.showMessageDialog(this, message, "Information", JOptionPane.PLAIN_MESSAGE);
// cancel the requests
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));
// execution time
jLabelDuration.setText(String.valueOf(new Date().getTime() - start));
// Add the JSON string of the response to the JList model of responses
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);
// [Cancel] button hidden
jButtonCancel.setVisible(false);
// Hide the wait cursor
jTabbedPane1.setCursor(Cursor.getDefaultCursor());
// Selected tab
jTabbedPane1.setSelectedIndex(1);
// update duration one last time
jLabelDuree.setText(String.valueOf(new Date().getTime() - start));
}
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:
// subscriptions to observables
private List<Subscription> subscriptions = new ArrayList<Subscription>();
....
@Override
protected void doCancel() {
// end waiting
endWaiting();
// if there are subscriptions
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());
// clear responses
model.clear();
// Rx subscriptions
subscriptions.clear();
// display the answers view
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuration.setText("");
// start execution
start = 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));
// execution time
jLabelDuration.setText(String.valueOf(new Date().getTime() - start));
// Add the JSON string of the response to the JList model of responses
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.








