javaswingoopswingworker

How to implement UI agnostic background worker task


I'm developing a desktop GUI app. I'm using Swing at the moment, but I want to be able to port it to JavaFX or Android sometime in the future.

Currently, I'm struggling with implementing a background task class which would be "UI agnostic": the goal is to be able to submit background tasks from application's core packages (not depending on a particular GUI) and observe the progress of tasks if there is a GUI (using GUI specific progress bars etc.).

Swing has it's SwingWorker<T, V> (link), JavaFX has Task<V> (link), Android has AsyncTask<Params, Progress, Result> (link). They all serve the same purpose and in fact have similar APIs.

This interface provides the essential functionality for a background task to be useful, which is in fact found in all mentioned toolkits.

public interface BackgroundTask<Result, PartialResult> {

    /**
     * This is where the background computation belongs. Background thread.
     * <p>
     * Equivalent of {@link javax.swing.SwingWorker#doInBackground()}, {@link javafx.concurrent.Task#call()}
     * and {@link android.os.AsyncTask#doInBackground()}.
     */
    public Result computeResult();

    /**
     * Can be called from {@link #computeResult()} to update GUI with partial
     * results. Background thread.
     * <p>
     * Equivalent of {@link javax.swing.SwingWorker#publish(Object[])}, and
     * {@link android.os.AsyncTask#publishProgress(Progress)} (sort of). The equivalent in
     * JavaFX is not just a simple method.
     */
    public void partialUpdate(PartialResult... partials);

    /**
     * Called to process partial results. UI thread.
     * <p>
     * Equivalent of {@link javax.swing.SwingWorker#process(java.util.List)} and
     * {@link android.os.AsyncTask#onProgressUpdate(Progress)}
     */
    public void onPartialResult(List<PartialResult> partials);
}

My background tasks will implement this interface. I need some code to turn instances of BackgroundTask to a SwingWorker in Swing (Task in JavaFX and AsyncTask in Android later).

A straightforward implementation follows:

public class SwingBackgroundTask<Result, PartialResult> extends SwingWorker<Result, PartialResult> {
    private final BackgroundTask<Result, PartialResult> task;

    public SwingBackgroundTask(BackgroundTask<Result, PartialResult> task) {
        this.task = task;
    }

    @Override
    protected Result doInBackground() throws Exception {
        return task.computeResult();
    }

    @Override
    protected void process(List<PartialResult> chunks) {
        task.onPartialResult(chunks);
    }
}

However, it is not useful. Obviously, I cannot call SwingWorker's publish(partialResult) from inside BackgroundTask's computeResult(). Therefore my users will never see meaningful progress of background tasks. Sure I can use indeterminate progress bars but I don't like them.

How can I overcome this problem and make a generic background task?


Solution

  • Seeing as you're trying to decouple the process from a specific implementation, you will need to provide some kind of API which would allow you to provide feedback in some uniform manner.

    Probably the simplest approach would be to provide the BackgroundTask class with some kind of observer/listener, which interested parties could register with for notification and the BackgroundTask could use to provide feedback.

    This would allow BackgroundTask to raise events about a given state change or other notification and the implementations would be able to handle in an implementation specific manner.