3.5.8.1. Background Task Usage Examples
Display and control background task operation with BackgroundWorkProgressWindow

Often when launching a background task one needs to display a simple UI:

  1. to show to the user that requested action is in the process of execution,

  2. to allow user to abort requested long operation,

  3. to show operation progress if progress percent can be determined.

Platform satisfies these needs with BackgroundWorkWindow and BackgroundWorkProgressWindow utility classes. These classes have static methods allowing to associate background task with a modal window that has a title, description, progress bar and optional Cancel button. The difference between two classes is that BackgroundWorkProgressWindow uses determinate progress bar, and it should be used in case if you can estimate progress of the task. Conversely, BackgroundWorkWindow should be used for tasks of indeterminate duration.

Consider the following development task as an example:

  • A given screen contains a table displaying a list of students, with multi-selection enabled.

  • When the user presses a button, the system should send reminder emails to selected students, without blocking UI and with an ability to cancel the operation.

bg task emails

Sample implementation:

import com.haulmont.cuba.gui.backgroundwork.BackgroundWorkProgressWindow;

public class StudentBrowse extends StandardLookup<Student> {

    @Inject
    private Table<Student> studentsTable;

    @Inject
    private EmailService emailService;

    @Subscribe("studentsTable.sendEmail")
    public void onStudentsTableSendEmail(Action.ActionPerformedEvent event) {
        Set<Student> selected = studentsTable.getSelected();
        if (selected.isEmpty()) {
            return;
        }
        BackgroundTask<Integer, Void> task = new EmailTask(selected);
        BackgroundWorkProgressWindow.show(task, (1)
                "Sending reminder emails", "Please wait while emails are being sent",
                selected.size(), true, true (2)
        );
    }

    private class EmailTask extends BackgroundTask<Integer, Void> { (3)
        private Set<Student> students; (4)

        public EmailTask(Set<Student> students) {
            super(10, TimeUnit.MINUTES, StudentBrowse.this); (5)
            this.students = students;
        }

        @Override
        public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
            int i = 0;
            for (Student student : students) {
                if (taskLifeCycle.isCancelled()) { (6)
                    break;
                }
                emailService.sendEmail(student.getEmail(), "Reminder", "Don't forget, the exam is tomorrow",
                        EmailInfo.TEXT_CONTENT_TYPE);

                i++;
                taskLifeCycle.publish(i); (7)
            }
            return null;
        }
    }
}
1 - launch the task and show modal progress window
2 - set dialog options: total number of elements for progress bar, user can cancel a task, show progress percent
3 - task progress unit is Integer (number of processed table items), and result type is Void because this task doesn’t produce result
4 - selected table items are saved into a variable which is initialized in the task constructor. This is necessary because run() method is executed in a background thread and cannot access UI components.
5 - set timeout to 10 minutes
6 - periodically check isCancelled() so that the task can stop immediately after the user pressed Cancel dialog button
7 - update progress bar position after every email sent
Periodically refresh screen data in the background using Timer and BackgroundTaskWrapper

BackgroundTaskWrapper is a tiny utility wrapper around BackgroundWorker. It provides simple API for cases when background tasks of the same type get started, restarted and cancelled repetitively.

As a usage example, consider the following development task:

  • A rank monitoring screen needs to display and automatically update some data.

  • Data is loaded slowly and therefore it should be loaded in the background.

  • Show time of the latest data update on the screen.

  • Data is filtered with simple filter (checkbox).

bg ranks ok
  • If data refresh fails for some reason, the screen should indicate this fact to the user:

bg ranks error

Sample implementation:

@UiController("playground_RankMonitor")
@UiDescriptor("rank-monitor.xml")
public class RankMonitor extends Screen {
    @Inject
    private Notifications notifications;
    @Inject
    private Label<String> refreshTimeLabel;
    @Inject
    private CollectionContainer<Rank> ranksDc;
    @Inject
    private RankService rankService;
    @Inject
    private CheckBox onlyActiveBox;
    @Inject
    private Logger log;
    @Inject
    private TimeSource timeSource;
    @Inject
    private Timer refreshTimer;

    private BackgroundTaskWrapper<Void, List<Rank>> refreshTaskWrapper = new BackgroundTaskWrapper<>(); (1)

    @Subscribe
    public void onBeforeShow(BeforeShowEvent event) {
        refreshTimer.setDelay(5000);
        refreshTimer.setRepeating(true);
        refreshTimer.start();
    }

    @Subscribe("onlyActiveBox")
    public void onOnlyActiveBoxValueChange(HasValue.ValueChangeEvent<Boolean> event) {
        refreshTaskWrapper.restart(new RefreshScreenTask()); (2)
    }

    @Subscribe("refreshTimer")
    public void onRefreshTimerTimerAction(Timer.TimerActionEvent event) {
        refreshTaskWrapper.restart(new RefreshScreenTask()); (3)
    }

    public class RefreshScreenTask extends BackgroundTask<Void, List<Rank>> { (4)
        private boolean onlyActive; (5)
        protected RefreshScreenTask() {
            super(30, TimeUnit.SECONDS, RankMonitor.this);
            onlyActive = onlyActiveBox.getValue();
        }

        @Override
        public List<Rank> run(TaskLifeCycle<Void> taskLifeCycle) throws Exception {
            List<Rank> data = rankService.loadActiveRanks(onlyActive); (6)
            return data;
        }

        @Override
        public void done(List<Rank> result) { (7)
            List<Rank> mutableItems = ranksDc.getMutableItems();
            mutableItems.clear();
            mutableItems.addAll(result);

            String hhmmss = new SimpleDateFormat("HH:mm:ss").format(timeSource.currentTimestamp());
            refreshTimeLabel.setValue("Last time refreshed: " + hhmmss);
        }

        @Override
        public boolean handleTimeoutException() { (8)
            displayRefreshProblem();
            return true;
        }

        @Override
        public boolean handleException(Exception ex) { (9)
            log.debug("Auto-refresh error", ex);
            displayRefreshProblem();
            return true;
        }

        private void displayRefreshProblem() {
            if (!refreshTimeLabel.getValue().endsWith("(outdated)")) {
                refreshTimeLabel.setValue(refreshTimeLabel.getValue() + " (outdated)");
            }
            notifications.create(Notifications.NotificationType.TRAY)
                    .withCaption("Problem refreshing data")
                    .withHideDelayMs(10_000)
                    .show();
        }
    }
}
1 - initialize BackgroundTaskWrapper instance with no-arg constructor; for every iteration a new task instance will be supplied
2 - immediately trigger a background data refresh after checkbox value has changed
3 - every timer tick triggers a data refresh in the background
4 - task publishes no progress so progress unit is Void; task produces result of type List<Rank>
5 - checkbox state is saved into a variable which is initialized in the task constructor. This is necessary because run() method is executed in a background thread and cannot access UI components.
6 - call custom service to load data (this is the long operation to be executed in the background)
7 - apply successfully obtained result to screen’s components
8 - update UI in the special case if data loading timed out: show notification in the screen corner
9 - inform user that data loading has failed with exception by showing notification