Using SwingWorker to update Swing Components asynchronously

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on January 03, 2016 · 3 mins read

I have a JList, the contents of which are updated asynchronously by a different thread. The way I update the JList is by updating the model:

	private final DefaultListModel streamedDataModel;
...
        streamedDataModel.addElement(element);

The only problem is, if this is not done in the EventDispatchThread, the UI becomes unresponsive, and starts behaving weird.
The way to solve this to use the SwingWorker.
I have a Jini server, which streams data to a Jini client, in this case, a Swing JFrame. I have a JList to which I publish the data as and when it becomes available.

Jini Service API

Its a simple streaming service:

public interface BankDetailStreamingService extends Remote {
    void streamAllBankDetails(RemoteDataListener bankDetailRemoteListener) throws RemoteException;
}

At its heart is the interface RemoteDataListener, on to which the server publishes data as it becomes available.

public interface RemoteDataListener extends Remote {
    void newData(T data) throws RemoteException;
    void endOfData() throws RemoteException;
}

Client Implementation

The key is to implement the interface RemoteDataListener along with the SwingWorker as shown below as an inner class in the JFrame:

    class StreamingTask extends SwingWorker implements RemoteDataListener {
	private final DefaultListModel streamedDataModel;
	public StreamingTask(DefaultListModel streamedDataModel) {
	    this.streamedDataModel = streamedDataModel;
	}
	@Override
	protected Void doInBackground() {
	    try {
		Exporter exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0), new BasicILFactory());
		@SuppressWarnings("unchecked")
		RemoteDataListener exportedRemoteDataListener = (RemoteDataListener) exporter.export(this);
		bankDetailStreamingService.streamAllBankDetails(exportedRemoteDataListener);
	    } catch (RemoteException e) {
		throw new RuntimeException(e);
	    }
	    return null;
	}
	@Override
	protected void process(List chunks) {
	    for (BankDetail element : chunks) {
		streamedDataModel.addElement(element);
	    }
	    // scroll to end
	    listStreamedData.ensureIndexIsVisible(streamedDataModel.size() - 1);
	}
	@Override
	public void newData(BankDetail data) throws RemoteException {
	    publish(data);
	}
	@Override
	public void endOfData() throws RemoteException {
	}
	@Override
	protected void done() {
	    prgStreamingData.setIndeterminate(false);
	    JOptionPane.showMessageDialog(RmiStreamingDemoFrame.this, "All data streamed successfully", "End of data", JOptionPane.INFORMATION_MESSAGE);
	    btnStartStreaming.setEnabled(true);
	    setCursor(null);
	}
    }

We invoke the long running service in the doInBackground() method and add itself as a listener. When new data is received in the newData() method, we immediately call the publish() method, which delegates it to the process() method. The process() method is invoked within the eventDispatcherThread. This ensures that any updates made to the model of the JList is reflected on the UI. So, the code to update Swing components reside here. Also note that the done() method is called by the SwingWorker after the processing thread is finished.

Running the example

Run the SpringNonSecureRmiServer to start the Reggie and the Jini Server. After the Jini Server starts up, run the RmiStreamingDemoFrame.

rmi-data-streaming-1
rmi-data-streaming-2

Sources

The sources can be found here: https://github.com/paawak/blog/tree/master/code/jini/unsecure/streaming-with-jini
There are 3 Maven projects under that:

  1. rm-api
  2. rmi-client
  3. rmi-server